程序逻辑等价于检查
由于给了一组满足要求的 username, serial,所以是可以解的,计算 xor(public_serial, public_username, 'KCTF'.ljust(16, '\x00'))
即可。
Google 搜索 simpower site:pediy.com
可以找到一堆作者以前出的题的 writeup。 照猫画虎可以提取出一个 js,里面判断了输入开头必须是 Simpower91
。 后面看起来也没什么变化,还是传回 Delphi 写的程序里。由于代码完全没变,作者只是把一些明显的字符串改了改。 关键代码定位可以直接参照以前的 writeup,搜索里面的比较大的结构体偏移常数即可。
一些对应关系:
提取验证的时候执行的代码之后会发现这次作者完全执行的是一整段 simvm。 simvm 的每个指令长度是 100 字节的一个奇怪结构体。最开头的一个字节是 opcode。 在 474B80 处可以找到一些 mnemonic:
这里的顺序与 004742B4 函数里注册指令 handler 的时候的顺序是一样的,注册指令 handler 的时候传了一个 opcode 参数。 所以不用逆向指令 handler 也可以知道每条指令大概是干嘛的。
简单逆一下指令解码的部分,随便输出一下 check 指令。因为指令解码没逆清楚,估计除了 mnemonic 以外基本不对。 但这不影响,结构上可以很明显的看出来是在 check 啥,怎么 check 的。之后到对应的指令的 handler 上下断点把数据拿出来即可。
最后答案是 Simpower91a321
。
一个 bug 非常多的儿童 glibc & ptmalloc 搏斗题,没什么意思。 这里用的 bug 是 edit 的时候输入的 idx 没检查,从而可以直接改 stdout 的 bug。
做法就是先改一次 stdout,leak 指向 libc 里的一些指针,得到 libc 地址,然后再改一次 stdout 控制 rip。 由于 libc 是 2.23,_IO_FILE 的 vtable 可以随便改。
非常乱的 C++ 程序,check 的逻辑大概是把输入切成几段,看成二维迷宫里的路径,走到就算OK。最后跟了一个 3DES。
作者自己写了一个简单的堆内存分配器,堆块大概长这样:
BUG 是 free 之后指针没清零,从而任意 double free。 这个简单的分配器里没有任何 hardening,可以先构造一次写一个很大的值到 .bss 上的 size 数组。 这样能控制整个堆区域,接下来控制 free list,把 next 指向 .bss 上的内容指针数组。 修改这里的内容为栈上指针,之后直接修改栈上的返回地址即可。
作者写的分配器 mmap 内存的时候设的是 rwx,因此不需要 ROP。
纯脑洞题,按照每一位都是 0 或者 1 暴力 cookie 里的 key0 ~ key5。
作者通过打出一个提示信息的方式假装实现了一个 ECC,实际上实现的是错的。 但由于后面是把 flag 的每个字符加密之后判断,只要作者实现的那个不知道实际上是什么鬼的东西是个双射就能解,实际上也确实是。 直接用 Frida 暴力跑一下即可。
输出的是第几位应该是什么字符,拼起来即可得到答案。
题目给的是一个洋葱一样的东西。最外层读取输入,然后开始一层一层的解码下一层,迁移代码/数据和栈到另一块内存区域,继续执行。每一层里面都执行了一点点跟 flag 验证有关的逻辑,最后一层执行的特别多。中间似乎有一些 rdtsc 反调试和 in 指令检测 VMware。反正丢进调试器里直接拿给的序列号跑跑试试,发现行为正常,反调试应该影响不到我们。
注意到每次跳到下一层之前都写了 fs:8,此时本层的代码都是解码好的,用一个 x64dbg 脚本 dump 下所有代码。
第一层和最后一层的代码没有这里找的特征,需要手动存下来,第一层就是整个函数的 epilogue:
最后一层存成 tail.bin。顺便可以分析出验证逻辑就是把输入的序列号 hex decode 之后进行一些计算,然后和输入的用户名比较,要求相符。
接下来,注意到作者保存现场的时候用的方式比较单一,这些代码里面夹杂的真正逻辑都在
和对应的 push 之间。写个脚本把它们都提取出来,可以发现每一层里面最前面四条指令是重定位 esi 到数据上,且每一层里的都一样,可以去掉。
运行即可得到一个 code.bin
,它的功能与 CrakeMe.exe
(这 exe 的名字有个 typo...)里变换输入序列号的部分完全一致,可以写个脚本用模拟器跑一下验证一下:
接下来分析提取出的代码。Hex-Rays 可以在其上正常工作。反编译输出里可以看到大量的形如 (xxxx >> 7) * 0x1B
的东西,眼尖的人应该一眼就意识到了这很可能是 Rijndael's finite field 上的乘法操作里的片段。仔细看一下可以确定是个修改过的 AES 解密(看 subkey 是倒着用的就可以认出来了)。主要改过的部分是一些轮里的 MixColumns 和 AddRoundKey 的顺序换了,以及基本每轮过 inv S-box 之后都动了点手脚。对应写出一个加密代码即可:
运行即可得到答案。
简单逆向结果:
BUG 主要是在 0x133D 里面满足一些条件的时候可以任意 free Staff::data
,从而造出 double free / UAF。这里的对象是分配在 kmalloc_caches[10]
上的,比较大,可以用 tty 占上。
利用稍微有一点蛋疼,是因为作者编译的内核精简过度了,甚至连 SMP 都没开(那为什么 qemu 启动的时候要给多核呢?搞不懂。),导致 workqueue 相关的函数没有编译进去,没法利用里面的一个很好用的 gadget。由于开了 SMAP,需要内核 ROP。栈迁移可以用内核里fault的时候用到的一个天然的保存栈/栈迁移指令序列,可以把 rsp 迁到 rax 指向的地方。
接下来 ROP 关掉 SMAP,把栈迁回用户态,写一个长一点的 ROP 提权,再返回用户态即可。
没怎么碰过 v8,有说错的地方请多指教。 观赏给出的 diff:
可以注意到引入的 bug 是把 Array.prototype.fill
处理 FastDoubleArray 之类的东西的时候的范围检查给删了。应该是个挺儿童的题目。 直接传一个很大的值并不 work,是因为前面的代码里处理负数 start 和 end 的时候顺便修了 start 和 end 的范围。 如果你找不到前面的代码在哪,推荐使用 ccls (shameless plug /s
因为获取 end 啊之类的时候可以触发 callback,可以在 callback 里触发 shrink,这样的话在上面的边界检查被删除的情况下就可以触发一个 OOB 写了。 接下来的做法就是到处搜一搜,拼凑一些利用技巧,组装成 exploit 即可。路径大概是:
作者实现了一个模 n 的 Lucas sequence 上的 RSA。n 是个 256bit 的合数,有三个素因子。参照着解密即可。
Python 尽量 1:1 翻译的作者的程序,如果你看不出这是 Lucas sequence,不妨输出前几个值然后 OEIS 一下:
里面有一些干扰项,去除后可以看到就是两次 Lucas 序列上的 RSA 加密要求结果等于一个常数。对应解密即可:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-9-25 09:51
被kanxue编辑
,原因: