Use After Free 例题分析
hitcontraining_uaf
main 函数分析
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
1 | int menu() |
atoi(ASCII to integer)的功能是:把以 null 结尾的字符串 buf 转换成对应的十进制整数,赋值给变量 n2
通过第一次输入,只能选择 1.2.3.4 四个选项,分别对应着不同的操作。
add_node()函数分析:
1 | int add_note() |
1 | int __cdecl print_note_content(int a1) |
对于 add_note()函数当中的结构体分析:
*((_DWORD *)¬elist + n4) = malloc(8u); 在 32 位系统下一个指针占 4字节,8 字节正好是两个指针
((_DWORD **)¬elist + n4) = print_note_content;
第二次解引用,本质是对 note_ptr 解引用:*note_ptr,也就是 note_ptr[0]
**(二级指针) = *(一级指针) = 一级指针[0] = 结构体偏移0x0处的值。两次引用相当于创建了一个结构体:
1 | struct Note { |
del_node()函数分析:
1 | int del_note() |
以指针代替数组下标,free 掉输入的 chunk,没有置 0,有 UAF 漏洞.
print_note()函数分析:
1 | int print_note() |
(**((int (__cdecl ***)(_DWORD))¬elist + count))(*((_DWORD *)¬elist + count));
((int (__cdecl ***)(_DWORD))¬elist + count)):相当于¬elist[count]
(**(…))(((_DWORD )¬elist + count))相当于¬elist[count] => *notelist[count] => notelist
指针直到最后相当于指向了结构体指针,等价于 printf_func(notelist[dount]);
也就是当前 return (**((int (__cdecl ***……ORD *)¬elist + count));起到了一个调用 print 的作用。
1 | // 原逆向代码的等价写法 |
简单来说 print_note 函数当中并没有真正能够打印的函数,而是调用了 add_note 当中的能够实现打印的 print_note_content 函数去实现该函数的打印功能。
magic()后门函数:
1 | int magic() |
我们前面说(**(…))(((_DWORD )¬elist + count))相当于¬elist[count] => *notelist[count],那如果 nodelist[count] = magic 函数的话,就会指向 magic 的函数地址,直接调用这个函数。
具体思路大概就是这样,不过问题是我们的堆块中是这样的结构:
1 | note_ptr → 0xXXXX0000(结构体起始地址) |
函数指针会调用内容指针,内容指针会去相应的地址寻找内容
(这里再解释一下):内容地址存放的并不是真正的内容,真正的内容在另一个可写的空间,内容地址存放的是可写空间的地址,从而跳转到可写空间去输出内容,就像这样:
1 | 结构体堆块 0x1000: |
造成的主要问题就是,无论我们输入什么都会先调用 printf,之后再寻找地址去打印输入的内容。那么我们就需要利用主要的漏洞 uaf 去利用悬空指针修改堆内容:
具体思路:
我们可以先创建两个堆块然后删除掉,由于 free 后没有置 0,该地址依旧存在悬空指针,再次 add 的时候就可以绕过 if ( !count ) ,由于 fastbin 特性就可以直接往复用堆中存入东西,当我们存入的是 magic 时,再次调用 print_note 函数就会直接调用 magic 函数实现 system(‘/bin/sh’);
两次 add_note 函数创建四个堆块
第一次addnote(b’16’,b’aaaa’)分别创建了一个结构体堆块 chunk1(8u)和一个内容堆块 chunk2(16u)
同样的第二次addnote(b’16’,b’aaaa’)分别创建了一个结构体堆块 chunk3(8u)和一个内容堆块 chunk4(16u)
两次 del_note 函数删除并 free 四个堆块
第一次和第二次 free 后的堆块链表:8u 内存链表: chunk1(8u) -> chunk3(8u)
第三次 add_note(b’8’,p32(magic))创建两个堆块
add_note(b’8’,p32(magic))先创建一个 8u 的结构体,取chunk1(8u)的内存空间写入,
1 | chunk3 → 0xXXXX0000(结构体起始地址) |
之后创建一个 8u 的空间写内容,取 chunk3(8u)的内存空间写入。
1 | chunk3 → 0xXXXX0000(结构体起始地址) |
上面地址是瞎写的,chunk1 和 chunk3 地址肯定是不一样的,随便表示一下
调用printnote(b’0’)
再调用printnote(b’0’)就会造成执行 p32(magic)的地址,从而获取 shell 了。
1 | from pwn import * |
更新: 2026-03-01 13:39:56
原文: https://www.yuque.com/idcm/wnemg9/uuaw2b69q1udv5uk