长亭百川云 - 文章详情

ELangPatcher - 一个简单的基于特征码的易语言静态编译程序膨胀与混淆工具

吾爱破解论坛

78

2024-07-13

作者坛账号:爱飞的猫

针对易语言静态编译的代码进行轻微混淆处理,避免被插件一键识别部分关键函数。处理过的内容参考下方。

注意该轻微膨胀/混淆只能用来对抗现有的“一键识别”工具,不能和加密壳的效果比。

如果你能拿到这类工具的源码缝缝补补,应该也能让它重新识别。

原理

对已知的部分特征进行魔改,大部分时候都是对简单操作进行膨胀,然后开辟新的内存空间跳转过去。

因为生成代码计算绝对地址比较麻烦,所以依赖 CALL 指令自动入栈的返回地址来计算正确跳转位置。

处理过的特征

只处理了一小部分特征。手动整起来太麻烦了,感觉不如利用特征码自动标记然后上加密壳批量处理了。

  • 对抗 查找易语言按钮事件 插件

  • 对抗 EWnd v0.2 插件的一键分析

  • 对抗 EWnd Ultimate 插件的一键分析

  • 对抗 E-Debug 程序/插件的一键分析

  • 对抗 E-Decompiler 插件的一键分析

  • 对抗 易语言逆向分析助手 的窗体信息分析

  • 对抗易语言初始化入口识别(cld; fninit; call xxxx

  • 对抗控件处理事件识别(重写了个简单的,混淆程度不高 call dword[ebp - 4]

  • 处理了找到的一些乱七八糟的特征…

从源码构建

首先确保安装有 VS 2022、CMake、Git for Windows 这三个程序。CMake 安装时需要选择将 cmake.exe 注册到系统。

复制代码 隐藏代码:: 克隆仓库 git clone https://github.com/FlyingRainyCats/ELangPatcher.git cd ELangPatcher :: 更新子模组 git submodule update --init --recursive :: 开始构建 cmake -Bcmake-build-vs2022 -G "Visual Studio 17 2022" -A Win32 cmake --build cmake-build-vs2022 --config Release

使用方法

将可执行文件拖放到 ELangPatcher.exe 即可处理。

可以指定额外参数:

  • --suffix _p 指定新的后缀,写出到新的文件。

  • -b, --backup 是否备份原始文件,默认启用。

  • --fake-stub 插入假的特征码内容到文件,默认启用。

  • -h, --help 查看帮助信息

示例输出:

复制代码 隐藏代码* (2). "M:\Programs\E_5.1\tools\ELangPatcher.exe" --suffix _ -- "M:\Projects\e-AntiWnd\测试3_5.1_静态编译.exe" ELang Patcher v0.1 by FlyingRainyCats (爱飞的猫 @52pojie.cn) INFO: processing: M:\Projects\e-AntiWnd\测试3_5.1_静态编译.exe   INFO: [PatchDllFunctionInvokeCall] found (offset=0x0001ac80, call_delta=0xffffffda)   INFO: [PatchEWndV02] found (offset=0x00013a40, ecx=0x004b3658, call_delta=0xffffc787)   INFO: [PatchEWndUltimate] found (offset=0x00038adf, data=0x004816a0, wnd_data_offset=0x000816a0)     - stub added: 0x0048074d (file offset: 0008074d)   INFO: [PatchWndEventHandlerMain] found (fn_end=0001adb6, offset=0x0001ad10, inst=0x0001ad3a(p: 0x0041ad3a), delta=0xffff9ab1)   INFO: [PatchKernelInvokeCall#0] found (offset=0x0001ac90, len=003b, replace_len=002c)   INFO: [PatchKernelInvokeCall#1] found (offset=0x0001acd0, len=0031, replace_len=0025)   INFO: [ELibInvokeCall#0] found (offset=0x0001ac60, args=[1], ecx=0x004b3658, call_delta=0xffff7dbf)   INFO: [ELibInvokeCall#1] found (offset=0x0001b0a0, args=[1], ecx=0x004b3658, call_delta=0xffff718f)   INFO: [LoadInitWindow#0] found (offset=0x0001b020, args=[4], ecx=0x004b3658, call_delta=0xffff8b33)   INFO: [UnknownCtrlRelated#0] found (offset=0x0001b040, args=[6], ecx=0x004b3658, call_delta=0xffff9a4b)   INFO: [PatchLoadWndCall] found (offset=0x000015cb, ebx=0x004017e0, call_delta=0x0000012a)   INFO: [PatchLoadWndCall] found (offset=0x00001007, ebx=0x004017e0, call_delta=0x000006f7)   INFO: [PatchLoadWndCall] found (offset=0x000016aa, ebx=0x004017e0, call_delta=0x00000054)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x000012f1, push_value=0x52010048, call_delta=0x00000457)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x000013a1, push_value=0x52010048, call_delta=0x000003a7)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x00001449, push_value=0x00000006, call_delta=0x000002ed)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x000014d9, push_value=0x00000006, call_delta=0x0000025d)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x00001584, push_value=0x00000006, call_delta=0x000001b2)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x00001626, push_value=0x00000006, call_delta=0x00000110)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x00001686, push_value=0x52010048, call_delta=0x000000aa)   INFO: [PatchSuspiciousCallWithParam] found (offset=0x00001707, push_value=0x52010048, call_delta=0x00000011)   INFO: [PatchELangLoaderInitStub] found (offset=0x000016ed)   INFO: [MiscAddFakeEWndStub] add stub (len=140 bytes)

集成到易语言

  1. 打开易语言目录;

  2. 打开该目录下的 tools 目录;

  3. 将 ELangPatcher.exe 放入该目录;

  4. 打开 link.ini 配置文件;

  5. 找到结尾的 post_link_action 区域,并添加新的操作。

  6. 静态编译后自动处理,参考添加 post_link_action1="$(E_TOOLS)\ELangPatcher.exe" $(TARGET)

  7. 如果有自动加壳,你需要调整序号,让 ELangPatcher.exe 先执行;

部分代码展示

混淆 cld/fninit 特征 (LibELangPatch/ELangInitFnGen.cpp):

本质上就是抽取 cld; fninit; call xxxx 这串代码到别的地方,并插入随机垃圾代码避免现有特征码定位:

复制代码 隐藏代码/** * 0040116D | FC          | cld             <-- addr start * 0040116E | DBE3        | fninit * 00401170 | E8 ECFFFFFF | call exe.401161 <-- call_delta = 0x0xFFFFFFEC * @param call_delta This can be `{}` if the call is empty. * @return */ std::vector<uint8_t> GenerateELangLoaderInit(std::optional<uint32_t> call_delta); class ELangLoaderInitGen : public CodeGenHelper { public:     explicit ELangLoaderInitGen(std::optional<uint32_t> call_delta) {         auto regs = shuffled<Reg32>({eax, edx, ecx});         fillWithJunkSlideInst(rand_int(1, 5), regs);         shuffle_exec({                 [&]() { cld(); genJunk(regs); },                 [&]() { fninit(); genJunk(regs); },         });         fillWithJunkSlideInst(rand_int(1, 5), regs);         bool use_ret_trick{false};         if (call_delta) {             use_ret_trick = next_bool();             auto reg_ret_addr = pop_last_item(regs);             mov(reg_ret_addr, dword[esp]);             genJunk(regs);             if (use_ret_trick) {                 add(dword[esp], 3);                 genJunk(regs);             }             auto delta_signed = static_cast<int32_t>(*call_delta);             if (delta_signed > 0) {                 add(reg_ret_addr, delta_signed);             } else {                 sub(reg_ret_addr, -delta_signed);             }             genJunk(regs);             if (use_ret_trick) {                 jmp(reg_ret_addr);             } else {                 call(reg_ret_addr);             }         }         regs = shuffled<Reg32>({eax, edx, ecx});         if (!use_ret_trick) {             genJunk(regs);             add(dword[esp], 3);         }         genJunk(regs);         ret();         std::vector<uint8_t> junk(rand_int(4, 10));         std::generate(junk.begin(), junk.end(), mt_);         db(junk.data(), junk.size());     } }; std::vector<uint8_t> GenerateELangLoaderInit(std::optional<uint32_t> call_delta) {     return ELangLoaderInitGen{call_delta}.vec(); }

扩充 .text 段,若无剩余空间则建立新的 .txt2 段储存代码 (src/PEParser.h):

复制代码 隐藏代码            inline uint8_t *ExpandTextSection(uint32_t size) {                 auto p_nt_header = INNER_PIMAGE_NT_HEADERS((uint8_t *) (exe_data_.data()) + PIMAGE_DOS_HEADER(exe_data_.data())->e_lfanew);                 auto p_file_header = &p_nt_header->FileHeader;                 auto section_count = std::size_t(p_file_header->NumberOfSections);                 auto section = (PIMAGE_SECTION_HEADER) ((uint8_t *) (p_nt_header) + offsetof(INNER_IMAGE_NT_HEADERS, OptionalHeader) + p_nt_header->FileHeader.SizeOfOptionalHeader);                 auto &image_size = GetNtOptionalHeader()->SizeOfImage;                 for (std::size_t i = 0; i < section_count; i++) {                     if (strcmp((char *) section->Name, ".text") == 0) {                         if (section->Misc.VirtualSize + size < section->SizeOfRawData) {                             auto offset = section->Misc.VirtualSize;                             section->Misc.VirtualSize += size;                             return &exe_data_.at(section->PointerToRawData + offset);                         }                     } else if (strcmp((char *) section->Name, ".txt2") == 0) {                         exe_data_.resize(exe_data_.size() + size);                         auto offset = section->SizeOfRawData;                         section->SizeOfRawData += size;                         image_size -= section->Misc.VirtualSize;                         section->Misc.VirtualSize = helper::round_up_to_section_size(section->SizeOfRawData);                         image_size += section->Misc.VirtualSize;                         return &exe_data_.at(section->PointerToRawData + offset);                     }                     section++;                 }                 auto last_section = §ion[-1];                 p_file_header->NumberOfSections++;                 memset(section, 0, sizeof(*section));                 memcpy(section->Name, ".txt2", 6);                 section->PointerToRawData = exe_data_.size();                 section->SizeOfRawData = size;                 section->Misc.VirtualSize = helper::round_up_to_section_size(size);                 image_size = helper::round_up_to_section_size(image_size) + section->Misc.VirtualSize;                 section->VirtualAddress = helper::round_up_to_section_size(last_section->VirtualAddress + last_section->Misc.VirtualSize);                 section->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE;                 exe_data_.resize(exe_data_.size() + size);                 return &exe_data_.at(exe_data_.size() - size);             }


下载

更新记录

  • v0.1.1: 修正 GenerateVArgsProxyCode 的代码生成(如 取余数 传参),感谢 谁的坏叔叔 报告。

  • v0.1: 初版发布

结语

代码写得比较乱,大概率不会有后续更新了…

拿 Xbyak 生成字节码,调试错误的时候也是磕磕碰碰。因为都是手动插入的垃圾指令,早期写得那些代码生成的混淆“相对温和”。后期整得有点走火入魔,也尝试了下隐藏常数。

一开始就想着对抗下 “一键 PUSH”,这个目的倒是达到了。不过其它的特征… 手动根本就处理不完。

在 .text 外的内容如果有匹配上特征码也会尝试进行魔改。读者有兴趣可以限制特征码检索为 .text 区段的代码。

不过,这些混淆/膨胀的手段在 IDA 的帮助下分析起来倒是不怎么费劲就是…

碎碎念

  • 易语言的编译顺序太“稳定”了,可以通过定位目标函数附近的函数来快速定位。

  • 没处理过特征的函数依然可以手动检索特征码找到。

  • 再做下去感觉需要自动化识别函数、反编译、然后随机混淆/膨胀了。

  • 要考虑的东西太多了,不适合我。

致谢

  • fjqisba 老师的易语言逆向专栏

  • 易语言程序分析笔记 - 看雪/PlaneJun

-官方论坛

www.52pojie.cn

👆👆👆

公众号设置“星标”,不会错过新的消息通知

开放注册、精华文章和周边活动等公告

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2