본문 바로가기

보안

[시스템해킹] Return Oriented Programming(ROP); GOT Overwrite

- ASLR이 걸린 환경에서 system 함수를 사용하려면 프로세스에서 libc가 매핑된 주소를 찾고, 그 주소로부터 system 함수의 오프셋을 이용하여 함수의 주소를 계산해야 함

 

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

 

  • 코드
// 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); //1번 파일 = 표준 출력
  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;
}

 

  • system 함수 주소 계산

- system 함수는 libc.so.6에 정의되어 있으며, 해당 라이브러리에는 read, puts, printf도 정의되어 있음

- 바이너리가 system 함수를 직접 호출하지 않아서 system 함수가 GOT에 등록되지 않음

- read, puts, prinf는 GOT에 등록되어 있음

- main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있다면 libc.so.6 라이브러리 파일이 매핑된 영역의 주소를 구할 수 있음

- rop.c에서는 read, puts, printf 함수가 GOT에 등록되어 있으므로, 하나의 함수를 정해서 그 함수의 GOT 값을 읽은 후 그 함수 주소와 system 함수 사이의 거리를 이용해 system 함수의 주소를 구할 수 있음

 

- ELF파일의 기본 정보가 포함된 헤더에서 오프셋 찾기

$ readelf -s libc.so.6 | grep " read@" -> read 함수 오프셋 0x114980

$ readelf -s libc.so.6 | grep " system@" -> system 함수 오프셋 0x50d60

=> read함수와 system함수의 거리는 0x114980 - 0x50d60 = 0xc3c20

 

- pwntools ELF symbols

e = ELF("./libc.so.6")

read_offset = e.symbols["read"] // read 함수 offset 
binsh = list(e.search("/bin/sh"))[0] // /bin/sh 문자열의 주소

 

  • "/bin/sh"

- 이 바이너리는 데이터 영역에 "/bin/sh/" 문자열이 없음

- 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일(libc.so.6을 많이 사용)에 포함된 것을 사용해야 함

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

 

  • GOT Overwrite

- system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로 알아낸 system 함수의 주소를 페이로드에 사용하려면 main 함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 함; ret2main 공격 패턴

- system 함수의 주소를 어떤 함수의 GOT에 쓰고 그 함수를 재호출하도록 ROP 체인 구성; GOT Overwrite

 

  • ROPgadget으로 가젯 주소 구하기

- 함수 호출 규약에 따라 인자는 rdi, rsi, rdx, rcx ... 순으로 들어가므로 read함수의 rdi, rsi, rdx 에 

 

 

  • 2번째 read로 buf에 넣는 payload

 

 

 

* read, write 함수의 3번째 인자는 rdx에 넣어주는데 이미 충분히 큰 값이 들어가있어 따로 처리하지 않음

 

 

 

 

 

 

-main의 ret 실행 시 rip가 return address(pop rdi;)를 가리키고 rsp는 8 증가하여 1을 가리킴

-프로그램의 실행 흐름이 rip이므로 pop rdi;가 실행되면 rdi에 1 들어가고 rsp 8 증가

 

-2번째 인자가 들어가는 rsi에 값을 넣어주기 위해 pop rsi; 를 찾았는데 pop rsi; pop r15; ret; 가젯밖에 없어서 저거 사용

 

 

 

 

 

 

-pop rsi; 하면 rsi에 read got 주소 들어가고 rsp 8 증가

-pop r15; 해서 아무거나 넣어주고 rsp 8 증가

-ret; = pop rip, jmp rip 라서 rsp 값이 rip 에 저장, rip가 가리키는 값으로 이동

=> write(1, read_got, ...); got에 저장된 read의 실제 주소를 구해 libc 베이스 찾기

 

-> read(0, read_got, ...); read의 got를 system의 got로 overwrite하기 위함

-> read("/bin/sh"); 이때 read의 주소는 system으로 overwrite 되었을 테니까 실제 실행되는 함수는 system("/bin/sh")

-> read(0, read_got, ...)에서 입력을 기다리고 있으므로 p.send로 system 주소와 "/bin/sh" 보내주면 쉘 획득

 

드림핵 워게임 rop

'보안' 카테고리의 다른 글

[시스템해킹] PIE  (0) 2024.04.26
[시스템해킹] Return Oriented Programming(ROP); ret2main  (0) 2024.04.26
[시스템해킹] Return to Library  (0) 2024.04.23
[시스템해킹] Background: Library  (0) 2024.04.23
[시스템해킹] NX & ASLR  (0) 2024.04.23