密码逆向


作业-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 的字节,其实就是相当于在找字符串的结尾位置。当查找完成后,函数使用 notdec 指令计算出查找到的字节与起始地址之间的距离,并将结果存储在寄存器 ecx 中,那么也就是说,我们只要将我们的目标字符串的内存地址 0x40004E 存到edi中,最后查看ecx中的值即可得到准确长度的答案。

  • 分析完毕,打上断点后开始动调

    单步步过后修改edi中的地址值为 0x40004E

    继续单步步过到运算位置之后检查ecx中的值,为2B位,转换为10进制为43位

  • 因此,不难得出如下结论:开头“This program cannot be run ...” 这个字符串的内存起始地址为 0x40004E ,字符串长度为43位


问题-2
  • 定位到main函数了解程序结构,在printf函数将提示信息打印后调用了三个函数,分别为sub_4015C0sub_401770sub_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命名为inputbyte_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_401700python程序实现如下(该程序只用做逻辑结构展示而无实际运行能力,因为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中,之后将Str1Str2进行了对比,如果二者相等且如果经过了变换之后的输入值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文件进行验证:

    实验成功


文章作者: autumnwt
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 autumnwt !
  目录