题目信息
题目链接
https://github.com/ctfs/write-ups-2014/tree/master/codegate-preliminary-2014/weirdsnus
准备环境
1 | $ echo HEHE_I_DONT_KNOW_YOU > admin_passwd |
基本信息
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070101.jpg)
静态分析
x86二进制文件分析
使用IDA
- 重命名变量
- stack上的变量,全局变量等,考虑他们在哪里被使用
- 确定变量的size
- 特别是char[]类型的数组
- 确定结构体的结构
- IDA中可以定义结构体成员
- 猜测函数功能
- 根据变量名,变量size,结构体结构等,可能看出函数功能
- 重命名函数
- 根据函数的功能重命名函数
- 发现处理不当(错误)的地方
- 找出可能利用点
重复以上流程,找出漏洞点,分析编写exploit
main
整体大概是这样
- 从函数定义,函数实现,参数传递,canary相关等进行分析
- 复杂的处理可以从library call为起点确定变量
- 因为library call有固定的参数个数和参数类型
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070102.jpg)
第一块
- 保存旧的ebp,在stack frame上更新当前的ebp,准备好函数使用的各种寄存器的值,准备好stack空间,做好各种准备,以便函数启动后能立即就绪
- 将函数参数(这里是argv)保存到stack上。x86中通常将参数重新保存在栈上
- 从TLS区域(gs+0x14)将stack canary复制到栈上
以上三个是常见模式
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070103.jpg)
- buffer是0x110-0x10=0x100=256字节
- memset的内部展开,”rep stosd”四个字节是memset高速实现
- stosd: EAX存储在[EDI]中
- rep: ecx!=0时[重复ecx-=N(这里是DWORD,N=4)]
- strlen是library call,需要注意signed和unsigned的区别
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070104.jpg)
第2到5快
- 图上已经做了函数重命名(分析后的结果)
- 函数结束时,释放使用的stack,还原用到的寄存器,还原ebp的值。如果函数有返回值,则返回值给eax
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070105.jpg)
转换成C语言代码
大概是这样:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070106.jpg)
password_check
第一块
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070107.jpg)
第二块~
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070108.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070109.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070110.jpg)
转换成C语言代码
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070111.jpg)
- memcmp的第三个参数len,是用户可控的
- 输入\x00的话,len(“\x00”)=0
- 这样就跳过了memcmp
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070112.jpg)
doit
第一块
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070201.jpg)
第三~块
- EAX范围检查按照unsigned,如果不在0x28~0x7a则nop
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070202.jpg)
第七~块
- switch 保存在.rodata区域的jump_table
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070203.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070204.jpg)
- 分支分析大概是这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070205.jpg)
- 如果argv[1]有’(‘, ‘)’,则可以调用更深的函数,但需要先bypass前面的认证
转换成C语言代码
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070206.jpg)
- 实际上signed比较函数也有bug,一般size比较都是signed,如果输入一个字节的负数(0x80~0xff),能够通过比较。但是没法进行后续利用
- case ‘z’那里的free(ptr)之后没有清空ptr,后续进行写入会造成Use After Free,但是这里也无法进行exploit
do_command_loop
- 可以看到一个malloc(16)的结构体
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070207.jpg)
- 结构体大概这样,定义后应用
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070208.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070209.jpg)
- 根据需要应用结构体偏移
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070210.jpg)
分析之后大概是这样
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070211.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070212.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070213.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070214.jpg)
转换成C语言代码
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070215.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070216.jpg)
do_command_loop的bug
- 存在两个bug
- 第一个bug,read_key(new_buffer, buffer_len),两个参数完全没关系,但是read_key内部将buffer_len作为new_buffer的size,buffer_len用户可控
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070217.jpg)
- 第二个bug,free(ptr)之后,没有将ptr设置为NULL,也就能够进行Use After Free
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070218.jpg)
- ptr != NULL的检查不能确定UAF。也就是说,如果UAF之后ptr用作其他用途,则可能修改func_ptr,之后可能调用func_ptr。
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070219.jpg)
漏洞思考
- 如何控制EIP
- 重写函数指针
我们已经有一个16字节的结构体:
1 | struct struc_1 { |
首先构造Use After Free
- do_A - malloc
- do_M - free
- free时,因为是16字节,会连接到fastbins区域
- LIFO,下次申请16字节内存时会重新使用这部分内存
- 寻找一个16字节分配以及任意写入的点
- 这里会根据我们指定的size进行malloc,之后read_key用到new_buffer
- getcwd()通过new_buffer读取当前工作目录,如果当亲工作目录path超过16字节,就能够覆盖函数指针
利用流程
大概是这样:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070302.jpg)
动态调试
- 测试运行
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070401.jpg)
- 已经控制了EIP
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070402.jpg)
getshell
因为有NX,shellcode比较困难,尝试system(“/bin/sh”)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070403.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070404.jpg)
- 常规方式需要将参数加载到stack上
- 第一个参数必定是0x0a,不能指定文件名
Use After Free
这是可能的利用方式
func_ptr overwrite + ROP
- 先在stack上准备好ROP
- 通过Use After Free,在func_ptr之前,pop×N; ret
- 这样当调用该函数时,popxN除去了多余的参数,执行ROP
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070405.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070406.jpg)
- command使用fgets()读取输入到buffer
- 因此可以考虑在换行前输入字符串
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070407.jpg)
- 但是这种方法只能读取10个字节
- 而这种ROP需要至少12个字节
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070408.jpg)
- 注意到do_D的buffer可以控制
- 考虑使用command D
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070409.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070410.jpg)
- 尝试
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070411.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070412.jpg)
- 计算偏移
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070413.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070414.jpg)
- 需要一个add esp, 0x144+α的gadget
- 找到一个add 0x140 + pop3回 = 0x14c的gadget
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070415.jpg)
exploit
大概流程是这样:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070501.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070502.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070503.jpg)
替换为system, /bin/sh的地址
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070504.jpg)
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070505.jpg)
img
可以看到测试能够getshell
实际运行
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070506.jpg)
img
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070507.jpg)
img
- 发生了SIGSEGV
- 实际上成功了
- system(“/bin/sh”),command从标准输入读取
- 但是,标准输入绑定input,因此无法读取到
- 因此,不执行任何操作,/bin/sh结束
- 之后因为stack已经被破坏,发生SIGSEGV
- 我们需要做一点处理
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019070508.jpg)
img
- (cat input; cat -)
- 这样能够向input继续输入
- 成功getshell