作业-2:加密算法
黄芃洋 2021212021
题目要求
问题-1:找到开头“This program cannot be run …” 这个字符串的内存起始地址(即字符串的指针),并准确计算该字符串的长度;
问题-2:需要结合具体的加密操作逻辑,说明所发现的加密算法。
以下是作答:
问题-1
首先我们尝试运行
cryptp.exe
文件查看操作前被打印语句情况,简单对目标字符串进行初步定位。观察到“This program cannot be run ...”
这一字符串处于两横线间,退出文件并将其用IDA工具打开首先猜测目标字符串有可能是存于
.data
数据段的静态字符串数据,因此通过Alt+t
查找该字符串的关键字,但未查询到任何结果因此改变思路在 Functions Window 点击定位至
_main
函数查看可用信息。来到
_main
函数后观察到了存于.data
数据段一个内容为'--------------FOR FUN------------------...'
名为aForFunS
的字符串,这段汇编代码表示在栈上存储了一个指向字符串aForFunS
的指针,然后调用printf
函数来打印该字符串双击
aForFunS
查看该字符串的具体内容不难发现,在
--------------FOR FUN------------------
和---------------------------------------
间有占位符%s
,根据位置推测该位置为我们的目标字符串This...
的位置回到
_main
函数位置,使用F5
快捷键对函数进行反编译不难看出
printf
函数有两个参数。第一个参数是先前我们所看到的aForFunS
字符串,第二个参数是一个指针,它指向内存地址0x40004E
处的字符串。当printf
函数执行时,%s
会被替换为内存地址0x40004E
处的字符串。分析至此,我们不难推测我们的目标字符串
This...
位于内存地址0x40004E
处,该内存地址是一个绝对地址,它指向程序crypto.exe
的内存空间中的一个位置但是查找后发现IDA的内存地址是从
.text:00401000
处开始的,也就是说在静态情况下我们无法查看内存地址0x40004E
处的内容,因此可选择在IDA
中对该exe
文件进行动态调试进行查看(使用010Editor
也可以找到该地址所存内容)在
printf
函数处打上断点,开启动调此时我们再双击内存地址
0x40004E
即可进入查看该地址所存内容,正是我们要找的目标字符串This...
所存位置因此该字符串的内存起始地址即为
0x40004E
,而通过人工计数可知该字符串长度为43位当然,我们也可以通过更为稳妥的方式对字符串长度进行计数:如果能在该程序中找到一个
strlen
函数的话通过动调更改字符串指针即可自动测出准确的目标字符串长度,strlen
函数在加密函数中比较常见,因此可能能够找到。使用
F5
快捷键对函数进行反编译快速浏览整个程序,果然在名为sub_401700()
的函数中找到了一个strlen
函数该函数原本测量的是名为
byte_407444
字符串的长度返回汇编代码查看
strlen
函数的具体实现这段代码将寄存器
edi
的值设置为内存地址0x407444
处的字节的地址接下来,函数使用repne scasb
指令在以edi
为起始地址的内存区域中查找与al
寄存器相等的字节。由于之前将寄存器eax
清零,因此这个指令实际上是在查找第一个值为0x00
的字节,其实就是相当于在找字符串的结尾位置。当查找完成后,函数使用not
和dec
指令计算出查找到的字节与起始地址之间的距离,并将结果存储在寄存器ecx
中,那么也就是说,我们只要将我们的目标字符串的内存地址0x40004E
存到edi
中,最后查看ecx
中的值即可得到准确长度的答案。分析完毕,打上断点后开始动调
单步步过后修改
edi
中的地址值为0x40004E
继续单步步过到运算位置之后检查
ecx
中的值,为2B
位,转换为10进制为43位因此,不难得出如下结论:开头
“This program cannot be run ...”
这个字符串的内存起始地址为0x40004E
,字符串长度为43位
问题-2
定位到
main
函数了解程序结构,在printf
函数将提示信息打印后调用了三个函数,分别为sub_4015C0
、sub_401770
和sub_40161C
,下面逐一进行分析进入
sub_4015C0
函数本段代码的用于接收输入值,并将输入值存入
byte_407444
中,接下来是一段熟悉的刚刚分析过的代码,用于测量输入值的长度(也就是用于实现strlen
函数功能),并存入eax
中;后将长度与byte_4040E4
值比较,若二者不相等,则退出程序。进入
byte_4040E4
查看到其存值为9,也就是说输入值的长度必须为9位。在上一步的分析中,无意中发现存于
.data
数据段的其他静态字符串数据,察觉到熟悉数据串67452301
等等,该数据串为MD5
加密方式中的标准幻数,因此跳跃分析该程序可能是基于MD5
方式并加以改进加密的。为了进一步确定推测,对该数据串使用快捷键
x
进行交叉引用分析,定位至调用该数据串的位置使用
F5
辅助分析,经过代码阅读可确定sub_40210C
函数是MD5
加密函数,且该函数的第一个参数用于指向密文结果,第二个参数为待加密字符串,第三个参数为加密位数。为了避免之后分析忘记,使用快捷键n
将该函数重命名为md5_encryption
。处理完上述内容后,继续回到第一个函数
sub_4015C0
进行分析,经过刚刚的分析我们不难得知,第一个函数的作用在于判断输入内容是否满足9位的条件,因此我们可将函数sub_4015C0
重命名为if_input9
,并将存储输入值的参数byte_407444
命名为input
、byte_4040E4
重命名为len_9
。接下来分析第二个函数
sub_401770
,通过汇编代码可初步分析出该段函数在对input
字符串进行字符类型的核查使用
F5
辅助分析,发现函数确实在对input
字符串进行字符类型的核查,且要求字符串input
中的每个字符都为为数字、大写字母或下划线,如果字符串中存在不符合条件的字符则退出程序。继续分析发现函数的
return
值为另一个函数sub_401700
,下面对其进行分析这段函数的主要内容是通过一种特定规则从字符串
a1234567890Abcd
中选定一些字符生成一个新的9位字符串dword_4040C0
,我们不妨命名其为new
,其中参数v2
的值为38,因此我们不妨把函数sub_401700
称作gener_new
,把函数sub_401770
重命名为check_and_gener
。根据该伪代码写了一个函数
sub_401700
的python
程序实现如下(该程序只用做逻辑结构展示而无实际运行能力,因为input
值还未知):abc = '1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ' new = [1, 2, 3, 4, 5, 6, 7, 8, 9] input_list = [None] * 9 for v0 in range(5): v2=38 v3=0 while v3<37: if input_list[v0] == abc[v3]: new[v0] = v3 v3+=1 print(new)
这时间突然发现了一个问题:关于
input
字符串,在IDA
中显示,该字符串只开辟了一个5位的数组,然而函数的种种逻辑表明input
存储内容为9位且没有溢出,而且在对该字符串检验长度时也依然为9位没有问题,这着实让人感到困扰。继续浏览该地址下方的内容发现,紧接着该数组,有一个名为
byte_407449[7]
的数组开辟了7位空间,因此猜测该数组应该能够承接input
字符串溢出的最后4位数据带着猜测进行动调验证,输入了9位测试数据再回到
byte_407449[7]
处,发现该数组确实存储了input
字符串溢出的最后4位数据,这确实很有意思,但当前还不知道这样处理的意义,暂且将该数组命名为last_4
最后来分析第三个函数
sub_40161C
,该函数的汇编代码有些复杂,直接F5
进行辅助分析其中
byte_404021
存储的内容为3,因此v1
值为3,byte_404020
存储的内容为9,因此v6
值为9,下方紧接着是一个字符串变换操作,其大致作用在于对input
的9位内容根据一定方式进行变换,数据均选自字符串a1234567890Abcd
,同样的,根据该伪代码写一个该变换的python
程序实现如下(该程序同样只用做逻辑结构展示而无实际运行能力):0=9 v1 = 3 v6 = 9 v2 = 0 while v2 != v0: input_list[v2] = abc[ (9 + 3 * new[v2]) % 37 ] v2+=1 print(input_list)
在这个变换之后进行的就是我们先前已经提到的
MD5
加密,值得一提的是,该程序只将input
的前5位进行了加密操作,并将密文存储到Str1
中,之后将Str1
和Str2
进行了对比,如果二者相等且如果经过了变换之后的输入值input
的后四位的值为32Z2
则会进行弹窗操作,那么我们不难推测Str2
中存储着正确的密文,进入Str2
查看,果不其然,得到正确密文:f8728f24e01c1aaf54e23f7f0d591384
现在我们已经知道该加密方法为
MD5
,密文为f8728f24e01c1aaf54e23f7f0d591384
,明文长度为5,那么通过暴力破解可以获得明文为:K502G
该程序的全部内容已分析完毕,下面进行输入字符的推演,根据上述内容的分析,不难写出一个
input
字符串转换的全过程python
程序:abc = '1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ' new = [1, 2, 3, 4, 5, 6, 7, 8, 9] input_list = [None] * 9 for v0 in range(5): v2=38 v3=0 while v3<37: if input1_list[v0] == abc[v3]: new[v0] = v3 v3+=1 print(new) v0=9 v1 = 3 v6 = 9 v2 = 0 while v2 != v0: input_list[v2] = abc[ (9 + 3 * new[v2]) % 37 ] v2+=1 print(input_list) #input_list=K502G32Z2
那么情况已经很明朗了,根据我们前面的分析,已经知道
input
字符串在经过不断变化之后最终输出结果为K502G32Z2
,我们只需要对这个程序进行逆向即可,我们根据转换过程可以写出一个逆向程序:pre_list = [None] * 9 abc = '1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ' abc_list =list(abc) input = 'K502G32Z2' input_list = list(input) new = [None] * 9 v0=9 v1 = 3 v6 = 9 v2 = 0 for i in range (9): for x in range(37): if(input_list[i] == abc[ (9 + 3 * x) % 37 ]): new[i]=x continue print(new) for v0 in range(9): v2=38 v3=0 while v3<37: if new[v0] == v3: pre_list[v0] = abc[v3] v3+=1 print(pre_list)
这样我们就能获得
pre_list
即先前的input
字符串为:5M1LE_L0L
我们进入
cryptp.exe
文件进行验证:实验成功