吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9738|回复: 47
收起左侧

[原创] 编写Ollydbg插件对某国产强壳 CB版本进行脱壳修复

  [复制链接]
寞叶 发表于 2023-9-8 03:10
本帖最后由 寞叶 于 2023-9-14 14:04 编辑

本来是想出个很详细的脱壳教程的从upx开始教,录了几个小时语音没声音。心态有点崩,基础的内容就略过了,放音乐当背景了。
我录了个视频演示教程,建议和本文一起食用(视频分析的是最近的样本,本文使用的是去年底的发现的一个很适合拿来分析的crackme)


叠甲声明
1、本文不提供任何破解成品与样本。(无法保证安全性)
2、插件是我花了很长时间,修了无数个bug才写出来的,开源在github。如果你觉得本插件不错,可以给我一个免费的评分或者star,这也能给我继续更新文章的动力。(详细用例在仓库,功能如下截图)
3、你不应该将此插件用于任何不适当的行为包括但不限于将此插件源码或编译后的成品进行售卖,进行任何违法行为等等。
4、此插件是实验性的,可能不完善并且存在bug(比如在分析时摇晃od的主窗口会使得od崩溃,以及不知原因崩在qemu中等等)
5、如果你觉得本文有任何不妥或者描述有误的地方还请联系我进行更改


Free Link
【应官方要求,现已删除开源代码,不提供任何文件下载】
视频教程链接:https://share.weiyun.com/h1gSqpuG 密码:52pj52
2.png

plugin1

plugin1


先简单介绍一下插件的框架,再进行分析

插件所用到的库

1、OllyDbg sdk        (我修复了一些sdk中的bug,找bug找了几天甚至对编译器失去信任,最后发现是sdk的问题,6)

2、unicorn2.0.1        (仿真,自动化分析)

3、capstone4.0.2  (提供强大的反汇编功能)


Analyzer.h, Analyzer.cpp

此源代码主要提供反汇编的功能(我删除了一些不必要的代码)



Emulator.h, Emulator.cpp

此源代码对unicorn进行二次封装,主要提供模拟器与OD进行数据交互、模拟执行的功能



dllmain.cpp

插件的主要逻辑,具体函数实现都在此。提供方便的字符串、数据格式提取功能,多段内存dump提取功能,od 一键trace功能,一键模拟执行,api调用跟踪、通用iat修复、内存访问分析功能等等



接下来结合代码与截图进行脱壳分析

1、找到程序OEP

考虑到源程序是易语言程序,对GetVersion进行下断即可断在oep下方

1

1


2、iat加密分析

可以发现api调用被加密成了call; nop的形式

在执行了以下代码后跳转到GetVersion


[Asm] 纯文本查看 复制代码
004D854D    57              push edi          
004D854E    9C              pushfd
004D854F    E9 30000000     jmp SPceshi_.004D8584

004D8584    BF 00000000     mov edi,0x0
004D8589    E9 27000000     jmp SPceshi_.004D85B5

004D85B5    8DBF 07C14B00   lea edi,dword ptr ds:[edi+0x4BC107]
004D85BB  ^ EB DD           jmp short SPceshi_.004D859A

004D859A    8DBF A75CED42   lea edi,dword ptr ds:[edi+0x42ED5CA7]
004D85A0    E9 05000000     jmp SPceshi_.004D85AA

004D85AA    8DBF EDAB13BD   lea edi,dword ptr ds:[edi-0x42EC5413]
004D85B0  ^ EB DF           jmp short SPceshi_.004D8591

004D8591    8B3F            mov edi,dword ptr ds:[edi]
004D8593    8B3F            mov edi,dword ptr ds:[edi]
004D8595  ^ EB C0           jmp short SPceshi_.004D8557

004D8557    47              inc edi                               
004D8558    81C7 970D90CF   add edi,0xCF900D97
004D855E    C1CF 1B         ror edi,0x1B
004D8561    C1C7 0B         rol edi,0xB
004D8564    81F7 259B3F25   xor edi,0x253F9B25
004D856A    8DBF D4F7BBBD   lea edi,dword ptr ds:[edi-0x4244082C]
004D8570    E9 03000000     jmp SPceshi_.004D8578

004D8578    877C24 04       xchg dword ptr ss:[esp+0x4],edi        
004D857C    E9 26000000     jmp SPceshi_.004D85A7

004D85A7    9D              popfd
004D85A8    C3              retn


我看了几个api调用都是一样的形式

首先算出一个地址,这个地址里存放了一个动态分配的内存地址,这个地址里存了加密后的api地址,解密后通过ret跳转。



有点像shellcode

我最先是用od trace进行测试修复,但是我发现速度慢的不正常,然后发现有部分api调用被虚拟化

2

2


解密部分被虚拟化,通过计算加密数据,新建一个jmp表ret过去的方法就不是很好用。

3、编写iat修复代码

加密前调用的三种类型

call dword ptr [mem]

jmp dword ptr [mem]

