本道逆向题涉及的知识点如下:
这是今年12月份帮一朋友做的一道CTF题,看题目描述是某春秋平台的,做这道题也花了2小时,因为以前没遇到过chacha20加密,做题的时在论坛也没有搜到chacha20算法,故而写一篇文章记录一下,供大家参考。
首先我们观察下这道题目,解压后如下,看样子加密后的flag就在flag.enc
中:
按照做题惯例,先查个壳,发现就是普通的32位程序,使用VC++编译器编译,没壳:

我们直接拖到IDA中进行静态分析。
进去之后,没什么好说的,直接找main函数按F5开始分析整个程序逻辑;我们先大略的分析一下,见下图注释,函数名都还没有修改,都是IDA自动生成的。

如上注释,通过初步的分析,我们大致了解到,整个程序就是将flag读入,然后经过中间一系列未识别到的函数,最后把文件改名为flag.enc
并将加密后的内容覆盖输出到flag.enc
。要注意在上图的第23行有一个全局变量的字节序列,这个序列是在函数sub_401610
生成的,这里的内容我们最后再分析,接下来先分析最主要的中间那三个函数。
我们看到在22行,有一个名为loc_401450
的函数,我们点进去看看为什么该函数没有被IDA正确识别,如下图:

点进去之后,发现左边的地址部分一片红,有经验的逆向人员一看0x401468~0x40146C
这部分,就是明显的花指令特征,在地址0x40146A
处的跳转无论如何都会跳转到0x40146F
处,使得IDA未能识别这种跳转破坏了函数的栈帧,因此IDA没有将该部分正确识别为函数。
看过我另一篇文章的大家应该清楚花指令的还原,也可以用脚本,但是这里的花指令不多,故而我们直接手动来快速还原。将光标定位在地址0x40146E
,然后直接按键盘上的D
,即可将该部分转换为数据。

弹出如下框,这是在IDA在质问我们人类智慧的一个警告,直接Yes就可以。

然后在正确的代码地址0x40146F
处按C
键,使得该处的数据转变为正确的代码。

此外,由于0x40146A~0x40146E
都是人工添加的无用代码,我们可以直接将该部分数据全部转为空指令nop
,即0x90
,如下图:

将这5个无用的字节替换为0x90
,然后点OK。

至此,上述去花操作完成。
然后,我们选中整个函数的部分0x401450~0x401566
,然后按快捷键P
,让整个函数能被IDA正确识别。此时我们再次回到main函数的伪代码窗口,看到该部分函数已经被正确识别了,如下图:

剩下的loc_401940
和loc_401AA0
处的函数还原,和上述处理方法相同,需要注意的是函数的结尾一般是以retn结束。
接下来我们双击点进去sub_401450
,开始分析该函数的算法,进去之后也是一脸茫然,看不明白;

继续点进去sub_401380
函数看看,我们发现其中有这样一个字符串expand 32-byte k
,如下图:

经过一番搜索,才知道这个加密函数是chacha20加密,找到了这个算法的C代码实现,780K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6K9r3W2X3k6Y4c8Z5M7g2)9J5c8X3y4Z5j5h3y4Z5j5e0t1H3i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1%4i4@1q4q4i4K6V1%4i4@1f1$3i4@1t1K6i4K6V1#2i4@1f1#2i4@1p5@1i4@1p5%4i4@1f1^5i4K6R3%4i4@1t1@1i4@1f1#2i4K6R3#2i4K6R3^5i4@1f1^5i4@1u0r3i4K6W2n7i4@1f1^5i4@1p5I4i4K6S2o6i4@1f1#2i4K6R3^5i4K6W2p5i4@1f1#2i4@1p5%4i4K6S2n7i4@1f1#2i4K6S2o6i4K6V1$3i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1%4i4K6W2r3i4@1p5&6i4@1f1&6i4K6V1^5i4@1t1#2i4@1f1%4i4@1u0p5i4@1q4q4i4@1f1$3i4K6S2p5i4@1p5J5i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1%4i4K6R3@1i4@1t1$3i4@1f1#2i4K6V1H3i4K6S2q4i4@1f1#2i4K6R3$3i4K6S2p5i4@1f1$3i4K6V1^5i4@1q4r3i4@1f1^5i4@1u0p5i4@1q4q4i4@1f1#2i4K6R3%4i4@1u0p5i4@1f1$3i4K6V1#2i4@1t1H3i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1$3i4K6W2o6i4K6R3H3i4@1f1#2i4K6V1H3i4K6S2q4i4@1f1%4i4K6V1@1i4K6W2r3i4@1f1$3i4K6R3^5i4K6V1H3i4@1f1@1i4@1u0m8i4K6R3$3i4@1f1#2i4@1q4r3i4K6R3$3i4@1f1&6i4K6V1J5i4@1p5#2i4@1f1$3i4@1t1#2i4K6R3I4i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1@1i4@1u0n7i4@1p5#2i4@1f1@1i4@1t1^5i4K6S2n7i4@1f1$3i4K6V1^5i4@1q4r3i4@1f1^5i4@1p5J5i4@1q4n7i4@1f1^5i4@1t1H3i4K6R3K6i4@1f1%4i4K6V1@1i4@1p5^5i4@1f1#2i4K6S2m8i4@1p5H3i4K6u0r3i4@1f1^5i4@1p5%4i4@1p5K6i4@1f1#2i4@1q4r3i4K6R3$3i4@1f1#2i4K6R3%4i4@1u0p5i4@1f1$3i4K6V1#2i4@1t1H3i4@1f1$3i4K6S2q4i4@1p5#2i4@1f1#2i4K6S2r3i4@1p5K6i4@1g2r3i4@1u0o6i4K6W2m8
经过对比,我们发现这个函数就是chacha20的加解密算法,对比我们找到的源码,把这个算法拿过来稍微改改,只要把原来函数的in[j]参数直接换作是out[j]即可和题目一样,该参数既当作输入又当作输出。
注:我们使用的话,将github源码下载下来,把cpp和h文件导入即可;ChaCha20是一种流密码,可以将其理解为对称加密算法。
然后剩下的两个函数,如果有一定逆向题目积累的话,就不难猜测出这是RC4流密码。该算法先是对S盒的一个初始化,然后进行加解密操作,对该密码算法的详情可参考文末附带的链接。
加解密算法分析完了,接下来我们呼应一下本题的题目名称Random,看看前边遗留的sub_401610()
函数,该函数生成了一个加密密钥,如下图:

