PELock 1.06 加密壳脱壳

前言:PELock1.06.d的脱壳笔记

什么是Stolen Bytes

识别Stolen Bytes技巧点

1、CPU的EIP寄存器在壳程序的EP的时候,先记录此时的ESP栈顶指针的地址,后面到来你觉得是OEP的时候,然后通过比较此时的OEP和EP的ESP栈顶指针是否相同,如果你确定是OEP,但是此时OEP的栈顶指针不同,那么可以怀疑是存在Stolen Bytes

2、到达了你觉得是OEP的地方,并且存在Stolen Bytes的话,那么尝试通过多个入口特征来进行定位

什么是KiUserExceptionDispatcher

参考文章:https://www.cnblogs.com/yilang/p/11505459.html

如果程序设置了硬件断点,则 dr0 - dr7 寄存器的值就会改变,其中 dr0 - dr3
存放硬件断点的地址,dr4 - dr5 保留,dr6 存放调试事件的详细信息,dr7 存放断点触发的条件。

编译器OEP入口特征

参考文章:https://www.cnblogs.com/lyshark/p/13729534.html

脱壳过程

需要知道的:KiUserExceptionDispatcher的领空的当前堆栈地址ESP+0x14就是异常触发地址,这个函数是系统派发异常函数,也就是说所有异常都要经过该函数来派发,那么该函数中的参数也就会指向异常的位置,也就是ESP+0x14

方法:KiUserExceptionDispatcher也可以说是最后一次异常法的升级版,原因是如果异常数量过多的话就可以用这个最后一次异常法来进行定位OEP,手动的话工作量太大

运行程序,查看记录窗口的内容如下

利用最后一次的异常来定位,发现最后一次的异常地址为003D6744,那么我们就可以给KiUserExceptionDispatcher函数设置条件记录断点,条件如下

然后F9进行运行,发现成功断下,此时靠近OEP,然后对程序代码段进行内存断点,F9进行成功来到OEP

到如下的时候发现虽然是OEP,但并不是真正的入口,真正的入口是在前面

需要重新找到它编程语言的默认OEP头才行,该程序是C++的,那大概样子应该是如下

默认的程序OEP头可以参考 https://blog.csdn.net/x356982611/article/details/48370297

    Microsoft Visual C++ 6.0

    00496EB8 >/$  55            PUSH EBP                                 ;  (初始 cpu 选择)
    00496EB9  |.  8BEC          MOV EBP,ESP
    00496EBB  |.  6A FF         PUSH -1
    00496EBD  |.  68 40375600   PUSH Screensh.00563740
    00496EC2  |.  68 8CC74900   PUSH Screensh.0049C78C                   ;  SE 处理程序安装
    00496EC7  |.  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
00496ECD  |.  50            PUSH EAX
    00496ECE  |.  64:8925 00000>MOV DWORD PTR FS:[0],ESP
    00496ED5  |.  83EC 58       SUB ESP,58

窃取代码的操作不会在程序运行完了之后再进行修改,所以这里的话就从头开始一直F7进行单步步入,不用F8的原因是F8可能遇到CALL直接运行程序,会跑飞

所以F7单步一直走,慢慢来寻找之后的过程就是先找PUSH EBP,MOV EBP ESP...,在其间要注意EIP,遇到了回头跳的就直接F4来绕过就行了,比如下面这种

第一个stolen byte

总共找到的代码有如下

003D6A4A    55              PUSH EBP
003D6A6F    8BEC            MOV EBP,ESP
003D6A93    6A FF           PUSH -1
003D6AB9    68 600E4500     PUSH 450E60
003D6AE3    68 C8924200     PUSH 4292C8
003D6B0D    64:A1 00000000  MOV EAX,DWORD PTR FS:[0]
003D6B35    50              PUSH EAX
003D6B59    64:8925 00000000   MOV DWORD PTR FS:[0],ESP
003D6B85    83C4 A8            ADD ESP,-58
003D6BAC    53                 PUSH EBX
003D6BCF    56                 PUSH ESI
003D6BF6    57                 PUSH EDI
003D6C1B    8965 E8            MOV DWORD PTR SS:[EBP-18],ESP

这个壳还有个特征点就是返回OEP的时候是先通过PUSH一个假OEP的地址然后接着RETN返回回去的

003D6C67 68 D6714200 PUSH 4271D6 需要注意的是这条不是,因为最后的时候还有个return,那么这个压入的数据就是出栈时出去的地址

要填充的机器码:558BEC6AFF68600E450068C892420064A100000000506489250000000083C4A85356578965E868D6714200,将这个机器码对假的OEP前面的部分进行替换,效果如下,发现刚刚好,同样验证了找回来的stolen bytes是没有问题的

发现转储之后,然后修复IAT发现依然是打不开的,如下图所示

把修复完IAT的程序继续载入,F8跟随,发现JMP的区段已经不存在了

如果你观察了JMP的区段就会发现这个区段是内存中申请的私有区段,也就是这个区段并不是属于PE结构的,所以最后导致的话DUMP工具就不会DUMP该区段作为PE结构中的一部分

我们再看下原程序内存中的AC区段,发现是存在的,所以我们需要把这个原程序内存中的AC区段拷贝一份,在脱壳后的程序中进行填充

然后填充到脱壳后的程序中

因为是直接把原程序的AC片段直接拷贝的,所以还需要修改下虚拟的偏移地址

默认情况下,EXE文件的基址为0x00400000,DLL文件的基址为0x10000000。

虚拟内存地址(VA):PE文件中的指令被装入内存后的地址,OllyDbg动态反汇编产生。
相对虚拟地址(RVA):内存地址相对与映射基址的偏移量。
VA = Image Base + RVA。

该程序的EXE文件的基址也是00400000,所以我们需要计算下它的相对虚拟地址,那么就是AC0000-400000,结果就是6C0000

最后还需要重新构建PE头,也就是把新加的区段和sizeofimage等属性进行修正,然后重新打开如下

第二种方法解决stolen bytes

可以通过编译器入口OEP特征,比如你识别到了上面缺失的代码时VC6.0的话,那么就可以用VC6.0的来进行替换上进行解决,常见的VC6编译器OEP入口特征如下图所示

PUSH EBP                                 ;  (初始 cpu 选择)
MOV EBP,ESP
PUSH -1
PUSH Screensh.00563740
PUSH Screensh.0049C78C                   ;  SE 处理程序安装
MOV EAX,DWORD PTR FS:[0]
PUSH EAX
MOV DWORD PTR FS:[0],ESP
SUB ESP, 0x58
PUSH EBX
PUSH ESI
PUSH EDI
MOV DWORD PTR SS:[EBP-18],ESP

第三种方法解决stolen bytes

我个人认为可以通过跟踪run方式记录相关指令来进行寻找,好处就是还能绕过不可执行类型的花指令

缺点:但是不一定管用但是有些壳可能就是会回跳,这种情况就会导致一直重复循环

posted @ 2019-12-28 15:12  zpchcbd  阅读(679)  评论(0编辑  收藏  举报