mov reg, dword ptr [mem]

加密后均为call nop

如何区分加密前的类型?

1、将模拟eip指向api调用前,一直模拟直至遇到api调用或者eip处于代码段。如果处于代码段,那就是mov类型,否则是 call或者jmp

2、判断【esp】处的数值,如果位于call的下方,则为call类型否则为jmp类型

3、将模拟器除esp外7个寄存器置0,通过模拟结束时寄存器的值判断mov reg,dword ptr [mem]是哪个寄存器


代码太长,请参考dllmain.cpp中的函数(有注释)

DWORD __stdcall FixSpIAT(LPVOID lpThreadParameter)


OD提供了函数 Findsymbolicname可以查询地址与符号关联性,用它来识别具体是调用的哪个api



然后是iat表重建,不同名称的函数用4个字节存放,不同dll的函数之间用0间隔


[Asm] 纯文本查看 复制代码
if (vec_iat_data.empty())
    {
        MessageBoxA(0, "未查找到需要修复的地方", "提示", MB_TOPMOST | MB_ICONWARNING | MB_OK);
        return 0;
    }
    sort(vec_iat_data.begin(), vec_iat_data.end());     //排序
DWORD tmp_addr = tmp_iat_begin;    //当前api存放的地址
char tmp[50] = {};                 //汇编字符串缓冲区
char errtext[TEXTLEN];
t_asmmodel asmmodel;
vec_iat_data.push_back({});   //末尾标记,并且防越界
for (DWORD i = 0; i < vec_iat_data.size() - 1; i++)
{
    _Progress(i * 1000 / (vec_iat_data.size() - 1), (char*)"重建IAT表中...进度");
    switch (vec_iat_data[i].type)
    {
        case 1:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            _Writememory((char*)"\xFF\x25", vec_iat_data[i].fix_addr, 2, MM_SILENT);
            _Writememory(&tmp_addr, vec_iat_data[i].fix_addr + 2, 4, MM_SILENT);
            break;
            //在这种修复方式下,不需要call类型的修复
            // case 2:
            //     _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            //     _Writememory((char*)"\x58\xFF\x25", vec_iat_data[i].fix_addr, 3, MM_SILENT);
            //     _Writememory(&tmp_addr, vec_iat_data[i].fix_addr + 3, 4, MM_SILENT);
            //     break;
        case 3:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov eax, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        case 4:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov ecx, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        case 5:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov edx, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        case 6:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov ebx, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        case 7:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov ebp, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        case 8:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov esi, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        case 9:
            _Writememory(&vec_iat_data[i].api_addr, tmp_addr, 4, MM_SILENT);
            sprintf_s(tmp, "mov edi, dword ptr [%08X]", tmp_addr);
            _Assemble(tmp, vec_iat_data[i].fix_addr, &asmmodel, 0, 0, errtext);
            _Writememory(asmmodel.code, vec_iat_data[i].fix_addr, asmmodel.length, MM_SILENT);
            _Writememory((void*)"\xC3", vec_iat_data[i].fix_addr + 6, 1, MM_SILENT);
            break;
        default:
            MessageBoxA(0, "重建IAT表时异常:不正确的类型", "错误", MB_TOPMOST | MB_ICONERROR | MB_OK);
            break;
    }

    if (vec_iat_data[i].dll_name != vec_iat_data[i + 1].dll_name)
    {
        //不同dll的函数集合,用0隔开
        DWORD data = 0;
        tmp_addr += 4;
        _Writememory(&data, tmp_addr, 4, MM_SILENT);
        tmp_addr += 4;
    }
    else if (vec_iat_data[i].api_name != vec_iat_data[i + 1].api_name)
    {
        //如果不是相同的函数名,代表碰到了新的函数,需要扩大4字节放它的地址
        tmp_addr += 4;
    }
}`



然后用scylla dump并且重建输入表即可

载入修复的程序,如果将你的程序拖入虚拟机中能f8步过GetVersion调用,就代表到此为止是成功的




antidump分析

&#8203;        此易语言程序的几个库函数地方被虚拟化并塞入antidump代码

3

3


4

4


函数头部直接jmp到虚拟机入口

antidump不过,程序会直接异常或者死循环

使用插件内存访问分析(具体代码请参考DWORD __stdcall MemAccessAnalysis(LPVOID lpThreadParameter))

该函数可记录访问的敏感内存区域与执行过的特殊的指令如(rdtsc,cpuid)

第一个antidump函数分析如图


5

5


可以看出程序校验了分配的内存存放的数据,资源段,代码段api调用是否被修复,枚举区段数量判断是否被脱壳,记录的进程pid

后续还有几个antidump,是越来越复杂,还有不同的antidump方法,不一一演示了。


大致的流程是这样的

--保存环境--

进行各种antidump校验

--恢复环境--

执行原来代码


我个人是想到了几个修复antidump的方法

1、直接对虚拟机handler进行hook(缺点:累,handler太多)

2、还原整个易语言库函数(缺点:特征匹配,要求熟悉语言特征)

3、还原函数头+补数据



下文使用第三种方法

图中的Write .svmp表示程序已经执行过了antidump


还原函数头加补数据如图

6

6


即可绕过此antidump,虽然后续执行的反dump更多而且不一样,均可像这样绕过。最后一处要补的数据挺多的

7

7


这样补完就行了,如果有sdk的话,上述方法就不好使了。视具体情况采用不同方法

脱壳分析就到这结束



反dump的方法(个人思路)
        PE结构层次的反dump
                1、修改SizeofImage
                2、检测VirtualAddress,区段数量等等
                3、检测代码段是否被修改,api调用是否被修复
                4、Stolen code 偷走几个字节
        基于内存的反dump
                把部分数据存放到堆或者栈上,dump无法保存这些数据,会丢失
        程序运行环境反dump
                进程id,线程id,进程运行时间,程序的启动时间等等
        系统环境反dump
                cpuid
                系统启动时间
                用户名
                cpu?显卡?硬盘大小?
思路打开,只要是另一个电脑没有的数据,你都可以获取一个保存甚至主动创造一个

免费评分

参与人数 37吾爱币 +52 热心值 +33 收起 理由
FRSS + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
WGL + 1 + 1 感谢大佬!
blackant99 + 1 鼓励转贴优秀软件安全工具和文档!
BerTom + 1 + 1 我很赞同!
Sunnyzbh + 1 + 1 用心讨论,共获提升!
銀鈅 + 1 + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
小菜鸟一枚 + 1 + 1 大佬666
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
anye321 + 1 + 1 谢谢@Thanks!
213xhy + 1 + 1 我很赞同!
sirchin + 1 + 1 谢谢@Thanks!
skywalker0123 + 3 谢谢@Thanks!
alalalsk163 + 1 + 1 我很赞同!
yp17792351859 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wyl0205 + 2 + 1 就服这种大佬
dioderen + 1 + 1 谢谢@Thanks!
gqdsc + 1 大神就是大神啊
winsonge + 1 谢谢@Thanks!
CrazyNut + 3 + 1 用心讨论,共获提升!
流浪天涯 + 1 + 1 热心回复!
成熟的美羊羊 + 2 + 1 热心回复!
FDE9 + 1 + 1 用心讨论,共获提升!
owouwu + 1 + 1 谢谢@Thanks!
冥界3大法王 + 4 + 1 不用OD很多年,友情点赞!
冰河洗剑 + 1 + 1 我很赞同!
gunxsword + 1 + 1 热心回复!
m1n9yu3 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
tomhex + 1 + 1 热心回复!
caojian162411 + 1 + 1 我很赞同!
Marken888 + 2 + 1 太厉害的插件了
月夜克星 + 1 + 1 视频教程声音丢了真是太可惜了,虽然不太用od,x96dbg用的多一点,但还是感.
hszt + 2 + 1 太牛了,感谢分享
cdj68765 + 2 + 1 谢谢@Thanks!
忆魂丶天雷 + 2 + 1 谢谢@Thanks!
fghtiger + 1 + 1 建议增加x64dbg插件,可能log会快点。
tzblue + 2 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

0xN0tu10lV1rus 发表于 2023-9-8 17:05
赞一个
第一个在论坛里面把CB版本的AntiDump讲得体无完肤的, 膜拜
call nop是很久很久以前的cb版本, 为了解决这个特征, 我改成reg call, A版本更是加入了ret指令
AntiDump讲得不错了, 但也有人会直接根据api来过掉检测
CB版本的AntiDump最大的缺点就是加密的代码是集中执行的, 而且是在最后才执行, 所以只要过掉了前面的检测, 就行了.
A版本开始每个版本的AntiDump都有一些不同, 跟CB相差还是很多的, 被加密的代码会随机分散在检测的代码中一起执行了, 需要多花一些时间抠出来, 但我相信难不倒你
楼主用心了,

我需要思考一下是否更新一下CB这个古老的版本 ->来自SProtect最最最最最最原始的打工人

点评

哦豁?  发表于 2023-9-20 13:56
ciker_li 发表于 2023-9-8 08:53
wpdzdx 发表于 2023-9-8 09:01
wpdzdx 发表于 2023-9-8 09:03
OD要是能升级下就好了 界面太乱 版本也老了
rjqg2023 发表于 2023-9-8 09:03
大佬大佬
170077000 发表于 2023-9-8 09:11
有没有生成好的  没有安装VS  也不知道用的是哪个版的VS
月夜克星 发表于 2023-9-8 09:26
视频教程声音丢了真是太可惜了,虽然不太用od,x96dbg用的多一点,但还是感谢大佬。
scbzwv 发表于 2023-9-8 09:26
感谢分享
天下 发表于 2023-9-8 09:56
这么优秀的帖子坐等加精
董督秀 发表于 2023-9-8 09:57
你好,我想学习没有声音的教程,请问能否提供一份?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-30 21:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表