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,右键…)直至全部解出。最后在待求解字符串所在的内存中读取即可。