WriteUp-长安杯2021-baigei
warning
免责声明
本文所述 PWN 均属 CTF(Capture The Flag)参赛行为或赛前训练行为.笔者所 PWN 的对象均为 CTF 比赛或练习中平台方提供的靶机.
本文意在分享网络安全与 CTF 相关技术与技巧,共同提升实力.
请本文读者谨记相关法律法规与政策.读者需为自身行为承担相应的法律后果.笔者(Y7n05h)不为读者的行为承担责任.
基本分析
1 | filetype: ELF64 |
题目提供的 libc 附件的版本是 2.27-3ubuntu1.4_amd64
,SHA-1
为:46e93283ff53133360e02a73ae5b5ba375410855
.
程序分析
下面是通过逆向工程分析得到的伪码:
1 | int Memu() |
除了代码之外,全局变量的内存分布如下:
1 | .bss:0000000000202060 ; __int64 Size_Arr[16] |
漏洞定位
本题的漏洞还是有些隐蔽的.
相信各位师傅在做本题的时候一定会关注 Edit()
1 | if ( nbytes <= 0 || nbytes >= (unsigned __int64)Size_Arr[v1] ) |
这里这处可疑的 强制类型转换 实在太可疑了.nbytes
的型别很容易推断出是 int
.
接下来推断 Size_Arr
的型别为:
Size_Arr
中的值只有一个来源就是 Alloc()
中的:
1 | Size_Arr[idx] = nbytes; |
对应的汇编代码是:
1 | .text:0000000000000A48 ; 12: Size_Arr[idx] = nbytes; |
可以看到 movsxd
也就是 nbytes
被符号扩展为 Size_Arr[idx]
.
那么就能推断出 Size_Arr
的型别为:int64_t[]
.那么
问题来了:Edit()
中 nbytes
和 Size_Arr[v1]
的型别分别是 int32_t
和 int64_t
,两者比较大小时,若无强制类型转换,编译器应当由隐式类型转换规则对 int32_t
进行符号扩展,也就是将 int32_t
转换为 int64_t
,然后比较根据有符号数的比较规则比较两者大小即可.
为了验证,可以查看汇编代码:
1 | .text:0000000000000C0A ; 13: if ( nbytes <= 0 || nbytes >= (unsigned __int64)Size_Arr[v1] ) |
在汇编代码中能看到这处语句中对 nbytes
先进行了符号扩展:
1 | .text:0000000000000C10 mov eax, dword ptr [rbp+nbytes] |
但 rdx
与 rax
的比较却使用了比较无符号数的汇编指令 jnb
.
这反常的行为提醒着需要提高警惕.
所以笔者就猜测此处应该存在整数溢出的问题.
但看起来出题者通过检测 nbytes <= 0
堵住了 nbytes
发生整数溢出的可能.
那么,Size_Arr[v1]
能发生溢出吗?
确实能,但不用也行……
回看 Alloc()
1 | nbytes = read_Int(); |
会惊讶的发现,读取道德 nbytes
被直接写入了 Size_Arr
,写入之后才验证数据是否合法.
这意味着可以通过执行 Alloc()
来任意改变 Size_Arr
中的元素.
例如在对 idx 为 0 分配 0x20 的空间后,能通过对 idx 为 0 再次执行 Alloc()
,输入 size 大于 1024,实现将 Size_Arr[idx]
修改.
接下来就能通过 Edit()
实现堆溢出.
好了,这就是本题可供利用的漏洞了.若说整数溢出,唯一于此相关的就是:在对 idx 再次执行 Alloc
输入 size 时可以出入 -1
,这样在后面调用 Edit()
时就能输入任意的整数作为 size 进行 edit 了.
但不利用这点也完全能解出本题(笔者也想不通出题人为什么在这里放一个整数溢出的 bugs,或许就是为了干扰答题者的思路?).
解题思路
上面说了漏洞所在.下面聊聊解题思路吧.
首先,利用 Unsort Bin Attack
泄露 main_area
的地址,进而泄露 libc 基址.
其后,利用 tcache poisoning
劫持 __free_hook
为 system
,free
写着 /bin/sh\x00
的内存即可.
通过逆向工程得到的伪代码能轻易写出:
1 | from pwn import * |
下面申请通过 #0 的溢出能将 #1 的 size 修改为 1-5 的 chunk size 总和.#6 是用来防止 #5 与 top chunk 相邻.
1 | Alloc(0, 0x10, b"123") |
通过这一步,libc 认为 free 掉了一个大小为 0x500 的块,这个块超过了 tcache 的最大值,所以被放进了 Unsort Bin.chunk 的 fd 和 bk 均指向 main_area+96,通过这里得到 libc 基址.
就是要泄露 fd 指针了.通过 show #0 泄露 fd.
1 | Edit(0, 0x20, cyclic(0x20)) |
好了,泄露部分完毕,只剩下使用 tcache poisoning
劫持 __free_hook
的部分了.
1 | payload = cyclic(0x18)+p64(0x501) |
这次分配中,将 0x500
的 chunk 切分出了 0x100 的部分.通过 Delete 掉分配的 chunk,它进入了 tcache
.
1 | payload = cyclic(0x18)+p64(0x101)+p64(__free_hook_Addr) |
再次利用溢出,修改 tcache 的 next.并通过第二次 Alloc 获得指向 __free_hook
的指针,并 __free_hook
改为 system_Addr
.
1 | Delete(3) |
最后,利用被改成 system_Addr
的 __free_hook
,free
掉指向 /bin/sh\x00
的指针.
解题完毕.
完整 exp
1 | from pwn import * |