我们发现这是一个伪随机生成的,关键是要知道其伪随机生成的种子Seed,v0是根据当前时间的时间戳生成的,所以本题的一个坑点应该是在这里,当前的进程ID我可以猜测他的区间为1~9000
。
做题的时候,刚开始我以为时间戳就是flag.enc
文件的时间戳,后来发现怎么都出不来,于是从题目的出题时间开始算起(也就是 2022年9月11日,23:22:02),写了一个爆破,由于流密码的速度非常快,所以我很快就爆破出来了。
根据以上分析,我们写出其主要的wp代码,完整wp代码我放在了文末的附件,大家打开就能运行。
为了防止该题目再次出现,flag我就不以文本形式展现了。

另外本文中对另外一个反调试的函数没有进行过多分析,这类文章很多,大家搜一下就知道了,绕过方式也很简单。因为本题目的难度还没有用到动态分析。此外,虽是一道CTF题目,但是其中包含的反调试、ChaCha20、RC4流密码、花指令以及函数的识别,也值得我们多多去学习。
void ChaCha20XOR(uint8_t key[
32
], uint32_t counter, uint8_t nonce[
12
], uint8_t
*
in
, uint8_t
*
out,
int
inlen) {
int
i, j;
uint32_t s[
16
];
uint8_t block[
64
];
/
/
static void chacha20_init_state(uint32_t s[
16
], uint8_t key[
32
], uint32_t counter, uint8_t nonce[
12
])
chacha20_init_state(s, key, counter, nonce);
for
(i
=
0
; i < inlen; i
+
=
64
) {
/
/
static void chacha20_block(uint32_t
in
[
16
], uint8_t out[
64
],
int
num_rounds)
chacha20_block(s, block,
20
);
s[
12
]
+
+
;
for
(j
=
i; j < i
+
64
; j
+
+
) {
if
(j >
=
inlen) {
break
;
}
out[j]
=
in
[j] ^ block[j
-
i];
}
}
}
void ChaCha20XOR(uint8_t key[
32
], uint32_t counter, uint8_t nonce[
12
], uint8_t
*
in
, uint8_t
*
out,
int
inlen) {
int
i, j;
uint32_t s[
16
];
uint8_t block[
64
];
/
/
static void chacha20_init_state(uint32_t s[
16
], uint8_t key[
32
], uint32_t counter, uint8_t nonce[
12
])
chacha20_init_state(s, key, counter, nonce);
for
(i
=
0
; i < inlen; i
+
=
64
) {
/
/
static void chacha20_block(uint32_t
in
[
16
], uint8_t out[
64
],
int
num_rounds)
chacha20_block(s, block,
20
);
s[
12
]
+
+
;
for
(j
=
i; j < i
+
64
; j
+
+
) {
if
(j >
=
inlen) {
break
;
}
out[j]
=
in
[j] ^ block[j
-
i];
}
[培训]科锐逆向工程师培训第53期2025年7月8日开班!
最后于 2022-12-8 19:27
被newu编辑
,原因: 添加附件