2022DASCTFXSU三月春季挑战赛-pwn-wp
2022DASCTFXSU三月春季挑战赛-pwn-wp
今天终于有空来写下wp
,比赛那天恰好有事,所以就上午做了下题。最后一题的CVE-2022-0185
在学习中,未完待续。
- 2022-03-31: 更新了
wedding
的exp
,可打远程。 - 2022-04-09:忘记更新了,补上第三题。
checkin
这题最开始想用one gadget
去做,后来发现libc-2.31
的one gadget
都比较严格,于是换成puts
泄露再读取输入执行system("/bin/sh")
。
checksec
漏洞点
栈溢出,可溢出0x10
字节,覆盖掉rbp
和ret
。
利用思路
观察0x4011BF
处的汇编可知,rax
等于rbp-0xb0
,然后在0x4011CB
处将rax
赋值给了rsi
,因此,只要控制了rbp
,相当于可以在任意地址处写入0xb0
个字节。
至少两种思路,主要后面不一样。
思路一:
-
栈迁移到
bss
段 -
控制
rbp
后再进入0x4011BF
,然后在bss
段上rop
-
使用
partial overwrite
修改read@got
,使其为syscall; ret
。这里由于read
的地址偏移为0xff0
,加个0x10
直接进位了,所以还有半个字节需要猜测一下,概率为1/16
-
使用
read
控制rax
为10
,并修改read@got
,随即利用ret2csu
执行mprotect(bss, 0x1000, 7)
-
跳转到准备好的
shellcode
执行获取shell
思路二:
- 栈迁移到
bss
段 - 控制
rbp
后再进入0x4011BF
,然后在bss
段上rop
- 使用
magic gadget
:add [rbp-0x3d], ebx; ret
,将setvbuf@got
修改为puts
的地址 - 泄露出
read
地址,计算得到system
地址 - 再次
read
读取输入,跳转执行system('/bin/sh')
即可
EXP
|
|
打远程:
爆破:
wedding
这题刚开始被libc
给坑了,最开始本地使用的是2.31-0ubuntu9.2_amd64
调试的,这个版本的file_jump_table
是可写的;但是远程给的是2.31-0ubuntu9.7_amd64
,这个版本的file_jump_table
都是不可写的。因为我最初使用的思路是改写stdout->flags
为/bin/sh
,修改_IO_file_jumps->_IO_file_xsputn
为system
去拿shell
,所以那天上午爆破了好久都失败了……所以以后,还是老老实实用给的libc
去调试吧。调试的时候建议关闭aslr
。
checksec
漏洞点
-
prepare
中没有校验offset
: -
revise
中没有校验index
:
建议把这个标识变量改一下,要不然wish/wlsh/w1sh
容易看花眼。。。
利用思路
题目给的条件为:
- 可以分配任意大小的内存
- 可以在任意偏移处覆盖,但是只能覆盖为
0x135
或者0x1314
,覆盖机会为3
次 - 可以在
heap
任意偏移处的指针写入8
或者3
个字节,各有1
次机会。
当然,上面说的任意也不是完全任意,受限于my_read
只读取8
个字节,所以实际能控制的偏移(数字)为:-9999999
到99999999
。
我们知道,在申请内存足够大,大概大于128K
的时候,会调用mmap
映射虚拟内存页,此时映射的虚拟内存页会位于libc.so
映射空间的上方。此时的偏移可控,也就是可以修改libc.so
上的任意的数据,修改的内容为2
字节,固定。
由于没有地址,朴素的想法就是先泄露地址,因此,打_IO_2_1_stdout_
结构体去泄露地址。有地址后就好办了,思路为:
-
申请大内存,利用任意偏移修改
stdout->flags
和stdout->_IO_write_base
,泄露地址并计算出PIE
基地址和libc
基地址 -
利用一个跳板,和两次分别写
8/3
字节的机会,修改change3
和change8
为小负数,这样就能继续写很多次。我选择的跳板在0x3e20
偏移处,第一次可以写8
个字节,修改为任意地址,第二次就能将change3
修改为小负数然后,使用
0x4008
这个跳板,就可以把change8
也修改为小负数 -
继续使用跳板,将
stderr->vtable
修改为_IO_str_jumps
;将_IO_2_1_stderr_+131
处修改为sh;
;将__free_hook
修改为system
;将stderr->flags
修改为0x80
;最后把bss
段上的stdout
修改为_IO_2_1_stderr_
。接着,在调用puts(xxx)
的时候,会调用stderr->vtable->_IO_file_xsput
,实际调用的是_IO_str_finish
,接着调用free(fp->_IO_buf_base)
,就是调用system("sh;")
EXP
|
|
这里使用0xfffffffffffffff8
来找地址,有代码段地址和libc
地址:
本地多试几次就出来了:
远程不知道是不是偏移不对,stdout
那里一直没有泄露,不知道啥情况。所以,这个大概率是打远程失败的非预期解了。
wedding-again
今天装了个ubuntu-20.04
的pwn
环境,重新审视一下这道题,发现上面那个EXP
打远程是有问题的。正好有很多师傅咨询我这道题的解法,我就更新一下能打通buu
的exp
。
存在的问题
上面的exp
的调试环境是:ubuntu-18.04 + patchelf + libc-2.31-0ubuntu9.7_amd64.so
,其实我还是被patchelf
给坑了……
实际上,上面利用的0x3e20
这里的跳板是不可行的,远程环境上不可写:
也就是没了code_base + 0x3e20
这个跳板。
补充漏洞点
说一个新的漏洞点,其实不完全算漏洞:
prepare
的时候,如果满了8
个,其实还可以任意次数分配内存,但是此时的v2
不来源于i
,而是未初始化变量,调试后发现为0
,也就会一直覆盖第一个堆指针。下面的利用方法会用到这一漏洞。
新的利用思路
总体的利用思路与上面是一样的,分成四步走:
- 打
stdout
结构体泄露地址,此时需要code_base/libc_base/heap_base
三个地址,都可以泄露出来 - 想办法修改
change3
和change8
- 寻找到一个可以任意地址写的跳板
- 打
stderr
和改写__free_hook
,控制程序流走到_IO_str_finish
然后get_shell
其中,第一步和第四步和上面的EXP
一样,重点在于第二步与第三步,即如何修改change3
和change8
这两个变量以及寻找一个新的跳板。
首先说一下第二步如何做:
我们还是利用这里的一个指针:
这个指针指向了自己,相对于下方heap
的偏移为-19
,计算出对应的size
的地址为0x4014
,内容为0
。也就是说,如果第一次revise(-19, xxx)
,只能写3
个字节,可以指向数据段的任意位置。但是,考虑到我们还剩一次prepare
的机会,还能任意偏移修改内容为0x135/0x1314
。
这里可利用数据段与堆挨得很近的特性,在堆上申请一块内存,反向溢出到数据段,那么,就能修改code_base + 0x4014
处的内容,将其修改为大于0x999
。也就能再一次revise(-19, xxxx)
,此时就可以写8
个字节。
因此,这里的修改方法为:
- 第一次修改
__dso_handle
,利用revise(-19, xxx)
部分写3
个字节,使其指向code_base + 0x4050
- 利用
prepare
,修改code_base + 0x4014
的内容大于0x999
- 此时利用
revise(-19, xxx)
,写8
个字节,将change3
和change8
均修改为小负数
此时,可以任意次数执行revise
。
再来看第三步如何做:
我们还是利用数据段和堆挨得很近的特性,向下溢出。第三次分配的内存在堆上,假设其地址为addr1
,如果我们要溢出到这个地方,且将addr1
作为size
,计算出索引:index = (addr1 - (code_base + 0x4060)) / 4
,那么,在该索引下对应的heap
的地址为:overflow_heap_addr = (code_base + 0x40a0) + index * 8
此时已经有了任意次数的revise
,可以修改*addr1 > 0x999
,然后利用上面说的任意次数内存分配到overflow_heap_addr
,修改*overflow_heap_addr = target_addr
。此时revise(index, xxx)
,即可往任意地址(target_addr)
写任意值,并且可以写8
个字节。
截图如下:
这里调试的时候没有开aslr
,所以没有分配满。
EXP
|
|
打buu
的远程
wedding-again-again
和tomato
师傅交流了一下,补充一个新的利用思路。
新的利用思路
前面的过程还是一样的,首先需要泄露地址。然后,攻击mp.tcache_bins
这个变量。这个变量可以使得很大的堆块也被tcache bins
所管理。例如,利用heap_base+0x298
处的值作为size
,计算出如果放置到tcache bins
中管理,chunk
的大小为0x1460
,对应的堆地址为heap_base + 0xab0
。然后,可以利用两次revise
修改change8
为小负数,最后继续使用相同的办法往任意地址写任意值。
这里我用了一个小trick
:
|
|
利用tcache bin poisoning
分配到code_base + 0x4048
处,利用e->key = NULL;
,可以将change3
和change8
刷为0
。就可以再执行两次revise
。然后还是利用__dso_handle
将change8
修改为小负数,如图:
这里选择_IO_2_1_stdin_
,避免e->key = NULL
修改掉了stdout->flags
。
EXP
|
|
综上,pwn
题的环境真的非非非非非常影响解题。
SU_message
这里有出题人的出题报告:https://kagehutatsu.com/?p=551
好吧,忘记把这一题更新了,有位师傅提醒我才想起来还有这事。
基本研究完CVE-2022-0185
后,这一题利用就很简单。可以用msg_msg
结构体构造堆喷,之后可以构造任意地址写。由于未开启KASLR
,所以都不需要泄露地址,但是我自己下来做的时候还是按照开启kaslr
来做的。
简单总结一下利用思路:
msgsnd
布局msgmsg
和msgmsg_seq
结构体,堆喷- 修改
msgmsg->mts
泄露地址 - 修改
msgmsg->next
任意地址写,这里可以使用userfault
增加竞争成功的几率 - 修改
modprobe_path
进行提权
EXP
exp
如下,helpful.h
,记录了一些常用的函数:
|
|
exp.c
:
|
|
本地的效果如下:
最后打远程使用的exp.py
:
|
|
基本试个几次就出来了
远程打:
tips
补充一个小技巧,有时候使用普通用户调试内核题,发现目录的权限不对,比如下面这样:
那么只需要使用fakeroot
命令,切换到一个fake root
账户(原理其实和docker
差不多,有namespace
的隔离),再启动就正常了:
引用与参考
1、My Blog
2、Ctf Wiki
3、pwncli