样本概况

应用程序的信息

应用程序名称:010Editor.exe
版本:8.01

分析环境及工具

系统环境:win7虚拟机
工具:OD

分析目标

暴力破解、算法分析、编写注册机

分析思路

暴力破解思路分析

  • 找到注册的窗口
  • 测试注册窗口的反应
  • 根据反应做出下一步分析的打算

    • 猜测API,在API处下断点动态调试
    • 挑出敏感字符串,在程序中搜索
  • 动态分析,定位关键跳转,修改代码
  • 动态分析,定位关键CALL,修改代码

暴力破解分析

我们拿到程序之后,首先使用ExeinfoPE查看程序的信息

图中显示使用的是VC++ 2013,没有检测到壳
接下来我们继续查看程序PE结构

从图中我们可以看出,程序有重定位,稍后我们分析的时候可以手动将重定位关闭
基本信息看过之后,我们打开程序运行,分析一下从哪里开始入手。打开程序之后会显示我们有30天的试用时间,说明我们还没有注册,这时我们就在上方菜单栏点击Tools->register可以弹出注册对话框

由于我们主要的目的就是破解程序,所以我们入口的地方就是破解它的算法或者绕过验证,如果要完成上述两个方法,经过分析我们需要在输入完用户名和密码之后的CheckLicense按钮的触发事件进行分析,接下来我们就测试一下输入错误的用户名和密码看有什么提示。

当我们输入错误的时候,会弹出一个提示框,提示你输入的用户名或密码无效,到这里我们就发现了一个可以让手的点,就是弹窗,由于他是在我们CheckLicense之后触发的,也就是在验证过账号密码之后做出的判断,所以在这个函数下断点,然后向上分析应该能够找到用户名和密码验证相关的代码,或者我们搜索错误提示的字符串也是可以的。
简单分析之后,我们就打开OD附加010Editr程序,开始寻找弹窗的API。在我们附加程序之后,我们在模块部分找到我们进程的主模块双击进入主模块,这个时候我们可以右键->查找->当前模块中的名称(标签),通过这个我们可以查看当前模块的一些导入函数和它自己的一些导出的信息。

打开之后,我们可以看到主模块导入了大量的函数,查找起来比较麻烦,所以我们可以使用IDA来进行函数的查找,接下来我们就在IDA中打开010Editor的主程序,然后进入到IMPORT导入函数界面

在导入函数界面我搜索了Window来查找,最后发现和窗口相关的API只有下面的几个,且都不是创建窗口的函数,剩下的基本都是QT相关的函数,我们可以判断它弹出窗口的函数是使用QT的界面库,其中也可以找到多个QT创建窗口的函数,不过我们还是对API进行分析,虽然使用的是QT的函数,但是其最终还是会调用系统API,而系统中和窗口有关的模块是USER32,所以我们可以继续在OD中找到USER32模块创建窗口的函数,对每一个可以创建窗口的函数进行下断点。CreateWindow、MessageBox、DialogBox、CreateDialog

既然断点已经下好,我们就对CheckLicense弹出的窗口进行测试,当点击CheckLicense之后,我们可以发现其断在了CreateWindowExW这个函数,我们继续运行还是断在了CreateWindowExW这个函数,最终弹出窗口,所以我们可以确定弹出错误提示窗口的函数是CreateWindowExW,将其他函数的断点取消。

我们找到了调用系统函数API,这个时候我们还需要知道主模块中是在哪里调用了这个函数,接下来我们点击OD上方菜单栏中的K进入栈回溯查看是主模块中调用这个函数的地方。

我们逐个进入每个主模块调用的地方,并在调用的代码附近查看是否有敏感的信息,最终我们找到一个包含无效字符串的一处调用,是第三个主模块的调用。

为了了解程序是如何进行到这一步的,我们需要对之前的流程进行分析,这里我们首先回到跳转到这里的判断语句,我们可以在中间的信息栏看到(如果没有,我们ctrl+a进行分析)跳转过来的地址,我们转到那个地址,然后依次向上分析,找到最开始的判断关键点。

从上面的那一条的跳转为下面代码的判断结果。其中前两个JE都没有进行跳转,只有在JNZ的时候进行了跳转,且这段代码也是上面的代码跳转到CMP EDI,0xED处,所以我们继续向上追溯

我们从上面的代码回到了下面的JNZ处,在这里判断了EDI和0xDB是否相等,在判断上面的代码都是没有执行的,之前的跳转直接跳转到CMP处命令,所以我们继续向上追溯。

