程序先是打开了/flag文件
只有输入金额为0xff时才能进入下面的逻辑,否则关闭flag并清空token变量 随后将flag的头打印出来,将内存中的flag清空,并允许我们向user.log写入内容由于使用了scanf("%s"),导致可以写入无限长内容 当输入长度超过0x100,便可以覆盖format 可以自由格式化字符串,但由于是data段的格式化字符串 但这里可以输入0x10字节,所以可以将got表写在栈上,但是由于token清空我们无法再次进入scanf("%s")如果我们将exit的got表改写为main函数,在退出时便可以再次进入main中,token便会刷新,我们又可以控制格式化字符串参数然后可以使用格式化字符串泄露堆地址由于flag是文件,所以被串在_IO_list_all的链表中,位于堆上,其中的文件指针中包含flag的内容,只要稍微调试便可以找到flag字符串相对于堆地址的偏移再次使用exit退出,最后一次格式化字符串将flag的地址写在栈上并使用%s参数泄露flag内容,便可以获得flag 在leak中,可以输入0x28字节数据,泄露libc地址 随后进入一个堆管理菜单
其中edit,show,free都没有什么用add功能只能使用一次,但是没有限制申请堆块的大小 这里的问题是没有对malloc大小进行检查,边直接进行read与ptr+size-1的置零当malloc大小过大会malloc失败并返回null所以当我们申请addr+1大小(足够大),便可以实现对任意addr的null写由于我们已经知道了libc地址,便可以实现对任意libc地址的null写libc的攻击面(got表,IO结构体,IO_list_all)其中2.39中got已经不可写了,使用null攻击IO_list_all远远不够如果攻击stdout结构体,最多只能泄露,但是我们已经有了libc地址所以只剩下stdin可以攻击 在写入null时,stdin结构体的read_base为0x74ee68c03963,read_ptr为0x74ee68c03964如果在read_base的末尾置0,下次走IO的输入函数便可以复写stdin结构体中read的三枚指针,可以在libc中任意长度写 由于开启了沙箱,使用orw绕过沙箱即可直接在IO结构体中FSOP,使用setcontext进行栈迁移即可执行ROP 在leak中,可以输入0x28字节数据,泄露libc地址 随后进入一个堆管理菜单 其中edit,show,free都没有什么用add功能只能使用一次,但是没有限制申请堆块的大小 这里的问题是没有对malloc大小进行检查,边直接进行read与ptr+size-1的置零当malloc大小过大会malloc失败并返回null所以当我们申请addr+1大小(足够大),便可以实现对任意addr的null写由于我们已经知道了libc地址,便可以实现对任意libc地址的null写libc的攻击面(got表,IO结构体,IO_list_all)其中2.39中got已经不可写了,使用null攻击IO_list_all远远不够如果攻击stdout结构体,最多只能泄露,但是我们已经有了libc地址所以只剩下stdin可以攻击 在写入null时,stdin结构体的read_base为0x74ee68c03963,read_ptr为0x74ee68c03964如果在read_base的末尾置0,下次走IO的输入函数便可以复写stdin结构体中read的三枚指针,可以在libc中任意长度写
由于开启了沙箱,使用orw绕过沙箱即可直接在IO结构体中FSOP,使用setcontext进行栈迁移即可执行ROP 程序的逻辑十分简单,可以溢出0xd8字节,存在沙箱且有一个小后门(将读入的字节数写入栈上) 可以看到沙箱留下了wr和Sigreturn以及一些无关紧要的系统调用 通过dockerfile可以分析出flag作为环境变量,被放在栈上省去了orw中open和read两步,我们只需要获得栈地址再遍历栈就可以获得flag第一步,由于我们没有栈地址,所以应该先进行栈迁移到bss上想要使用SROP又需要syscall地址,而syscall只能来自于libc所以应该在bss上写入libc地址那么可以在栈迁移后调用start函数,将大量地址写入bss段 可以看到已经有不少libc地址被写入bss段下一步想使用magic_gadget将任意libc地址修改为syscall地址,但是使用magic_gadget的前提是控制rbx与rbp寄存器,想控制这两个寄存器则需要pop rbx;ret这样的指令我们有需要从libc中获得,通过观察可以发现 在bss段的libc地址的附近有这样的gadget,但是与libc地址相差了2字节,由于libc偏移的页偏移机制,只有1.5字节是固定的,所以这里需要爆破半字节(1/16)通过末位覆盖达成这个目的(p16(0xa2dc))注意这里的gadget使用的是从add rsp,0x38开始的,至于为什么要这样选择要看后续利用此时这个libc地址变成了可以控制rbx/rbp的gadget,我们还需要在这个gadget的高地址处布置magic_gadget与其他ROP,所以要把target设置为这个libc地址的下一个bss地址 从此处开始布置新的ROP链,此次布置要包含两个部分这也是刚刚选择控制rbx的gadget时要带上一段跳栈汇编add rsp,0x38的原因,需要保证两个ROP不能重叠此后向低地址垫入ret滑梯,便可以将RSP滑到布置了好久的ROP上,执行将libc地址修改为syscall地址的ROP
经过这个ROP,bss上的Libc地址被成功改写为syscall地址,离我们使用SROP进了一步我们在写SROP前要先梳理下目标结构 高地址处要写入SigreturnFrame,低地址要写入ret滑梯,并且要在滑梯中插入一个read.plt(控制rax用)上次布置的ROP允许我们直接向syscall的高地址写入内中我们除了要写SigreturnFrame,还要写入syscall后的后续利用:我们通过SROP获取libc地址后对栈遍历的ROP我们先写入然后通过刚刚的leave将RSP移到syscall的低地址,再调用read,便可以向低地址到syscall中间填充ret滑梯最后一次栈溢出,便将RSP沿着ret滑梯,滑倒了syscall上 上半部分是ret滑梯,下半部分是后续的SigreturnFrame与后续ROP的重合结构
SROP转为write调用,将got表打印出来,获得了libc地址此时再次调用read,read会根据我们刚刚设定的rbp确定rsi的地址,所以SigreturnFrame中要确定rbp的位置 通过调整rbp大小,我们可以直接覆盖read.plt的返回地址实现最后一次ROP,此时我们得知了libc地址,可以打印出envrion成员(栈地址),再调用write函数对栈进行遍历 可以最终获得栈上的环境遍历FLAG baby_stack是最最最简单的栈溢出,就不写了 通过gdb调试发现可以泄
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288947.htm
[原创] 2025 强网杯和强网拟态部分题解
213 浏览
1 回复
包含了两场比赛的wp,内容充实,感谢分享~