逆向分析 —SMC
了解 SMC 代码自修改程序在逆向分析中的表示,并体验其分析过程。
初识程序
打开所给程序,发现里面什么都没有,在里面试着输入一些内容后,发现它返回 try again
,看来只有想办法看到源代码了。
把这个程序拖入 IDA,发现可以找到 main
函数,且结构并不是很复杂,所以现在可以着手开始看了~
逐层分析并解密
第一层加密
从结构图来看,程序整体是分支 - 循环结构,且观察下边的分支可以大概分析出,我们的输入多半走了最左端的红线,输出了一个 Try again
(但是因为没有 system("pause")
而直接退出了程序)。而想要继续分析下去,找到 flag,右边的循环部分是重点。来,看看循环吧。
输入的内容长度必须为 28
(1Ch
),才能进入到右边的绿线。
当最后一个字符为 7Dh(}
右花括号)时,进入右侧的循环。右侧的循环一共进行了 67 次(43h
),每次都对 byte_414C3C[i]
中的内容异或了一个常数 0x7D
。67 次循环结束后,程序 call 了一下 [ebp-6Ch]
中的内容,结合刚才的过程,[ebp-6Ch]
中的东西应该是一个函数,但是现在我们还不知道它是什么,我们需要写一个脚本来把隐藏的函数弄出来。
Hex-Rays 为快速修改二进制码提供了一个 API 接口,其 Python 接口称为 IDAPython,文档为 https://www.hex-rays.com/products/ida/support/idapython_docs/,我们只需要其中的 patch_bytes(address, buf)
即可。
IDA-Python 常用的 API
可以看到,idaapi.py 提供了这些常用函数,对于本次实验来说已经足够。
1 | get_bytes(address,count) # 从address处读取count个字节的内容 |
1 | def xorize(start_loc, end_loc, num): |
可以观察到,执行脚本后,byte_414C3C
中的内容发生了改变,我们紧接着将其转换为汇编代码(按 C 键),可以看到.data
字段变红,然后,右键起始地址,选择 create function,将其反编译为函数,
现在这样就非常容易分析了,我们输入字符的前五位一定是 flag{
,加上结尾的}
,已经解决了 28 个字符的 6 个,剩下的还需慢慢来。
第二层加密
我们留意到,下方有一个 do-while
循环,一共循环了 90 次,它的作用是将 a2
中的内容与 0x43
异或,想要解密,我们异或回去即可。留意到前面 main
函数调用的方式,可知这次利用到了 unk_414BE0
中的内容。在这一步结束后,对剩余的部分调用 a2
函数,我们现在不知道它是什么,必须先把它解出来。
继续写 payload,加载 payload。来到第三层加密。
1 | xorize(0x00414be0, 0x00414c3a, 0x43) # for part 2, see previous for xorize() definition |
第三层加密
终于来到了第三层函数,留意一下 sub_414C3C
中的 a2(a1 + 5, &unk_414A84);
。unk_414A84
其中的内容需要经过 347 次循环,每一次将一位内容与 0x55
异或,步骤类似前面。
1 | xorize(0x00414a84, 0x00414bdf, 0x55) # for part 3, see previous for xorize() definition |
经过以上 Payload 得到以下这样:
注意到这里还有一层异或加密,先解开再说。由 414be0
中的 a2(a1 + 4, (const char *)&unk_414A30);
得知,是 414a30
中的东西被调用了,所以要把它解开,逐位异或 0x4d
83 次即可
1 | xorize(0x00414a30, 0x00414a84, 0x4d) # for part 3, see previous for xorize() |
(截图中 v2 的值不太对,实际上是 - 1)
第三层的主要作用则是判断接下来的 4 个字符与 0xCC
异或后的结果要与一串硬编码的值相同(注意到 v5 = 0x93A9A498
)。经过运算该值为 The_
第三层的解密脚本如下(注意逆序)
1 | def part_3(): |
第四层加密
经过提示,sub_414A84
中进行的是 base64 运算,之后的几个字符串加密应该得到 cmVhbEN0Rl8=
,随便找一个在线 base64 解密网站得到该字符串为 realCtF_
第五层加密
第五层,所得字符每一个加一,得到字符串 kvtu`C4h"o (s [5]=`),经脚本计算后得到字符串为 just_B3g!n
第五层的解密代码如下
1 | def last_part(): |
所有的 payload 代码如下所示
1 | from idaapi import * |
快结束了!
所有的东西拼在一起为 flag{The_realCtF_just_B3g!n}
,此即为最终的 flag 目标。