强网杯qwb 2018 reverse writeup
强网杯2018题目:《simple》《picture lock》《hide》、《babyre》、《re》writeup。
文件链接:https://github.com/LeadroyaL/attachment_repo/tree/master/qwb_2018
一、simple
一个安卓题目,简单题,java 层做了一些数学运算,总结一下就是一元二次方程,我懒得解,反正128种可能,直接爆破就好了。
1 | a = [0, 146527998, 205327308, 94243885, 138810487, 408218567, 77866117, 71548549, 563255818, 559010506, 449018203, 576200653, 307283021, 467607947, 314806739, 341420795, 341420795, 469998524, 417733494, 342206934, 392460324, 382290309, 185532945, 364788505, 210058699, 198137551, 360748557, 440064477, 319861317, 676258995, 389214123, 829768461, 534844356, 427514172, 864054312] |
二、picture-lock
安卓题,和加密勒索软件的套路有点像,输入一个文件,输出其加密后的结果。目标是将某个加密后的文件解密出来,flag 就在里面。
java 层基本没东西,算一下签名的md5,将原本文件、加密后文件、md5带入 native。
没有init_array,没有JNI_OnLoad,直接看JNI 方法。
一进来先初始化了 AES 的 SBox,比较骚的地方在于他初始化了2组 AES 的 SBox,也就是相当于有2个 AES_Cipher,使用的 key 不同,这部分其实我看不大懂,只是调试时候发现的。
1 | if ( new_fd ) |
然后开始读文件,每次读取 md5[i&0x1F]
个字节,如果长度小于16,就 PKCS5 到16字节。
1 | left_or_right = (int **)&g_buf_0x180_p0x30; |
对读入的字节前16byte 进行 AES_ECB 加密,使用的 KEY 是第奇数次使用md5[0:16]
,第偶数次使用 md5[16:32]
。
1 | if ( dataLen >= 0x11 ) |
16字节以后的,plain[index]
逐位 xor上md5[index]
。之后将这些 byte 写到加密后的文件里。
写一点 testcase 验证一下我们的猜想,发现是正确的,下文是解密的 python 脚本。
1 | from Crypto.Cipher import AES |
三、hide
这题偷鸡了,不会做,说是的 upx 的壳,但似乎做了一些修改,瞎 jb 做居然做出来了。
1、运行过程中尝试去 attach,发现已经被 trace了,那肯定是被反调试了。
2、直接运行和使用 gdb 运行结果不一致,调试情况下连输出都没有,直接 exit 掉了,所以肯定是被反调试了。
最开始比较害怕是多层 upx,因为调试时候看到很多次 mmap,比较害怕。反正不会做,不小心看到一个叫“在所有syscall 上下断点”,叫 catch syscall 。
既然是加壳的,肯定会有 mmap 、mprotect 这样的操作,于是就"catch syscall"、"c",这样一直按,一直按,大概按到五六十次时候,发现了一些 ptrace,管他呢,跳过再说。之后就到了要求输入 flag 的位置,开心,dump 一下这个内存块,ida 打开就可以看到逻辑了!
看起来非常舒服,检查了首尾,然后按照顺序交替调用了6次加密函数。
1 | __int64 __usercall sub_C8CC0@<rax>(unsigned int *input@<rdi>) |
这个很像 tea 加密,是可逆的。
1 | char *__usercall sub_C8E50@<rax>(char *a1@<rdi>) |
这个就是普通的 xor,也是可逆的。
写个 python 反一下
1 | keyPool = [1883844979, 1165112144, 2035430262, 861484132, ] |
四、baby_re
直接执行文件,输出"nope"。
代码里有大量没用的反调试代码,最后发现有个函数有用,而且有两个特征。
- 输出"nope"是在这个函数里的
- 这个函数有读文件的操作,打开了叫"nothing"的文件
于是手动创建"nothing"的文件,随便写点东西进去,再执行这个exe,发现确实被加密了,但最后的几个byte是完整的,看起来是16byte一组的ECB模式。
这时候直接set RIP到这个函数,发现功能没有出问题,确实其他代码是反调试代码,全都NOP掉就行了。
主要就是逆sub_140002B60吧,没什么好讲的,还是这个套路。
python如下
1 | target = [0xb, 0xe8, 0xa3, 0xd6, 0xf7, 0x19, 0x19, 0x4c, 0x12, 0x42, 0x0, 0x54, 0x3d, 0x41, 0xbb, 0x16, 0xe5, 0x6a, 0x87, 0xec, 0xd0, 0xeb, 0xfa, 0x62, 0x3d, 0xce, 0x61, 0x1e, 0xe, 0xc9, 0x11, 0xed, 0x68, 0x74, 0x3f, 0x7d, ] |
五、re
这是一个固件逆向,找对工具是关键。
https://raw.githubusercontent.com/themadinventor/ida-xtensa/master/xtensa.py 这个可以让 ida 正确识别文件。
https://github.com/espressif/esp-idf 这个是编译器和反汇编器。
每个寄存器是32bit,函数调用时a2-a3 是返回值,a10-a13 是参数,还有各种各样奇奇怪怪的指令,没法调试,只能猜各个指令是干嘛的,非常浪费时间。
大概呢,就是顺序执行 qwb1/2/3/4 ,求和结果为0,也就是说每个的结果都是0。这几个函数里面长得基本一样,只是数字不一样,做一些浮点运算,最后取一个约等于(round 取整函数,一开始我脑抽了,以为是循环的函数,逆了好久)。
qwb1/2/3/4 是差不多的,只是某几个常量有点不一样,将输入的4个char拓展为double,按照一定次序,分别带入qwb5,返回的值乘以16,再相加。大致是下面这样:
qwb5(a) * 16 + qwb5(b) == X
qwb5(c) * 16 + qwb5(d) == Y
qwb5的逻辑是将0-9,a-z,A-z 转为0~36 ,所以a 和 b 其实就是 X 的十六进制表示。
比如qwb1里的 X=87,Y=113 。可以拿到8个数字,再拿到它们的hexString,拼起来就是 flag 了。
这里存在大小写多解的情况,所以有2个 flag “577155095533b964 ”,“577155095533B964 ”。