查看保护,发现栈有可执行权限,猜测跟ret2shellcode有关main函数设置了一个canary,checksec才看不出来。从canary = (__int64)&v1可以看出,这个canary是全局变量,存放栈上的地址接着在while循环里用了个switch,对应四种功能,首先看case 0,因为v8[128]的空间是0x3e8,跟rbp的距离是0x3FA,而read(0, v8, 0x410uLL);则会导致栈溢出,溢出长度是0x28,可以覆盖到返回地址+0x6的位置。注意memset(v8, 0, sizeof(v8));会清空v8的内容。这里对应case 1,把dis数组对应索引位置清0.这个函数用来打印dis对应索引的值,要求索引值是正数。但这里存在个问题LOWORD(v0) = -v2;首先%hd输入的是两个字节,v2是用补码表示的,-v2则是将v2做取反运算再+1。比如说+5 二进制是 101:对 0x0005:所以:对 -5 的比特 0xFFFB:得到:这里有个特殊情况:最小负数无法变成正数
比如 16 位有符号数的最小值是 -32768,它的相反数 32768 超出范围,会溢出,取反+1后反而等于自身。 可以看到canary在dis的低地址方向,差了0x40000,再除以8,刚好等于32768,所以我们可以输入index为-32768达到数组越界打印canary,从而泄露栈地址。case3同样存在整数溢出的问题,输入index为-32768,就可以修改canary值检查canary值需要注意写入的shellcode只能写到返回地址之前,返回地址需要存放shellcode的地址,所以shellcode最多只能写入0x1A(0x3FA-0x3E8+0x8)。不过这里为了方便对齐,我直接从rbp-0x10(注意这里要修改canary的值等于shellcode的前八个字节,绕过检查)开始写,只写入0x18长度的shellcode。把断点打在设置canary之后,查看canary的值,存着栈地址再看rbp的值计算到canary也就是rbp-0x10的偏移是0x408,后面我们泄露出栈地址,加上这个偏移就是shellcode的地址了在2.32版本,ptmalloc引入了PROTECT_PTR,即保护指针的概念,其指针是被异或加密的,如果对系统的堆地址一无所知,将无法正确解读泄露的指针的真实值。tcache_put当然也引入了这一机制,其next指针(fd)将会与entry首块进行异或加密。结合两个代码,其实就是e->next =((&e->next) >> 12 ) ^ tcache -> entries[tc_idx]触发这个 PROTECT_PTR 宏,有两种情况:第一种是当前 free 的堆块是第一个进入 tcache bin 的(此前 tcache bin 中没有堆块),这种情况原本 next 的值就是 0 。第二种情况则是原本的 next 值已经有数据了。如果是第一种情况的话,对于 safe-Linking 机制而言,可能并没有起到预期的作用,因为将当前堆地址右移 12 位和 0 异或,其实值没有改变,如果我们能泄露出这个运算后的结果,再将其左移 12 位就可以反推出来堆地址,如果有了堆地址之后,那我们依然可以篡改 next 指针,达到任意地址申请的效果举个栗子:当前tcachebins是空的我们free一个size为0x100的chunk,可以看到这个chunk加密后的next指针是0x000000055924f45f。还是看e->next =((&e->next) >> 12 ) ^ tcache -> entries[tc_idx]这个代码,首先&e->next就是next指针的地址,也就是0x55924f45fbd0,再就是tcache -> entries[tc_idx],在我们free之前,这个tcachebin是空的,所以就是0了,也就是变成了e->next =((&e->next) >> 12 ) ^ tcache -> entries[tc_idx] = (0x55924f45fbd0 >> 12) ^ 0 = 0x55924f45f。这对应第一种情况。接着我们再free一个同样是size为0x100的chunk,首先&e->next就是next指针的地址,也就是0x55924f45fcd0,再就是tcache -> entries[tc_idx],在我们free之前,这个tcachebin是有一个chunk的,指向的是0x55924f45fbd0,也就是变成了e->next =((&e->next) >> 12 ) ^ tcache -> entries[tc_idx] = (0x55924f45fcd0 >> 12) ^ 0x55924f45fbd0 =0x559716610f8f 。恢复 next 的宏为 #define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr) ,其实这个宏最终还是调用了 PROTECT_PTR ,原理就是 A=B^C ; C=A^B所以我们要想解密next指针,就变成了e->next = (&tcache -> entries[tc_idx] >> 12) ^ tcache -> entries[tc_idx]。这里的&tcache -> entries[tc_idx]其实就是&e->next以第二种情况为例子:e->next = (&tcache -> entries[tc_idx] >> 12) ^ tcache -> entries[tc_idx] = (0x55924f45fcd0 >> 12) ^ 0x559716610f8f = 0x55924f45fbd0所以只要我们成功泄露&e->next的值或者heap基址,就可以通过设置加密的next指针为e->next = ((&e->next) >> 12 ) ^ target_addr ,实现申请任意地址的chunk原理:利用off by null修改掉chunk的size域的P位,绕过unlink检查,在堆的后向合并过程中构造出chunk overlapping。例子:参考40dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6@1N6s2c8S2L8X3N6Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7X3y4Z5K9i4k6W2i4K6u0r3x3e0R3@1y4g2)9J5c8R3`.`.模板保护全开。。还是一个菜单题,共有四种功能,前三种分别对应写、删除、打印日记,最后一种对应退出程序,使用exit(0)退出。当执行exit函数时会触发<font style="color:rgba(0, 0, 0, 0.87);">_IO_flush_all_lockp</font>该函数首先需要输入一个小于0x64的索引,且需要unk_4040[index]为空,unk_4040用来存放malloc返回的用户地址。接着就是输入申请的内存大小v2,最终申请的内存大小会在v2基础上加上16。申请完
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-290174.htm
[原创]HGAME 2026复现(1)
261 浏览
1 回复
感谢分享!