第19章:寻找UPack OEP
PE头在进程装载的时候使用格式比较固定,从中找到需要的信息后不必过多关注,只需要找到还原后的节区体即可。
从文件中的EntryPoint找到进程入口:
占用不需要的元素,其在节区如下区域中插入代码:
蓝色框中是可选头所占区域,其余的到16F为多余的区域。
这条命令将ESI所指的区域中的27个Dword大小的数据移动到EDI所指的地址中(即F0~16F),即将解码代码释放出来,以供后面执行:
红色框中是第一个节区头的部分数据(前五个Dword元素),前面是利用SizeOfOptionalHeader增加出来的区域。
而EDI所指的地方恰好是PE文件的最后部分,并且这个区域是第二节区所处的位置:
下一条指令将两个Word元素(NumberOfRelocations/LineNumbers)压入栈。
然后用400H填充后面1C00个Dword,一直填充到1026DC处(已经快把第二节区[102700]填充满了)。
后面的解码中,首先会反复使用一个函数,大概会看到两种解码方式:
1.对几个变量进行计算,通过adc指令,在循环体中通过对AX寄存器进行反复的计算,得到值,写入EDI寄存器中。
2.通过rep movsb 指令,对一块区域进行解码。
这一区域的代码是两种解码方式最开始都会用到的,通过对函数返回的Flag值进行判断,从而选择了解码方式。
首先看看Decryption()函数,参数通过EDX传入:
首先需要明确一点 EBX 在程序的运行过程是一个固定的值,不会改变,即:0101FEC4
[EBX] , [EBX-4] , [EBX+4],这三个变量(分别命名为A,B,C)与解码密切相关。
1.将变量 A 与传入的变量EDX相乘,然后除以1000H,放在EAX中。
2.由变量 B (地址,即指针) 寻址找到数据,进行字节转置。
3. [B] - C 的值与变量 A 进行对比
(1)若小于 A则不跳转 , A = EAX,[EDX] += (800 - [EDX]) / 20H) , 通常[EDX] <= 400H 。
(2)跳转后:C += EAX , B -= EAX , [EDX] -= [EDX] / 20H .然后置CF位为1。
4.对比AL寄存器是否为0,若为0,则 B++, C /= 3000H , A /= 3000H 。
执行返回。
总结一下这个函数的功能:通过对几个变量的计算,解密出值写入内存,并设置了CF位,后面会用到。
先看第一种解码方式,即不跳转的方式,代表没有置CF位为1,前面计算出来的值小于变量A。
对EAX寄存器进行一系列的操作,如果ECX != 0 就进入循环,通过adc 指令,实现解码:
将AL寄存器写入EDI中,EDI会从第二节区0101Fxxx开始一直写到节区后面[ESI+34]即1014B5A为止。
当然有时候也会写前面的数据。
第二种解码方式是在第一个地方跳转:
这当中有一个执行了Decryption()函数,且执行了stc指令都会跳转。
若执行到最后的jmp,则会最后跳转,然后将EAX中的值写入[EDI]:
若没有执行jmp就跳转了则他们经过一系列对EAX的计算,最后都会到:
逐渐填充:
重建IAT
出循环后会进入第一个循环,从01001000H开始比较,一字节一字节的寻找,直到 [EDI] + 18 == 00 or x1.
第二个循环继续寻找,直到 [EDI] + 18 == x1. 第三个循环是循环132次。
总结一下:一共循环133次,每次都要满足 [EDI] + 18 == x1 。
然后从栈中获取LoadLibrary() 以及 GetProcAddress()的地址,然后先调用LoadLibrary() ,将需要的库载入。
然后通过GetProcAddress()函数依次将获得到的函数地址写入EDI所指向的地址。
ret 返回时就到了正常notepad程序入口。
这篇文章写的杂乱没有头绪,是因为没有真正懂得它的解压缩算法是怎么执行的,只是看出了一个大概的流程。