到这里之后,我们看到一条CMP EBX,0xE7,而EBX的值来自于CALL010Edito.0123A826的返回值,经过我们的对这一块代码的分析这里就是判断的起始位置,所以我们分析整个判断就从这里开始分析。

既然找到了判断的最开始的部分,那么我们就可以找到结果为正确验证码的那一条线来进行暴力破解。我们从判断开始的位置向下观察,可以看到一条带有字符串语句如下,从字符串的意思我们可以判断出这是我们输入正确用户密码提示的信息,所以我们接下来就来分析如何让代码可以执行到这条语句的位置。

我们从这条命令向上看,一直看到我们之前分析过的代码找到了关键跳转处,只要EDI等于0xDB那么就不会进行跳转,一直执行到正确的代码,所以我们还按照之前的路回溯,找到之前的判断,看什么情况下EDI等于0xDB。

这样又回到我们判断的初始位置,我们可以看到EDI的值是EAX的值,由于在一开始我们已经知道是VC++的程序,所以我们可以知道可以是函数的返回值,所以我们需要知道什么情况下函数的返回值是0xDB,接下来我们针对这个CALL函数进行分析,我们进入到这个函数中。

进到函数中之后我们就发现了关键的地方,就是对EAX进行判断跳转,我们可以看到有三个地方对EAX进行赋值并进行跳转,最终这三个跳转的返回的EAX都是不一样的,对于其返回的值我对其进行了注释,通过这里我们得知如果需要我们返回的值为0xDB,就需要EAX为0x2D,就需要分析上面的CALL的函数,如果我们仔细观察会发现,这里CALL的函数和外面第一函数是同一个函数。

从上面的条件我们可以的得出一个结果就是我们可以修改这个函数(两次调用的这个函数)的的返回值,将它的返回值修改为我们需要的值。所以我们进入这个函数进行观察。我们看到有一些OPCODE后面跟的值有下划线,这表明这个值是一个重定位的值,我们在开始的时候验证了这个程序是有重定位的,所以我们接下来就手动将其重定位进行关闭。

我们使用010Editor打开我们样本程序的主程序执行文件,然后在NT_HEADER->OPTIONAL_HEADER->DLL_CHARACTERISTICS->DYNAMIC_BASE的值改为0。

接下来我们继续用通用的方式定位到刚才的位置,修改我们找到的关键函数进行修改其返回内容为0x2D,我修改的时候只直接在函数的开始将EAX的值赋值为0x2D。

修改完这里的值之后,我们找到我们之前分析到的关键跳转

我们将这里的JUMP直接NOP掉

接下来我们选中我们修改的部分然后右击选择复制到可执行文件->选择,然后在弹出的界面继续右击保存文件,然后我们运行我们修改后的010Editor,随便输入用户名和密码进行注册发现注册成功。

最终总结,我们从一开始的分析到使用API函数来进行关键代码定位,其实我们还可以使用另一种方式,就是使用字符串搜索的方式来进行找到关键代码,因为我们在分析的过程中也发现了其错误提示是以字符串的形式,所以我们也可以在OD中搜索字符串来定位。

算法分析

我们在上面使用了暴破的方式破解了010Editor这个程序,但是我们还是要留下了一个问题就是我们修改了返回值的那个关键函数怎么才能返回0x2D,这是一个问题,接下来我们就针对这个关键函数进行分析。
我们首先定位到函数的位置,由于我们之前把程序的重定位已经关闭了,所以我们可以直接搜索地址来找到关键函数所在的位置(地址是0x40A826)。
我们以下的图中我们可以看到首先对ECX进行了赋值,我们知道C++中我们通常对ECX赋值为this指针来进行传递,然后又push了两个立即数。

既然这里传递了this指针,为了方便我们后面的分析,我们就在这里查看this指针指向的地址存储着什么数据,我们右击ECX在数据窗口中跟随。

我们分别对这四个类似于地址的数值进行右键->数据窗口跟随DWORD(如果地址是可以访问的,则会出现数据窗口跟随DWORD这个选项)
第一个地址中内容,分析不出有什么内容,看起来像是一个数组

第二个地址中内容,这里我们可看到,我们注册时候使用的用户名就显示在了这里

第三个地址中内容,这个数也可以轻易的看出是我们输入的password,而且每隔4个会有一个横杠来做分割。

第四个地址中内容

