应某童鞋的要求,写个新手能跟着照做的题解,我想就拿这个题试试看吧。毕竟我也常有这样的苦恼,大神们觉得很普通的操作过程,一句话说完了,我百思不得其道,有点同病相怜吧。
直接ida载入,发现入口被改,此时入口是直接调用有代码修改功能的函数sub_44701F
。此函数先读取程序文件.text
区段开始的内容,再进行异或及算术运算,把运算结果复制到当前运行程序的相应地址空间。然后还进行了部分api的加载,最后跳转到像是VC程序OEP特征的入口。部分代码如下:
看到这,我就想如果我直接把计算后的结果dump出来,是不是就可以了。至于加载的api什么的看看情况再说吧。
我就是这么做的,dump出来,程序直接能运行,图标也似乎正常了。没看出哪有区别。但是为了保险起见,静态都是用的这个dump出来的看的,动态都是用的原程序,那也只是多了个加断点的过程,其它并不影响。在最后我特意验证了下,程序功能似乎更改了。
OD载入程序,一路粗跟,最后来到401690
函数,检查两个全局变量是否为0,第一个变量439B50
为0就直接返回了;第二个变量439B51
不为零,后面似乎还有几个函数调用,但是没有启动窗体。
继续走,窗体创建,设etDlgItemTextW
api断点(至于GetDlgItemTextW
肯定不是,看看就知道),输入确定。来到401740
,还是看伪代码吧。方便看些:
很明白,取输入,然后进401A40
进行计算,这个计算函数比较简单,主要是算术及异或计算,最后与一定值比较。我不知道别人有没有算,反正我是没有计算的,这个简单得太假,而且当时我猜测可能算不出什么来。在研究这个计算代码的时候,发现401A40
下面有函数有明显的字符范围的检查(这个就是常看汇编的好处啊,光盯伪代码肯定发现不了)。这就更肯定了,此处算法为假算法。
再联想到窗体如果不创建,那后面调用的那些函数是干什么的呢,窗体创建了,那串函数就没用了。
先看看控制窗体启动的flag byte_439B51
是在哪设置的。只有一个写的地方40145D
,在401240
函数中。设置过程大概是:先获取本进程快照,然后获取快照中的进程句柄,然后判断此进程的父进程是不是explorer.exe
或cmd.exe
,如果是并且检查一个堆数据成功则flag为0(检查堆数据这个我一直不理解),否则为1。第一个flag的值也是在此设置的,似乎不影响程序的正常运行,就没有关注。再继续溯源,回到401000
,41B35A
,41B28A
,4181DF
,最后来到的函数4181DF
是VC入口的___tmainCRTStartup
。
上面的发现可以推断出两点:一,这应该可以用作反调试,二,此程序有两个进程。
直接运行了下程序,确实有两个进程。我尝试对两个进程进行了附加,一个有窗体,一个没有窗体。没有窗体的可以进行附加,入口为程序壳的入口地址(实际过程中此处还没有发现dump出来的程序不一样,为了保险用的原始程序,包括后面的一些都是用的原始程序);而有窗体的程序不能附加。不能附加说明什么问题:进程已经处于调试状态,调试者当然是父进程。
我又用OD改名和不改名分别跟了下这个过程,因为检查堆数据的问题,flag一直为1。
到这里再思考下,现在不管flag什么条件下为0,有一点可以肯定,正确流程应该是flag先为0,开启另一个进程启动窗体,非窗体进程就是窗体进程的父进程,父进程对子进程进行调试,修改流程。但是由于子进程不能附加,所以是看不了代码的。下一步要做的就是:掌握父进程对子进程的修改或想办法搞到子进程的代码。
考虑到跟踪父进程太累,先使用dump的办法。用petools对刚运行的程序进行dump,没进行任何修复改动,直接进ida,发现本来401740
中是这样的
后来变成了这样:
dword_437A08
已经为0。 还是要看父进程,除0异常应该被调试者接收吧。
还有一个办法,如果父进程对子进程进行了更改,然后并未还原呢?抱着试试看的想法,我随便输入并确定,提示错误,然后再dump。果然不一样了。这里提醒一下,第二次dump出来的文件的PE头的dosheader
已经被修改了,需要还原下。下面对比下原始与dump出来的变化情况。
为了方便看,我直接上汇编了,开始是这样的:
第一次dump后是这样的:
唯一的区别就是dword_437A08
变成了0。第二次dump是这样的:
这一次变化比较大,除操作已经被nop掉了,401A40
调用实际上已经被改成了402300
调用。其它地方有没有更改不太清楚,但是更改的可能性比较小,改动开始没有显式调用的函数反倒可能会显露目标,而且这种手法明显只是为了隐藏真实的检验流程,目标已经达到(另外,我当时也简单对比下402300
,没看出什么改动)。暂定先尝试手动完成父进程完成的工作,看程序运行情况,及检验流程情况。 直接改成这样:
动态前先看下静态的情况,函数过程比较复杂,代码量比较大。我们从输入输出入手,函数参数只有一个输入的字串,返回是True
则成功,函数对参数及返回读写的区域在4029D5
到402B34
之间。所以这部分是重点跟的。
动态跟了下这个函数开头,此处利用开始壳读取并修改的文件内容(从这里可以看出,开始从堆中dump出来的程序是不能完成验证的),加载库及其它操作。直接跳过,在下断4029D5
。
这里开始的算法比较简单。就是用输入前8位作为一个hex数a1,9-10位为一个hex数a2。对一堆区0x5000 byte的数据进行运算,最后与byte_4340B0
处的数据比较前0x60个字节。运算部分的代码翻译过来如下:
反解,得到前10字节,0x75A29C09E1,注意,在unhex的函数中均将输入以大写hex进行转换,反解代码如下:
后面memset两个栈区后,将第11位开始的输入unhex(也要求输入是大写hex)。接着读入!HelloHaniella!
常量,并将硬编码的19970907
写入栈区,并调用前面刚运算完的堆区,原来上面是为后面解码代码的。
进去看了看,算法比较明显,DES。原因有2:一是19970907
应该是作为密钥的,进去后立即被变换成56bit;二是再看下变换表,就是DES的表。
后面还有IP变换表,S-BOX变换表,移位表,P变换表等,我一一比对,没有改动。这里有个细节有注意了下,轮密钥是从后面开始用的,说明这是解密过程。下面的代码就是轮密钥与扩展密文的异或过程,轮密钥从后往前用的:
DES加密完成后,与!HelloHaniella!
按byte比较,直到遇到0,结束比较,0也进行了比较。
到这里,我直接将检验值进行des加密,结果竟然不对。此处我花了太多时间,重新对比变换表,及算法过程。确实没有发现问题。没办法,我直接将改待解密数据为检验值,将轮密钥前后倒置,最后看了结果与我算的还是不一样。因为此,我花了半天时间。
最后直接一步一步跟,并写了des,打出各个过程中值,进行比对。解密完成也没有发现不对。后面再跟,发现最后结果提取作了手脚,结果数组下标为偶数的进行了取反。我的天,这是玩心理学啊。其代码如下:
DES的过程如下,数字为偏移:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!