一道逆向题目的个人详细分析和总结
本帖最后由 m1n9yu3 于 2021-9-7 12:32 编辑# 前言
本文写出来,以锻炼自己的文笔, 以后可能会有很多写文章的机会,然后的话,如果我自身或者文章里有不足之处,请师傅提出来, 谢谢(虚心求教ing)
也是对自己的ctf逆向做一个总结, 已经玩ctf快两年了.技术仍然提不上去.
本文讲述了我做一道ctf 逆向题目的详细过程, 希望能帮助新人提供一点思路.
# 正文
题目来源:某ctf 比赛
使用到的工具:ida7.5 ,x64dbg , exeinfoPE
涉及关键对抗技术:花指令, tea算法, c++逆向
附件:
## 程序外观
程序样式: 控制台, 只能输入一次 flag
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070932815.png)
详细程序信息: exeinfoPE 载入
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070933872.png)
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070933364.png)
32位 exe 程序, 无保护壳
## 载入 ida
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070942566.png)
nck老师说过: 构造恒成立的跳转, 中间插入无效字节.这个符合 花指令的特征,直接使用 ida 插件 keypatch 来 patch 这个, 因为当时我以为就只有 main 函数有这个 花指令...
## 去花讲解
先把ida硬编码显示功能打开: options -> disassembly -> display disassembly -> number of opcode bytes (no-graph)
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070946530.png)
将这里改成16 , 因为我学硬编码的时候,滴水海东老师说过 一条汇编最多只有16个硬编码对应(我就记得这一条,其他都忘完了)
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070948073.png)
然后硬编码就显示出来了
这里 xor xxx, xxx; jzxxxx+2 , 就说明, eip经过此处时,必定会跳过两个字节, 跳到第三个字节处, 所以我们选中 下面的 e9 ed 58 b9 05 这里
edit-> Patch program -> change byte
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070952904.png)
> 这里 吐槽一下, 要是有快捷键可以直接 change byte 就好了,或者右键 change byte 也可以, 这个插件后面肯定会去写,要是评论区有大佬带我写就更好了
将 两个字节改成 90 也就是 nop ,nop在汇编里的作用是对齐代码块, 因为这里的 e9 ed 是无效字节,不会被执行,所以我们改成啥都没问题,只要能够让反汇编器识别到这是一条无效指令即可
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070955771.png)
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070956480.png)
改完之后,自动识别并转换下面的指令, 如果低版本ida 不自动识别代码的话, 那就选中下面的数据, 按 c 转换成代码 code
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070956721.png)
然后向下翻
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070957432.png)
如法炮制即可
有时去花之后,会出现 把正常编码,识别成单独字节的情况, 我们只需要选中这行字节, 按c 转换成 代码即可
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070959660.png)
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071000606.png)
中间有什么提示的话,一般不用管.
去花完成后, 来到函数头部 按 p创建函数 或者edit-> function -> create function .... 也可, 注意一定要是 函数头部, 其他地方恐怕不得行
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071003360.png)
## 加载符号
这里还有一个小tips。
ida 拥有 符号库, 可以利用这些符号库来识别一些系统函数, 减少逆向分析人员的工作量, 利用刚才的 exeinfoPe 得到的信息, 这是 vs2017 编写的肯定基于 MSVC 啥的, 所以直接
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071012435.png)
然后效果不佳,,, 可以试一下,虽然不一定稳, 有总比没有强, 位置在:file-> load file -> flirt signature file
sig 库的话, 网上有大神转换好的:https://github.com/fjh658/flirtdb
注意复制路径 windows 是../sig/pc/*
## 分析过程
然后直接 f5 , 看伪代码即可.
>关于伪代码: 我觉得尽量少用伪代码功能比较好, 虽然我自己很依赖f5, 但是我有机会就去读汇编代码.
找到一连串的关键点
字符串格式:
![](https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071138837.png)
长度:
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071140001.png)
一组疑似 base64 和 通过的字样
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071017112.png)
>现在我们知道了 一个 符合条件的flag 字符串的格式: flag{'1' * 32}
## base64解密过程
尝试解密, 发现乱码, 观察 编码表:
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071018042.png)
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071018501.png)
遇到这种情况的话, 选中 30h按 a 转换成 ascii码字符串
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071020092.png)
想要美观一点的话, 选中数据头部 按 n 重命名变量
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071022360.png)
如果数据类型不是你想要的话, 按 y, 修改数据类型
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071022517.png)
这样就好看了"很多"
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071023904.png)
通过刚才观察 base64编码表,我们发现 这个编码表被换表了, 正常的解密是解不出来的
拿过来解密
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071024084.png)
乱码, 怎么可能, 震惊!!! 这里我还以为是 base64编码时, 改变了加密方式,或者再次改变了编码表导致的解码错误。
硬生生跟了一下汇编代码, 发现这个编码确实是正经的 base64. 中间还自己写了一下过程, 太好了顺便熟悉了一下 base64编码,然后好长时间没有阅读汇编代码,对汇编有一种恐惧感, 现在也克服了。
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071205055.png)
``` python
# coding = utf-8
s = "flag{11111111111111111111111111111111}"
for i in range(0, len(s), 3):
# 打印 四段 分割字符串
s1, s2, s3 = ord(s), ord(s), ord(s)
r1 = (s1 >> 2 )
r2 = (((s1 << 4) | (s2 >> 4)) & 0b111111)
r3 = (((s2 << 2) | (s3 >> 6)) & 0b111111)
r4 = (s3 & 0b111111)
print(r1, r2, r3, r4)
break
#25 38 49 33
```
## tea 解密过程
正当我一筹莫展时, 我打开了 神器Findcrypt
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071029186.png)
tea 居然!! 太离谱了, 我在除了安恒的比赛之外还没有见过tea的题目, 安恒逆向必有一题tea, 要么全是。。
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071029490.png)
迅速跟了过去, 发现是一个未被创建的函数, 按p 创建
函数头部 按住ctrl +x , 查找交叉引用, 这个引用是指这个函数或地址有没有被别的函数或地址应用, 就是有没有调用关系, 这个东西应该是去年飞机君教我的,他应该不认识我,我是看他做的攻防世界wp,才会的,也差不多是在那个时候入的门, 不管怎么样,谢谢他, 好人一生平安.
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071032192.png)
交叉引用, 没找到引用, 下断点运行试试在 IDA View 视图中, 点击代码旁边的小蓝点,或者选中代码按 f2 都可以下断点, 再操作一次,取消断点
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071035236.png)
然后 f9 , 选择 Local Windows debugger-> ok
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071037087.png)
报错直接 yes,
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071039120.png)
输入一个符合前面条件的flag: 断下来了, 说明代码被引用,但是未被 反编译器识别到。
函数尾部下断点, 然后 f9 跑到尾部, f8 一下
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071041233.png)
跳转到这里, 这是啥, 花指令.没啥好说的 , 继续修复, 其实可以写脚本来的,但是我懒...
停下来, 继续修复,如果你找不到是哪里调用的, 可以记录一下 地址,对了,这里地址不同是因为 ida 加载PE文件时, 基地址默认为 0x400000, 而调试的时候,有时有aslr 机制,有时候没有。 这就造成地址不对, 不过没关系, 后面四位地址是不变的(是海东老师说的,我听他讲虚拟内存时说的,然后的话,我就记住了缺页异常) ida 有一点值得夸赞, 停止调试后, 基地址会随着调试地址改变. 所以刚才记录的地址还有效果。
然后就是漫长的花指令patch 时间了
这里我又有话说了, 遇到这样的花指令的话, 直接给 jmp call 什么的全给他 nop 掉, 省事。
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071050559.png)
c,d,p,要组合使用, 这里的花指令可难去了
这里脚本的话, 我总结了一下特征码:
``` tex
eb 03 12 34 e8 f9 ff ff ff-> 90 90 90 90 90 90 90 90 90
33 c0 74 02 e9 ed 58 ->90 90 90 90 90 90 58
```
改错了的话,也不要紧, ida 7.5 版本有 ctrl + z 可以撤销 上次操作
最后 f5 出来的结果
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071102913.png)
调试了一下, 发现
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071105804.png)
至于这个 XTEA 咋识别出来的
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071106695.png)
32 轮次, 每次加密 8 位, 就可以基本判定出是 xtea.
然后我们现在要做的就是 把 base64 换表加密的结果提取出来, 然后每组8个数组,然后进行解密
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071115082.png)
解密代码:
```
/* take 64 bits of data in v and v and 128 bits of key - key */
void xtea_encrypt(unsigned int num_rounds, uint32_t v, uint32_t const key)
{
unsigned int i;
uint32_t v0 = v, v1 = v, sum = 0, delta = 0x9E3779B9;
for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
v = v0; v = v1;
}
void xtea_decrypt(unsigned int num_rounds, uint32_t* v, uint32_t const key) {
unsigned int i;
uint32_t v0 = v, v1 = v, delta = 0x9E3779B9, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key);
}
v = v0; v = v1;
}
int main() {
// 是一个标准的 xxtea
chardata[] =
{
0x08,0x42,0x17,0x12,0x8b,0x77,0xdf,0x7a
};
char data1[] = { 0x0a,0x30,0xed,0x77,0x21,0x4b,0xc8,0xa2 };
char data2[] = { 0x44,0x18,0x86,0xc2,0xae,0x3d,0xf2,0x96 };
char data3[] = { 0xd9,0xcc,0x8f,0x40,0x5a,0x3b,0x79,0xca };
uint32_t const key[] = { 106,120,115,122 };
xtea_decrypt(32, (uint32_t*)data, key);
for (int i = 0; i < 8; i++) {
printf("%c", data);
}
xtea_decrypt(32, (uint32_t*)data1, key);
for (int i = 0; i < 8; i++) {
printf("%c", data1);
}
xtea_decrypt(32, (uint32_t*)data2 , key);
for (int i = 0; i < 8; i++) {
printf("%c", data2);
}
xtea_decrypt(32, (uint32_t*)data3 , key);
for (int i = 0; i < 8; i++) {
printf("%c", data3);
}
return 0;
}
```
解密代码来源: https://bbs.pediy.com/thread-266933.htm
xtea 解密结果:
> 61b6dd62f344ci46d5be4ahgde5be4`b
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109061933843.png)
这里我有一事不明:
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071114725.png)
XTEA 好难。。。解密也好玄学..希望评论区有大佬教教我
## 最后一个check
在我输入的时候, 惊悚的一幕出现了, 结果不对!!!
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071116163.png)
而且 是 base64 加密的结果 不对, 吓得我去仔细调试了一下 TEA加密, 发现, 程序对输入的数据进行了一个变换
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071120600.png)
因为这个 flag 内部 有 32 位字符串, 所以可以分成4 组, 每组8位刚好. 可以发现我们输入的6 变成了 f. 某个d变成了4。
ida 提取数据选中要提取的数据块shift + e , 即可 , 有多种格式
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071155528.png)
提取局部变量的话, 让程序跑起来, 等局部变量加载到堆栈中时,到堆栈中 shift + e 提取即可
做一个小小的变换
> 61b6dd62->f1bf4dfb
>
> f344ci46->fc4dc9df
>
> d5be4ahg-> 45b5da87
>
> de5be4`b-> d552ed02
>
>
我一开始猜想的是,有个映射表, 然后 x64dbg 调的时候,发现是一个 func 改变了这个字符串.
关于为什么用x64dbg 调: 是因为 x64dbg 对字符串观察更加细致,适合我这种懒狗
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071123247.png)
进入函数内部阅读代码, 这里也是拥有花指令的, 我发现重要的地方都拥有花指令.
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071124356.png)
这里我将 函数重命名了一下, 函数 重命名的方法和 变量一致
通过 动态调试, 我知道 这个函数是将 输入的字符串分成两组, 一组对另一组进行 运算
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071128063.png)
当时想的是 写脚本,然后楞了半天,开始逼着自己写脚本
先测试一下数据,
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071129398.png)
突然发现这里存在着一种映射关系,
('f' & 0xf ) | ('4' & 0xf0) = 54 = 0x36 = '6'
('4' & 0xf ) | ('f' & 0xf0) = 100 = 0x64 = 'd'
('6' & 0xf ) | ('d' & 0xf0) = 54 = 0x36 = '6'
('d' & 0xf ) | ('6' & 0xf0) = 54 = 0x36 = '6'
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071135609.png)
```
tra = lambda x1,x2:(x1 & 0xf ) | (x2 & 0xf0)
chr(tra(ord('d'), ord('6')))
chr(tra(ord('6'), ord('d')))
chr(tra(ord('f'), ord('4')))
chr(tra(ord('4'), ord('f')))
```
## getflag!!!
所以程序的字符串变换是可逆的
> flag{f1bf4dfbfc4dc9df45b5da87d552ed02}
直接输入程序自身逆向的结果,就可以得到最终的结果, 这相当于 xor算法
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109070923348.png)
# 总结
分析ctf 程序的话, 硬刚可能是不行的, 还要带一点小技巧, 要揣摩出题人的心思
这道题的话, 从9月4号一直做到9月7号,总共用了三天时间, 中间只要一有时间就做做, 我太难了
本文讲了一道 ctf 逆向题目, 和一些 ida 分析程序的 小tip,希望对新手有所帮助, 大佬绕路,哦不,我自己爬。
附上 脑图 一份
!(https://gitee.com/shenshuoyaoyouguang/blogimage/raw/master/img/202109071151060.png) 对于DWORD
0x9E3779B9 = not(0x61C88646) m1n9yu3 发表于 2021-9-7 18:24
tea解密的源码 放在文章里面了, 使用vs2019 即可编译运行, 模数使用 0x9e3779b9 ,具体为啥用这个,我也 ...
0x9e3779b9是黄金分割线相关 生成出来的.
我写的文章: https://www.cnblogs.com/iBinary/p/13844861.html 牛啊,楼主。像我这种懒懒就望而却步了 太强悍了
牛啊,楼主 学习了,谢谢 这个写的挺不错,尤其是花指令的部分,学习了 不知道是我模数算错了,还是巧合。。。楼主能把TEA过程的源码分享一下吗? 搜索曾经的回忆 发表于 2021-9-7 16:39
不知道是我模数算错了,还是巧合。。。楼主能把TEA过程的源码分享一下吗?
tea解密的源码 放在文章里面了, 使用vs2019 即可编译运行, 模数使用 0x9e3779b9 ,具体为啥用这个,我也不懂{:301_999:} 写得不错,学习了