# Pwn
# challenge UserManager
菜单题 musl
二叉树
add 里面有 uaf。
连续两次 id 一样。
用的 calloc,得想办法 leak。
堆风水先把 avail chunk 给消耗完。再 free user。 再分配的时候就可以完成用 user 占位 name。然后用 name 来 leak libc 和 elf base
再用 name 反过来占位 user,写 secret 的地址就可以 leak 出 secret。
dequeue 打类似 unlink 攻击,打 io file,fsop。堆风水,注意到 parent 指针会有出现解引用的问题,重新调整一下就可以。
1 | # encoding=utf-8 |
# challenge houseofcat
开局是一个类似于 httpd 的代码快,正确后才能进入堆菜单,这里就不多说了
最开始需要对 is_run [0] 进行初始化为 1:
1 | run(b'LOGIN | r00t QWBQWXFaadmin\x00 ') |
然后之后每次调用菜单的 pyload:
1 | def run(payload): |
漏洞出在 delete 有 UAF,有两次 edit 机会,能构造两次 largebin attack (不用修复 largebin 链表,用 0x400 大小域和 0x440 大小域即可)。glibc2.35 的话,由于这道题 stderr 指针在 libc 上(应该是编译时加上了参数,否则正常 stderr 指针不会在 libc 段上)。因此两次 largebin attack 分别打 guard (glibc2.35 指针异或)和 stderr 指针,伪造 stderr 指针结构体即可,最后触发 stderr,调用 setcontext 进行 orw。
这道题本来是很正常的 house of emma,但是我卡在如何触发 stderr。
因为两次 edit 之后,构造了两次 largebin attack 后,没有剩余的 edit 次数了。
以往的 house of emma 都是通过 UAF,先 free 掉一个靠近 top chunk 的 unsortedbin p1,然后申请一个小于原来 p1 的堆块,这样 top chunk 就留在原来那个 UAF 的 p1 里,可以通过 UAF 更改 top chunk size,使其落入 top chunk size 不够分割的情况,这时候就会检查:
此处会对 top_chunk 的 size|flags
进行 assert 判断
-
old_size >= 0x20;
-
old_top.prev_inuse = 0;
-
old_top 页对齐
然后可触发 malloc_assert,其中的 fxprintf 函数会调用 stderr。
不过这道题我们没有多余的 edit 机会了。
我思考之后,想到一种可能,若 largebin attack 和修改 top chunk size 可以同时进行的话。并且查阅 glibc 源码后发现,处理 largebin attack 部分源码在对 top chunk size 进行判断之前。
参考 CSDN 上一篇关于 house of apple 博客里的堆风水启迪了我:
可以先 malloc 0x480,0x480,(这两都紧挨着 top chunk) 然后都 free 掉,再 add 0x460,0x500 之类的,构造出一个 UAF 指针,指向我们下图中的 chunk3 位置。通过 chunk1 的 edit 功能可以改掉 bk_nextsize 和 chunk3 的 size,那就伪造一下 chunk3 的 size(用 add 函数输入内容伪造,不用 edit),改成 chunk3 距离 top chunk 的距离,然后 free 掉 UAF 的 chunk3 指针,这样就能使 top chunk 合并到 chunk3 附近。
然后就能用 edit 函数同时改 bk_nextsize 和 top chunk size。
然后直接干!
最后沙盒禁用了 execve,只能 orw,但是甚至 read 只能 read fd 为 0 的,因此需要先 close (0),然后再 orw,新打开的 flag 文件文件描述符为 0。
exp:
1 | from sys import stderr |
# challenge yakagame
用 ida 打开 yaka.so,找到类似于主函数的函数,在一堆去符号函数中间,代码最长的那个(这里我重命名为 maybethis 了):
同时发现了后门函数:
上 Ayaka 看雪上温习了一下 llvm,之前国赛也出过。
程序上刚开始 malloc 了一个 cmd 的堆块,里面存放了一个初始的字符串。
分析一下各地方功能:
-
程序必须包含在 gamestart 名函数中才能进入主循环,意为,最外层函数名需为 gamestart ()
-
gamestart 函数中有几个小的函数体:
①"fight" 函数:参数个数为 1,将传入的参数作为 idx,将该 weapon [idx] 与 boss 比较,判断是否赢,赢了的话操作 score = weapon [idx]-boss。输了的话没有影响。无论输赢都会判断是否进入后门函数。进入后门函数的要求为 score>0x12345678。
②"merge" 函数:参数个数为 2,将传入的参数作为 idx1,idx2,执行 weapon [idx1]+=weapon [idx2]
③"destroy" 函数,参数个数为 1,将传入的参数作为 idx,令 weapon [idx]=0
④"upgrade" 函数,参数个数为 1,将传入的参数作为 count (char 大小,有符号位扩展),对每一个 weapon [k] 执行 weapon [k]+=count
⑤四个对 cmd 进行整体字符串操作的函数。同时会减 boss 的值。
⑥else:当函数名不等于以上所有时,会将该该函数名加进 map 中,同时往 weaponlist 指定偏移写一个 char 类型参数。这里 map 和 cmap 对应的位置一样,参考 glibc 中的 srand 和本地起的 srand 同步。可以通过这种同步来进行预测,因此本地写一个 c map 来模拟。
1 |
|
这样就能知道程序里 map 对应的位置了。
我原本以为漏洞出在这里:
其中 boss 是 unsigned int,v53 为 int,并且 <= 是 signed 比较,那么理论上就可以通过不断调用 "tiandongwanxiang" 函数来将 boss 整数溢出,这要 v53 和 boss 有符号比较的时候,v53 肯定大于 boss,而 boss 是一个负数,*score 就能刷上来。
然后通过那四种函数组合,按理来说是能够爆破出来 /bin/sh\x00 的(将字符串替换成 /bin/sh\x00)。
于是丢给战队逆向手,写了个脚本没跑出来。。。
1 | start = 0x6668936D277B6892 |
实际执行的时候,取 boss 值放到 ecx 里面,然后比较时是用 rcx 做的比较,相当于无符号扩展。此时,boss 值不可能为负数,那这种方法就不可行了。
又看了看其他地方有没有符号位扩展的洞,记忆里 upgrate 有,但是没什么用,符号位扩展是针对写进的字符的,不是针对于 idx。
最后发现,v33 是 char,并且这里是对于 v33 是带符号扩展,通过不断 ++v3 可以让 v33 带符号扩展后成为负数。
同时 weapon 数组上方有 score 和 cmd 的指针。
那就通过自己写的 c++ map,来得知程序中 map 的位置,然后负索引部分覆写 score 指针,让她指向一个大的值。本来我还想着在堆附近找一个大一点的值,不过后面一想,随便找了个值错位一下就变得很大了。
改掉 score 指针后,因为逆向手没有跑出来,所以我这还是得改 cmd 指针。本来想着挺简单的,就 add weapon 的时候,c 艹会申请堆块,那把函数名改为 sh 即可。但是调试发现这两堆块地址差太多了,部分覆写没用。爆破了一会也没爆破出来。
不过注意到程序提供的 opt-8 没有开 pie,嘿嘿,那就用她的字符串。
题外话是我最开始运行的时候报错了,后来一看是题目提供的 clang 和我版本不一致,下了个 clang-8。
1 | clang-8 -emit-llvm -S yakagame.c -o yakagame.ll |
Exp:
1 | #include<stdio.h> |
# 强网先锋
# challenge devnull
fgets 长度刚好合适,但是 fgets 会自动添加一个 \x00。所以有 off by null 可以修改 fd。
所以接下来为 read (0,v3,0x2c)。
可以覆盖栈上的 buf 指针以及 rbp,rip。
第一时间考虑栈迁移,不过这道题动用 mprotect 改了所写页的权限为 1,因此不可读。
看看周围唯一可读的就是堆地址。那就爆破。
知道往哪栈迁移之后,我们需要明确栈迁移之后能做什么。首先 close (1)+mprotect 设置页表权限让我们重回代码段不可行。所以栈迁移得一次到位。并且 close (1) 后并不好泄露 libc 基址。
同时程序没开启 pie 保护因此可以利用程序中自带的函数,第一时间想到用 mprotect 改权限然后 shellcode。同时发现栈迁移完 rdx 为 7,直接上 mprotect!
因为 gadget 比较少,用程序自带的原生 shellcode。
从这里开始可以控制 rsi,但是 rdi 和 rax 关联。
去找 gadget:有一条很好用:
用这条控制 rdx 即可。
然后就是 shellcode 调用新的 read,然后 orw,write fd 用 stderr 的 2 即可(这里 read 的 rsi 需要离远一点,不然会崩溃)本来打算直接 getshell 之后用 exec 1&>0 来重定向的,不过不知道为什么这样不可行。
exp:
1 | from pickle import TRUE |