├── .clang-format ├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── ELangPatcher.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── CMakeLists.txt ├── LICENSE ├── LibELangPatch ├── CMakeLists.txt ├── CallProxyStubWithEcxGen.cpp ├── CdeclPushAndCallGen.cpp ├── CodeGenHelper.h ├── ELangBulkPushGen.cpp ├── ELangInitFnGen.cpp ├── ELangLoaderInitGen.cpp ├── ResolveCallDllFunctionGen.cpp ├── VArgsProxyGen.cpp ├── WndHandlerGen.cpp └── include │ ├── CallProxyStubWithEcxGen.h │ ├── CdeclPushAndCallGen.h │ ├── ELangBulkPushGen.h │ ├── ELangInitFnGen.h │ ├── ELangLoaderInitGen.h │ ├── ResolveCallDllFunctionGen.h │ ├── VArgsProxyGen.h │ └── WndHandlerGen.h ├── README.MD ├── assets └── screenshot.webp ├── build_vs2022.cmd └── src ├── ELangPatchFile.cpp ├── ELangPatchFile.h ├── ELangPatcher.cpp ├── ELangPatcher.h ├── ELangPatcher ├── ELangPatcherImpl.h ├── PatchAddFakeEWndStub.cpp ├── PatchDllResolveAndCall.cpp ├── PatchELangLoaderInitStub.cpp ├── PatchEWndUltimate.cpp ├── PatchEWndV02.cpp ├── PatchKernelInvokeCall.cpp ├── PatchLoadWndCall.cpp ├── PatchProxyStub.cpp ├── PatchSuspiciousCallWithParam.cpp └── PatchWndEventHandlerMain.cpp ├── PEParser.h ├── SearchMatcher.h ├── main.cpp └── test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | # Generated from CLion C/C++ Code Style settings 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: None 6 | AlignOperands: Align 7 | AllowAllArgumentsOnNextLine: false 8 | AllowAllConstructorInitializersOnNextLine: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Always 14 | AllowShortLambdasOnASingleLine: All 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakTemplateDeclarations: Yes 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterCaseLabel: false 21 | AfterClass: false 22 | AfterControlStatement: Never 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterUnion: false 27 | BeforeCatch: false 28 | BeforeElse: false 29 | IndentBraces: false 30 | SplitEmptyFunction: false 31 | SplitEmptyRecord: true 32 | BreakBeforeBinaryOperators: None 33 | BreakBeforeTernaryOperators: true 34 | BreakConstructorInitializers: BeforeColon 35 | BreakInheritanceList: BeforeColon 36 | ColumnLimit: 0 37 | CompactNamespaces: false 38 | ContinuationIndentWidth: 8 39 | IndentCaseLabels: true 40 | IndentPPDirectives: None 41 | IndentWidth: 4 42 | KeepEmptyLinesAtTheStartOfBlocks: true 43 | MaxEmptyLinesToKeep: 2 44 | NamespaceIndentation: All 45 | ObjCSpaceAfterProperty: false 46 | ObjCSpaceBeforeProtocolList: true 47 | PointerAlignment: Right 48 | ReflowComments: false 49 | SpaceAfterCStyleCast: true 50 | SpaceAfterLogicalNot: false 51 | SpaceAfterTemplateKeyword: false 52 | SpaceBeforeAssignmentOperators: true 53 | SpaceBeforeCpp11BracedList: false 54 | SpaceBeforeCtorInitializerColon: true 55 | SpaceBeforeInheritanceColon: true 56 | SpaceBeforeParens: ControlStatements 57 | SpaceBeforeRangeBasedForLoopColon: false 58 | SpaceInEmptyParentheses: false 59 | SpacesBeforeTrailingComments: 0 60 | SpacesInAngles: false 61 | SpacesInCStyleCastParentheses: false 62 | SpacesInContainerLiterals: false 63 | SpacesInParentheses: false 64 | SpacesInSquareBrackets: false 65 | TabWidth: 4 66 | UseTab: Never 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### C++ template 2 | # Prerequisites 3 | *.d 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | *.smod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | 35 | ### CLion template 36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 37 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 38 | 39 | # User-specific stuff 40 | .idea/**/workspace.xml 41 | .idea/**/tasks.xml 42 | .idea/**/usage.statistics.xml 43 | .idea/**/dictionaries 44 | .idea/**/shelf 45 | 46 | # AWS User-specific 47 | .idea/**/aws.xml 48 | 49 | # Generated files 50 | .idea/**/contentModel.xml 51 | 52 | # Sensitive or high-churn files 53 | .idea/**/dataSources/ 54 | .idea/**/dataSources.ids 55 | .idea/**/dataSources.local.xml 56 | .idea/**/sqlDataSources.xml 57 | .idea/**/dynamic.xml 58 | .idea/**/uiDesigner.xml 59 | .idea/**/dbnavigator.xml 60 | 61 | # Gradle 62 | .idea/**/gradle.xml 63 | .idea/**/libraries 64 | 65 | # Gradle and Maven with auto-import 66 | # When using Gradle or Maven with auto-import, you should exclude module files, 67 | # since they will be recreated, and may cause churn. Uncomment if using 68 | # auto-import. 69 | # .idea/artifacts 70 | # .idea/compiler.xml 71 | # .idea/jarRepositories.xml 72 | # .idea/modules.xml 73 | # .idea/*.iml 74 | # .idea/modules 75 | # *.iml 76 | # *.ipr 77 | 78 | # CMake 79 | cmake-build-*/ 80 | 81 | # Mongo Explorer plugin 82 | .idea/**/mongoSettings.xml 83 | 84 | # File-based project format 85 | *.iws 86 | 87 | # IntelliJ 88 | out/ 89 | 90 | # mpeltonen/sbt-idea plugin 91 | .idea_modules/ 92 | 93 | # JIRA plugin 94 | atlassian-ide-plugin.xml 95 | 96 | # Cursive Clojure plugin 97 | .idea/replstate.xml 98 | 99 | # SonarLint plugin 100 | .idea/sonarlint/ 101 | 102 | # Crashlytics plugin (for Android Studio and IntelliJ) 103 | com_crashlytics_export_strings.xml 104 | crashlytics.properties 105 | crashlytics-build.properties 106 | fabric.properties 107 | 108 | # Editor-based Rest Client 109 | .idea/httpRequests 110 | 111 | # Android studio 3.1+ serialized cache file 112 | .idea/caches/build_file_checksums.ser 113 | 114 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/xbyak"] 2 | path = vendor/xbyak 3 | url = https://github.com/herumi/xbyak.git 4 | [submodule "vendor/cxxopts"] 5 | path = vendor/cxxopts 6 | url = https://github.com/jarro2783/cxxopts.git 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/ELangPatcher.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(ELangPatcher VERSION 0.1.1) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | add_subdirectory(LibELangPatch) 6 | 7 | add_executable(ELangPatcherTest src/test.cpp) 8 | target_link_libraries(ELangPatcherTest LibELangPatch) 9 | 10 | add_executable(ELangPatcher 11 | src/ELangPatcher.cpp 12 | src/main.cpp 13 | src/ELangPatchFile.cpp 14 | src/ELangPatcher/PatchEWndV02.cpp 15 | src/ELangPatcher/PatchEWndV02.cpp 16 | src/ELangPatcher/PatchEWndUltimate.cpp 17 | src/ELangPatcher/PatchEWndUltimate.cpp 18 | src/ELangPatcher/PatchWndEventHandlerMain.cpp 19 | src/ELangPatcher/PatchKernelInvokeCall.cpp 20 | src/ELangPatcher/PatchKernelInvokeCall.cpp 21 | src/ELangPatcher/PatchProxyStub.cpp 22 | src/ELangPatcher/PatchAddFakeEWndStub.cpp 23 | src/ELangPatcher/PatchELangLoaderInitStub.cpp 24 | src/ELangPatcher/PatchDllResolveAndCall.cpp 25 | src/ELangPatcher/PatchLoadWndCall.cpp 26 | src/ELangPatcher/PatchSuspiciousCallWithParam.cpp 27 | ) 28 | target_include_directories(ELangPatcher PRIVATE vendor/cxxopts/include) 29 | target_compile_definitions(ELangPatcher PRIVATE 30 | WIN32_LEAN_AND_MEAN=1 31 | ELANG_PATCHER_VERSION="${PROJECT_VERSION}" 32 | ) 33 | target_link_libraries(ELangPatcher LibELangPatch) 34 | 35 | if(MSVC) 36 | target_compile_options(ELangPatcher PRIVATE "/MP") 37 | endif() 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 爱飞的猫 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LibELangPatch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(LibELangPatcher) 3 | 4 | add_library(LibELangPatch STATIC 5 | WndHandlerGen.cpp 6 | ELangInitFnGen.cpp 7 | CallProxyStubWithEcxGen.cpp 8 | VArgsProxyGen.cpp 9 | ELangLoaderInitGen.cpp 10 | ResolveCallDllFunctionGen.cpp 11 | ELangBulkPushGen.cpp 12 | CdeclPushAndCallGen.cpp 13 | CdeclPushAndCallGen.cpp 14 | ) 15 | 16 | target_compile_definitions(LibELangPatch PRIVATE XBYAK32=1) 17 | target_include_directories(LibELangPatch 18 | PRIVATE ../vendor/xbyak/xbyak 19 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 20 | 21 | if(MSVC) 22 | target_compile_options(LibELangPatch PRIVATE "/MP") 23 | endif() 24 | -------------------------------------------------------------------------------- /LibELangPatch/CallProxyStubWithEcxGen.cpp: -------------------------------------------------------------------------------- 1 | #include "include/CallProxyStubWithEcxGen.h" 2 | 3 | #include "CodeGenHelper.h" 4 | 5 | class CallProxyStubWithEcxGen : public CodeGenHelper { 6 | public: 7 | explicit CallProxyStubWithEcxGen(bool is_cdecl, int arg_count, int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta) { 8 | if (is_cdecl) { 9 | generate_cdecl(arg_count, pre_junk_len, post_junk_len, ecx_value, call_delta); 10 | } else { 11 | generate_stdcall(pre_junk_len, post_junk_len, ecx_value, call_delta); 12 | } 13 | } 14 | 15 | void generate_cdecl(int arg_count, int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta) { 16 | int max_junk_per_inst{}; 17 | if (arg_count <= 2) { 18 | max_junk_per_inst = 1; 19 | } else if (arg_count <= 4) { 20 | max_junk_per_inst = 2; 21 | } else if (arg_count <= 6) { 22 | max_junk_per_inst = 3; 23 | } else { 24 | max_junk_per_inst = 4; 25 | } 26 | 27 | auto regs = shuffled({eax, ecx, edx}); 28 | 29 | int prefix_junk_left {pre_junk_len - arg_count * 3 - 2/* mov reg, esp */}; 30 | auto junk_start = rand_int(0, std::min(max_junk_per_inst, prefix_junk_left)); 31 | getJunkInstByLen(junk_start, {edx, eax, ecx}); 32 | prefix_junk_left -= junk_start; 33 | 34 | auto reg_stack = pop_last_item(regs); 35 | mov(reg_stack, esp); 36 | 37 | int esp_offset = 4 * arg_count; 38 | for(int i = 0; i < arg_count; i++) { 39 | auto junk_this_round = rand_int(0, std::min(max_junk_per_inst, prefix_junk_left)); 40 | getJunkInstByLen(junk_this_round, regs); 41 | prefix_junk_left -= junk_this_round; 42 | 43 | push(dword[reg_stack + esp_offset]); 44 | esp_offset -= 4; 45 | } 46 | fillWithJunk(prefix_junk_left, {eax, ecx, edx}); 47 | 48 | post_junk_len -= 1; 49 | auto slide_junk_len = rand_int(0, post_junk_len); 50 | post_junk_len -= slide_junk_len; 51 | 52 | mov(ecx, ecx_value); 53 | fillWithJunkSlideInst(slide_junk_len, {eax, edx}); 54 | db(0xE8); 55 | dd(call_delta - slide_junk_len); 56 | ret(); 57 | 58 | if (post_junk_len != 0) { 59 | std::vector dummy(post_junk_len); 60 | std::generate(dummy.begin(), dummy.end(), mt_); 61 | db(dummy.data(), dummy.size()); 62 | } 63 | } 64 | 65 | void generate_stdcall(int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta) { 66 | fillWithJunk(pre_junk_len, {eax, ecx, edx}); 67 | auto slide_junk_len = rand_int(0, post_junk_len); 68 | post_junk_len -= slide_junk_len; 69 | 70 | mov(ecx, ecx_value); 71 | fillWithJunkSlideInst(slide_junk_len, {eax, edx}); 72 | db(0xE9); 73 | dd(call_delta - slide_junk_len); 74 | 75 | if (post_junk_len != 0) { 76 | std::vector dummy(post_junk_len); 77 | std::generate(dummy.begin(), dummy.end(), mt_); 78 | db(dummy.data(), dummy.size()); 79 | } 80 | } 81 | }; 82 | 83 | std::vector GenerateCallProxyStubWithEcx(int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta) { 84 | return CallProxyStubWithEcxGen{false, 0, pre_junk_len, post_junk_len, ecx_value, call_delta}.vec(); 85 | } 86 | 87 | std::vector GenerateCallProxyStubWithEcxCdecl(int arg_count, int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta) { 88 | return CallProxyStubWithEcxGen{true, arg_count, pre_junk_len, post_junk_len, ecx_value, call_delta}.vec(); 89 | } 90 | -------------------------------------------------------------------------------- /LibELangPatch/CdeclPushAndCallGen.cpp: -------------------------------------------------------------------------------- 1 | #include "CdeclPushAndCallGen.h" 2 | #include "CodeGenHelper.h" 3 | 4 | class CdeclPushAndCallGen : public CodeGenHelper { 5 | public: 6 | explicit CdeclPushAndCallGen(uint32_t push_value, uint32_t call_delta, uint32_t ret_delta) { 7 | auto regs = shuffled({eax, ecx, edx}); 8 | fillWithJunkSlideInst(rand_int(2, 5), regs); 9 | 10 | auto reg_ret_delta = pop_last_item(regs); 11 | IntGenerator int_gen_ret_delta{rand_int(2, 5), ret_delta}; 12 | while(!int_gen_ret_delta.generate_step(*this, reg_ret_delta)) { 13 | maybeGenJunk(regs); 14 | } 15 | add(dword[esp], reg_ret_delta); 16 | regs = shuffled({eax, ecx, edx}); 17 | 18 | std::optional reg_push_const = std::nullopt; 19 | std::optional reg_call_addr = std::nullopt; 20 | 21 | IntGenerator int_gen_push_value{rand_int(2, 5), push_value}; 22 | IntGenerator int_gen_call_delta{rand_int(2, 5), call_delta - ret_delta}; 23 | 24 | bool xchg_done{false}; 25 | bool target_addr_fixed{false}; 26 | ExecItem fns_decode{}; 27 | ExecItem::value_type fn_exchange_stack_value = [&]() { 28 | if (!int_gen_push_value.done()) { 29 | fns_decode.emplace_back(fn_exchange_stack_value); 30 | return; 31 | } 32 | genJunk(regs); 33 | xchg(*reg_push_const, dword[esp]); 34 | xchg_done = true; 35 | fns_decode.emplace_back([&]() { 36 | genJunk(regs); 37 | push(*reg_push_const); 38 | }); 39 | }; 40 | ExecItem::value_type fn_increment_ret_addr = [&]() { 41 | if (!int_gen_call_delta.done()) { 42 | fns_decode.emplace_back(fn_increment_ret_addr); 43 | return; 44 | } 45 | 46 | genJunk(regs); 47 | if (xchg_done) { 48 | add(*reg_call_addr, *reg_push_const); 49 | } else { 50 | add(*reg_call_addr, dword[esp]); 51 | } 52 | target_addr_fixed = true; 53 | }; 54 | ExecItem::value_type fn_decode_call_delta = [&]() { 55 | maybeGenJunk(regs); 56 | if (!reg_push_const) { 57 | reg_push_const = std::make_optional(pop_last_item(regs)); 58 | } 59 | if (!int_gen_push_value.generate_step(*this, *reg_push_const)) { 60 | fns_decode.emplace_back(fn_decode_call_delta); 61 | } 62 | }; 63 | ExecItem::value_type fn_decode_push_value = [&]() { 64 | maybeGenJunk(regs); 65 | if (!reg_call_addr) { 66 | reg_call_addr = std::make_optional(pop_last_item(regs)); 67 | } 68 | if (!int_gen_call_delta.generate_step(*this, *reg_call_addr)) { 69 | fns_decode.emplace_back(fn_decode_push_value); 70 | } 71 | }; 72 | fns_decode.emplace_back(fn_decode_call_delta); 73 | fns_decode.emplace_back(fn_decode_push_value); 74 | fns_decode.emplace_back(fn_exchange_stack_value); 75 | fns_decode.emplace_back(fn_increment_ret_addr); 76 | shuffle_exec_2(fns_decode); 77 | regs.push_back(*reg_push_const); 78 | 79 | genJunk(regs); 80 | test(*reg_call_addr, *reg_call_addr); 81 | jz("junk"); 82 | genJunk(regs); 83 | jmp(*reg_call_addr); 84 | regs = shuffled({eax, ecx, edx, esi, edi, ebx}); 85 | fillWithJunkSlideInst(rand_int(5, 10), regs); 86 | L("junk"); 87 | fillWithJunkSlideInst(rand_int(10, 15), regs); 88 | jnz("junk"); 89 | fillWithJunkSlideInst(rand_int(2, 5), regs); 90 | } 91 | }; 92 | 93 | std::vector GenerateCdeclPushAndCall(uint32_t push_value, uint32_t call_delta, uint32_t ret_delta) { 94 | return CdeclPushAndCallGen{push_value, call_delta, ret_delta}.vec(); 95 | } 96 | -------------------------------------------------------------------------------- /LibELangPatch/CodeGenHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../vendor/xbyak/xbyak/xbyak.h" 10 | 11 | #ifdef min 12 | #undef min 13 | #endif 14 | #ifdef max 15 | #undef max 16 | #endif 17 | 18 | class IntGenerator { 19 | public: 20 | enum class OpCode { 21 | XOR = 0, 22 | ADD, 23 | SUB, 24 | ROR, 25 | ROL, 26 | NEG, 27 | NOT, 28 | 29 | MOV = 0x10, 30 | }; 31 | 32 | inline IntGenerator(int steps, uint32_t value) { 33 | std::uniform_int_distribution<> dist_u32_bits(1, 31); 34 | std::uniform_int_distribution<> dist(0, 6); 35 | std::mt19937 mt(std::random_device{}()); 36 | auto next_opcode = [&]() { return static_cast(dist(mt)); }; 37 | 38 | auto v{value}; 39 | for (int i = 0; i < steps; i++) { 40 | auto opcode = next_opcode(); 41 | auto operand = mt(); 42 | if (mt() & 1) { 43 | operand &= 0x7F; 44 | } 45 | switch (opcode) { 46 | case OpCode::XOR: 47 | v ^= operand; 48 | break; 49 | case OpCode::ADD: 50 | v -= operand; 51 | break; 52 | case OpCode::SUB: 53 | v += operand; 54 | break; 55 | case OpCode::ROR: 56 | operand = dist_u32_bits(mt); 57 | v = std::rotl(v, static_cast(operand)); 58 | break; 59 | case OpCode::ROL: 60 | operand = dist_u32_bits(mt); 61 | v = std::rotr(v, static_cast(operand)); 62 | break; 63 | case OpCode::NEG: 64 | operand = 0; 65 | v = -v; 66 | break; 67 | case OpCode::NOT: 68 | operand = 0; 69 | v = ~v; 70 | break; 71 | 72 | // special: do nothing 73 | case OpCode::MOV: 74 | break; 75 | } 76 | steps_.emplace(opcode, operand); 77 | } 78 | steps_.emplace(OpCode::MOV, v); 79 | } 80 | inline bool generate_step(Xbyak::CodeGenerator &code, const Xbyak::Operand &op) { 81 | if (steps_.empty()) { 82 | return true; 83 | } 84 | 85 | auto [opcode, operand] = steps_.top(); 86 | steps_.pop(); 87 | 88 | switch (opcode) { 89 | case OpCode::XOR: 90 | code.xor_(op, operand); 91 | break; 92 | case OpCode::ADD: 93 | code.add(op, operand); 94 | break; 95 | case OpCode::SUB: 96 | code.sub(op, operand); 97 | break; 98 | case OpCode::ROR: 99 | code.ror(op, static_cast(operand)); 100 | break; 101 | case OpCode::ROL: 102 | code.rol(op, static_cast(operand)); 103 | break; 104 | case OpCode::NEG: 105 | code.neg(op); 106 | break; 107 | case OpCode::NOT: 108 | code.not_(op); 109 | break; 110 | case OpCode::MOV: 111 | code.mov(op, operand); 112 | break; 113 | } 114 | 115 | return steps_.empty(); 116 | } 117 | 118 | inline bool done() { 119 | return steps_.empty(); 120 | } 121 | 122 | private: 123 | std::stack> steps_{}; 124 | }; 125 | 126 | class CodeGenHelper : public Xbyak::CodeGenerator { 127 | public: 128 | [[nodiscard]] inline std::vector vec() { 129 | ready(); 130 | return {getCode(), getCurr()}; 131 | } 132 | 133 | protected: 134 | typedef std::vector> ExecItem; 135 | typedef Xbyak::Reg32 Reg32; 136 | typedef std::vector Reg32List; 137 | 138 | std::mt19937 mt_{std::random_device{}()}; 139 | template 140 | inline std::vector shuffled(std::vector items) { 141 | std::shuffle(items.begin(), items.end(), mt_); 142 | return items; 143 | } 144 | template 145 | inline void shuffle(T &items) { 146 | std::shuffle(items.begin(), items.end(), mt_); 147 | } 148 | 149 | inline void shuffle_exec_2(ExecItem &items) { 150 | while (!items.empty()) { 151 | std::shuffle(items.begin(), items.end(), mt_); 152 | auto next_fn = items.back(); 153 | items.pop_back(); 154 | next_fn(); 155 | } 156 | } 157 | inline void set_zero(Xbyak::Operand ®) { 158 | if (reg.getKind() == Xbyak::Operand::REG) { 159 | pick_exec({ 160 | [&]() { xor_(reg, reg); }, 161 | [&]() { mov(reg, 0); }, 162 | [&]() { and_(reg, 0); }, 163 | }); 164 | } else { 165 | pick_exec({ 166 | [&]() { mov(reg, 0); }, 167 | [&]() { and_(reg, 0); }, 168 | }); 169 | } 170 | } 171 | 172 | inline void shuffle_exec(std::vector> items) { 173 | while (!items.empty()) { 174 | std::shuffle(items.begin(), items.end(), mt_); 175 | auto next_fn = items.back(); 176 | items.pop_back(); 177 | next_fn(&items); 178 | } 179 | } 180 | inline void shuffle_exec(std::vector> items) { 181 | std::shuffle(items.begin(), items.end(), mt_); 182 | for (auto &fn: items) { 183 | fn(); 184 | } 185 | } 186 | inline void pick_exec(const std::vector> &items) { 187 | if (!items.empty()) { 188 | std::uniform_int_distribution<> distr(0, int(items.size() - 1)); 189 | auto &it = *(items.begin() + distr(mt_)); 190 | it(); 191 | } 192 | } 193 | template 194 | inline T pick_random_item(std::vector items) { 195 | std::uniform_int_distribution<> distr(0, int(items.size() - 1)); 196 | return items.at(distr(mt_)); 197 | } 198 | std::uniform_int_distribution<> dist_bool_{0, 1}; 199 | inline bool next_bool() { 200 | return dist_bool_(mt_) == 1; 201 | } 202 | template 203 | inline T rand_int() { 204 | return static_cast(mt_()); 205 | } 206 | template 207 | inline T rand_int(T min_value, T max_value) { 208 | std::uniform_int_distribution dist(min_value, max_value); 209 | return static_cast(dist(mt_)); 210 | } 211 | inline void getJunkInstByLen(size_t len, const std::vector ®s) { 212 | std::uniform_int_distribution<> dist_register(0, static_cast(regs.size()) - 1); 213 | auto rand_reg = [&]() { return regs[dist_register(mt_)]; }; 214 | std::uniform_int_distribution dis_signed_byte(0, 0xFF); 215 | auto rand_signed_byte = [&]() { return static_cast(static_cast(static_cast(dis_signed_byte(mt_)))); }; 216 | std::uniform_int_distribution dist_lea_shift(0, 3);// 1/2/4/8 217 | auto rand_lea_multiplier = [&]() { return 1 << dist_lea_shift(mt_); }; 218 | 219 | auto ro_regs = shuffled({eax, ebx, ecx, edx, ebp, esp, esi, edi}); 220 | std::uniform_int_distribution<> dist_ro_register(0, static_cast(ro_regs.size()) - 1); 221 | auto rand_ro_reg = [&]() { return ro_regs[dist_ro_register(mt_)]; }; 222 | 223 | switch (len) { 224 | case 1: 225 | pick_exec({ 226 | [&]() { inc(rand_reg()); }, 227 | [&]() { dec(rand_reg()); }, 228 | [&]() { nop(); }, 229 | }); 230 | break; 231 | 232 | case 2: 233 | pick_exec({ 234 | [&]() { or_(rand_reg(), rand_ro_reg()); }, 235 | [&]() { xor_(rand_reg(), rand_ro_reg()); }, 236 | [&]() { and_(rand_reg(), rand_ro_reg()); }, 237 | [&]() { add(rand_reg(), rand_ro_reg()); }, 238 | [&]() { sub(rand_reg(), rand_ro_reg()); }, 239 | [&]() { adc(rand_reg(), rand_ro_reg()); }, 240 | [&]() { test(rand_reg(), rand_ro_reg()); }, 241 | [&]() { cmp(rand_reg(), rand_ro_reg()); }, 242 | }); 243 | break; 244 | 245 | case 3: 246 | pick_exec({ 247 | [&]() { or_(rand_reg(), rand_signed_byte()); }, 248 | [&]() { xor_(rand_reg(), rand_signed_byte()); }, 249 | [&]() { and_(rand_reg(), rand_signed_byte()); }, 250 | [&]() { add(rand_reg(), rand_signed_byte()); }, 251 | [&]() { sub(rand_reg(), rand_signed_byte()); }, 252 | [&]() { adc(rand_reg(), rand_signed_byte()); }, 253 | }); 254 | break; 255 | 256 | case 4: 257 | pick_exec({ 258 | [&]() { 259 | Xbyak::Reg32 index_reg; 260 | do { 261 | index_reg = rand_ro_reg(); 262 | } while (index_reg == esp); 263 | lea(rand_reg(), ptr[rand_ro_reg() + index_reg * rand_lea_multiplier() + rand_signed_byte()]); 264 | }, 265 | [&]() { lea(rand_reg(), ptr[rand_ro_reg() + rand_signed_byte()]); }, 266 | }); 267 | break; 268 | 269 | case 5: 270 | pick_exec({ 271 | [&]() { mov(rand_reg(), mt_()); }, 272 | }); 273 | break; 274 | 275 | case 0: 276 | default: 277 | break; 278 | } 279 | } 280 | inline void genJunk(const std::vector ®s) { 281 | if (!regs.empty()) { 282 | std::uniform_int_distribution<> dist_len(1, 4); 283 | getJunkInstByLen(dist_len(mt_), regs); 284 | } 285 | } 286 | inline void fillWithJunkSlideInst(size_t size, const std::vector ®s) { 287 | int junk_code_left = static_cast(size); 288 | while (junk_code_left > 0) { 289 | auto this_round = rand_int(1, std::min(4, junk_code_left)); 290 | getJunkInstByLen(this_round, regs); 291 | junk_code_left -= this_round; 292 | } 293 | } 294 | inline void fillWithJunk(size_t size, const std::vector ®s) { 295 | // too short to have anything meaningful 296 | if (size <= 20) { 297 | fillWithJunkSlideInst(size, regs); 298 | return; 299 | } 300 | 301 | auto end_label = Xbyak::Label{}; 302 | std::uniform_int_distribution dist_u8(0, 0xff); 303 | auto gen_byte = [&]() { return static_cast(dist_u8(mt_)); }; 304 | 305 | std::vector buffer(size); 306 | std::generate(buffer.begin(), buffer.end(), gen_byte); 307 | 308 | int junk_inst_len = rand_int(6, 8); 309 | 310 | int junk_padding = static_cast(size) - 5 - 4 - 1 - junk_inst_len; 311 | int padding_start = rand_int(2, junk_padding - 4); 312 | int padding_end = junk_padding - padding_start; 313 | 314 | call(end_label); 315 | db(buffer.data(), padding_start); 316 | L(end_label); 317 | 318 | add(dword[esp], static_cast(size) - 5); 319 | fillWithJunkSlideInst(junk_inst_len, regs); 320 | ret(); 321 | 322 | fillWithJunkSlideInst(padding_end, regs); 323 | } 324 | 325 | inline void maybeGenJunk(const std::vector ®s) { 326 | if (next_bool()) { 327 | genJunk(regs); 328 | } 329 | } 330 | }; 331 | 332 | 333 | template 334 | std::optional find_and_remove_item(std::vector &list, F fn) { 335 | std::optional result{}; 336 | auto it = std::find_if(list.begin(), list.end(), fn); 337 | if (it != list.end()) { 338 | result = std::make_optional(std::move(*it)); 339 | list.erase(it); 340 | } 341 | return result; 342 | } 343 | 344 | template 345 | inline T pop_last_item(std::vector &list) { 346 | T result{}; 347 | result = std::move(list.back()); 348 | list.pop_back(); 349 | return result; 350 | } 351 | -------------------------------------------------------------------------------- /LibELangPatch/ELangBulkPushGen.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangBulkPushGen.h" 2 | #include "CodeGenHelper.h" 3 | 4 | class BulkPushInstruction : public CodeGenHelper { 5 | public: 6 | explicit BulkPushInstruction(uint32_t ret_delta, std::vector values_to_push, uint32_t ebx_delta_to_ret_addr, uint32_t call_delta_to_ret_addr) { 7 | auto regs = Reg32List{eax, ecx, edx}; 8 | shuffle(regs); 9 | 10 | fillWithJunkSlideInst(rand_int(2, 4), regs); 11 | { 12 | auto reg_stack_offset = pop_last_item(regs); 13 | auto reg_arg_count_mul_4 = pop_last_item(regs); 14 | 15 | IntGenerator int_gen_stack_delta{rand_int(2, 5), ret_delta}; 16 | IntGenerator int_gen_arg_count{rand_int(2, 5), static_cast(values_to_push.size() - 1) * 4}; 17 | 18 | while (!int_gen_stack_delta.done() || !int_gen_arg_count.done()) { 19 | pick_exec({ 20 | [&]() { int_gen_stack_delta.generate_step(*this, reg_stack_offset); }, 21 | [&]() { int_gen_arg_count.generate_step(*this, reg_arg_count_mul_4); }, 22 | }); 23 | } 24 | 25 | add(dword[esp], reg_stack_offset); 26 | regs.push_back(reg_stack_offset); 27 | genJunk(regs); 28 | 29 | sub(esp, reg_arg_count_mul_4); 30 | regs.push_back(reg_arg_count_mul_4); 31 | genJunk(regs); 32 | } 33 | 34 | regs = shuffled({eax, ecx, edx}); 35 | std::vector>> int_gens_idle{}; 36 | std::vector>> int_gens_working{}; 37 | 38 | auto arg_count = values_to_push.size(); 39 | for (auto i = 1; i < arg_count; i++) { 40 | int offset = (static_cast(arg_count) - i - 1) * 4; 41 | int_gens_idle.emplace_back(offset, std::make_shared(rand_int(2, 5), values_to_push[i])); 42 | } 43 | shuffle(int_gens_idle); 44 | int_gens_idle.insert(int_gens_idle.begin(), {-4, std::make_shared(rand_int(2, 5), values_to_push[0])}); 45 | 46 | while (!regs.empty()) { 47 | auto temp_reg = pop_last_item(regs); 48 | auto [op, int_gen] = pop_last_item(int_gens_idle); 49 | int_gens_working.emplace_back(op, temp_reg, int_gen); 50 | } 51 | 52 | Reg32 reg_last_value{}; 53 | while (!int_gens_working.empty()) { 54 | auto idx = rand_int(0, int(int_gens_working.size() - 1)); 55 | auto [esp_offset, reg_temp, int_gen] = int_gens_working[idx]; 56 | int_gen->generate_step(*this, reg_temp); 57 | 58 | if (int_gen->done()) { 59 | int_gens_working.erase(int_gens_working.begin() + idx); 60 | 61 | // Don't bother 62 | if (esp_offset == -4) { 63 | reg_last_value = reg_temp; 64 | continue; 65 | } 66 | 67 | mov(dword[esp + esp_offset], reg_temp); 68 | 69 | regs.push_back(reg_temp); 70 | shuffle(regs); 71 | genJunk(regs); 72 | reg_temp = pop_last_item(regs); 73 | 74 | if (!int_gens_idle.empty()) { 75 | auto [op_next, int_gen_next] = pop_last_item(int_gens_idle); 76 | int_gens_working.emplace_back(op_next, reg_temp, int_gen_next); 77 | } 78 | } 79 | } 80 | 81 | regs = shuffled({eax, ecx, edx}); 82 | find_and_remove_item(regs, [&](auto &r) { return r == reg_last_value; }); 83 | genJunk(regs); 84 | xchg(reg_last_value, dword[esp + (arg_count * 4 - 4)]); 85 | genJunk(regs); 86 | 87 | if (ebx_delta_to_ret_addr && call_delta_to_ret_addr) { 88 | auto reg_temp_delta_to_ret = pop_last_item(regs); 89 | IntGenerator ebx_gen{rand_int(2, 5), ebx_delta_to_ret_addr}; 90 | while(!ebx_gen.done()){ 91 | ebx_gen.generate_step(*this, reg_temp_delta_to_ret); 92 | genJunk(regs); 93 | } 94 | lea(ebx, dword[reg_last_value + reg_temp_delta_to_ret]); 95 | regs.push_back(reg_temp_delta_to_ret); 96 | shuffle(regs); 97 | genJunk(regs); 98 | 99 | push(reg_last_value); 100 | regs.push_back(reg_last_value); 101 | shuffle(regs); 102 | genJunk(regs); 103 | 104 | reg_last_value = pop_last_item(regs); 105 | lea(reg_last_value, dword[ebx + (call_delta_to_ret_addr - ebx_delta_to_ret_addr)]); 106 | } 107 | 108 | genJunk(regs); 109 | if (next_bool()) { 110 | test(reg_last_value, reg_last_value); 111 | jz("out"); 112 | fillWithJunkSlideInst(rand_int(2, 5), regs); 113 | } 114 | pick_exec({ 115 | [&]() { jmp(reg_last_value); }, 116 | [&]() { 117 | push(reg_last_value); 118 | genJunk({eax, ecx, edx}); 119 | ret(); 120 | }, 121 | }); 122 | fillWithJunkSlideInst(rand_int(5, 10), {eax, ecx, edx, esi, edi, ebx}); 123 | L("out"); 124 | } 125 | }; 126 | 127 | std::vector GenerateBulkPushInstruction(uint32_t ret_delta, std::vector values_to_push) { 128 | return BulkPushInstruction{ret_delta, std::move(values_to_push), 0, 0}.vec(); 129 | } 130 | std::vector GenerateBulkPushInstructionWithEBXCall(uint32_t ret_delta, std::vector values_to_push, uint32_t ebx_delta_to_ret_addr, uint32_t call_delta_to_ret_addr) { 131 | return BulkPushInstruction{ret_delta, std::move(values_to_push), ebx_delta_to_ret_addr, call_delta_to_ret_addr}.vec(); 132 | } 133 | -------------------------------------------------------------------------------- /LibELangPatch/ELangInitFnGen.cpp: -------------------------------------------------------------------------------- 1 | #include "include/ELangInitFnGen.h" 2 | #include "CodeGenHelper.h" 3 | 4 | class InitHandlerGen : public CodeGenHelper { 5 | public: 6 | explicit InitHandlerGen(uint32_t offset_process_heap, uint32_t offset_has_ole, uint32_t *header_data) { 7 | auto ebx_offset = static_cast(mt_()); 8 | if (next_bool()) { 9 | ebx_offset &= 0x7F; 10 | } 11 | // ebx_offset &= 0b1111'1100; 12 | fillWithJunk((mt_() & 0b1111) | 1, {ecx, edx, esi, edi}); 13 | 14 | auto regs = shuffled({ecx, edx, esi, edi}); 15 | 16 | auto reg_temp_heap = pop_last_item(regs); 17 | auto new_ebx = pop_last_item(regs); 18 | 19 | shuffle_exec({ 20 | [&]() { 21 | genJunk(regs); 22 | mov(reg_temp_heap, eax); 23 | regs.push_back(eax); 24 | }, 25 | [&]() { 26 | genJunk(regs); 27 | mov(new_ebx, ebx); 28 | regs.push_back(ebx); 29 | }, 30 | }); 31 | 32 | auto mov_to_value = [&](const Xbyak::Operand &op, uint32_t imm) { 33 | if (imm == 0) { 34 | pick_exec({ 35 | [&]() { mov(op, imm); }, 36 | [&]() { and_(op, imm); }, 37 | }); 38 | } else { 39 | mov(op, imm); 40 | } 41 | }; 42 | 43 | lea(new_ebx, ptr[new_ebx + ebx_offset]); 44 | shuffle_exec({ 45 | [&]() { genJunk(regs); }, 46 | [&]() { genJunk(regs); }, 47 | [&]() { genJunk(regs); }, 48 | [&]() { mov_to_value(dword[new_ebx + (0xC4 - ebx_offset)], header_data[0]); }, 49 | [&]() { mov_to_value(dword[new_ebx + (0xC8 - ebx_offset)], header_data[1]); }, 50 | [&]() { mov_to_value(dword[new_ebx + (0xCC - ebx_offset)], header_data[2] + 1); }, 51 | [&]() { 52 | mov(dword[new_ebx + (offset_process_heap - ebx_offset)], reg_temp_heap); 53 | regs.push_back(reg_temp_heap); 54 | }, 55 | }); 56 | 57 | regs = shuffled({eax, ebx, edx, ecx, esi, edi}); 58 | std::erase_if(regs, [&](auto &r) { return r == new_ebx; }); 59 | 60 | Xbyak::Reg32 reg_jump_offset = *find_and_remove_item(regs, [&](auto &r) { return r != esi && r != edi && r != ebx; }); 61 | std::shuffle(regs.begin(), regs.end(), mt_); 62 | 63 | maybeGenJunk(regs); 64 | cmp(dword[new_ebx + (offset_has_ole - ebx_offset)], 0); 65 | setne(reg_jump_offset.cvt8()); 66 | maybeGenJunk(regs); 67 | 68 | shuffle_exec({ 69 | [&](void *) { genJunk(regs); }, 70 | [&](void *) { genJunk(regs); }, 71 | [&](void *p_vec) { 72 | pick_exec({ 73 | [&]() { neg(reg_jump_offset.cvt8()); }, 74 | [&]() { neg(reg_jump_offset); }, 75 | }); 76 | auto &vec = *reinterpret_cast> *>(p_vec); 77 | vec.emplace_back([&](void *p_vec) { 78 | and_(reg_jump_offset, 0x12); 79 | 80 | auto &vec = *reinterpret_cast> *>(p_vec); 81 | vec.emplace_back([&](void *) { add(reg_jump_offset, 0x2F); }); 82 | }); 83 | }, 84 | [&](void *) { 85 | if (new_ebx == ebx) { 86 | sub(ebx, ebx_offset); 87 | } else { 88 | lea(ebx, dword[new_ebx - ebx_offset]); 89 | regs.push_back(new_ebx); 90 | } 91 | std::erase_if(regs, [&](auto &r) { return r == ebx; }); 92 | }, 93 | }); 94 | maybeGenJunk(regs); 95 | add(dword[esp], reg_jump_offset); 96 | regs.push_back(reg_jump_offset); 97 | maybeGenJunk(regs); 98 | ret(); 99 | 100 | std::vector junk_padding((mt_() & 0b1111) | 1); 101 | std::generate(junk_padding.begin(), junk_padding.end(), mt_); 102 | db(junk_padding.data(), junk_padding.size()); 103 | } 104 | }; 105 | 106 | std::vector GenerateELangInitSnippet(uint32_t offset_process_heap, uint32_t offset_has_ole, uint32_t *header_data) { 107 | return InitHandlerGen{offset_process_heap, offset_has_ole, header_data}.vec(); 108 | } 109 | -------------------------------------------------------------------------------- /LibELangPatch/ELangLoaderInitGen.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangLoaderInitGen.h" 2 | #include "CodeGenHelper.h" 3 | 4 | class ELangLoaderInitGen : public CodeGenHelper { 5 | public: 6 | explicit ELangLoaderInitGen(std::optional call_delta) { 7 | auto regs = shuffled({eax, edx, ecx}); 8 | fillWithJunkSlideInst(rand_int(1, 5), regs); 9 | shuffle_exec({ 10 | [&]() { cld(); genJunk(regs); }, 11 | [&]() { fninit(); genJunk(regs); }, 12 | }); 13 | fillWithJunkSlideInst(rand_int(1, 5), regs); 14 | 15 | bool use_ret_trick{false}; 16 | if (call_delta) { 17 | use_ret_trick = next_bool(); 18 | auto reg_ret_addr = pop_last_item(regs); 19 | mov(reg_ret_addr, dword[esp]); 20 | genJunk(regs); 21 | if (use_ret_trick) { 22 | add(dword[esp], 3); 23 | genJunk(regs); 24 | } 25 | auto delta_signed = static_cast(*call_delta); 26 | if (delta_signed > 0) { 27 | add(reg_ret_addr, delta_signed); 28 | } else { 29 | sub(reg_ret_addr, -delta_signed); 30 | } 31 | genJunk(regs); 32 | 33 | if (use_ret_trick) { 34 | jmp(reg_ret_addr); 35 | } else { 36 | call(reg_ret_addr); 37 | } 38 | } 39 | 40 | regs = shuffled({eax, edx, ecx}); 41 | if (!use_ret_trick) { 42 | genJunk(regs); 43 | add(dword[esp], 3); 44 | } 45 | genJunk(regs); 46 | ret(); 47 | 48 | std::vector junk(rand_int(4, 10)); 49 | std::generate(junk.begin(), junk.end(), mt_); 50 | db(junk.data(), junk.size()); 51 | } 52 | }; 53 | 54 | std::vector GenerateELangLoaderInit(std::optional call_delta) { 55 | return ELangLoaderInitGen{call_delta}.vec(); 56 | } -------------------------------------------------------------------------------- /LibELangPatch/ResolveCallDllFunctionGen.cpp: -------------------------------------------------------------------------------- 1 | #include "CodeGenHelper.h" 2 | #include "VArgsProxyGen.h" 3 | 4 | class ResolveCallDllFunctionGen : public CodeGenHelper { 5 | public: 6 | explicit ResolveCallDllFunctionGen(uint32_t call_delta) { 7 | Reg32List regs({eax, ecx, edx}); 8 | shuffle(regs); 9 | auto reg_to_save = pop_last_item(regs); 10 | if (reg_to_save != eax) { 11 | fillWithJunkSlideInst(rand_int(2, 5), {edx, ecx}); 12 | mov(reg_to_save, eax); 13 | } 14 | 15 | int fn_offset = 0; 16 | fillWithJunkSlideInst(rand_int(2, 5), regs); 17 | 18 | Reg32 reg_fn_call{}; 19 | shuffle_exec({ 20 | [&]() { 21 | push(reg_to_save); 22 | fn_offset += 4; 23 | regs.push_back(reg_to_save); 24 | shuffle(regs); 25 | fillWithJunkSlideInst(rand_int(2, 7), regs); 26 | }, 27 | [&]() { 28 | reg_fn_call = pop_last_item(regs); 29 | mov(reg_fn_call, dword[esp + fn_offset]); 30 | pick_exec({ 31 | [&]() { add(reg_fn_call, call_delta); }, 32 | [&]() { 33 | auto reg_fn_call_new = pop_last_item(regs); 34 | lea(reg_fn_call_new, dword[reg_fn_call + call_delta]); 35 | regs.push_back(reg_fn_call); 36 | shuffle(regs); 37 | reg_fn_call = reg_fn_call_new; 38 | }, 39 | }); 40 | }, 41 | }); 42 | call(reg_fn_call); 43 | regs = {eax, ecx, edx}; 44 | shuffle(regs); 45 | 46 | auto reg_next_fn_addr = pop_last_item(regs); 47 | if (reg_next_fn_addr != eax) { 48 | genJunk({ecx, edx}); 49 | mov(reg_next_fn_addr, eax); 50 | } 51 | genJunk(regs); 52 | 53 | int stack_delta = 8; 54 | if (next_bool()) { 55 | stack_delta -= 4; 56 | pop(pick_random_item(regs)); 57 | } 58 | Xbyak::Label lb_junk; 59 | pick_exec({ 60 | [&]() { 61 | add(esp, stack_delta); 62 | jz(lb_junk); 63 | genJunk(regs); 64 | jmp(reg_next_fn_addr); 65 | }, 66 | [&]() { 67 | if (stack_delta - 4 != 0) { 68 | add(esp, stack_delta - 4); 69 | jz(lb_junk); 70 | genJunk(regs); 71 | } 72 | mov(dword[esp], reg_next_fn_addr); 73 | genJunk({eax, ecx, edx}); 74 | ret(); 75 | }, 76 | }); 77 | L(lb_junk); 78 | fillWithJunkSlideInst(rand_int(10, 20), {eax, ecx, edx}); 79 | jnz(lb_junk); 80 | ret(static_cast(mt_() & 0x3C)); 81 | } 82 | }; 83 | 84 | 85 | std::vector GenerateResolveCallDllFunction(uint32_t call_delta) { 86 | return ResolveCallDllFunctionGen{call_delta}.vec(); 87 | } 88 | -------------------------------------------------------------------------------- /LibELangPatch/VArgsProxyGen.cpp: -------------------------------------------------------------------------------- 1 | #include "VArgsProxyGen.h" 2 | #include "CodeGenHelper.h" 3 | 4 | class VArgsProxyGen : public CodeGenHelper { 5 | public: 6 | explicit VArgsProxyGen() { 7 | Reg32List regs({eax, ebx, ecx, edx}); 8 | shuffle(regs); 9 | 10 | auto reg_stack_tracker = pop_last_item(regs); 11 | auto reg_zero = *find_and_remove_item(regs, [&](auto &r) { return r != ebx; }); 12 | auto reg_next_func = pop_last_item(regs); 13 | auto reg_stack_offset = (std::uniform_int_distribution<>(0, 0x7C - 0x14)(mt_) & 0x7C); 14 | 15 | fillWithJunkSlideInst(rand_int(2, 4), {ecx, edx}); 16 | 17 | shuffle_exec({ 18 | [&]() { 19 | if (reg_next_func != ebx) { 20 | mov(reg_next_func, ebx); 21 | } 22 | }, 23 | [&]() { set_zero(reg_zero); }, 24 | }); 25 | 26 | int stack_offset = 0; 27 | bool stack_reg_set{false}; 28 | for (int i = 0; i < 3; i++) { 29 | pick_exec({ 30 | [&]() { push(reg_zero); }, 31 | [&]() { push(0); }, 32 | }); 33 | if (stack_reg_set) continue; 34 | 35 | stack_offset += 4; 36 | if (next_bool()) { 37 | stack_reg_set = true; 38 | lea(reg_stack_tracker, dword[esp + (stack_offset + reg_stack_offset + 8)]); 39 | } 40 | } 41 | regs.push_back(reg_zero); 42 | shuffle(regs); 43 | 44 | if (!stack_reg_set) { 45 | lea(reg_stack_tracker, dword[esp + (stack_offset + reg_stack_offset + 8)]); 46 | } 47 | genJunk(regs); 48 | auto reg_param_value = pop_last_item(regs); 49 | lea(reg_param_value, ptr[reg_stack_tracker - reg_stack_offset]); 50 | genJunk(regs); 51 | push(reg_param_value); 52 | genJunk(regs); 53 | push(dword[reg_stack_tracker - 4 - reg_stack_offset]); 54 | genJunk(regs); 55 | pick_exec({ 56 | [&]() { sub(reg_stack_tracker, (0x14 + reg_stack_offset)); }, 57 | [&]() { lea(reg_stack_tracker, ptr[reg_stack_tracker - (0x14 + reg_stack_offset)]); }, 58 | }); 59 | genJunk(regs); 60 | push(reg_stack_tracker); 61 | regs.push_back(reg_stack_tracker); 62 | if (reg_next_func != ebx) { 63 | mov(ebx, reg_next_func); 64 | } 65 | genJunk(regs); 66 | call(reg_next_func); 67 | 68 | regs = {ecx, edx, eax}; 69 | genJunk(regs); 70 | add(esp, 0x0C); 71 | while (!regs.empty()) { 72 | genJunk(regs); 73 | pop(pop_last_item(regs)); 74 | } 75 | ret(); 76 | } 77 | }; 78 | 79 | 80 | std::vector GenerateVArgsProxyCode() { 81 | return VArgsProxyGen{}.vec(); 82 | } 83 | -------------------------------------------------------------------------------- /LibELangPatch/WndHandlerGen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "CodeGenHelper.h" 5 | #include "include/WndHandlerGen.h" 6 | 7 | class WndHandlerGen : public CodeGenHelper { 8 | public: 9 | explicit WndHandlerGen(uint32_t call_inst_address, uint32_t call_inst_delta) : call_inst_address_(call_inst_address), call_inst_delta_(call_inst_delta) { 10 | inLocalLabel(); 11 | 12 | enterProc(); 13 | 14 | // backup "this" ctx. 15 | mov(ptr_local_this_, ecx); 16 | resolveNextWndHandler(); 17 | 18 | exitProc(); 19 | 20 | outLocalLabel(); 21 | ready(); 22 | } 23 | 24 | private: 25 | uint32_t call_inst_address_{}; 26 | uint32_t call_inst_delta_{}; 27 | std::vector backup_regs_{}; 28 | Xbyak::Address ptr_local_this_ = dword[ebp - 4]; 29 | Xbyak::Address ptr_arg_1_ = dword[ebp + 0x0C]; 30 | Xbyak::Address ptr_arg_ret_addr_ = dword[ebp + 0x04]; 31 | inline void initBackupRegs() { 32 | backup_regs_ = {edi, esi, ebx}; 33 | 34 | if (next_bool()) backup_regs_.push_back(edx); 35 | if (next_bool()) backup_regs_.push_back(ecx); 36 | 37 | std::shuffle(backup_regs_.begin(), backup_regs_.end(), mt_); 38 | } 39 | inline void enterProc() { 40 | push(esp); 41 | mov(ebp, esp); 42 | 43 | auto locals_size = (mt_() & 0b1'1100) + 12; 44 | sub(esp, locals_size); 45 | auto local_select = std::uniform_int_distribution<>(1, (locals_size) >> 2)(mt_) << 2; 46 | ptr_local_this_ = dword[ebp - local_select]; 47 | 48 | initBackupRegs(); 49 | for (auto reg: backup_regs_) { 50 | push(reg); 51 | } 52 | } 53 | inline void exitProc() { 54 | L("prepare_exit"); 55 | for (auto it = backup_regs_.crbegin(); it != backup_regs_.crend(); ++it) { 56 | maybeGenJunk({*it}); 57 | pop(*it); 58 | } 59 | genJunk({ecx, edx}); 60 | mov(esp, ebp); 61 | genJunk({ecx, edx}); 62 | pop(ebp); 63 | genJunk({ecx, edx}); 64 | add(esp, 4); 65 | genJunk({ecx, edx}); 66 | ret(4); 67 | } 68 | inline void resolveNextWndHandler() { 69 | std::vector regs_for_access = {eax, ebx, edx, esi, edi}; 70 | shuffle(regs_for_access); 71 | auto reg_temp = pop_last_item(regs_for_access); 72 | auto reg_temp_2 = pop_last_item(regs_for_access); 73 | 74 | // 00418810 | 55 | push ebp 75 | // 0041883A | E8 B19AFFFF | call 测试2_5.1_静态编译_q1.4122F0 76 | // (0041883A - 00418810) + 0xFFFF9AB1 + 5 === (getSize() + ?) 77 | mov(reg_temp_2, ptr_arg_ret_addr_); 78 | genJunk(regs_for_access); 79 | add(reg_temp_2, call_inst_delta_); 80 | 81 | mov(reg_temp, ptr_arg_1_); 82 | 83 | for (int i = 0x0C; i >= 0; i -= 4) { 84 | maybeGenJunk(regs_for_access); 85 | push(dword[reg_temp + i]); 86 | } 87 | maybeGenJunk(regs_for_access); 88 | call(reg_temp_2); 89 | 90 | pick_exec({ 91 | [&]() { test(eax, eax); }, 92 | [&]() { cmp(eax, 0); }, 93 | }); 94 | je("prepare_exit"); 95 | 96 | prepareAndCallNextHandler(); 97 | saveAndAssignHandlerResult(); 98 | } 99 | 100 | inline void prepareAndCallNextHandler() { 101 | auto regs = shuffled({eax, ecx, edx, esi, ebx}); 102 | 103 | auto reg_next_handler = regs[0]; 104 | auto reg_arg_count = regs[1]; 105 | auto reg_list_begin = regs[2]; 106 | auto reg_list_end = regs[3]; 107 | auto reg_arg_1 = regs[4]; 108 | 109 | if (reg_next_handler != eax) { 110 | mov(reg_next_handler, eax); 111 | } 112 | mov(reg_arg_1, ptr_arg_1_); 113 | shuffle_exec({ 114 | [&](void *) { lea(reg_list_begin, dword[reg_arg_1 + 0x10]); }, 115 | [&](void *) { mov(reg_arg_count, dword[reg_arg_1 + 0x0C]); }, 116 | }); 117 | lea(reg_list_end, dword[reg_list_begin + reg_arg_count * 4]); 118 | cmp(reg_list_begin, reg_list_end); 119 | je("lb_end_push_args"); 120 | /**/ maybeGenJunk({reg_arg_1, reg_arg_count}); 121 | L("lb_push_next_arg"); 122 | bool added{false}; 123 | /**/ shuffle_exec({ 124 | [&](void *) { push(dword[reg_list_begin - (added ? 4 : 0)]); }, 125 | [&](void *) { maybeGenJunk({reg_arg_1, reg_arg_count}); }, 126 | [&](void *) { add(reg_list_begin, 4); added = true; }, 127 | }); 128 | /**/ cmp(reg_list_begin, reg_list_end); 129 | /**/ jne("lb_push_next_arg", T_SHORT); 130 | /**/ maybeGenJunk({reg_arg_1, reg_arg_count}); 131 | L("lb_end_push_args"); 132 | call(reg_next_handler); 133 | } 134 | inline void saveAndAssignHandlerResult() { 135 | auto regs = shuffled({eax, ecx, edx, ebx, esi, edi}); 136 | 137 | Xbyak::Reg32 reg_handler_result = *find_and_remove_item(regs, [&](auto ®) { 138 | return reg != ebx; 139 | }); 140 | Xbyak::Reg32 reg_flag = *find_and_remove_item(regs, [&](auto ®) { 141 | return reg != esi && reg != edi; 142 | }); 143 | 144 | auto reg_temp = regs[0]; 145 | auto reg_temp_const = regs[1]; 146 | 147 | if (reg_handler_result != eax) { 148 | mov(reg_handler_result, eax); 149 | } 150 | 151 | pick_exec({ 152 | [&]() { test(ebx, ebx); }, 153 | [&]() { cmp(ebx, 0); }, 154 | }); 155 | setne(reg_flag.cvt8()); 156 | jz("skip_set_extra_flags"); 157 | shuffle_exec({ 158 | [&]() { mov(reg_temp, ptr_arg_1_); }, 159 | [&]() { movzx(reg_flag, reg_flag.cvt8()); }, 160 | }); 161 | 162 | shuffle_exec({ 163 | [&]() { mov(dword[reg_temp + reg_flag * 8 + 0x20], reg_handler_result); }, 164 | [&]() { mov(dword[reg_temp + 0x24], reg_flag); }, 165 | }); 166 | 167 | L("skip_set_extra_flags"); 168 | genJunk({reg_temp_const, reg_flag, esi, edi}); 169 | shuffle_exec({ 170 | [&]() { mov(reg_temp, ptr_local_this_); }, 171 | [&]() { xor_(reg_temp_const, reg_temp_const); }, 172 | }); 173 | mov(dword[reg_temp + 0x1F0], reg_temp_const /* 0 */); 174 | pick_exec({ 175 | [&]() { mov(eax, 1); }, 176 | [&]() { lea(eax, ptr[reg_temp_const + 1]); }, 177 | [&]() { mov(eax, reg_temp_const); inc(eax); }, 178 | }); 179 | } 180 | }; 181 | 182 | std::vector GenerateWndHandlerCode(uint32_t call_inst_address, uint32_t call_inst_delta) { 183 | return WndHandlerGen{call_inst_address, call_inst_delta}.vec(); 184 | } 185 | -------------------------------------------------------------------------------- /LibELangPatch/include/CallProxyStubWithEcxGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @desc Replace the following function 8 | * 15 bytes => junk nop 9 | * mov ecx, ... 10 | * call => jmp 11 | * 00411540 | 8B4424 0C | mov eax,dword ptr ss:[esp+C] 12 | * 00411544 | 8B4C24 08 | mov ecx,dword ptr ss:[esp+8] 13 | * 00411548 | 8B5424 04 | mov edx,dword ptr ss:[esp+4] 14 | * 0041154C | 50 | push eax 15 | * 0041154D | 51 | push ecx 16 | * 0041154E | 52 | push edx 17 | * 0041154F | B9 08F64A00 | mov ecx, exe.4AF608 <- `ecx_value` 18 | * 00411554 | E8 87C7FFFF | call exe.40DCE0 <- `call_delta` 19 | * 00411559 | C2 0C00 | ret C 20 | * @param pre_junk_len The number of bytes until mov ecx. e.g. `15`. 21 | * @param post_junk_len The number of bytes available afterwards, .e.g `3`. 22 | * @param ecx_value 23 | * @param call_delta 24 | * @return 25 | */ 26 | std::vector GenerateCallProxyStubWithEcx(int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta); 27 | 28 | std::vector GenerateCallProxyStubWithEcxCdecl(int arg_count, int pre_junk_len, int post_junk_len, uint32_t ecx_value, uint32_t call_delta); 29 | -------------------------------------------------------------------------------- /LibELangPatch/include/CdeclPushAndCallGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * 00401545 | 68 48000152 | push 52010048 <- this should not be relocatable 8 | * 0040154A | E8 11000000 | call exe.401560 <- call_delta = 0x11 9 | * 0040154F | 83C4 04 | add esp,4 <- stack_adjustment 10 | */ 11 | std::vector GenerateCdeclPushAndCall(uint32_t push_value, uint32_t call_delta, uint32_t ret_delta); 12 | -------------------------------------------------------------------------------- /LibELangPatch/include/ELangBulkPushGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::vector GenerateBulkPushInstruction(uint32_t ret_delta, std::vector values_to_push); 7 | std::vector GenerateBulkPushInstructionWithEBXCall( 8 | uint32_t ret_delta, std::vector values_to_push, 9 | uint32_t ebx_delta_to_ret_addr, uint32_t call_delta_to_ret_addr); 10 | -------------------------------------------------------------------------------- /LibELangPatch/include/ELangInitFnGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * 0043659F | 8983 18040000 | mov dword ptr ds:[ebx+418],eax | <-- start address offset_process_heap: 0x418 8 | * 004365A5 | A1 A0E64700 | mov eax,dword ptr ds:[47E6A0] | <-- header_data[0] 9 | * (inst_address=0x004365A5, wnd_data_address=0x47E6A0) 10 | * 004365AA | 8983 C4000000 | mov dword ptr ds:[ebx+C4],eax | 11 | * 004365B0 | 8B0D A4E64700 | mov ecx,dword ptr ds:[47E6A4] | <-- header_data[1] 12 | * 004365B6 | 8B83 10040000 | mov eax,dword ptr ds:[ebx+410] | <-- offset_has_ole: 0x410 13 | * 004365BC | 898B C8000000 | mov dword ptr ds:[ebx+C8],ecx | 14 | * 004365C2 | 8B15 A8E64700 | mov edx,dword ptr ds:[47E6A8] | <-- header_data[2] 15 | * 004365C8 | 42 | inc edx | 16 | * 004365C9 | 85C0 | test eax,eax | 17 | * 004365CB | 8993 CC000000 | mov dword ptr ds:[ebx+CC],edx | <-- end address (size = 44) 18 | * @param offset_process_heap 19 | * @param offset_has_ole 20 | * @param header_data 21 | * @return 22 | */ 23 | std::vector GenerateELangInitSnippet(uint32_t offset_process_heap, uint32_t offset_has_ole, uint32_t* header_data); 24 | -------------------------------------------------------------------------------- /LibELangPatch/include/ELangLoaderInitGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * 0040116D | FC | cld <-- addr start 9 | * 0040116E | DBE3 | fninit 10 | * 00401170 | E8 ECFFFFFF | call exe.401161 <-- call_delta = 0x0xFFFFFFEC 11 | * @param call_delta This can be `{}` if the call is empty. 12 | * @return 13 | */ 14 | std::vector GenerateELangLoaderInit(std::optional call_delta); 15 | -------------------------------------------------------------------------------- /LibELangPatch/include/ResolveCallDllFunctionGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * .text:00419280 50 | push eax 8 | * .text:00419281 E8 DA FF FF FF | call sub_419260 <-- call_delta: 0xFFFFFFDA 9 | * .text:00419286 83 C4 04 | add esp, 4 10 | * .text:00419289 FF E0 | jmp eax 11 | * @param call_delta 12 | * @return 13 | */ 14 | std::vector GenerateResolveCallDllFunction(uint32_t call_delta); 15 | -------------------------------------------------------------------------------- /LibELangPatch/include/VArgsProxyGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * 00418C90 | 8D4424 08 | lea eax,dword ptr ss:[esp+8] | 8 | * 00418C94 | 83EC 0C | sub esp,C | 9 | * 00418C97 | 50 | push eax | 10 | * 00418C98 | FF7424 14 | push dword ptr ss:[esp+14] | 11 | * 00418C9C | 33C0 | xor eax,eax | 12 | * 00418C9E | 894424 08 | mov dword ptr ss:[esp+8],eax | 13 | * 00418CA2 | 894424 0C | mov dword ptr ss:[esp+C],eax | 14 | * 00418CA6 | 894424 10 | mov dword ptr ss:[esp+10],eax | 15 | * 00418CAA | 8D5424 08 | lea edx,dword ptr ss:[esp+8] | 16 | * 00418CAE | 52 | push edx | 17 | * 00418CAF | FFD3 | call ebx | <-- core: call to the function 18 | * 00418CB1 | 8B4424 0C | mov eax,dword ptr ss:[esp+C] | 19 | * 00418CB5 | 8B5424 10 | mov edx,dword ptr ss:[esp+10] | 20 | * 00418CB9 | 8B4C24 14 | mov ecx,dword ptr ss:[esp+14] | 21 | * 00418CBD | 83C4 18 | add esp,18 | 22 | * 00418CC0 | C3 | ret | 23 | */ 24 | std::vector GenerateVArgsProxyCode(); 25 | -------------------------------------------------------------------------------- /LibELangPatch/include/WndHandlerGen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * 0041883A | E8 B19AFFFF | call exe.4122F0 <-- CALL to get event handler 8 | * 0041887D | FF55 FC | call dword ptr ss:[ebp-4] <-- ELang Button Event Handler call 9 | * call_inst_address: 0x0041883A 10 | * call_inst_delta: 0xFFFF9AB1 11 | * @param call_inst_address 12 | * @param call_inst_delta 13 | * @return 14 | */ 15 | std::vector GenerateWndHandlerCode(uint32_t call_inst_address, uint32_t call_inst_delta); 16 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # ELangPatcher 2 | 3 | 针对易语言静态编译的代码进行轻微混淆处理,避免被插件一键识别部分关键函数。处理过的内容参考下方。 4 | 5 | 注意该轻微膨胀/混淆只能用来对抗现有的“一键识别”工具,不能和加密壳的效果比。 6 | 7 | 如果你能拿到这类工具的源码缝缝补补,应该也能让它重新识别。 8 | 9 | ![效果展示截图](assets/screenshot.webp) 10 | 11 | ## 原理 12 | 13 | 对已知的部分特征进行魔改,大部分时候都是对简单操作进行膨胀,然后开辟新的内存空间跳转过去。 14 | 15 | 因为生成代码计算绝对地址比较麻烦,所以依赖 CALL 指令自动入栈的返回地址来计算正确跳转位置。 16 | 17 | ## 处理过的特征 18 | 19 | 只处理了一小部分特征。手动整起来太麻烦了,感觉不如利用特征码自动标记然后上加密壳批量处理了。 20 | 21 | - 对抗 [查找易语言按钮事件] 插件 22 | - 对抗 [EWnd v0.2] 插件的一键分析 23 | - 对抗 [EWnd Ultimate] 插件的一键分析 24 | - 对抗 [E-Debug] 程序/插件的一键分析 25 | - 对抗 [E-Decompiler] 插件的一键分析 26 | - 对抗 [易语言逆向分析助手] 的窗体信息分析 27 | - 对抗易语言初始化入口识别(`cld; fninit; call xxxx`) 28 | - 对抗控件处理事件识别(重写了个简单的,混淆程度不高 `call dword[ebp - 4]`) 29 | - 处理了找到的一些乱七八糟的特征… 30 | 31 | [EWnd Ultimate]: https://www.52pojie.cn/thread-1466188-1-1.html 32 | [EWnd v0.2]: https://www.52pojie.cn/thread-396634-1-1.html 33 | [E-Debug]: https://www.52pojie.cn/thread-1527446-1-1.html 34 | [E-Decompiler]: https://www.52pojie.cn/thread-1684608-1-1.html 35 | [易语言逆向分析助手]: https://www.52pojie.cn/thread-1586374-1-1.html 36 | [查找易语言按钮事件]: https://www.52pojie.cn/thread-1393607-1-1.html 37 | 38 | ## 从源码构建 39 | 40 | 首先确保安装有 [VS 2022]、[CMake]、[Git for Windows] 这三个程序。CMake 安装时需要选择将 `cmake.exe` 注册到系统。 41 | 42 | [VS 2022]: https://visualstudio.microsoft.com/zh-hans/downloads/ 43 | [CMake]: https://cmake.org/download/#latest 44 | [Git for Windows]: https://github.com/git-for-windows/git/releases/latest 45 | 46 | ```bat 47 | :: 克隆仓库 48 | git clone https://github.com/FlyingRainyCats/ELangPatcher.git 49 | cd ELangPatcher 50 | 51 | :: 更新子模组 52 | git submodule update --init --recursive 53 | 54 | :: 开始构建 55 | cmake -Bcmake-build-vs2022 -G "Visual Studio 17 2022" -A Win32 56 | cmake --build cmake-build-vs2022 --config Release 57 | ``` 58 | 59 | ## 集成到易语言 60 | 61 | 1. 打开易语言目录; 62 | 2. 打开该目录下的 `tools` 目录; 63 | 3. 将 `ELangPatcher.exe` 放入该目录; 64 | 4. 打开 `link.ini` 配置文件; 65 | 5. 找到结尾的 `post_link_action` 区域,并添加新的操作。 66 | 1. 静态编译后自动处理,参考添加 `post_link_action1="$(E_TOOLS)\ELangPatcher.exe" "$(TARGET)"`; 67 | 2. 如果有自动加壳,你需要调整序号,让 `ELangPatcher.exe` 先执行; 68 | 69 | 70 | ## 碎碎念 71 | 72 | - 易语言的编译顺序太“稳定”了,可以通过定位目标函数附近的函数来快速定位。 73 | - 没处理过特征的函数依然可以手动检索特征码找到。 74 | - 再做下去感觉需要自动化识别函数、反编译、然后随机混淆/膨胀了。 75 | - 要考虑的东西太多了,不适合我。 76 | 77 | ## 致谢 78 | 79 | - [fjqisba 老师的易语言逆向专栏] 80 | - [易语言程序分析笔记 - 看雪/PlaneJun] 81 | 82 | [fjqisba 老师的易语言逆向专栏]: https://fjqisba.github.io/categories/%E6%98%93%E8%AF%AD%E8%A8%80%E9%80%86%E5%90%91/ 83 | [易语言程序分析笔记 - 看雪/PlaneJun]: https://bbs.kanxue.com/thread-274503.htm 84 | -------------------------------------------------------------------------------- /assets/screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlyingRainyCats/ELangPatcher/fc351df0d979c440918aecda6485ff80d334a70a/assets/screenshot.webp -------------------------------------------------------------------------------- /build_vs2022.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: git submodule update --init --recursive 3 | 4 | cmake -Bcmake-build-vs2022 -G "Visual Studio 17 2022" -A Win32 5 | cmake --build cmake-build-vs2022 --config Release 6 | pause 7 | -------------------------------------------------------------------------------- /src/ELangPatchFile.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangPatchFile.h" 2 | #include "ELangPatcher.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fs = std::filesystem; 9 | 10 | template 11 | const char *get_stream_error(const T &file) { 12 | if (file.bad()) { 13 | return "Unrecoverable I/O error occurred."; 14 | } else if (file.eof()) { 15 | return "End-of-File reached."; 16 | } else if (file.fail()) { 17 | return "Non-fatal I/O error occurred."; 18 | } else if (file.good()) { 19 | return "No error occurred."; 20 | } 21 | return "Unknown error."; 22 | } 23 | 24 | bool ELangPatchFile(const fs::path &file_path, const std::u8string &suffix, bool backup, bool fake_stub) { 25 | if (!fs::exists(file_path)) { 26 | fprintf(stderr, " ERR: file does not exist.\n"); 27 | return false; 28 | } 29 | 30 | fs::path output_path{file_path}; 31 | auto output_file_name = output_path.stem().u8string() + suffix + output_path.extension().u8string(); 32 | output_path.replace_filename(output_file_name); 33 | 34 | if (backup && fs::exists(output_path)) { 35 | auto bak_file{output_path}; 36 | bak_file.replace_extension(output_path.extension().u8string() + u8".bak"); 37 | 38 | if (!fs::exists(bak_file)) { 39 | std::error_code ec_backup{}; 40 | fs::copy_file(file_path, bak_file, ec_backup); 41 | if (ec_backup) { 42 | fprintf(stderr, " ERR: backup failed: %s\n", ec_backup.message().c_str()); 43 | return false; 44 | } 45 | } 46 | } 47 | 48 | auto file_size = static_cast(fs::file_size(file_path)); 49 | std::vector exe_data(file_size); 50 | exe_data.reserve(file_size * 2 + 0x4000); 51 | { 52 | std::ifstream ifs(file_path, std::ifstream::binary); 53 | if (!ifs.is_open()) { 54 | fprintf(stderr, " ERR: could not open file for read: %s\n", get_stream_error(ifs)); 55 | return false; 56 | } 57 | ifs.read(reinterpret_cast(exe_data.data()), file_size); 58 | } 59 | 60 | auto patcher = MakeELangPatcher(exe_data); 61 | patcher->PatchAll(); 62 | if (fake_stub) patcher->MiscAddFakeEWndStub(); 63 | 64 | { 65 | std::ofstream ofs(output_path, std::ifstream::binary); 66 | if (!ofs.is_open()) { 67 | fprintf(stderr, " ERR: could not open file for write: %s\n", get_stream_error(ofs)); 68 | return false; 69 | } 70 | ofs.write(reinterpret_cast(exe_data.data()), static_cast(exe_data.size())); 71 | } 72 | return true; 73 | } 74 | -------------------------------------------------------------------------------- /src/ELangPatchFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | bool ELangPatchFile(const std::filesystem::path &file_path, const std::u8string& suffix, bool backup, bool fake_stub); 7 | -------------------------------------------------------------------------------- /src/ELangPatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangPatcher.h" 2 | #include "ELangPatcher/ELangPatcherImpl.h" 3 | 4 | std::unique_ptr MakeELangPatcher(std::vector& exe_data) { 5 | return std::make_unique(exe_data); 6 | } 7 | -------------------------------------------------------------------------------- /src/ELangPatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "SearchMatcher.h" 5 | 6 | class ELangPatcher { 7 | public: 8 | virtual ~ELangPatcher() = default; 9 | 10 | virtual void PatchDllFunctionInvokeCall() = 0; 11 | virtual void PatchEWndV02() = 0; 12 | virtual void PatchEWndUltimate() = 0; 13 | virtual void PatchWndEventHandlerMain() = 0; 14 | virtual void PatchKernelInvokeCall() = 0; 15 | virtual void PatchProxyStub() = 0; 16 | virtual void PatchLoadWndCall() = 0; 17 | virtual void PatchSuspiciousCallWithParam() = 0; 18 | virtual void PatchELangLoaderInitStub() = 0; 19 | 20 | virtual void MiscAddFakeEWndStub() = 0; 21 | 22 | inline void PatchAll() { 23 | PatchDllFunctionInvokeCall(); 24 | PatchEWndV02(); 25 | PatchEWndUltimate(); 26 | PatchWndEventHandlerMain(); 27 | PatchKernelInvokeCall(); 28 | PatchProxyStub(); 29 | PatchLoadWndCall(); 30 | PatchSuspiciousCallWithParam(); 31 | PatchELangLoaderInitStub(); 32 | } 33 | }; 34 | 35 | std::unique_ptr MakeELangPatcher(std::vector& exe_data); 36 | -------------------------------------------------------------------------------- /src/ELangPatcher/ELangPatcherImpl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ELangPatcher.h" 4 | #include "../PEParser.h" 5 | #include "../SearchMatcher.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class ELangPatcherImpl : public ELangPatcher { 12 | typedef ELang::PatternSearch::PatternSegment PatternSegment; 13 | typedef ELang::PatternSearch::SearchMatcher SearchMatcher; 14 | typedef FlyingRainyCats::PEParser::PEParser PEParser; 15 | 16 | public: 17 | explicit ELangPatcherImpl(std::vector& data): data_(data), pe_(data_), mt_(std::random_device{}()) {} 18 | 19 | void PatchDllFunctionInvokeCall() override; 20 | void PatchEWndV02() override; 21 | void PatchEWndUltimate() override; 22 | void PatchWndEventHandlerMain() override; 23 | void PatchKernelInvokeCall() override; 24 | void PatchProxyStub() override; 25 | void PatchLoadWndCall() override; 26 | void PatchELangLoaderInitStub() override; 27 | void PatchSuspiciousCallWithParam() override; 28 | void MiscAddFakeEWndStub() override; 29 | 30 | private: 31 | std::vector& data_; 32 | PEParser pe_; 33 | std::mt19937 mt_; 34 | std::vector> code_caves_; 35 | 36 | inline int rand_int(int min, int max) { 37 | return std::uniform_int_distribution<>(min, max)(mt_); 38 | } 39 | 40 | inline uint32_t read_u32 (size_t offset) { 41 | return *reinterpret_cast(&data_[offset]); 42 | }; 43 | 44 | inline void write_jmp(uint32_t foa_inst_offset, uint32_t foa_target_pos, uint8_t opcode = 0xE9) { 45 | auto rva_inst = pe_.FOAtoRVA(foa_inst_offset); 46 | auto rva_target = pe_.FOAtoRVA(foa_target_pos); 47 | data_[foa_inst_offset] = opcode; 48 | *reinterpret_cast(&data_.at(foa_inst_offset + 1)) = rva_target - (rva_inst + 5); 49 | } 50 | 51 | inline void write_call(uint32_t foa_inst_offset, uint32_t foa_target_pos) { 52 | write_jmp(foa_inst_offset, foa_target_pos, 0xE8); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchAddFakeEWndStub.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangPatcherImpl.h" 2 | 3 | void ELangPatcherImpl::MiscAddFakeEWndStub() { 4 | std::vector junk_data_inner = { 5 | // start of function 6 | 0x55, 0x8B, 0xEC, 7 | // junk 1 8 | 0xe8, 9 | 0x50, 0x64, 0x89, 0x25, 0x00, 0x00, 0x00, 0x00, 0x81, 0xec, 0xac, 0x01, 0x00, 0x00, 0x53, 0x56, 0x57, 10 | // junk 2 11 | 0xea, 12 | 0x8b, 0x44, 0x24, 0x0c, 0x8b, 0x4c, 0x24, 0x08, 0x8b, 0x54, 0x24, 0x04, 0x50, 0x51, 0x52, 0xb9, 13 | // junk 3 14 | 0xe9, 15 | 0x83, 0xec, 0x0c, 0x33, 0xc0, 0x56, 0x8b, 0x74, 0x24, 0x1c, 0x57, 0x8b, 0x7c, 0x24, 0x18, 0xc7, 0x07, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x4e, 0x14, 0x85, 0xc9, 0x74, 0x13, 0x50, 0x8b, 0x46, 0x0c, 0x50, 0x68, 0xd6, 0x07, 0x00, 0x00, 16 | // junk 4 17 | 0x01, 0x52, 0xE8, 0x11, 0x00, 0x00, 0x00, 18 | // end of function 19 | 0x8B, 0xE5, 0x5D, 0xC2, 0x0C, 0x00}; 20 | 21 | auto prefix_len = rand_int(0x10, 0x20); 22 | auto suffix_len = rand_int(0x10, 0x20); 23 | auto payload_len = prefix_len + junk_data_inner.size() + suffix_len; 24 | fprintf(stderr, " INFO: [MiscAddFakeEWndStub] add stub (len=%d bytes)\n", static_cast(payload_len)); 25 | std::generate_n(pe_.ExpandTextSection(payload_len), payload_len, mt_); 26 | } 27 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchDllResolveAndCall.cpp: -------------------------------------------------------------------------------- 1 | #include "ResolveCallDllFunctionGen.h" 2 | #include "ELangPatcherImpl.h" 3 | 4 | void ELangPatcherImpl::PatchDllFunctionInvokeCall() { 5 | ELang::PatternSearch::SearchMatcher pattern{{ 6 | {0x50, 0xE8}, 7 | PatternSegment::Skip(4), // call_delta 8 | {0x83, 0xC4, 0x04, 0xFF, 0xE0}, 9 | }}; 10 | 11 | for (auto it = data_.begin(); (it = pattern.search(it, data_.end())) != data_.end(); it += pattern.size()) { 12 | auto offset = std::distance(data_.begin(), it); 13 | auto call_delta = read_u32(offset + pattern.offset_at_item(1)); 14 | fprintf(stderr, " INFO: [PatchDllFunctionInvokeCall] found (offset=0x%08x, call_delta=0x%08x)\n", static_cast(offset), call_delta); 15 | 16 | auto padding_beg = rand_int(2, 7); 17 | auto padding_end = rand_int(2, 7); 18 | auto snippet = GenerateResolveCallDllFunction(call_delta + 1); 19 | 20 | auto ptr_output = pe_.ExpandTextSection(padding_beg + snippet.size() + padding_end); 21 | it = data_.begin() + offset; 22 | 23 | std::generate_n(ptr_output, padding_beg, mt_); 24 | std::copy(snippet.cbegin(), snippet.cend(), ptr_output + padding_beg); 25 | std::generate_n(ptr_output + padding_beg + snippet.size(), padding_end, mt_); 26 | 27 | std::generate_n(it, pattern.size(), mt_); 28 | write_call(offset, ptr_output + padding_beg - data_.data()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchELangLoaderInitStub.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangLoaderInitGen.h" 2 | #include "ELangPatcherImpl.h" 3 | 4 | void ELangPatcherImpl::PatchELangLoaderInitStub() { 5 | ELang::PatternSearch::SearchMatcher pattern{{ 6 | {0xFC, 0xDB, 0xE3, 0xE8, 0xEC}, 7 | }}; 8 | 9 | auto it = data_.begin(); 10 | while ((it = pattern.search(it, data_.end())) != data_.end()) { 11 | auto offset = std::distance(data_.begin(), it); 12 | fprintf(stderr, " INFO: [PatchELangLoaderInitStub] found (offset=0x%08tx)\n", offset); 13 | 14 | const auto call_delta = *reinterpret_cast(data_.data() + offset + 4); 15 | 16 | // Generate call inst 17 | auto snippet = GenerateELangLoaderInit({call_delta + 3}); 18 | auto p_snippet_out = pe_.ExpandTextSection(snippet.size()); 19 | it = data_.begin() + offset; 20 | std::copy(snippet.cbegin(), snippet.cend(), p_snippet_out); 21 | 22 | // Write call inst + 3 byte junk 23 | auto offset_snippet = p_snippet_out - data_.data(); 24 | write_call(offset, offset_snippet); 25 | std::generate_n(it + 5, 3, mt_); 26 | 27 | it += pattern.size(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchEWndUltimate.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangInitFnGen.h" 2 | #include "ELangPatcherImpl.h" 3 | 4 | #include 5 | 6 | void ELangPatcherImpl::PatchEWndUltimate() { 7 | ELang::PatternSearch::SearchMatcher pattern{{ 8 | {0x89, 0x83}, 9 | PatternSegment::Skip(4),// offset: heap 10 | {0xA1}, 11 | PatternSegment::Skip(4),// wnd_data_offset 12 | {0x89, 0x83, 0xC4, 0x00, 0x00, 0x00, 0x8B, 0x0D}, 13 | PatternSegment::Skip(4), 14 | {0x8B, 0x83}, 15 | PatternSegment::Skip(4),// offset: ole_enabled 16 | {0x89, 0x8B, 0xC8, 0x00, 0x00, 0x00, 0x8B, 0x15}, 17 | PatternSegment::Skip(4), 18 | {0x42, 0x85, 0xC0, 0x89, 0x93, 0xCC, 0x00, 0x00, 0x00}, 19 | }}; 20 | 21 | std::array data_elang_header{}; 22 | constexpr size_t kPostCallJunkLen = 0x2F; 23 | 24 | auto it = data_.begin(); 25 | while ((it = pattern.search(it, data_.end())) != data_.end()) { 26 | auto offset = std::distance(data_.begin(), it); 27 | auto heap_offset = read_u32(offset + pattern.offset_at_item(1)); 28 | auto has_ole_offset = read_u32(offset + pattern.offset_at_item(7)); 29 | auto wnd_data_address = read_u32(offset + pattern.offset_at_item(3)); 30 | auto wnd_data_offset = static_cast(pe_.RVAtoFOA(wnd_data_address)); 31 | fprintf(stderr, " INFO: [PatchEWndUltimate] found (offset=0x%08tx, data=0x%08x, wnd_data_offset=0x%08x)\n", offset, wnd_data_address, wnd_data_offset); 32 | 33 | std::copy_n(data_.begin() + wnd_data_offset, data_elang_header.size(), data_elang_header.begin()); 34 | 35 | // Inject our new header (junk + code) 36 | size_t pre_stub_junk_len = rand_int(0x04, 0x20); 37 | size_t post_stub_junk_len = rand_int(0x04, 0x20); 38 | auto snippet = GenerateELangInitSnippet(heap_offset, has_ole_offset, reinterpret_cast(data_elang_header.data())); 39 | auto injected_code_ptr = pe_.ExpandTextSection(pre_stub_junk_len + snippet.size() + post_stub_junk_len); 40 | it = data_.begin() + offset; 41 | 42 | // Write junk + stub + junk 43 | std::generate_n(injected_code_ptr, pre_stub_junk_len, mt_); 44 | std::copy_n(snippet.begin(), snippet.size(), injected_code_ptr + pre_stub_junk_len); 45 | std::generate_n(injected_code_ptr + pre_stub_junk_len + snippet.size(), post_stub_junk_len, mt_); 46 | 47 | auto offset_stub = injected_code_ptr + pre_stub_junk_len - data_.data(); 48 | fprintf(stderr, " - stub added: 0x%08x (file offset: %08x)\n", static_cast(pe_.FOAtoRVA(offset_stub)), static_cast(offset_stub)); 49 | write_call(offset, offset_stub); 50 | 51 | // Stack frame adjustment to bypass some sig matching 52 | { 53 | uint32_t len = std::uniform_int_distribution(0x04, 0xFF)(mt_) & 0xFC; 54 | auto p_stack_offset = reinterpret_cast(data_.data() + offset - (0x00436A9F - 0x00436A88) + 2); 55 | *p_stack_offset += len; 56 | } 57 | 58 | // Write junk to where the header data was and post call to our stub 59 | std::generate_n(data_.begin() + wnd_data_offset, data_elang_header.size(), mt_); 60 | std::generate_n(it + 5, kPostCallJunkLen, mt_); 61 | 62 | it += pattern.size(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchEWndV02.cpp: -------------------------------------------------------------------------------- 1 | #include "CallProxyStubWithEcxGen.h" 2 | #include "ELangPatcherImpl.h" 3 | 4 | void ELangPatcherImpl::PatchEWndV02() { 5 | ELang::PatternSearch::SearchMatcher pattern{{ 6 | {0x8B, 0x44, 0x24, 0x0C, 0x8B, 0x4C, 0x24, 0x08, 0x8B, 0x54, 0x24, 0x04, 0x50, 0x51, 0x52, 0xB9}, 7 | PatternSegment::Skip(4), 8 | {0xE8}, 9 | PatternSegment::Skip(4), 10 | {0xC2, 0x0C, 0x00}, 11 | }}; 12 | 13 | auto it = data_.begin(); 14 | while ((it = pattern.search(it, data_.end())) != data_.end()) { 15 | auto offset = std::distance(data_.begin(), it); 16 | auto ecx_value = read_u32(offset + pattern.offset_at_item(1)); 17 | auto call_delta = read_u32(offset + pattern.offset_at_item(3)); 18 | fprintf(stderr, " INFO: [PatchEWndV02] found (offset=0x%08x, ecx=0x%08x, call_delta=0x%08x)\n", static_cast(offset), ecx_value, call_delta); 19 | 20 | auto snippet = GenerateCallProxyStubWithEcx(15, 3, ecx_value, call_delta); 21 | std::copy(snippet.cbegin(), snippet.cend(), it); 22 | it += pattern.size(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchKernelInvokeCall.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangPatcherImpl.h" 2 | #include "VArgsProxyGen.h" 3 | 4 | void ELangPatcherImpl::PatchKernelInvokeCall() { 5 | std::vector patterns{ 6 | ELang::PatternSearch::SearchMatcher{{ 7 | {0x8D, 0x54, 0x24, 0x08, 0x83, 0xEC, 0x0C, 0x52, 0xFF, 0x74, 0x24, 0x14, 0xC7, 0x44, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x44, 0x24, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 0x00, 0x8D, 0x54, 0x24, 0x08, 0x52, 0xFF, 0xD3, 0x8B, 0x44, 0x24, 0x0C, 0x8B, 0x54, 0x24, 0x10, 0x8B, 0x4C, 0x24, 0x14, 0x83, 0xC4, 0x18, 0xC3}, 8 | }}, 9 | ELang::PatternSearch::SearchMatcher{{ 10 | {0x8D, 0x44, 0x24, 0x08, 0x83, 0xEC, 0x0C, 0x50, 0xFF, 0x74, 0x24, 0x14, 0x33, 0xC0, 0x89, 0x44, 0x24, 0x08, 0x89, 0x44, 0x24, 0x0C, 0x89, 0x44, 0x24, 0x10, 0x8D, 0x54, 0x24, 0x08, 0x52, 0xFF, 0xD3, 0x8B, 0x44, 0x24, 0x0C, 0x8B, 0x54, 0x24, 0x10, 0x8B, 0x4C, 0x24, 0x14, 0x83, 0xC4, 0x18, 0xC3}, 11 | }}, 12 | }; 13 | 14 | int pattern_id{0}; 15 | for (auto &pattern: patterns) { 16 | auto pattern_size = pattern.size(); 17 | 18 | for (auto it = data_.begin(); (it = pattern.search(it, data_.end())) != data_.end(); it += pattern_size) { 19 | auto offset = std::distance(data_.begin(), it); 20 | 21 | auto padding_beg = rand_int(2, 7); 22 | auto padding_end = rand_int(2, 7); 23 | auto snippet = GenerateVArgsProxyCode(); 24 | 25 | auto ptr_output = pe_.ExpandTextSection(padding_beg + snippet.size() + padding_end); 26 | it = data_.begin() + offset; 27 | 28 | fprintf(stderr, " INFO: [PatchKernelInvokeCall#%d] found (offset=0x%08tx, len=%04x, replace_len=%04x)\n", pattern_id, offset, static_cast(pattern_size), static_cast(snippet.size())); 29 | 30 | std::generate_n(ptr_output, padding_beg, mt_); 31 | std::copy(snippet.cbegin(), snippet.cend(), ptr_output + padding_beg); 32 | std::generate_n(ptr_output + padding_beg + snippet.size(), padding_end, mt_); 33 | 34 | std::generate_n(it, pattern.size(), mt_); 35 | write_jmp(offset, ptr_output + padding_beg - data_.data()); 36 | } 37 | pattern_id++; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchLoadWndCall.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangBulkPushGen.h" 2 | #include "ELangPatcherImpl.h" 3 | 4 | void ELangPatcherImpl::PatchLoadWndCall() { 5 | // @formatter:off 6 | std::vector patterns{ 7 | ELang::PatternSearch::SearchMatcher{{ 8 | {0x68}, PatternSegment::Skip(4), 9 | {0x6A}, PatternSegment::Skip(1), 10 | {0x68}, PatternSegment::Skip(4), 11 | {0x68}, PatternSegment::Skip(4), 12 | {0x68}, PatternSegment::Skip(4), 13 | {0x68}, PatternSegment::Skip(4), 14 | {0x68}, PatternSegment::Skip(4), 15 | {0x68}, PatternSegment::Skip(4), 16 | {0x68}, PatternSegment::Skip(4), 17 | {0x68, 0x03, 0x00, 0x00, 0x00},// 3 args 18 | {0xBB}, PatternSegment::Skip(4), 19 | {0xE8}, PatternSegment::Skip(4), 20 | }}, 21 | ELang::PatternSearch::SearchMatcher{{ 22 | {0x68}, PatternSegment::Skip(4), 23 | {0x6A}, PatternSegment::Skip(1), 24 | {0x68}, PatternSegment::Skip(4), 25 | {0x6A}, PatternSegment::Skip(1), 26 | {0x6A}, PatternSegment::Skip(1), 27 | {0x6A}, PatternSegment::Skip(1), 28 | {0x68}, PatternSegment::Skip(4), 29 | {0x68}, PatternSegment::Skip(4), 30 | {0x68}, PatternSegment::Skip(4), 31 | {0x68, 0x03, 0x00, 0x00, 0x00},// 3 args 32 | {0xBB}, PatternSegment::Skip(4), // mov ebx, ??? 33 | {0xE8}, PatternSegment::Skip(4), // call ??? 34 | }}, 35 | }; 36 | 37 | std::vector values{}; 38 | values.reserve(10); 39 | 40 | for (auto &pattern: patterns) { 41 | for (auto it = data_.begin(); (it = pattern.search(it, data_.end())) != data_.end(); it += pattern.size()) { 42 | auto offset = std::distance(data_.begin(), it); 43 | auto ebx = read_u32(offset + pattern.offset_at_item(20)); 44 | auto call_delta = read_u32(offset + pattern.offset_at_item(22)); 45 | 46 | values.resize(0); 47 | for (int i = 1; i < 18; i += 2) { 48 | auto len = pattern.size_at_item(i); 49 | if (len == 4) { 50 | values.push_back(read_u32(offset + pattern.offset_at_item(i))); 51 | } else { 52 | values.push_back(data_[offset + pattern.offset_at_item(i)]); 53 | } 54 | } 55 | values.push_back(3); 56 | 57 | // Might be data pointer, skip... 58 | if (values[0] != 0x80000002 || (values[3] != 0x10001 && values[3] != 0) || values[6] != 0x10001) { 59 | continue; 60 | } 61 | 62 | fprintf(stderr, " INFO: [PatchLoadWndCall] found (offset=0x%08x, ebx=0x%08x, call_delta=0x%08x)\n", static_cast(offset), ebx, call_delta); 63 | 64 | auto padding_beg = rand_int(2, 7); 65 | auto padding_end = rand_int(2, 7); 66 | auto ret_addr_foa = offset + pattern.size(); 67 | auto ret_addr_rva = pe_.FOAtoRVA(ret_addr_foa); 68 | auto snippet = GenerateBulkPushInstructionWithEBXCall(pattern.size() - 5, values, ebx - ret_addr_rva, call_delta); 69 | 70 | auto ptr_output = pe_.ExpandTextSection(padding_beg + snippet.size() + padding_end); 71 | it = data_.begin() + offset; 72 | 73 | // write new snippet 74 | std::generate_n(ptr_output, padding_beg, mt_); 75 | std::copy(snippet.cbegin(), snippet.cend(), ptr_output + padding_beg); 76 | std::generate_n(ptr_output + padding_beg + snippet.size(), padding_end, mt_); 77 | 78 | // write our call to new fn 79 | std::generate_n(it, pattern.size(), mt_); 80 | write_call(offset, ptr_output + padding_beg - data_.data()); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchProxyStub.cpp: -------------------------------------------------------------------------------- 1 | #include "CallProxyStubWithEcxGen.h" 2 | #include "ELangPatcherImpl.h" 3 | 4 | class ProxyStubContainer { 5 | public: 6 | ProxyStubContainer(const char *name, int arg_count, std::initializer_list pattern_segments) : name_(name) { 7 | pattern_ = {pattern_segments}; 8 | arg_count_ = {arg_count}; 9 | beg_padding_ = static_cast(pattern_.offset_at_item(1)) - 1; 10 | end_padding_ = static_cast(pattern_.size()) - static_cast(pattern_.offset_at_item(pattern_.pattern_count() - 1)); 11 | } 12 | 13 | const char *name_{}; 14 | ELang::PatternSearch::SearchMatcher pattern_{}; 15 | int arg_count_{}; 16 | int beg_padding_{}; 17 | int end_padding_{}; 18 | }; 19 | 20 | void ELangPatcherImpl::PatchProxyStub() { 21 | std::vector patterns{ 22 | {"ELibInvokeCall", 1, 23 | { 24 | {0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x08, 0x50, 0xB9}, 25 | PatternSegment::Skip(4), 26 | {0xE8}, 27 | PatternSegment::Skip(4), 28 | {0x5D, 0xC3}, 29 | }}, 30 | 31 | {"LoadInitWindow", 4, 32 | { 33 | {0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x14, 0x50, 0x8B, 0x4D, 0x10, 0x51, 0x8B, 0x55, 0x0C, 0x52, 0x8B, 0x45, 0x08, 0x50, 0xB9}, 34 | PatternSegment::Skip(4), 35 | {0xE8}, 36 | PatternSegment::Skip(4), 37 | {0x5D, 0xC3}, 38 | }}, 39 | 40 | {"UnknownCtrlRelated", 6, 41 | { 42 | {0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x1C, 0x50, 0x8B, 0x4D, 0x18, 0x51, 0x8B, 0x55, 0x14, 0x52, 0x8B, 0x45, 0x10, 0x50, 0x8B, 0x4D, 0x0C, 0x51, 0x8B, 0x55, 0x08, 0x52, 0xB9}, 43 | PatternSegment::Skip(4), 44 | {0xE8}, 45 | PatternSegment::Skip(4), 46 | {0x5D, 0xC3}, 47 | }}, 48 | }; 49 | 50 | for (const auto& item: patterns) { 51 | auto &pattern = item.pattern_; 52 | auto beg_padding = item.beg_padding_; 53 | auto end_padding = item.end_padding_; 54 | auto arg_count = item.arg_count_; 55 | const auto *name = item.name_; 56 | 57 | int count{0}; 58 | for (auto it = data_.begin(); (it = pattern.search(it, data_.end())) != data_.end(); it += pattern.size()) { 59 | auto offset = std::distance(data_.begin(), it); 60 | auto ecx_value = read_u32(offset + pattern.offset_at_item(1)); 61 | auto call_delta = read_u32(offset + pattern.offset_at_item(3)); 62 | fprintf(stderr, " INFO: [%s#%d] found (offset=0x%08x, args=[%d], ecx=0x%08x, call_delta=0x%08x)\n", name, count, static_cast(offset), arg_count, ecx_value, call_delta); 63 | 64 | auto snippet = GenerateCallProxyStubWithEcxCdecl(arg_count, beg_padding, end_padding, ecx_value, call_delta); 65 | std::copy(snippet.cbegin(), snippet.cend(), it); 66 | count++; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchSuspiciousCallWithParam.cpp: -------------------------------------------------------------------------------- 1 | #include "CdeclPushAndCallGen.h" 2 | #include "ELangPatcherImpl.h" 3 | #include "ResolveCallDllFunctionGen.h" 4 | 5 | #include 6 | 7 | void ELangPatcherImpl::PatchSuspiciousCallWithParam() { 8 | ELang::PatternSearch::SearchMatcher pattern{{ 9 | {0x68},// push imm32 10 | PatternSegment::Skip(4),// push_value 11 | {0xE8},// call $+???? 12 | PatternSegment::Skip(4), 13 | }}; 14 | 15 | constexpr std::array kSigJumpIndirect{0xFF, 0x25}; 16 | constexpr std::array kSigBalanceStack{0x83, 0xC4}; // add esp, ?? 17 | 18 | for (auto it = data_.begin(); (it = pattern.search(it, data_.end())) != data_.end(); it += pattern.size()) { 19 | auto offset = std::distance(data_.begin(), it); 20 | auto push_value = read_u32(offset + pattern.offset_at_item(1)); 21 | auto call_delta = read_u32(offset + pattern.offset_at_item(3)); 22 | 23 | auto offset_to_call_target = offset + 10 + call_delta; 24 | if (offset_to_call_target >= data_.size() + 1) continue; 25 | if (!std::equal(kSigJumpIndirect.cbegin(), kSigJumpIndirect.cend(), &data_[offset_to_call_target])) continue; 26 | if ((push_value & 0xFFFF0000) != 0 && !std::equal(kSigBalanceStack.cbegin(), kSigBalanceStack.cend(), &data_[offset + pattern.size()])) { 27 | continue; 28 | } 29 | 30 | fprintf(stderr, " INFO: [PatchSuspiciousCallWithParam] found (offset=0x%08x, push_value=0x%08x, call_delta=0x%08x)\n", static_cast(offset), push_value, call_delta); 31 | 32 | auto padding_beg = rand_int(2, 7); 33 | auto padding_end = rand_int(2, 7); 34 | auto snippet = GenerateCdeclPushAndCall(push_value, call_delta + 5, 5); 35 | 36 | auto ptr_output = pe_.ExpandTextSection(padding_beg + snippet.size() + padding_end); 37 | it = data_.begin() + offset; 38 | 39 | std::generate_n(ptr_output, padding_beg, mt_); 40 | std::copy(snippet.cbegin(), snippet.cend(), ptr_output + padding_beg); 41 | std::generate_n(ptr_output + padding_beg + snippet.size(), padding_end, mt_); 42 | 43 | std::generate_n(it, pattern.size(), mt_); 44 | write_call(offset, ptr_output + padding_beg - data_.data()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ELangPatcher/PatchWndEventHandlerMain.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangPatcherImpl.h" 2 | #include "WndHandlerGen.h" 3 | 4 | void ELangPatcherImpl::PatchWndEventHandlerMain() { 5 | ELang::PatternSearch::SearchMatcher pattern{{ 6 | {0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x18, 0x53, 0x56, 0x57, 0x89, 0x4D, 0xE8, 0x8B, 0x45, 0x08, 7 | 0x8B, 0x48, 0x0C, 0x51, 0x8B, 0x55, 0x08, 0x8B, 0x42, 0x08, 0x50, 0x8B, 0x4D, 0x08, 0x8B, 8 | 0x51, 0x04, 0x52, 0x8B, 0x45, 0x08, 0x8B, 0x08, 0x51, 0x8B, 0x4D, 0xE8}, 9 | {0xE8}, 10 | PatternSegment::Skip(4), 11 | }}; 12 | ELang::PatternSearch::SearchMatcher pattern_fn_end{ 13 | {0x8B, 0xE5, 0x5D, 0xC2, 0x04, 0x00}, 14 | }; 15 | 16 | constexpr size_t kMaxSearchFunctionSize = 0x200; 17 | 18 | for (auto it = data_.begin(); (it = pattern.search(it, data_.end())) != data_.end(); it += pattern.size()) { 19 | auto offset = std::distance(data_.begin(), it); 20 | auto call_foa = static_cast(offset + pattern.offset_at_item(1)); 21 | auto call_rva = static_cast(pe_.FOAtoRVA(call_foa)); 22 | auto call_inst_delta = read_u32(offset + pattern.offset_at_item(2)); 23 | 24 | auto it_fn_end_search_last = std::min(it + kMaxSearchFunctionSize, data_.end()); 25 | auto it_fn_end = pattern_fn_end.search(it, it_fn_end_search_last); 26 | if (it_fn_end == it_fn_end_search_last) { 27 | continue; 28 | } 29 | auto offset_fn_end = std::distance(data_.begin(), it_fn_end); 30 | fprintf(stderr, " INFO: [PatchWndEventHandlerMain] found (fn_end=%08tx, offset=0x%08tx, inst=0x%08x(p: 0x%08x), delta=0x%08x)\n", offset_fn_end, offset, call_foa, call_rva, call_inst_delta); 31 | 32 | auto function_size = static_cast(offset_fn_end + pattern_fn_end.size() - offset); 33 | 34 | auto padding_beg = rand_int(2, 7); 35 | auto padding_end = rand_int(2, 7); 36 | auto snippet = GenerateWndHandlerCode(static_cast(call_rva), call_inst_delta + pattern.size_at_item(0)); 37 | 38 | auto ptr_output = pe_.ExpandTextSection(padding_beg + snippet.size() + padding_end); 39 | it = data_.begin() + offset; 40 | 41 | std::generate_n(ptr_output, padding_beg, mt_); 42 | std::copy(snippet.cbegin(), snippet.cend(), ptr_output + padding_beg); 43 | std::generate_n(ptr_output + padding_beg + snippet.size(), padding_end, mt_); 44 | 45 | std::generate_n(it, function_size, mt_); 46 | write_call(offset, ptr_output + padding_beg - data_.data()); 47 | code_caves_.emplace_back(offset + 5, function_size - 5); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/PEParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #if !NDEBUG 7 | #include 8 | #include 9 | #endif 10 | 11 | #ifdef min 12 | #undef min 13 | #endif 14 | #ifdef max 15 | #undef max 16 | #endif 17 | 18 | namespace FlyingRainyCats { 19 | namespace helper { 20 | inline uint32_t round_up_to_section_size(uint32_t size) { 21 | if (size % 0x1000 != 0) { 22 | size += 0x1000 - (size % 0x1000); 23 | } 24 | return size; 25 | } 26 | }// namespace helper 27 | 28 | namespace PEParser { 29 | template 30 | class PEParser { 31 | public: 32 | using INNER_IMAGE_NT_HEADERS = typename std::conditional_t; 33 | using INNER_PIMAGE_NT_HEADERS = typename std::conditional_t; 34 | using INNER_PIMAGE_DOS_HEADER = PIMAGE_DOS_HEADER; 35 | using INNER_PIMAGE_OPTIONAL_HEADER = typename std::conditional_t; 36 | 37 | std::vector &exe_data_; 38 | inline explicit PEParser(std::vector &exe_data) : exe_data_(exe_data) {} 39 | 40 | inline INNER_PIMAGE_NT_HEADERS GetNtHeader() const { 41 | return INNER_PIMAGE_NT_HEADERS(exe_data_.data() + INNER_PIMAGE_DOS_HEADER(exe_data_.data())->e_lfanew); 42 | } 43 | 44 | inline INNER_PIMAGE_OPTIONAL_HEADER GetNtOptionalHeader() const { 45 | return &GetNtHeader()->OptionalHeader; 46 | } 47 | 48 | [[nodiscard]] inline uint32_t RVAtoFOA(DWORD address) const { 49 | auto p_nt_header = GetNtHeader(); 50 | auto p_file_header = &p_nt_header->FileHeader; 51 | auto section_count = std::size_t(p_file_header->NumberOfSections); 52 | 53 | address -= p_nt_header->OptionalHeader.ImageBase; 54 | 55 | auto section = (PIMAGE_SECTION_HEADER) ((uint8_t *) (p_nt_header) + offsetof(IMAGE_NT_HEADERS, OptionalHeader) + p_nt_header->FileHeader.SizeOfOptionalHeader); 56 | 57 | for (std::size_t i = 0; i < section_count; i++) { 58 | if ((section->VirtualAddress <= address) && (address < (section->VirtualAddress + section->Misc.VirtualSize))) { 59 | return section->PointerToRawData + (address - section->VirtualAddress); 60 | } 61 | section++; 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | [[nodiscard]] inline uintptr_t FOAtoRVA(const DWORD address) const { 68 | auto p_nt_header = INNER_PIMAGE_NT_HEADERS((uint8_t *) (exe_data_.data()) + PIMAGE_DOS_HEADER(exe_data_.data())->e_lfanew); 69 | auto p_file_header = &p_nt_header->FileHeader; 70 | auto section_count = std::size_t(p_file_header->NumberOfSections); 71 | 72 | auto section = (PIMAGE_SECTION_HEADER) ((uint8_t *) (p_nt_header) + offsetof(INNER_IMAGE_NT_HEADERS, OptionalHeader) + p_nt_header->FileHeader.SizeOfOptionalHeader); 73 | 74 | for (std::size_t i = 0; i < section_count; i++) { 75 | if ((section->PointerToRawData <= address) && (address < (section->PointerToRawData + section->SizeOfRawData))) { 76 | return p_nt_header->OptionalHeader.ImageBase + address - section->PointerToRawData + section->VirtualAddress; 77 | } 78 | section++; 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | inline uint8_t *ExpandTextSection(uint32_t size) { 85 | auto p_nt_header = INNER_PIMAGE_NT_HEADERS((uint8_t *) (exe_data_.data()) + PIMAGE_DOS_HEADER(exe_data_.data())->e_lfanew); 86 | auto p_file_header = &p_nt_header->FileHeader; 87 | auto section_count = std::size_t(p_file_header->NumberOfSections); 88 | 89 | auto section = (PIMAGE_SECTION_HEADER) ((uint8_t *) (p_nt_header) + offsetof(INNER_IMAGE_NT_HEADERS, OptionalHeader) + p_nt_header->FileHeader.SizeOfOptionalHeader); 90 | 91 | auto &image_size = GetNtOptionalHeader()->SizeOfImage; 92 | 93 | for (std::size_t i = 0; i < section_count; i++) { 94 | if (strcmp((char *) section->Name, ".text") == 0) { 95 | if (section->Misc.VirtualSize + size < section->SizeOfRawData) { 96 | auto offset = section->Misc.VirtualSize; 97 | section->Misc.VirtualSize += size; 98 | return &exe_data_.at(section->PointerToRawData + offset); 99 | } 100 | } else if (strcmp((char *) section->Name, ".txt2") == 0) { 101 | exe_data_.resize(exe_data_.size() + size); 102 | auto offset = section->SizeOfRawData; 103 | section->SizeOfRawData += size; 104 | image_size -= section->Misc.VirtualSize; 105 | section->Misc.VirtualSize = helper::round_up_to_section_size(section->SizeOfRawData); 106 | image_size += section->Misc.VirtualSize; 107 | return &exe_data_.at(section->PointerToRawData + offset); 108 | } 109 | section++; 110 | } 111 | 112 | auto last_section = §ion[-1]; 113 | p_file_header->NumberOfSections++; 114 | 115 | memset(section, 0, sizeof(*section)); 116 | memcpy(section->Name, ".txt2", 6); 117 | section->PointerToRawData = exe_data_.size(); 118 | section->SizeOfRawData = size; 119 | section->Misc.VirtualSize = helper::round_up_to_section_size(size); 120 | image_size = helper::round_up_to_section_size(image_size) + section->Misc.VirtualSize; 121 | section->VirtualAddress = helper::round_up_to_section_size(last_section->VirtualAddress + last_section->Misc.VirtualSize); 122 | section->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE; 123 | exe_data_.resize(exe_data_.size() + size); 124 | 125 | return &exe_data_.at(exe_data_.size() - size); 126 | } 127 | }; 128 | }// namespace PEParser 129 | }// namespace FlyingRainyCats 130 | -------------------------------------------------------------------------------- /src/SearchMatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ELang::PatternSearch { 8 | enum class Type { 9 | Match = 0, 10 | Skip, 11 | }; 12 | 13 | struct PatternSegment { 14 | Type type_; 15 | size_t len_; 16 | std::vector data_{}; 17 | 18 | explicit PatternSegment(size_t seek_len) : type_(Type::Skip), len_(seek_len) {} 19 | PatternSegment(std::initializer_list data) : type_(Type::Match) { 20 | len_ = data.size(); 21 | data_ = {data.begin(), data.end()}; 22 | } 23 | PatternSegment(size_t data_len, const void *data) : type_(Type::Match), len_(data_len) { 24 | auto p_data = reinterpret_cast(data); 25 | data_ = {p_data, p_data + data_len}; 26 | } 27 | 28 | static PatternSegment Skip(size_t n) { 29 | return PatternSegment(n); 30 | } 31 | }; 32 | 33 | class SearchMatcher { 34 | public: 35 | SearchMatcher(std::initializer_list pattern) { 36 | pattern_.insert(pattern_.end(), pattern.begin(), pattern.end()); 37 | for (auto &segment: pattern) { 38 | len_ += segment.len_; 39 | } 40 | } 41 | 42 | [[nodiscard]] size_t pattern_count() const { 43 | return pattern_.size(); 44 | } 45 | 46 | [[nodiscard]] size_t offset_at_item(size_t idx) const { 47 | size_t offset = 0; 48 | for (size_t i = 0; i < idx; ++i) { 49 | offset += pattern_[i].len_; 50 | } 51 | return offset; 52 | } 53 | 54 | [[nodiscard]] size_t size_at_item(size_t idx) const { 55 | return pattern_[idx].len_; 56 | } 57 | 58 | template 59 | bool matches(It first) const { 60 | for (const auto &segment: pattern_) { 61 | switch (segment.type_) { 62 | case Type::Match: 63 | if (!std::equal(segment.data_.cbegin(), segment.data_.cend(), first)) { 64 | return false; 65 | } 66 | break; 67 | case Type::Skip: 68 | break; 69 | } 70 | first += segment.len_; 71 | } 72 | 73 | return true; 74 | } 75 | 76 | template 77 | It search(It first, It last) const { 78 | It search_end = last - len_; 79 | while (first < search_end) { 80 | if (matches(first)) { 81 | return first; 82 | } 83 | first++; 84 | } 85 | 86 | return last; 87 | } 88 | 89 | [[nodiscard]] ptrdiff_t size() const { 90 | return static_cast(len_); 91 | } 92 | 93 | private: 94 | std::vector pattern_{}; 95 | size_t len_{0}; 96 | }; 97 | 98 | }// namespace ELang::PatternSearch 99 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ELangPatchFile.h" 2 | #include "ELangPatcher.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace fs = std::filesystem; 14 | 15 | std::string ConvertWideToMultibyte(UINT CodePage, const std::wstring &wstr) { 16 | if (wstr.empty()) return {}; 17 | int sizeNeeded = WideCharToMultiByte(CodePage, 0, wstr.data(), -1, nullptr, 0, nullptr, nullptr); 18 | std::string result(sizeNeeded, 0); 19 | WideCharToMultiByte(CodePage, 0, wstr.data(), -1, &result[0], sizeNeeded, nullptr, nullptr); 20 | return result; 21 | } 22 | 23 | bool checkCallingFromE() { 24 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 25 | 26 | PROCESSENTRY32 pe{}; 27 | pe.dwSize = sizeof(PROCESSENTRY32); 28 | 29 | DWORD pid = GetCurrentProcessId(); 30 | DWORD ppid{}; 31 | std::vector elang_pids{}; 32 | elang_pids.reserve(8); 33 | if (Process32First(hSnapshot, &pe)) { 34 | do { 35 | if (pe.th32ProcessID == pid) { 36 | ppid = pe.th32ParentProcessID; 37 | } else if (_stricmp(pe.szExeFile, "e.exe") == 0) { 38 | elang_pids.push_back(pe.th32ProcessID); 39 | } 40 | } while (Process32Next(hSnapshot, &pe)); 41 | } 42 | CloseHandle(hSnapshot); 43 | 44 | return std::find(elang_pids.cbegin(), elang_pids.cend(), ppid) != elang_pids.cend(); 45 | } 46 | 47 | int main_unicode(int argc, wchar_t *argv[]) { 48 | cxxopts::Options options("ELang-Patcher", "ELang AntiEWnd by FlyingRainyCats (爱飞的猫 @52pojie.cn)"); 49 | options.add_options() // 50 | ("b,backup", "Enable backup", cxxopts::value()->default_value("true")) // 51 | ("fake-stub", "Insert fake stub", cxxopts::value()->default_value("true"))// 52 | ("suffix", "Write to a different file with suffix, if specified.", 53 | cxxopts::value()->default_value(""))// 54 | ("h,help", "Show help") // 55 | ("files", "The file(s) to process", cxxopts::value>()); 56 | 57 | options.parse_positional({"files"}); 58 | 59 | std::vector args_utf8(argc); 60 | std::vector argv_utf8(argc); 61 | for (int i = 0; i < argc; i++) { 62 | args_utf8[i] = ConvertWideToMultibyte(CP_UTF8, argv[i]); 63 | argv_utf8[i] = args_utf8[i].data(); 64 | } 65 | auto result = options.parse(argc, argv_utf8.data()); 66 | if (result.count("help")) { 67 | fputs(options.help().c_str(), stderr); 68 | return 0; 69 | } 70 | bool useGBK = checkCallingFromE(); 71 | fprintf(stderr, "ELang Patcher v%s by FlyingRainyCats (%s @52pojie.cn)\n", ELANG_PATCHER_VERSION, useGBK ? "\xB0\xAE\xB7\xC9\xB5\xC4\xC3\xA8" : "爱飞的猫"); 72 | const auto file_count = result.count("files"); 73 | if (file_count == 0) { 74 | fprintf(stderr, "ERROR: no input files specified\n"); 75 | return 999; 76 | } 77 | auto files = result["files"].as>(); 78 | auto output_suffix_str = result["suffix"].as(); 79 | std::u8string output_suffix{output_suffix_str.cbegin(), output_suffix_str.cend()}; 80 | 81 | auto error_count{0}; 82 | auto fake_stub = result["fake-stub"].as(); 83 | auto backup = result["backup"].as(); 84 | for (auto &file_path: files) { 85 | std::u8string temp_path(file_path.cbegin(), file_path.cend()); 86 | fs::path exe_path{temp_path}; 87 | 88 | std::string exe_path_str{}; 89 | if (useGBK) { 90 | exe_path_str = ConvertWideToMultibyte(936, exe_path.wstring()); 91 | } else { 92 | exe_path_str = exe_path.string(); 93 | } 94 | fprintf(stderr, "INFO: processing: %s\n", exe_path_str.c_str()); 95 | 96 | if (!ELangPatchFile(exe_path, output_suffix, backup, fake_stub)) { 97 | error_count++; 98 | } 99 | } 100 | return error_count; 101 | } 102 | 103 | int main() { 104 | setlocale(LC_ALL, ".UTF8"); 105 | 106 | int argc{0}; 107 | auto argv = CommandLineToArgvW(GetCommandLineW(), &argc); 108 | return main_unicode(argc, argv); 109 | } 110 | -------------------------------------------------------------------------------- /src/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../LibELangPatch/include/CallProxyStubWithEcxGen.h" 6 | #include "../LibELangPatch/include/ELangInitFnGen.h" 7 | #include "../LibELangPatch/include/WndHandlerGen.h" 8 | #include "CdeclPushAndCallGen.h" 9 | #include "ELangBulkPushGen.h" 10 | 11 | void print_shellcode(const std::vector &code) { 12 | for (auto bytecode: code) { 13 | printf("%02x ", bytecode); 14 | } 15 | printf("\n"); 16 | } 17 | 18 | int main() { 19 | auto wnd_handler_code = GenerateWndHandlerCode(0x0041883A, 0xFFFF9AB1); 20 | printf("WndHandler: %zu bytes\n", wnd_handler_code.size()); 21 | print_shellcode(wnd_handler_code); 22 | 23 | uint32_t data[3]{0, 1, 2}; 24 | auto elang_init_code = GenerateELangInitSnippet(0x004365A5, 0x47E6A0, data); 25 | printf("ELangInitSnippet: %zu bytes\n", elang_init_code.size()); 26 | print_shellcode(elang_init_code); 27 | 28 | auto elang_wnd_proc_stub = GenerateCallProxyStubWithEcx(15, 3, 0x4AF608, 0xFFFFC787); 29 | printf("ELangWndProcStub: %zu bytes\n", elang_wnd_proc_stub.size()); 30 | print_shellcode(elang_wnd_proc_stub); 31 | 32 | auto elang_bulkPushInstruction = GenerateBulkPushInstruction(0x00401036 - 0x00401007, {0x80000002, 0, 1, 0x10001, 0x6010000, 0x52010001, 0x10001, 0x601000E, 0x5201000F, 3}); 33 | printf("elang_bulkPushInstruction: %zu bytes\n", elang_bulkPushInstruction.size()); 34 | print_shellcode(elang_bulkPushInstruction); 35 | 36 | // uint32_t push_value, uint32_t call_delta, uint32_t ret_delta 37 | auto elang_GenerateCdeclPushAndCall = GenerateCdeclPushAndCall(0x52010048, 0x11 + 5, 5); 38 | printf("elang_GenerateCdeclPushAndCall: %zu bytes\n", elang_GenerateCdeclPushAndCall.size()); 39 | print_shellcode(elang_GenerateCdeclPushAndCall); 40 | return 0; 41 | } 42 | --------------------------------------------------------------------------------