관리 메뉴

Bull

[System Hacking] Return To Library(RTL) with ROPgadget 본문

Computer Science/System Hacking

[System Hacking] Return To Library(RTL) with ROPgadget

Bull_ 2024. 4. 10. 17:07

개념


Return To Library(RTL)는 NX를 우회하는 공격 기법으로 널리 알려져있다.

 

NX로 인해 공격자가 버퍼에 주입한 셸 코드를 실행하기는 어렵지만, 스택 버퍼 오버플로우 취약점으로 반환 주소를 덮는 것은 여전히 가능하다.

프로세스에 실행 권한이 있는 메모리 영역은 일반적으로 바이너리의 코드 영역과 바이너리가 참조하는 라이브러리의 코드 영역이다.

Return-to-Libc: 공격자는 특정 libc 함수의 주소를 리턴 주소로 설정합니다.

 

가장 일반적인 목표는 system 함수로의 실행 흐름을 변경하는 것이며, 이를 통해 임의의 시스템 명령을 실행할 수 있다.

 

예를 들어, 공격자는 system("/bin/sh") 호출을 통해 셸을 획득할 수 있다.

 

Dreamhack 실습코드


// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

const char* binsh = "/bin/sh";

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Add system function to plt's entry
  system("echo 'system@plt'");

  // Leak canary
  printf("[1] Leak Canary\n");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  // Overwrite return address
  printf("[2] Overwrite return address\n");
  printf("Buf: ");
  read(0, buf, 0x100);

  return 0;
}

1. “/bin/sh”를 코드 섹션에 추가

const char* binsh = "/bin/sh";

문자열 "/bin/sh"를 프로그램의 코드 섹션에 저장한다.

 

ASLR이 적용되어도, PIE가 비활성화되어 있으면 프로그램의 코드 세그먼트데이터 세그먼트의 주소는 실행할 때마다 동일하게 유지된다.

 

"/bin/sh" 문자열의 메모리 주소가 고정되어 있음을 의미하며, 이 주소는 나중에 system 함수를 호출할 때 인자로 사용될 수 있다.

2. system 함수를 PLT에 추가

system("echo 'system@plt'");


이 코드는 system 함수를 호출함으로써 해당 함수의 주소를 Procedure Linkage Table(PLT)에 추가한다.

 

 함수의 주소가 한 번 PLT에 추가되면, 해당 함수는 ASLR에 영향을 받지 않는 고정된 주소를 통해 호출할 수 있다.

 

이러한 방법은 Return to PLT 공격에 사용된다.

 

ROPgadget


리턴 가젯 찾는 방법

설치

$ python3 -m pip install ROPgadget --user

가젯 찾기

$ ROPgadget --binary ./rtl --re "pop rdi"
Gadgets information
============================================================
0x0000000000400853 : pop rdi ; ret

찾아야할 가젯

addr of ("pop rdi; ret")   <= return address
addr of string "/bin/sh"   <= ret + 0x8
addr of "system" plt       <= ret + 0x10

system("bin/sh")를 실행하는 ROP gagdet이다.

 

가젯의 의미는 알겠는데 해당 흐름에 대해 이해가 가질 않았었다.

 

우선 GPT를 통해 내가 이해한 바를 표를 통해 정리하였다.

 

일단 "실행흐름", "SP(Stack Pointer)" 두 가지로 나눈 후 표를 보면 어느 정도 이해가 될 것이다.

 

순서 스택 포인터(SP) 위치 실행 중인 명령(실행 흐름) 설명
1 스택에 데이터를 배치하기 전 N/A  공격자는 스택에 다음 데이터를 순서대로 배치: "pop rdi; ret" 가젯의 주소, /bin/sh의 주소, system 함수의 주소.
2 "pop rdi; ret" 가젯 주소 실행 흐름 변경 중  실행 흐름이 "pop rdi; ret" 가젯으로 이동한다. 이 시점에서 SP는 "pop rdi; ret" 가젯의 다음 위치인 /bin/sh의 주소를 가리킨다.
3 /bin/sh의 주소 "pop rdi" 실행 "pop rdi" 명령이 실행되어 SP가 가리키는 값(/bin/sh의 주소)을 rdi 레지스터로 로드한다. SP는 이제 system 함수의 주소를 가리킨다.
4 system 함수의 주소 "ret" 실행  "ret" 명령이 실행되어 SP가 가리키는 값(즉, system 함수의 주소)을 프로그램 카운터(PC)로 로드하고, 실행 흐름이 system 함수로 이동한다.
5  system 함수 실행 system("/bin/sh") 호출 rdi에 로드된 /bin/sh의 주소가 system 함수의 인자로 사용되어 쉘이 실행된다.

 

정리하자면,

스택 포인터가 /bin/sh를 가리킬 때 pop rdi가 실행.  
스택 포인터가 system을 가리킬 때 ret이 실행.

그리니까, "addr of ("pop rdi; ret")   <= return address"는 실행흐름이 변경된거고,
실행 흐름이 변경된 상태에서 그 아래에 "/bin/sh"와 "system()"은 스택에 해당 주소가 올라가게 되는 것이다.

 

위와 같은 방법을 통해 가젯으로 구성된 페이로드를 작성하고, 페이로드로 반환 주소를 덮으면 셸을 획득할 수 있다.

여기서 한가지 주의할 점은, system 함수로 rip가 이동할 때, 스택은 반드시 0x10단위로 정렬되어 있어야 한다.

 

system 함수 내부에 있는 movaps 명령어 때문인데,

 

이 명령어는 스택이 0x10단위로 정렬되어 있지 않으면 Segmentation Fault를 발생시킨다.


참고자료


[DreamHack 강의]

[출처]: https://dreamhack.io/lecture/courses/83