网鼎杯pwn4
中午发的题,一看数据库心想没学过去看那道webpwn,没学过,rust,没学过。事已至此,先吃饭吧。
吃完饭后还是看了一眼附件,花了20分钟分析觉得能打,密码爆破+加密+堆orw,想到了之前打过的silverwolf,就是打tcache_struct+setcontext来绕过堆orw。最后还是卡在加密,找gpt要了两次脚本都没过,现在来看实在是gpt差距。最后争不动去上数电课了,整场比赛就出了一道300分的pwn签到,最战犯的一集
爆破账号密码

看到用户名检测后续想到爆破,第一步和绕过strncmp的原理相同,他只会比较你输入的字符串长度范围内的两个字符串有没有差异,所以我们可以逐字节爆破。但是之后他还是会检查两个字符串长度,所以我们需要爆破完整的账号。
一般账号密码只会使用键盘上有的字符,可以预先准备一个爆破字典,我的方法是直接爆破ascii 33-126范围的字符。可以直接用man命令查看ascii码对应的字符
1 | main ascii |
爆破脚本:
1 | for i in range(10): |
对于密码的爆破也同理
1 | p.sendlineafter("Input your username:",username) |
这个程序如果账号密码不正确会直接退出,还会贴心的回到输入账号密码的入口,所以我们不必每次都p.remote()和p.close()。但是每次爆破密码不成功都要重新输入一次账户。
最后爆破出来用户名是”4dmin”,密码是”985da4f8cb37zkj”。
在本地调试的时候记得自己添加一个password和username文件测试爆破脚本是否有效。
存储内容加密

之后就是经典的增删改查,在释放堆块时有uaf漏洞。但是他在每次存入和修改数据时都会先把数据输入,再对数据长度的内容加密,每次查看数据时再把数据取出来解密,显示数据内容再加密放回去



加密的过程看了一遍之后不能说是完全看懂,至少也是一窍不通。不仅是泄露释放堆块后的main_arena偏移还是堆地址,还是填入伪造地址都要绕过加密。
但是从最后的异或和解密来看可能加密和解密过程都是相同的,一开始我想能否通过重复填入加密后的内容得到加密前的内容,不用写解密脚本了?程序遵守填入/更改就加密,取回解密展示再加密放回,所以除了写解密脚本没有别的办法泄露内容。
只好把代码扔给了ai生成如下解密脚本(没有ai的世界是多么寸步难行……或许是我太捞了
1 | def sub_1152(state_array, output, length): |
但是我的解密脚本似乎有问题,当输入比较长时,就会发生这种情况:

后半段完全变成乱码了:-( 比赛的时候急得我上蹿下跳,完全忘了队里的re手
https://blog.csdn.net/Mr_Fmnwon/article/details/143355594
事后复现嫖走了这位师傅的解密函数: -)

好耶。
原来事rc4加密。这rc4加密真rc4啊。
泄露堆地址与libc地址
glibc版本2.27,引入了tcache还没有其他什么检测,甚至可以通过直接覆盖fd的方式请出任意已知位置的堆块,几乎是板子题。但程序沙箱禁用了exeve和exeveat

对于这个版本的堆orw,我想到了之前做过的ciscn2021 silverwolf,同样也是打tcache利用setcontext,但是那个题申请堆块大小有限,所以必须通过控制tcache_struct拼接两个堆块来填入足够长度的orw链。比赛的时候我局限了,照着silverwolf的脚本就是一顿复制粘贴,但实际上这个题对于堆块大小没有限制,完全可以不用打tcache_struct。
在通过账号密码之后用pwndbg看一眼堆会发现乱得一,这是因为沙箱的原因

我们首先通过tcache泄露堆地址,通过uaf泄露地址时内存中的确是原地址,但程序执行时先会取出解密再展示,所以我们还是要把取出来的数据走一遍解密
1 | add(0, 0x70, b'aaaa') |
然后我们需要填满tcache,再释放一个相同大小的堆块放入unsortedbin泄露libc基址。这个堆块需要大于0x70,不然会放入fastbin,同时为了防止沙箱残留的堆块造成影响,所以我选择了0x150大小的堆块。
1 | for i in range(9): |
setcontext & orw

如图是附件给的动态链接库里setcontext函数的样子,在setcontext+53的位置开始可以通过rdi的偏移设置各个寄存器,而在free一个堆块时会将rdi指向该堆块的位置。先把预备free的堆块上设置setcontext的frame,其中rsp设置为free_hook+8,然后把(setcontext+53)+(orw)写到free_hook上,这样就可以直接返回到orw
1 | '''frame = SigreturnFrame() |
我不直接用SigretureFrame()的原因是本地调试时会出错,所以人工对着偏移设置的各个寄存器:-(。记得在exp脚本开头设置面向操作系统和架构
1 | context(os='linux', arch='amd64', log_level='debug') |
记得预备free的堆块,就是设置setcontext上下文的那个堆块要申请的大一点(>0xa0),不然会导致后面寄存器写不上去的情况,同时也可以方便一点之后的orw,提前把open的寄存器设置好,当然求稳也可以写到orw里。
总exp
1 | from pwn import * |