SWPU2019-ReverseMe
方法一
IDA打开如下图所示(部分分析在图上):

基本可以确定,我们要让v16=0才是正确的,而能使v16=0有两条路径,第一条是伪代码中第89103行的 111行的while 循环,第二条是伪代码中第105if语句。显然,第二条路径是走不通的,因为不可能存在同时满足如下条件:
1 | |
所以唯一能行的是通过while循环的路径使得v16=0。
现在我们从头开始分析伪代码。首先是让我们输入长度为32bytes的字符串,存放于Block中,然后将字符串”SWPU_2019_CTF”复制给v33,接着是一个while循环使Block循环异或v33。之后有一个比较重要的函数sub_D625C0(约第77行),稍后详细说明。在这之后,又是while循环,里面让v12与v13作比较,v12来自数组v34(已知),v13来自数组v28,目前来看数组v28为0,显然v12与v13不可能相等,但在刚才的分析中,又必须是这里使得v16=0,所以极有可能是刚刚提到的函数sub_D625C0(约第77行)修改了数组v28。
这里使用动态调试来分析。首先在jnz处下断点,如下图所示:

随便输入长度为32的字符串。断点触发后,我们可以知道eax(数组v12)和edx(数组v13)的值,
1 | |
由于数组v12已知,数组v13未知,所以我们需要跟踪v13是怎么产生的。

显然应证了我们的猜测,v13是由函数sub_D625C0所产生的。
再进行新的动调,去除原先的断点,在call处下断点,运行后,再给ebp+var_88开始的32bytes的内存下内存断点(选中后按F2)。然后按F9,程序触发内存断点后会停下来。

可以看到,这里将ecx移动到目标内存中,而ecx是[esi+eax]异或[eax -4]得到的,esi+eax和eax -4开始的内存中的值如下(因为触发断点的时候已经执行了一步,所以不要少了前面4bytes):
1 | |
当然,现在无法确定这些值是不是固定的。
还能利用的应该就是Block(输入字符串)跟v33(已知字符串)异或后的结果,毕竟我们最终就是获取输入,而跟输入有关的就是异或后的结果(没有的话干嘛要这么做呢?)。
重新下断点,在cmp [ebp+var_8C],10h处下断点(即do…while…一开始的地方),如下图所示:

执行后,shift+F12搜索我们输入的字符串,可能有多个,以\0结尾的才是,情况如下图所示:

依图所示,就是倒数第二个。给这片内存下内存断点,F9执行触发断点(多执行几次),可以发现输入字符串所在的内存发生改变,即进行了异或加密。

在该部分汇编代码中,可以知道edi是字符串下表,ecx是输入字符串的起始地址,并且后面还将异或后的字符串复制到另一片内存中,即ebp+var_64开始的内存。
这一部分清楚后,我们清除原来的所有断点,然后给 call sub_D625C0下断点(就是伪代码中第79行的函数),执行到该处后(此时已将异或后的字符串复制到了ebp+var_64开始的内存),再给ebp+var_64开始的内存下断点。之后再F9触发内存断点,

还是原来那个地方,eax-4开始的内存存储的就是之前异或得到的字符串。因此可以确定的是esi+eax开始的内存中的值是固定的,而eax -4开始的内存中的值是异或得到的字符串。
总结一下,整个过程就是,输入的字符串循环异或 “SWPU_2019_CTF” ,得到的结果再跟
1 | |
异或,得到的结果为
1 | |
整个伪代码很复杂,但是最终我们分析出来的加密过程却这么简单。脚本如下:
1 | |
方法二
使用 Ponce 插件,参考IDA插件Ponce的使用-软件逆向-看雪-安全社区|安全招聘|kanxue.com。
使用该插件,我们需要找到待求解字符串的地址和长度,以及距离正确路径最近的一个条件跳转。下面详细说明一下怎么使用。
首先搜索输入字符串,找到后,选中32bytes大小(不要合并成一个字符串),右键 -> Symbolic -> Symbolize memory。

然后找到距离正确路径最近的一个条件跳转,经过我们前面的分析,可以确定的是伪代码中第94行,如图所示:

按F9执行到该处后,右键该处分支断点 -> SMT Solver -> Negate & Inject,然后就可以得到部分正确字符。
重复粗体字的操作(F9,右键…)直至全部解出。最后在待求解字符串所在的内存中读取即可。