吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4870|回复: 26
收起左侧

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

  [复制链接]
m1n9yu3 发表于 2021-9-7 12:18
本帖最后由 m1n9yu3 于 2021-9-7 12:32 编辑

前言

本文写出来,以锻炼自己的文笔, 以后可能会有很多写文章的机会,然后的话,如果我自身或者文章里有不足之处,请师傅提出来, 谢谢(虚心求教ing)

也是对自己的ctf逆向做一个总结, 已经玩ctf快两年了.  技术仍然提不上去.

本文讲述了我做一道ctf 逆向题目的详细过程, 希望能帮助新人提供一点思路.

正文

题目来源:  某ctf 比赛

使用到的工具:  ida7.5 ,  x64dbg , exeinfoPE

涉及关键对抗技术:  花指令, tea算法, c++逆向
附件: CTFRE.zip (207.68 KB, 下载次数: 33)

程序外观

程序样式: 控制台, 只能输入一次 flag

image-20210907093238725

详细程序信息: exeinfoPE 载入

image-20210907093341814

image-20210907093350286

32位 exe 程序, 无保护壳

载入 ida

image-20210907094259460

nck老师说过: 构造恒成立的跳转, 中间插入无效字节.  这个符合 花指令的特征,  直接使用 ida 插件 keypatch 来 patch 这个, 因为当时我以为就只有 main 函数有这个 花指令...

去花讲解

先把ida硬编码显示功能打开:   options -> disassembly -> display disassembly -> number of opcode bytes (no-graph)  

image-20210907094606401

将这里改成16 , 因为我学硬编码的时候,滴水海东老师说过 一条汇编最多只有16个硬编码对应(我就记得这一条,其他都忘完了)

image-20210907094858790

然后硬编码就显示出来了

这里 xor xxx, xxx;   jz  xxxx+2 , 就说明, eip经过此处时,必定会跳过两个字节, 跳到第三个字节处, 所以我们选中 下面的 e9 ed 58 b9 05 这里

edit-> Patch program -> change byte

image-20210907095211805

这里 吐槽一下, 要是有快捷键可以直接 change byte 就好了,或者右键 change byte 也可以, 这个插件后面肯定会去写,要是评论区有大佬带我写就更好了

将 两个字节改成 90   也就是 nop ,  nop在汇编里的作用是对齐代码块, 因为这里的 e9 ed 是无效字节,不会被执行,所以我们改成啥都没问题,只要能够让反汇编器识别到这是一条无效指令即可

image-20210907095502693

image-20210907095631404

改完之后,自动识别并转换下面的指令, 如果低版本ida 不自动识别代码的话, 那就选中下面的数据, 按 c 转换成代码 code

image-20210907095657624

然后向下翻

image-20210907095759371

如法炮制即可

有时去花之后,会出现 把正常编码,识别成单独字节的情况, 我们只需要选中这行字节, 按c 转换成 代码即可

image-20210907095949570

image-20210907100012474

中间有什么提示的话,一般不用管.

去花完成后, 来到函数头部 按 p  创建函数 或者  edit-> function -> create function .... 也可, 注意一定要是 函数头部, 其他地方恐怕不得行

image-20210907100339231

加载符号

这里还有一个小tips。

ida 拥有 符号库, 可以利用这些符号库来识别一些系统函数, 减少逆向分析人员的工作量, 利用刚才的 exeinfoPe 得到的信息, 这是 vs2017 编写的  肯定基于 MSVC 啥的, 所以直接

image-20210907101254358

然后效果不佳,,, 可以试一下,虽然不一定稳, 有总比没有强, 位置在:  file-> load file -> flirt signature file   

sig 库的话, 网上有大神转换好的:  https://github.com/fjh658/flirtdb

