hook
: 갈고리라는 뜻으로 운영체제가 어떤 코드를 실행하려고 할 때, 이를 낚아채어 다른 코드가 실행되게 하는 것을 Hooking(후킹) 이라고 부른다.
Hook Overwrite
: Glibc 2.33 이하 버전에서는 malloc()
과 free()
를 호출할 때 함께 훅(Hook)이 함수 포인터 형태로 존재한다. 이 함수 포인터를 Overwrite하여 코드를 실행하면, Full RELRO를 우회할 수 있다.
(Full RELRO에도 libc에는 쓰기가 가능하기 때문이다)
Hook의 작동 방식
- libc는 디버깅 편의를 위해 hook 변수가 정의 되어 있다.
ex)__malloc_hook
,__free_hook
,--realloc_hook
-malloc()
함수는__malloc_hook
의 값이 NULL인지 검사합니다.
- NULL이 아니라면 hook에 인자를 넘기고__malloc_hook
이 가리키는 함수를 먼저 실행한다.
.bss
영역에 있기 때문에 쓰기가 가능하므로 조작 가능하다.
Hook 변수를 덮어 쓰고, 인자를 전달하면 공격할 수 있다.
malloc("/bin/sh")
: __malloc_hook
을 system
함수의 주소로 덮는다.이제는 사라진 hook 변수
: 악용되기 쉽고, 다발적으로 사용할 때 성능에 악영향을 주는 이유로,
Glibc 2.34
부터는 제거되었다.
문제 🚩
// 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;
}
checksec 🔓
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
설계 📘
- Library의 변수 및 함수들의 주소 구하기
libc에서 __free_hook
, system@
, "/bin/sh"
문자열을 구하자
readelf -sr libc-2.27.so | grep " __free_hook@"
readelf -s libc-2.27.so | grep " system@"
strings -tx libc-2.27.so | grep "/bin/sh"
[1]에서 변수와 함수의 주소를 구한다.
[2]에서 __free_hook
의 값을 system
함수의 주소로 덮어쓴다.
[3]에서 "/bin/sh"
를 해제한다.
Exploit ⚔️
libc의 시작 주소를 구하기 위하여 __libc_start_main
을 확인한다.
__libc_start_main
: _start()
에서 __libc_start_main()
을 호출하면 main()
이 호출된다.
(gdb) b * main
(gdb) r
(gdb) bt
bt를 이용하면 함수를 호출한 관계를 볼 수 있다. 그 방법으로 main의 RET
를 참고하여 이전 함수인 __libc_start_main
을, __libc_start_main의 RET
를 이용하여 _start()
를 구할 수 있는 것 같다.
x/i (bt에서 나온 __libc_start_main의 주소)
를 이용하면 <__libc_start_main + offset>
으로 나타난 offset을 구할 수 있다.
__libc_start_main
의 offset을 readelf를 이용하여 구하면 0x21b10
이다.
offset은 +231
이다.
따라서 __libc_start_main+231
을 릭한 후, 해당값에서 0x21b10 +231
을 빼면 libc_base
를 구할 수 있다. 이를 이용하면 다른 libc기반 함수들도 사용할 수 있다.
참고로 __libc_start_main+231
의 값은 main의 ret주소
이다.
이를 이용하여 __libc_start_main+231
의 주소를 leak한 후, libc_base를 구하고, __free_hook
을 system
으로 바꾼다. 이후 /bin/sh
를 free의 인자로 넣는다.
다음에는 이를 활용한 예제를 write-up 하겠습니다.