m1n9yu3 发表于 2021-9-7 12:18

一道逆向题目的个人详细分析和总结

本帖最后由 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)

Suppose 发表于 2021-9-7 18:11

对于DWORD
0x9E3779B9 = not(0x61C88646)

IBinary 发表于 2021-9-8 13:42

m1n9yu3 发表于 2021-9-7 18:24
tea解密的源码 放在文章里面了, 使用vs2019 即可编译运行, 模数使用 0x9e3779b9 ,具体为啥用这个,我也 ...

0x9e3779b9是黄金分割线相关 生成出来的.
我写的文章: https://www.cnblogs.com/iBinary/p/13844861.html

myheartwillg 发表于 2021-9-7 12:43

牛啊,楼主。像我这种懒懒就望而却步了

无敌小儿 发表于 2021-9-7 12:54

太强悍了

muyu1314520 发表于 2021-9-7 14:11


牛啊,楼主

13232929610 发表于 2021-9-7 14:48

学习了,谢谢

bedmoon 发表于 2021-9-7 15:28

这个写的挺不错,尤其是花指令的部分,学习了

搜索曾经的回忆 发表于 2021-9-7 16:39

不知道是我模数算错了,还是巧合。。。楼主能把TEA过程的源码分享一下吗?

m1n9yu3 发表于 2021-9-7 18:24

搜索曾经的回忆 发表于 2021-9-7 16:39
不知道是我模数算错了,还是巧合。。。楼主能把TEA过程的源码分享一下吗?

tea解密的源码 放在文章里面了, 使用vs2019 即可编译运行, 模数使用 0x9e3779b9 ,具体为啥用这个,我也不懂{:301_999:}

_小白 发表于 2021-9-7 22:55

写得不错,学习了
页: [1] 2 3
查看完整版本: 一道逆向题目的个人详细分析和总结