注意复制路径 windows 是  ../sig/pc/*

分析过程

然后直接 f5 , 看伪代码即可.  

关于伪代码: 我觉得尽量少用伪代码功能比较好, 虽然我自己很依赖f5, 但是我有机会就去读汇编代码.

找到一连串的关键点

字符串格式:

长度:

image-20210907114005930

一组疑似 base64 和 通过的字样

image-20210907101757035

现在我们知道了 一个 符合条件的flag 字符串的格式:   flag{'1' * 32}

base64解密过程

尝试解密, 发现乱码, 观察 编码表:

image-20210907101839944

image-20210907101853422

遇到这种情况的话, 选中 30h  按 a 转换成 ascii码字符串

image-20210907102012045

想要美观一点的话, 选中数据头部 按 n 重命名变量

image-20210907102209271

如果数据类型不是你想要的话, 按 y  , 修改数据类型

image-20210907102245482

这样就好看了"很多"

image-20210907102314813

通过刚才观察 base64编码表,我们发现 这个编码表被换表了, 正常的解密是解不出来的

拿过来解密

image-20210907102443003

乱码, 怎么可能, 震惊!!!   这里我还以为是 base64编码时, 改变了加密方式,或者再次改变了编码表导致的解码错误。

硬生生跟了一下汇编代码, 发现这个编码确实是正经的 base64. 中间还自己写了一下过程, 太好了顺便熟悉了一下 base64编码,然后好长时间没有阅读汇编代码,对汇编有一种恐惧感, 现在也克服了。

image-20210907120551948

# 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

image-20210907102904115

tea 居然!! 太离谱了, 我在除了安恒的比赛之外还没有见过tea的题目, 安恒逆向必有一题tea, 要么全是。。

image-20210907102940440

迅速跟了过去, 发现是一个未被创建的函数, 按p 创建

函数头部 按住  ctrl +x , 查找交叉引用, 这个引用是指这个函数或地址有没有被别的函数或地址应用, 就是有没有调用关系, 这个东西应该是去年飞机君教我的,他应该不认识我,我是看他做的攻防世界wp,才会的,也差不多是在那个时候入的门, 不管怎么样,谢谢他, 好人一生平安.

image-20210907103226114

交叉引用, 没找到引用, 下断点运行试试  在 IDA View 视图中, 点击代码旁边的小蓝点,或者选中代码按 f2 都可以下断点, 再操作一次,取消断点

image-20210907103533146

然后 f9 , 选择 Local Windows debugger  -> ok

image-20210907103728015

报错直接 yes,

image-20210907103908021

输入一个符合前面条件的flag:   断下来了, 说明代码被引用,但是未被 反编译器识别到。

函数尾部下断点, 然后 f9 跑到尾部, f8 一下

image-20210907104115141

跳转到这里, 这是啥, 花指令.  没啥好说的 , 继续修复  , 其实可以写脚本来的,但是我懒...  

停下来, 继续修复,如果你找不到是哪里调用的, 可以记录一下 地址,  对了,这里地址不同是因为 ida 加载PE文件时, 基地址默认为 0x400000, 而调试的时候,有时有aslr 机制,有时候没有。 这就造成地址不对, 不过没关系, 后面四位地址是不变的(是海东老师说的,我听他讲虚拟内存时说的,然后的话,我就记住了缺页异常) ida 有一点值得夸赞, 停止调试后, 基地址会随着调试地址改变. 所以刚才记录的地址还有效果。

然后就是漫长的花指令patch 时间了

这里我又有话说了, 遇到这样的花指令的话, 直接给 jmp call 什么的全给他 nop 掉, 省事。

image-20210907105015413

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 出来的结果

image-20210907110257793

调试了一下, 发现

image-20210907110515715

至于这个 XTEA 咋识别出来的

image-20210907110606618

32 轮次, 每次加密 8 位, 就可以基本判定出是 xtea.

然后我们现在要做的就是 把 base64 换表加密的结果提取出来, 然后每组8个数组,然后进行解密

image-20210907111519977

解密代码:

/* 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

image-20210906193329695

这里我有一事不明:  

image-20210907111402564

XTEA 好难。。。解密也好玄学..  希望评论区有大佬教教我

最后一个check

在我输入的时候, 惊悚的一幕出现了, 结果不对!!!

image-20210907111629121

而且 是 base64 加密的结果 不对, 吓得我去仔细调试了一下 TEA加密, 发现, 程序对输入的数据进行了一个变换

image-20210907112055471

因为这个 flag 内部 有 32 位字符串, 所以可以分成4 组, 每组8位刚好. 可以发现我们输入的  6 变成了 f.    某个d变成了4。

ida 提取数据  选中要提取的数据块  shift + e , 即可 , 有多种格式

image-20210907115510415

提取局部变量的话, 让程序跑起来, 等局部变量加载到堆栈中时,到堆栈中 shift + e 提取即可

做一个小小的变换

61b6dd62  ->  f1bf4dfb

f344ci46  ->  fc4dc9df

d5be4ahg  -> 45b5da87

de5be4`b  -> d552ed02

我一开始猜想的是,有个映射表, 然后 x64dbg 调的时候,发现是一个 func 改变了这个字符串.

关于为什么用x64dbg 调: 是因为 x64dbg 对字符串观察更加细致,适合我这种懒狗

image-20210907112328131

进入函数内部阅读代码, 这里也是拥有花指令的, 我发现重要的地方都拥有花指令.

image-20210907112440285

这里我将 函数重命名了一下, 函数 重命名的方法和 变量一致

通过 动态调试, 我知道 这个函数是将 输入的字符串分成两组, 一组对另一组进行 运算

image-20210907112807984

当时想的是 写脚本,然后楞了半天,开始逼着自己写脚本

先测试一下数据,

image-20210907112910309

突然发现这里存在着一种映射关系,

('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'

image-20210907113502539

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算法

image-20210907092334286

总结

分析ctf 程序的话, 硬刚可能是不行的, 还要带一点小技巧, 要揣摩出题人的心思
这道题的话, 从9月4号一直做到9月7号,总共用了三天时间, 中间只要一有时间就做做, 我太难了

本文讲了一道 ctf 逆向题目, 和一些 ida 分析程序的 小tip,  希望对新手有所帮助, 大佬绕路,哦不,我自己爬。

附上 脑图 一份

image-20210907115104911

免费评分

参与人数 15威望 +1 吾爱币 +34 热心值 +13 收起 理由
caiji1 + 1 + 1 用心讨论,共获提升!
pdcba + 1 + 1 谢谢@Thanks!
fengbolee + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
743567274 + 1 + 1 我很赞同!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Lucifer_BW + 1 + 1 热心回复!
_小白 + 1 + 1 我很赞同!
wuaiwu77 + 1 + 1 用心讨论,共获提升!
石碎大胸口 + 1 + 1 用心讨论,共获提升!
yyb414 + 1 + 1 热心回复!
因为有钳 + 1 + 1 用心讨论,共获提升!
ZJevon + 2 我很赞同!
xzl9552547 + 1 我很赞同!
未晴雾香 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Suppose 发表于 2021-9-7 18:11
对于DWORD
0x9E3779B9 = not(0x61C88646)

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
m1n9yu3 + 1 + 1 谢谢@Thanks!

查看全部评分

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 ,具体为啥用这个,我也不懂
_小白 发表于 2021-9-7 22:55
写得不错,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 06:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表