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方式记录相关指令来进行寻找,好处就是还能绕过不可执行类型的花指令
缺点:但是不一定管用但是有些壳可能就是会回跳,这种情况就会导致一直重复循环
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY