版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37809075/article/details/82849388
堆的性质
- 是在程序运行动态分配内存(需要参考用户的反馈)
- 使用malloc函数或者new函数申请
- 堆的读,写,释放都是通过堆指针来完成
- 使用完成后,将堆指针交给释放函数回收这片内存
- 增长方向由低地址到高地址
堆的数据结构
- 堆分为块首和块身
- 堆管理系统一般指向块身的起始位置
- 块表位于堆区的起始位置,用于索引堆区中所有堆块的信息,决定了整个堆区的组织方式。
- 空表(空闲双向链表)
- 按照堆块的大小不同,空表分为128条
- 其中空表索引的第一项是所有大于等于1024字节的堆块。
- 快表(快速单向链表)
- 加速堆块分配采用的策略
- 这类单向链表不会发生堆块合并
- 快表总是初始化为空,每条快表最多只有4个结点
- 空表(空闲双向链表)
堆块的操作
堆块的分配
- 快表分配:1.寻找,2.将其改为占用态,3.从堆表中卸下,4.返回一个指针指向自身块身给程序使用
- 普通空表分配:1.寻找最优的空闲块分配(最小满足)
- 0号空表分配:反向查找,先找最大,然后看是否满足,再找最合适的
堆块的释放
将堆块的状态改为空闲,链入相应的堆表末尾
堆块的合并
将两个块从空闲链表中卸下,调整合并后大块的块信息,将新块重新链入空闲
堆的管理
堆的分配函数
所有的堆分配函数最终都使用位于ntdll.dll中的RtlAllocateHeap()函数进行分配。
堆溢出利用
原理
堆溢出的利用建立在堆块的释放,在释放的时候有往指定内存的写数据操作。
其中bink后向指针是目标,flink前向指针是子弹
比如覆盖后向地址为4444,前向指针为0000。在unlink的时候,就要把0000写入到4444地址处。假设我们0000的地址是shellcode的地址,4444处存储着一个库函数,并且之后会调用。那么在调用这个函数时,就会转而调用shellcode。这个过程叫DWORD SHOOT
常用目标
- 内存变量:能影响程序执行的重要标志位
- 代码逻辑:修改代码段的关键逻辑部分,比如NOP掉
- 函数返回地址:修改函数返回地址来劫持进程(不固定)
- 攻击异常处理机制:当产生异常时,会转入异常处理机制,将里面的数据结构作为目标
- 函数指针:调用动态链接库的函数,C++中的虚函数调用
- PEB线程同步函数的入口地址:每个进程的PEB都存放着一对同步函数指针,指向RtlEnterCriticalSection()和RtlLeaveCriticalSection(),并且进程退出的时候会被ExitProcess()调用(固定PEB的地址不会变)
实例
- memcpy进行字符拷贝的时候出现问题,超过200字节的数据将覆盖尾块的块首。
- 当h2分配时,将导致DWORD SHOOT。将0x7FFDF020处的RtlEnterCriticalSection()函数指针改为shellcode地址。
- DWORD SHOOT完毕后,堆溢出导致异常,最终调用ExitProcess函数结束进程
- 其中前向指针是子弹(shellcode的起始地址),后向指针是目标(PEB的函数指针地址0x7FFDF020固定的)
堆利用的注意事项
- 调试堆和常态堆的区别
int3下断点,attach进程 - 在shellcode中修复环境
DF的值
堆区的修复 - 定位shellcode跳板
堆的地址不固定,需要定位shellcode的地址。用到一些指令跳板来定位shellcode
call DWORD PTR [EDI+0x78]
call DWORD PTR [ESI+0x4C]
call DWORD PTR [EBP+0x74] - DWORD SHOOT后的“指针反射”现象
会发生第二次的DWORD SHOOT,会在shellcode的后4个偏移位置写入目标地址,这样会破坏shellcode,可能造成错误。(利用跳板技术)