前言
本文写出来,以锻炼自己的文笔, 以后可能会有很多写文章的机会,然后的话,如果我自身或者文章里有不足之处,请师傅提出来, 谢谢(虚心求教ing)
也是对自己的ctf逆向做一个总结, 已经玩ctf快两年了. 技术仍然提不上去.
本文讲述了我做一道ctf 逆向题目的详细过程, 希望能帮助新人提供一点思路.
正文
题目来源: 某ctf 比赛
使用到的工具: ida7.5 , x64dbg , exeinfoPE
涉及关键对抗技术: 花指令, tea算法, c++逆向
附件:
CTFRE.zip
(207.68 KB, 下载次数: 33)
程序外观
程序样式: 控制台, 只能输入一次 flag
详细程序信息: exeinfoPE 载入
32位 exe 程序, 无保护壳
载入 ida
nck老师说过: 构造恒成立的跳转, 中间插入无效字节. 这个符合 花指令的特征, 直接使用 ida 插件 keypatch 来 patch 这个, 因为当时我以为就只有 main 函数有这个 花指令...
去花讲解
先把ida硬编码显示功能打开: options -> disassembly -> display disassembly -> number of opcode bytes (no-graph)
将这里改成16 , 因为我学硬编码的时候,滴水海东老师说过 一条汇编最多只有16个硬编码对应(我就记得这一条,其他都忘完了)
然后硬编码就显示出来了
这里 xor xxx, xxx; jz xxxx+2 , 就说明, eip经过此处时,必定会跳过两个字节, 跳到第三个字节处, 所以我们选中 下面的 e9 ed 58 b9 05 这里
edit-> Patch program -> change byte
这里 吐槽一下, 要是有快捷键可以直接 change byte 就好了,或者右键 change byte 也可以, 这个插件后面肯定会去写,要是评论区有大佬带我写就更好了
将 两个字节改成 90 也就是 nop , nop在汇编里的作用是对齐代码块, 因为这里的 e9 ed 是无效字节,不会被执行,所以我们改成啥都没问题,只要能够让反汇编器识别到这是一条无效指令即可
改完之后,自动识别并转换下面的指令, 如果低版本ida 不自动识别代码的话, 那就选中下面的数据, 按 c 转换成代码 code
然后向下翻
如法炮制即可
有时去花之后,会出现 把正常编码,识别成单独字节的情况, 我们只需要选中这行字节, 按c 转换成 代码即可
中间有什么提示的话,一般不用管.
去花完成后, 来到函数头部 按 p 创建函数 或者 edit-> function -> create function .... 也可, 注意一定要是 函数头部, 其他地方恐怕不得行
加载符号
这里还有一个小tips。
ida 拥有 符号库, 可以利用这些符号库来识别一些系统函数, 减少逆向分析人员的工作量, 利用刚才的 exeinfoPe 得到的信息, 这是 vs2017 编写的 肯定基于 MSVC 啥的, 所以直接
然后效果不佳,,, 可以试一下,虽然不一定稳, 有总比没有强, 位置在: file-> load file -> flirt signature file
sig 库的话, 网上有大神转换好的: https://github.com/fjh658/flirtdb
注意复制路径 windows 是 ../sig/pc/*
分析过程
然后直接 f5 , 看伪代码即可.
关于伪代码: 我觉得尽量少用伪代码功能比较好, 虽然我自己很依赖f5, 但是我有机会就去读汇编代码.
找到一连串的关键点
字符串格式:
长度:
一组疑似 base64 和 通过的字样
现在我们知道了 一个 符合条件的flag 字符串的格式: flag{'1' * 32}
base64解密过程
尝试解密, 发现乱码, 观察 编码表:
遇到这种情况的话, 选中 30h 按 a 转换成 ascii码字符串
想要美观一点的话, 选中数据头部 按 n 重命名变量
如果数据类型不是你想要的话, 按 y , 修改数据类型
这样就好看了"很多"
通过刚才观察 base64编码表,我们发现 这个编码表被换表了, 正常的解密是解不出来的
拿过来解密
乱码, 怎么可能, 震惊!!! 这里我还以为是 base64编码时, 改变了加密方式,或者再次改变了编码表导致的解码错误。
硬生生跟了一下汇编代码, 发现这个编码确实是正经的 base64. 中间还自己写了一下过程, 太好了顺便熟悉了一下 base64编码,然后好长时间没有阅读汇编代码,对汇编有一种恐惧感, 现在也克服了。
# coding = utf-8
s = "flag{11111111111111111111111111111111}"
for i in range(0, len(s), 3):
# 打印 四段 分割字符串
s1, s2, s3 = ord(s[i]), ord(s[i+1]), ord(s[i+2])
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
tea 居然!! 太离谱了, 我在除了安恒的比赛之外还没有见过tea的题目, 安恒逆向必有一题tea, 要么全是。。
迅速跟了过去, 发现是一个未被创建的函数, 按p 创建
函数头部 按住 ctrl +x , 查找交叉引用, 这个引用是指这个函数或地址有没有被别的函数或地址应用, 就是有没有调用关系, 这个东西应该是去年飞机君教我的,他应该不认识我,我是看他做的攻防世界wp,才会的,也差不多是在那个时候入的门, 不管怎么样,谢谢他, 好人一生平安.
交叉引用, 没找到引用, 下断点运行试试 在 IDA View 视图中, 点击代码旁边的小蓝点,或者选中代码按 f2 都可以下断点, 再操作一次,取消断点
然后 f9 , 选择 Local Windows debugger -> ok
报错直接 yes,
输入一个符合前面条件的flag: 断下来了, 说明代码被引用,但是未被 反编译器识别到。
函数尾部下断点, 然后 f9 跑到尾部, f8 一下
跳转到这里, 这是啥, 花指令. 没啥好说的 , 继续修复 , 其实可以写脚本来的,但是我懒...
停下来, 继续修复,如果你找不到是哪里调用的, 可以记录一下 地址, 对了,这里地址不同是因为 ida 加载PE文件时, 基地址默认为 0x400000, 而调试的时候,有时有aslr 机制,有时候没有。 这就造成地址不对, 不过没关系, 后面四位地址是不变的(是海东老师说的,我听他讲虚拟内存时说的,然后的话,我就记住了缺页异常) ida 有一点值得夸赞, 停止调试后, 基地址会随着调试地址改变. 所以刚才记录的地址还有效果。
然后就是漫长的花指令patch 时间了
这里我又有话说了, 遇到这样的花指令的话, 直接给 jmp call 什么的全给他 nop 掉, 省事。
c, d, p, 要组合使用, 这里的花指令可难去了
这里脚本的话, 我总结了一下特征码:
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 出来的结果
调试了一下, 发现
至于这个 XTEA 咋识别出来的
32 轮次, 每次加密 8 位, 就可以基本判定出是 xtea.
然后我们现在要做的就是 把 base64 换表加密的结果提取出来, 然后每组8个数组,然后进行解密
解密代码:
/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
void xtea_encrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4])
{
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9;
for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
v[0] = v0; v[1] = v1;
}
void xtea_decrypt(unsigned int num_rounds, uint32_t* v, uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], 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[sum & 3]);
}
v[0] = v0; v[1] = v1;
}
int main() {
// 是一个标准的 xxtea
char data[] =
{
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[i]);
}
xtea_decrypt(32, (uint32_t*)data1, key);
for (int i = 0; i < 8; i++) {
printf("%c", data1[i]);
}
xtea_decrypt(32, (uint32_t*)data2 , key);
for (int i = 0; i < 8; i++) {
printf("%c", data2[i]);
}
xtea_decrypt(32, (uint32_t*)data3 , key);
for (int i = 0; i < 8; i++) {
printf("%c", data3[i]);
}
return 0;
}
解密代码来源: https://bbs.pediy.com/thread-266933.htm
xtea 解密结果:
61b6dd62f344ci46d5be4ahgde5be4`b
这里我有一事不明:
XTEA 好难。。。解密也好玄学.. 希望评论区有大佬教教我
最后一个check
在我输入的时候, 惊悚的一幕出现了, 结果不对!!!
而且 是 base64 加密的结果 不对, 吓得我去仔细调试了一下 TEA加密, 发现, 程序对输入的数据进行了一个变换
因为这个 flag 内部 有 32 位字符串, 所以可以分成4 组, 每组8位刚好. 可以发现我们输入的 6 变成了 f. 某个d变成了4。
ida 提取数据 选中要提取的数据块 shift + e , 即可 , 有多种格式
提取局部变量的话, 让程序跑起来, 等局部变量加载到堆栈中时,到堆栈中 shift + e 提取即可
做一个小小的变换
61b6dd62 -> f1bf4dfb
f344ci46 -> fc4dc9df
d5be4ahg -> 45b5da87
de5be4`b -> d552ed02
我一开始猜想的是,有个映射表, 然后 x64dbg 调的时候,发现是一个 func 改变了这个字符串.
关于为什么用x64dbg 调: 是因为 x64dbg 对字符串观察更加细致,适合我这种懒狗
进入函数内部阅读代码, 这里也是拥有花指令的, 我发现重要的地方都拥有花指令.
这里我将 函数重命名了一下, 函数 重命名的方法和 变量一致
通过 动态调试, 我知道 这个函数是将 输入的字符串分成两组, 一组对另一组进行 运算
当时想的是 写脚本,然后楞了半天,开始逼着自己写脚本
先测试一下数据,
突然发现这里存在着一种映射关系,
('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'
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算法
总结
分析ctf 程序的话, 硬刚可能是不行的, 还要带一点小技巧, 要揣摩出题人的心思
这道题的话, 从9月4号一直做到9月7号,总共用了三天时间, 中间只要一有时间就做做, 我太难了
本文讲了一道 ctf 逆向题目, 和一些 ida 分析程序的 小tip, 希望对新手有所帮助, 大佬绕路,哦不,我自己爬。
附上 脑图 一份