看完这些之后,我就开始跟踪这个函数的代码,在跟踪代码的时候,我们需要重点关注的是数据的访问和修改,就是针对我们的用户名和密码是否又被访问到,如果有访问到就去分析,没有我们暂时先不分析。函数最开始的部分是开辟栈帧等初始化操作,之后对我们输入的用户名和密码判断是否为空。


接下其中一个函数是将我们输入的内容转为16进制格式进行保存

下面一部分就是对我们的密码进行计算,计算的每一步的分析都已经注释,以便于我们后面的分析。

其中两个CALL中的代码分析如下,第一个

第二个

用户名调用完一个函数之后和我们输入的密码的第4位,第5位,第6位,第7位进行比较,如果相等则继续,不相等,用户名和密码就都是无效的,所以经过我们分析,用户名和密码是有关联的,只有我们输入的用户名在进行运算之后和密码的相应位置相等即为正确

通过以上的分析,其实我们就可以使用C++来编写代码,通过这些代码来算出能够执行到正确位置的密码,所以接下来我们先来编写一部分的密码生成代码,在这段代码中,实现的步骤和我们在汇编代码中对主要变量的注释是对应的,根据其在汇编代码汇总进行的操作生成一个公式来完成密码的计算。
在我们对汇编代码的分析中,大部分的密码位置没有给出明确却的值,只有对运算结果的条件限制,所以我们使用随机的数值。
但是像p[3],已经给出明确的几个值,所以我们随机选择一个值填充在其位置即可。

其中注释为test1的是我们需要一步分析的最终分析的情况如下

我们将这个函数中的条语句使用C++实现,其中有一段大量的计算,我们可以使用函数的地址,在IDA中找到对应的汇编代码然后使用IDA的反编译工具生成伪C语言代码,便于我们实现其算法。
在IDA中给我们生成的参数中,由于我们之前进行分析后,第四个参数是我们之前计算的一个结果,但是这里却使用char的格式进行传递,我们回到OD中查看接受这个参数的类型是一个DWORD类型,以及在调用函数之前对这个参数进行赋值的位置是一个WORD,毕竟在测试阶段,我们就将IDA中的第四个参数的数据类型修改为short类型进行尝试。

我们观察完代码之后,我们对生成密码的步骤进行稍微的调整,之前我们首先计算的是密码,然后再继续后续的计算,不过再后面的计算中,我们将已经将能够判断已经符合条件的值设置为确定的值,使用这种方式来进行计算,哪些没有固定要求的还是使用随机值。比如之前有一个值是>=0x3E8,这里我么就可以直接写为0x3E8。
我么这个验证的主要流程如下,首先我们确定了用户名,再验证中对用户名进行了加密运算,最终得到了一个值,然后根据这个值不同位数和我们输入的密码进行比对,如果全部符合条件,那么我们输入的用户名和密码就是正确的。

算法分析总结

  • 判断用户名是否为空
  • 将密码字符串转为16进制字节数数据
  • 验证密码16进制数据
  • P[3] = 0x9C或者0xFC或者0xAC
  • sub_00407644函数的返回值不能为0
  • sub_004083C8函数的返回值不能为0且小于等于0x3E8
  • 将用户名转为ascii字符串
  • 使用用户名计算处一个key,与密码的5-8位相等
  • 调用sub_402e50函数,参数是用户名字符串、1、0、sub_4083C8函数返回值的结果
  • 使用密码中的几个字节与sub_402e50函数的返回值进行比较
  • 判断参数

去掉网络验证

走到这一步我们完成了本地的验证,但是我们还有一个关卡,就是网络验证,我们需要想办法取消网络验证。首先我们去掉之前多余的断点,只在我们之前找到的关键函数附近下断点。

当我们继续Check的时候,断在了00DD5800的位置,这个函数的返回值是0x2d,返回这个说明之前的验证是成功的,但是我们继续执行到00DD5814这个函数之后,返回值位113,我们进入函数观察之后,发现这里又进行了二次验证。

最终会提示

我们再次验证,进入到二次验证的函数中,经过分析,在它验证算法之前,他会根据一个数据标识来判断我们是否进行网路验证,没有就将我们直接ret函数,这种情况,我们直接条件。

修改了上面的验证之后,我们发现还有一处函数进行验证,这里就是网络验证。

在这个函数之前,有两个跳转,如果进行了跳转,会直接跳过网络验证的函数,我们可以通过修改跳转来完成目标。

最终绕过了网路验证。

最后修改:2020 年 08 月 26 日
如果觉得我的文章对你有用,请随意赞赏