├── tests ├── rust │ └── string_encryption │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── c │ ├── .gitignore │ └── fixtures │ │ ├── issues │ │ └── issue_61_cpp_fmt_strenc │ │ │ ├── .gitignore │ │ │ ├── .gitmodules │ │ │ ├── main.cpp │ │ │ ├── CMakeLists.txt │ │ │ └── build.sh │ │ ├── integration │ │ ├── custom_calling_conv.c │ │ └── ama.c │ │ ├── function_wrapper │ │ └── function_wrapper_test.c │ │ ├── varargs │ │ ├── varargs_clone_function.c │ │ ├── varargs_function_wrapper.c │ │ └── varargs_indirect_call.c │ │ ├── exception_handling │ │ ├── cpp_exception_flatten.cpp │ │ ├── cpp_exception_indirect_branch.cpp │ │ └── cpp_exception_bcf.cpp │ │ ├── edge_cases │ │ ├── empty_function.c │ │ └── large_function.c │ │ ├── shuffle_blocks │ │ └── shuffle_test.c │ │ ├── phi_nodes │ │ ├── phi_flatten.c │ │ └── phi_split_basic_block.c │ │ ├── control_flow │ │ └── bogus_control_flow.c │ │ ├── string_encryption │ │ ├── repeated_strings.c │ │ └── const_strings.c │ │ ├── indirect_call │ │ └── indirect_call.c │ │ ├── inline_asm │ │ └── inline_asm_basic.c │ │ └── indirect_branch │ │ └── indirect_branch.c ├── indirect_call.rs ├── function_wrapper.rs ├── mba.rs ├── indirect_branch.rs ├── issue_61_cpp_fmt_strenc.rs ├── inline_asm.rs ├── edge_cases.rs ├── shuffle_blocks.rs ├── rust_string_encryption.rs ├── varargs.rs └── exception_handling.rs ├── amice-llvm ├── src │ ├── analysis.rs │ ├── inkwell2.rs │ ├── inkwell2 │ │ ├── instruction.rs │ │ ├── instruction │ │ │ ├── phi_inst.rs │ │ │ ├── call_inst.rs │ │ │ ├── branch_inst.rs │ │ │ ├── gep_inst.rs │ │ │ └── switch_inst.rs │ │ ├── refs.rs │ │ ├── module.rs │ │ ├── function.rs │ │ └── basic_block.rs │ ├── lib.rs │ ├── code_extractor.rs │ ├── analysis │ │ └── dominators.rs │ ├── ffi.rs │ └── annotate.rs ├── cpp │ ├── adt_ffi.cc │ ├── dominators_ffi.cc │ ├── verifier.cc │ └── instructions.cc └── Cargo.toml ├── .rustfmt.toml ├── amice-macro ├── Cargo.toml └── Cargo.lock ├── .gitignore ├── docs ├── README.md ├── Troubleshooting.md ├── PassOrder_zh_CN.md ├── LLVMSetup.md ├── PassOrder_en_US.md └── AndroidNDKSupport_zh_CN.md ├── src ├── aotu │ ├── mod.rs │ ├── mba │ │ └── expr.rs │ ├── alias_access │ │ └── mod.rs │ ├── bogus_control_flow │ │ └── mod.rs │ ├── custom_calling_conv │ │ └── mod.rs │ ├── shuffle_blocks │ │ └── mod.rs │ └── basic_block_outlining │ │ └── mod.rs ├── config │ ├── clone_function.rs │ ├── param_aggregate.rs │ ├── custom_calling_conv.rs │ ├── pass_order.rs │ ├── vm_flatten.rs │ ├── split_basic_block.rs │ ├── indirect_call.rs │ ├── lower_switch.rs │ ├── basic_block_outlining.rs │ ├── delay_offset_loading.rs │ ├── function_wrapper.rs │ ├── mba.rs │ ├── alias_access.rs │ └── shuffle_blocks.rs ├── lib.rs └── pass_registry.rs ├── Cargo.toml ├── .github └── workflows │ ├── windwos-x64-link-lld-build.yml │ ├── windwos-x64-link-opt-build.yml │ ├── rustfmt.yml │ └── macos-arm64-build.yml ├── PROJECT_STRUCTURE.md └── README.md /tests/rust/string_encryption/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /amice-llvm/src/analysis.rs: -------------------------------------------------------------------------------- 1 | pub mod dominators; 2 | -------------------------------------------------------------------------------- /tests/c/.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | test1* 3 | test_ndk* 4 | vm_flatten* 5 | test_md5.env -------------------------------------------------------------------------------- /tests/c/fixtures/issues/issue_61_cpp_fmt_strenc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | main.ir 3 | fmt -------------------------------------------------------------------------------- /amice-llvm/cpp/adt_ffi.cc: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/APInt.h" 2 | #include "llvm/IR/Instructions.h" 3 | 4 | extern "C" { 5 | 6 | 7 | 8 | } -------------------------------------------------------------------------------- /tests/c/fixtures/issues/issue_61_cpp_fmt_strenc/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fmt"] 2 | path = fmt 3 | url = https://github.com/fmtlib/fmt 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | match_block_trailing_comma = true 2 | max_width = 120 3 | merge_derives = true 4 | newline_style = "Unix" 5 | use_field_init_shorthand = true 6 | use_try_shorthand = true -------------------------------------------------------------------------------- /tests/rust/string_encryption/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "string_encryption_test" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/c/fixtures/issues/issue_61_cpp_fmt_strenc/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "fmt/format.h" 3 | 4 | int main() { 5 | char buf[1024]; 6 | fmt::format_to_n(buf, 1023, "{}", "hello"); 7 | puts(buf); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /tests/rust/string_encryption/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "string_encryption_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [profile.release] 7 | opt-level = 2 8 | debug = true 9 | strip = false 10 | lto = false 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /amice-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "amice-macro" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | ctor = "0.5.0" 11 | proc-macro2 = "1.0" 12 | quote = "1.0" 13 | syn = { version = "2.0", features = ["full"] } 14 | lazy_static = "1.5" -------------------------------------------------------------------------------- /tests/c/fixtures/issues/issue_61_cpp_fmt_strenc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(amice-fmt) 2 | cmake_minimum_required(VERSION 3.4...3.27) 3 | 4 | add_executable(amice-fmt main.cpp) 5 | target_link_libraries(amice-fmt fmt-header-only) 6 | target_compile_options(amice-fmt PRIVATE -fpass-plugin=$ENV{AMICE_PATH}) 7 | 8 | add_subdirectory(fmt) 9 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2.rs: -------------------------------------------------------------------------------- 1 | mod attributes; 2 | mod basic_block; 3 | mod builder; 4 | mod function; 5 | mod instruction; 6 | mod module; 7 | mod refs; 8 | 9 | pub use attributes::*; 10 | pub use basic_block::*; 11 | pub use builder::*; 12 | pub use function::*; 13 | pub use instruction::*; 14 | pub use module::*; 15 | pub use refs::*; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # RustRover 4 | .idea 5 | 6 | # My shell scripts 7 | shells/ 8 | 9 | /amice-llvm/target/ 10 | /amice-llvm/.llvm-config-path 11 | /amice-llvm/.llvm-prefix-path 12 | 13 | /amice-macro/target 14 | 15 | # Test fixtures - external dependencies 16 | tests/fixtures/issues/issue_61_cpp_fmt_strenc/fmt/ 17 | 18 | # Claude Code 19 | CLAUDE.md 20 | .claude/ -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 文档 2 | 3 | ## 简体中文 4 | 5 | - [Pass 运行顺序与优先级覆盖](https://github.com/fuqiuluo/amice/blob/master/docs/PassOrder_zh_CN.md) 6 | - [Android NDK支持](https://github.com/fuqiuluo/amice/blob/master/docs/AndroidNDKSupport_zh_CN.md) 7 | - [运行时环境变量](https://github.com/fuqiuluo/amice/blob/master/docs/EnvConfig_zh_CN.md) 8 | 9 | ## English 10 | 11 | - [Pass's Execution Order and Priority Override](https://github.com/fuqiuluo/amice/blob/master/docs/PassOrder_en_US.md) -------------------------------------------------------------------------------- /src/aotu/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alias_access; 2 | pub mod basic_block_outlining; 3 | pub mod bogus_control_flow; 4 | pub mod clone_function; 5 | pub mod custom_calling_conv; 6 | pub mod delay_offset_loading; 7 | pub mod flatten; 8 | pub mod function_wrapper; 9 | pub mod indirect_branch; 10 | pub mod indirect_call; 11 | pub mod lower_switch; 12 | pub mod mba; 13 | pub mod param_aggregate; 14 | pub mod shuffle_blocks; 15 | pub mod split_basic_block; 16 | pub mod string_encryption; 17 | pub mod vm_flatten; 18 | -------------------------------------------------------------------------------- /src/aotu/mba/expr.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub(super) enum Expr { 3 | Const(u128), 4 | Var(usize), // aux index: aux0, aux1, ... 5 | Not(Box), 6 | And(Box, Box), 7 | Or(Box, Box), 8 | Xor(Box, Box), 9 | Add(Box, Box), 10 | Sub(Box, Box), 11 | MulConst(u128, Box), // c * expr(按位宽溢出) 12 | } 13 | 14 | impl Expr { 15 | #[allow(dead_code)] 16 | pub(super) fn const0() -> Self { 17 | Expr::Const(0) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/c/fixtures/integration/custom_calling_conv.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define OBFUSCATE_CC __attribute__((annotate("custom_calling_conv"))) 4 | 5 | OBFUSCATE_CC 6 | int add(int a, int b) { 7 | return a + b; 8 | } 9 | 10 | OBFUSCATE_CC 11 | int multiply(int x, int y) { 12 | return x * y; 13 | } 14 | 15 | int main() { 16 | int result1 = add(10, 20); 17 | int result2 = multiply(5, 6); 18 | 19 | printf("add(10, 20) = %d\n", result1); 20 | printf("multiply(5, 6) = %d\n", result2); 21 | 22 | return 0; 23 | } -------------------------------------------------------------------------------- /tests/c/fixtures/issues/issue_61_cpp_fmt_strenc/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # git clone https://github.com/fmtlib/fmt 3 | 4 | LD_LIBRARY_PATH=~/Downloads/clang-linux-x86-ndk-r29-r563880c/lib:$LD_LIBRARY_PATH 5 | export LD_LIBRARY_PATH 6 | 7 | export AMICE_STRING_ENCRYPTION=true 8 | export AMICE_PATH=~/Downloads/libamice-linux-x64-android-ndk-r29/libamice.so 9 | 10 | NDK_ROOT=$ANDROID_HOME/ndk/29.0.14206865 11 | 12 | cmake \ 13 | -H. \ 14 | -Bbuild \ 15 | -DANDROID_ABI=arm64-v8a \ 16 | -DANDROID_PLATFORM=android-26 \ 17 | -DANDROID_NDK=$NDK_ROOT \ 18 | -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ 19 | -G Ninja 20 | cd build 21 | ninja 22 | -------------------------------------------------------------------------------- /tests/c/fixtures/function_wrapper/function_wrapper_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int add(int a, int b) { 5 | printf("In add function: %d + %d\n", a, b); 6 | return a + b; 7 | } 8 | 9 | int multiply(int a, int b) { 10 | printf("In multiply function: %d * %d\n", a, b); 11 | return a * b; 12 | } 13 | 14 | void greet(const char* name) { 15 | printf("Hello, %s!\n", name); 16 | } 17 | 18 | int main() { 19 | printf("Testing function wrapper pass\n"); 20 | 21 | int result1 = add(5, 3); 22 | printf("Result of add: %d\n", result1); 23 | 24 | int result2 = multiply(4, 7); 25 | printf("Result of multiply: %d\n", result2); 26 | 27 | greet("Function Wrapper"); 28 | 29 | return 0; 30 | } -------------------------------------------------------------------------------- /tests/c/fixtures/varargs/varargs_clone_function.c: -------------------------------------------------------------------------------- 1 | // Test varargs function with clone function obfuscation 2 | #include 3 | #include 4 | 5 | #define OBFUSCATE __attribute__((annotate("+clone_function"))) 6 | 7 | // Varargs function should not be cloned 8 | int min_varargs(int count, ...) { 9 | va_list args; 10 | va_start(args, count); 11 | 12 | int min_val = va_arg(args, int); 13 | for (int i = 1; i < count; i++) { 14 | int val = va_arg(args, int); 15 | if (val < min_val) { 16 | min_val = val; 17 | } 18 | } 19 | 20 | va_end(args); 21 | return min_val; 22 | } 23 | 24 | // Function that might be cloned - calls varargs 25 | OBFUSCATE 26 | int test_min(int a, int b, int c, int d) { 27 | return min_varargs(4, a, b, c, d); 28 | } 29 | 30 | int main() { 31 | int r1 = test_min(5, 2, 8, 3); // Should return 2 32 | int r2 = test_min(1, 1, 1, 1); // Should return 1 33 | 34 | return (r1 == 2 && r2 == 1) ? 0 : 1; 35 | } 36 | -------------------------------------------------------------------------------- /tests/c/fixtures/exception_handling/cpp_exception_flatten.cpp: -------------------------------------------------------------------------------- 1 | // Test C++ exception handling with flatten (should be properly handled) 2 | #include 3 | #include 4 | 5 | #define OBFUSCATE __attribute__((annotate("+flatten"))) 6 | 7 | // Function with exception and flatten - should be skipped by flatten 8 | OBFUSCATE 9 | int exception_with_flatten(int x) { 10 | try { 11 | if (x < 0) { 12 | throw std::runtime_error("Negative"); 13 | } 14 | if (x > 100) { 15 | throw std::range_error("Too large"); 16 | } 17 | return x * 2; 18 | } catch (const std::range_error& e) { 19 | return -2; 20 | } catch (const std::runtime_error& e) { 21 | return -1; 22 | } 23 | } 24 | 25 | int main() { 26 | int r1 = exception_with_flatten(10); // Should return 20 27 | int r2 = exception_with_flatten(-5); // Should return -1 28 | int r3 = exception_with_flatten(150); // Should return -2 29 | 30 | return r1 == 20 && r2 == -1 && r3 == -2 ? 0 : 1; 31 | } 32 | -------------------------------------------------------------------------------- /tests/c/fixtures/edge_cases/empty_function.c: -------------------------------------------------------------------------------- 1 | // Test empty function handling 2 | #define OBFUSCATE __attribute__((annotate("+flatten,+bcf,+mba,+indirect_branch"))) 3 | 4 | // Empty function - should be skipped by all passes 5 | OBFUSCATE 6 | void empty_function() { 7 | } 8 | 9 | // Single return function - should be skipped 10 | OBFUSCATE 11 | int single_return() { 12 | return 42; 13 | } 14 | 15 | // Single basic block function 16 | OBFUSCATE 17 | int single_block(int a, int b) { 18 | return a + b; 19 | } 20 | 21 | // Function with only declarations - no operations 22 | OBFUSCATE 23 | void only_declarations() { 24 | int x; 25 | int y; 26 | int z; 27 | } 28 | 29 | // Normal function for comparison 30 | OBFUSCATE 31 | int normal_function(int a, int b) { 32 | if (a > b) { 33 | return a; 34 | } else { 35 | return b; 36 | } 37 | } 38 | 39 | int main() { 40 | empty_function(); 41 | int r1 = single_return(); // 42 42 | int r2 = single_block(10, 20); // 30 43 | only_declarations(); 44 | int r3 = normal_function(r1, r2); // 42 45 | return r3 == 42 ? 0 : 1; 46 | } 47 | -------------------------------------------------------------------------------- /tests/c/fixtures/shuffle_blocks/shuffle_test.c: -------------------------------------------------------------------------------- 1 | // Simple test program for shuffle blocks functionality 2 | #include 3 | 4 | int test_function(int x) { 5 | // Create multiple basic blocks to test shuffling 6 | if (x > 10) { 7 | printf("Block 1: x > 10\n"); 8 | x = x * 2; 9 | } else if (x > 5) { 10 | printf("Block 2: x > 5\n"); 11 | x = x + 10; 12 | } else if (x > 0) { 13 | printf("Block 3: x > 0\n"); 14 | x = x - 1; 15 | } else { 16 | printf("Block 4: x <= 0\n"); 17 | x = 0; 18 | } 19 | 20 | // Additional blocks 21 | if (x % 2 == 0) { 22 | printf("Block 5: x is even\n"); 23 | x = x / 2; 24 | } else { 25 | printf("Block 6: x is odd\n"); 26 | x = x * 3; 27 | } 28 | 29 | return x; 30 | } 31 | 32 | int main() { 33 | int result1 = test_function(15); 34 | int result2 = test_function(7); 35 | int result3 = test_function(3); 36 | int result4 = test_function(-1); 37 | 38 | printf("Results: %d, %d, %d, %d\n", result1, result2, result3, result4); 39 | return 0; 40 | } -------------------------------------------------------------------------------- /amice-llvm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "amice-llvm" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [features] 7 | default = ["llvm21-1"] 8 | 9 | android-ndk = [] 10 | 11 | # On Windows, llvm plugins require linking with either opt.lib, 12 | # or lld.lib. 13 | win-link-opt = [] 14 | win-link-lld = [] 15 | 16 | llvm11-0 = ["inkwell/llvm11-0-no-llvm-linking"] 17 | llvm12-0 = ["inkwell/llvm12-0-no-llvm-linking"] 18 | llvm13-0 = ["inkwell/llvm13-0-no-llvm-linking"] 19 | llvm14-0 = ["inkwell/llvm14-0-no-llvm-linking"] 20 | llvm15-0 = ["inkwell/llvm15-0-no-llvm-linking"] 21 | llvm16-0 = ["inkwell/llvm16-0-no-llvm-linking"] 22 | llvm17-0 = ["inkwell/llvm17-0-no-llvm-linking"] 23 | llvm18-1 = ["inkwell/llvm18-1-no-llvm-linking"] 24 | llvm19-1 = ["inkwell/llvm19-1-no-llvm-linking"] 25 | llvm20-1 = ["inkwell/llvm20-1-no-llvm-linking"] 26 | llvm21-1 = ["inkwell/llvm21-1-no-llvm-linking"] 27 | 28 | [dependencies] 29 | lazy_static = "1.5.0" 30 | inkwell = { version = "0.7", default-features = false } 31 | strum = "0.27" 32 | strum_macros = "0.27" 33 | 34 | [build-dependencies] 35 | cc = "1" 36 | lazy_static = "1" 37 | regex = "1" 38 | semver = "1" 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/c/fixtures/phi_nodes/phi_flatten.c: -------------------------------------------------------------------------------- 1 | // Test PHI node handling with flatten (should work correctly) 2 | #define OBFUSCATE __attribute__((annotate("+flatten"))) 3 | 4 | // Complex PHI scenario with flatten 5 | OBFUSCATE 6 | int complex_phi_flatten(int x, int y, int z) { 7 | int a, b, c; 8 | 9 | // First decision - creates PHI nodes 10 | if (x > 0) { 11 | a = x * 2; 12 | b = y + 1; 13 | } else { 14 | a = x + 10; 15 | b = y * 2; 16 | } 17 | 18 | // Second decision - more PHI nodes 19 | if (b > a) { 20 | c = a + b; 21 | } else { 22 | c = a - b; 23 | } 24 | 25 | // Loop - PHI nodes for loop variables 26 | int result = c; 27 | for (int i = 0; i < z; i++) { 28 | if (i % 2 == 0) { 29 | result += i; 30 | } else { 31 | result -= i; 32 | } 33 | } 34 | 35 | return result; 36 | } 37 | 38 | int main() { 39 | int r1 = complex_phi_flatten(5, 3, 4); // 10, 4 -> 6 + (0-1+2-3) = 4 40 | int r2 = complex_phi_flatten(-2, 5, 3); // 8, 10 -> 18 + (0-1+2) = 19 41 | 42 | return (r1 == 4 && r2 == 19) ? 0 : 1; 43 | } 44 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/instruction.rs: -------------------------------------------------------------------------------- 1 | mod branch_inst; 2 | mod call_inst; 3 | mod gep_inst; 4 | mod phi_inst; 5 | mod switch_inst; 6 | 7 | pub use branch_inst::*; 8 | pub use call_inst::*; 9 | pub use gep_inst::*; 10 | use inkwell::values::InstructionValue; 11 | pub use phi_inst::*; 12 | pub use switch_inst::*; 13 | 14 | pub trait InstructionExt<'ctx> { 15 | fn into_branch_inst(self) -> BranchInst<'ctx>; 16 | 17 | fn into_switch_inst(self) -> SwitchInst<'ctx>; 18 | 19 | fn into_phi_inst(self) -> PhiInst<'ctx>; 20 | 21 | fn into_gep_inst(self) -> GepInst<'ctx>; 22 | 23 | fn into_call_inst(self) -> CallInst<'ctx>; 24 | } 25 | 26 | impl<'ctx> InstructionExt<'ctx> for InstructionValue<'ctx> { 27 | fn into_branch_inst(self) -> BranchInst<'ctx> { 28 | self.into() 29 | } 30 | 31 | fn into_switch_inst(self) -> SwitchInst<'ctx> { 32 | self.into() 33 | } 34 | 35 | fn into_phi_inst(self) -> PhiInst<'ctx> { 36 | self.into() 37 | } 38 | 39 | fn into_gep_inst(self) -> GepInst<'ctx> { 40 | self.into() 41 | } 42 | 43 | fn into_call_inst(self) -> CallInst<'ctx> { 44 | self.into() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/c/fixtures/exception_handling/cpp_exception_indirect_branch.cpp: -------------------------------------------------------------------------------- 1 | // Test C++ exception handling with indirect branch 2 | #include 3 | 4 | #define OBFUSCATE __attribute__((annotate("+indirect_branch"))) 5 | 6 | // Test invoke instruction handling 7 | OBFUSCATE 8 | int exception_with_indirect_branch(int x, int y) { 9 | try { 10 | if (x < 0) { 11 | throw std::runtime_error("Negative x"); 12 | } 13 | 14 | int result = 0; 15 | for (int i = 0; i < y; i++) { 16 | if (i == 5) { 17 | throw std::invalid_argument("i is 5"); 18 | } 19 | result += x; 20 | } 21 | return result; 22 | } catch (const std::runtime_error& e) { 23 | return -1; 24 | } catch (const std::invalid_argument& e) { 25 | return -2; 26 | } 27 | } 28 | 29 | int main() { 30 | int r1 = exception_with_indirect_branch(10, 3); // Should return 30 31 | int r2 = exception_with_indirect_branch(-5, 3); // Should return -1 32 | int r3 = exception_with_indirect_branch(10, 10); // Should return -2 33 | 34 | return (r1 == 30 && r2 == -1 && r3 == -2) ? 0 : 1; 35 | } 36 | -------------------------------------------------------------------------------- /docs/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting Guide 2 | 3 | ## LLVM Not Found 4 | 5 | **Error message:** 6 | ``` 7 | error: No suitable version of LLVM was found system-wide or pointed 8 | to by LLVM_SYS__PREFIX. 9 | 10 | Refer to the llvm-sys documentation for more information. 11 | 12 | llvm-sys: https://crates.io/crates/llvm-sys 13 | ``` 14 | 15 | **Cause:** LLVM is not installed or the build tools cannot locate it. 16 | 17 | **Solution:** See [LLVM Setup Guide](LLVMSetup.md) 18 | 19 | --- 20 | 21 | ## libffi Not Found 22 | 23 | **Error message:** Linker errors about missing `-lffi` 24 | 25 | ### Linux (Fedora/RHEL/CentOS) 26 | 27 | ```bash 28 | sudo dnf install libffi-devel 29 | ``` 30 | 31 | ### Linux (Ubuntu/Debian) 32 | 33 | ```bash 34 | sudo apt install libffi-dev 35 | ``` 36 | 37 | ### macOS 38 | 39 | ```bash 40 | brew install libffi 41 | 42 | # If still having issues, set PKG_CONFIG_PATH 43 | export PKG_CONFIG_PATH="$(brew --prefix libffi)/lib/pkgconfig:$PKG_CONFIG_PATH" 44 | ``` 45 | 46 | ### Windows 47 | 48 | libffi should be included with the LLVM installation. If issues persist, ensure you've installed the complete LLVM package with all components. 49 | -------------------------------------------------------------------------------- /tests/c/fixtures/control_flow/bogus_control_flow.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Simple test program with multiple basic blocks for bogus control flow obfuscation 4 | volatile int global_sink; 5 | 6 | void test_simple_branches(int x) { 7 | if (x > 10) { 8 | global_sink = x * 2; 9 | } else { 10 | global_sink = x + 1; 11 | } 12 | } 13 | 14 | void test_nested_conditions(int a, int b) { 15 | if (a > 5) { 16 | if (b > 3) { 17 | global_sink = a + b; 18 | } else { 19 | global_sink = a - b; 20 | } 21 | } else { 22 | global_sink = a * b; 23 | } 24 | } 25 | 26 | void test_loop(int n) { 27 | int sum = 0; 28 | for (int i = 0; i < n; i++) { 29 | sum += i; 30 | } 31 | global_sink = sum; 32 | } 33 | 34 | int main() { 35 | printf("Testing bogus control flow obfuscation...\n"); 36 | 37 | test_simple_branches(15); 38 | printf("Simple branches result: %d\n", global_sink); 39 | 40 | test_nested_conditions(7, 4); 41 | printf("Nested conditions result: %d\n", global_sink); 42 | 43 | test_loop(5); 44 | printf("Loop result: %d\n", global_sink); 45 | 46 | return 0; 47 | } -------------------------------------------------------------------------------- /tests/c/fixtures/varargs/varargs_function_wrapper.c: -------------------------------------------------------------------------------- 1 | // Test varargs function with function wrapper obfuscation 2 | #include 3 | #include 4 | 5 | #define OBFUSCATE __attribute__((annotate("+function_wrapper"))) 6 | 7 | // Custom varargs function that should not be wrapped 8 | int max_varargs(int count, ...) { 9 | va_list args; 10 | va_start(args, count); 11 | 12 | int max_val = va_arg(args, int); 13 | for (int i = 1; i < count; i++) { 14 | int val = va_arg(args, int); 15 | if (val > max_val) { 16 | max_val = val; 17 | } 18 | } 19 | 20 | va_end(args); 21 | return max_val; 22 | } 23 | 24 | // Function that calls varargs - wrapper should handle this correctly 25 | OBFUSCATE 26 | int call_varargs(int a, int b, int c) { 27 | return max_varargs(3, a, b, c); 28 | } 29 | 30 | // Function using printf - should not break 31 | OBFUSCATE 32 | void print_values(int x, int y, int z) { 33 | printf("Values: %d, %d, %d\n", x, y, z); 34 | printf("Product: %d\n", x * y * z); 35 | } 36 | 37 | int main() { 38 | int r1 = call_varargs(5, 10, 3); // Should return 10 39 | print_values(2, 3, 4); 40 | 41 | return r1 == 10 ? 0 : 1; 42 | } 43 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/instruction/phi_inst.rs: -------------------------------------------------------------------------------- 1 | use crate::inkwell2::refs::LLVMValueRefExt; 2 | use inkwell::llvm_sys::prelude::LLVMValueRef; 3 | use inkwell::values::{AsValueRef, InstructionOpcode, InstructionValue, PhiValue}; 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct PhiInst<'ctx> { 8 | inst: InstructionValue<'ctx>, 9 | } 10 | 11 | impl<'ctx> PhiInst<'ctx> { 12 | pub fn new(inst: InstructionValue<'ctx>) -> Self { 13 | assert_eq!(inst.get_opcode(), InstructionOpcode::Phi); 14 | Self { inst } 15 | } 16 | 17 | pub fn into_phi_value(self) -> PhiValue<'ctx> { 18 | (self.inst.as_value_ref() as LLVMValueRef).into_phi_value() 19 | } 20 | } 21 | 22 | impl<'ctx> Deref for PhiInst<'ctx> { 23 | type Target = InstructionValue<'ctx>; 24 | 25 | fn deref(&self) -> &Self::Target { 26 | &self.inst 27 | } 28 | } 29 | 30 | impl<'ctx> DerefMut for PhiInst<'ctx> { 31 | fn deref_mut(&mut self) -> &mut Self::Target { 32 | &mut self.inst 33 | } 34 | } 35 | 36 | impl<'ctx> From> for PhiInst<'ctx> { 37 | fn from(base: InstructionValue<'ctx>) -> Self { 38 | Self::new(base) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/instruction/call_inst.rs: -------------------------------------------------------------------------------- 1 | use inkwell::values::{AsValueRef, CallSiteValue, FunctionValue, InstructionOpcode, InstructionValue}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct CallInst<'ctx> { 6 | inst: InstructionValue<'ctx>, 7 | } 8 | 9 | impl<'ctx> CallInst<'ctx> { 10 | pub fn new(inst: InstructionValue<'ctx>) -> Self { 11 | assert_eq!(inst.get_opcode(), InstructionOpcode::Call); 12 | Self { inst } 13 | } 14 | 15 | pub fn get_call_function(&self) -> Option> { 16 | self.into_call_site_value().get_called_fn_value() 17 | } 18 | 19 | pub fn into_call_site_value(self) -> CallSiteValue<'ctx> { 20 | unsafe { CallSiteValue::new(self.inst.as_value_ref()) } 21 | } 22 | } 23 | 24 | impl<'ctx> From> for CallInst<'ctx> { 25 | fn from(base: InstructionValue<'ctx>) -> Self { 26 | Self::new(base) 27 | } 28 | } 29 | 30 | impl<'ctx> Deref for CallInst<'ctx> { 31 | type Target = InstructionValue<'ctx>; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | &self.inst 35 | } 36 | } 37 | 38 | impl<'ctx> DerefMut for CallInst<'ctx> { 39 | fn deref_mut(&mut self) -> &mut Self::Target { 40 | &mut self.inst 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/rust/string_encryption/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Test 1: Simple string literal 3 | let msg1 = "Hello, World!"; 4 | println!("{}", msg1); 5 | 6 | // Test 2: String in condition 7 | let msg2 = "This is a secret message"; 8 | if !msg2.is_empty() { 9 | println!("Message length: {}", msg2.len()); 10 | } 11 | 12 | // Test 3: Multiple strings 13 | let greeting = "Welcome"; 14 | let name = "User"; 15 | println!("{}, {}!", greeting, name); 16 | 17 | // Test 4: String concatenation 18 | let part1 = "Obfuscated "; 19 | let part2 = "String"; 20 | let combined = format!("{}{}", part1, part2); 21 | println!("{}", combined); 22 | 23 | // Test 5: Unicode string 24 | let unicode = "你好,世界!🦀"; 25 | println!("{}", unicode); 26 | 27 | // Test 6: Escaped characters 28 | let escaped = "Line1\nLine2\tTabbed"; 29 | println!("{}", escaped); 30 | 31 | // Test 7: Empty string 32 | let empty = ""; 33 | println!("Empty: '{}'", empty); 34 | 35 | // Test 8: Long string 36 | let long = "This is a very long string that should definitely be encrypted by the obfuscator plugin. It contains multiple sentences and various characters!"; 37 | println!("{}", long); 38 | 39 | println!("All tests completed!"); 40 | } 41 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/instruction/branch_inst.rs: -------------------------------------------------------------------------------- 1 | use inkwell::basic_block::BasicBlock; 2 | use inkwell::values::{InstructionOpcode, InstructionValue}; 3 | use std::ops::{Deref, DerefMut}; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct BranchInst<'ctx> { 7 | inst: InstructionValue<'ctx>, 8 | } 9 | 10 | impl<'ctx> BranchInst<'ctx> { 11 | pub fn new(inst: InstructionValue<'ctx>) -> Self { 12 | assert_eq!(inst.get_opcode(), InstructionOpcode::Br); 13 | Self { inst } 14 | } 15 | 16 | pub fn get_successor(&self, idx: u32) -> Option> { 17 | if self.inst.get_num_operands() == 1 { 18 | return self.inst.get_operand(0)?.block() 19 | } 20 | 21 | assert!(idx < 2); 22 | 23 | self.inst.get_operand(2 - idx)?.block() 24 | } 25 | } 26 | 27 | impl<'ctx> Deref for BranchInst<'ctx> { 28 | type Target = InstructionValue<'ctx>; 29 | 30 | fn deref(&self) -> &Self::Target { 31 | &self.inst 32 | } 33 | } 34 | 35 | impl<'ctx> DerefMut for BranchInst<'ctx> { 36 | fn deref_mut(&mut self) -> &mut Self::Target { 37 | &mut self.inst 38 | } 39 | } 40 | 41 | impl<'ctx> From> for BranchInst<'ctx> { 42 | fn from(base: InstructionValue<'ctx>) -> Self { 43 | Self::new(base) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/c/fixtures/string_encryption/repeated_strings.c: -------------------------------------------------------------------------------- 1 | // 示例代码来自 fuqiuluo ! 2 | #include 3 | #include 4 | #include 5 | 6 | int other_func() 7 | { 8 | char *literal1 = "This is a literal."; 9 | char *literal2 = "This is a literal."; 10 | printf("%s %p\n", literal1, literal1); 11 | printf("%s %p\n", literal2, literal2); 12 | printf("%s %p\n", literal1, literal1); 13 | printf("%s %p\n", literal2, literal2); 14 | printf("%s %p\n", literal2, literal2); 15 | printf("%s %p\n", literal1, literal1); 16 | printf("%s %p\n", literal2, literal2); 17 | printf("%s %p\n", literal2, literal2); 18 | printf("%s %p\n", literal1, literal1); 19 | printf("%s %p\n", literal2, literal2); 20 | return 0; 21 | } 22 | 23 | int main() 24 | { 25 | char *literal1 = "This is a literal."; 26 | char *literal2 = "This is a literal."; 27 | printf("%s %p\n", literal1, literal1); 28 | printf("%s %p\n", literal2, literal2); 29 | printf("%s %p\n", literal1, literal1); 30 | printf("%s %p\n", literal2, literal2); 31 | printf("%s %p\n", literal2, literal2); 32 | printf("%s %p\n", literal1, literal1); 33 | printf("%s %p\n", literal2, literal2); 34 | printf("%s %p\n", literal2, literal2); 35 | printf("%s %p\n", literal1, literal1); 36 | printf("%s %p\n", literal2, literal2); 37 | return 0; 38 | } -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/refs.rs: -------------------------------------------------------------------------------- 1 | use inkwell::basic_block::BasicBlock; 2 | use inkwell::llvm_sys::prelude::{LLVMBasicBlockRef, LLVMValueRef}; 3 | use inkwell::values::{BasicValueEnum, FunctionValue, InstructionValue, PhiValue}; 4 | 5 | pub trait LLVMBasicBlockRefExt<'ctx> { 6 | fn into_basic_block(self) -> Option>; 7 | } 8 | 9 | impl<'ctx> LLVMBasicBlockRefExt<'ctx> for LLVMBasicBlockRef { 10 | fn into_basic_block(self) -> Option> { 11 | unsafe { BasicBlock::new(self) } 12 | } 13 | } 14 | 15 | pub trait LLVMValueRefExt<'ctx> { 16 | fn into_instruction_value(self) -> InstructionValue<'ctx>; 17 | 18 | fn into_basic_value_enum(self) -> BasicValueEnum<'ctx>; 19 | 20 | fn into_function_value(self) -> Option>; 21 | 22 | fn into_phi_value(self) -> PhiValue<'ctx>; 23 | } 24 | 25 | impl<'ctx> LLVMValueRefExt<'ctx> for LLVMValueRef { 26 | fn into_instruction_value(self) -> InstructionValue<'ctx> { 27 | unsafe { InstructionValue::new(self) } 28 | } 29 | 30 | fn into_basic_value_enum(self) -> BasicValueEnum<'ctx> { 31 | unsafe { BasicValueEnum::new(self) } 32 | } 33 | 34 | fn into_function_value(self) -> Option> { 35 | unsafe { FunctionValue::new(self) } 36 | } 37 | 38 | fn into_phi_value(self) -> PhiValue<'ctx> { 39 | unsafe { PhiValue::new(self) } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/c/fixtures/varargs/varargs_indirect_call.c: -------------------------------------------------------------------------------- 1 | // Test varargs function with indirect call obfuscation 2 | #include 3 | #include 4 | 5 | #define OBFUSCATE __attribute__((annotate("+indirect_call"))) 6 | 7 | // Custom varargs function 8 | int sum_varargs(int count, ...) { 9 | va_list args; 10 | va_start(args, count); 11 | 12 | int sum = 0; 13 | for (int i = 0; i < count; i++) { 14 | sum += va_arg(args, int); 15 | } 16 | 17 | va_end(args); 18 | return sum; 19 | } 20 | 21 | // Function that calls varargs function - should not break printf 22 | OBFUSCATE 23 | void test_printf(int x, int y) { 24 | printf("x=%d, y=%d\n", x, y); 25 | printf("sum=%d\n", x + y); 26 | } 27 | 28 | // Function calling custom varargs 29 | OBFUSCATE 30 | int test_custom_varargs(int a, int b, int c) { 31 | int result = sum_varargs(3, a, b, c); 32 | return result; 33 | } 34 | 35 | // Function using sprintf 36 | OBFUSCATE 37 | void test_sprintf(char* buffer, int x) { 38 | sprintf(buffer, "Value: %d", x); 39 | } 40 | 41 | // Function using fprintf 42 | OBFUSCATE 43 | void test_fprintf(int x, int y) { 44 | fprintf(stderr, "Error: x=%d, y=%d\n", x, y); 45 | } 46 | 47 | int main() { 48 | test_printf(10, 20); 49 | 50 | int r1 = test_custom_varargs(1, 2, 3); // Should return 6 51 | 52 | char buffer[100]; 53 | test_sprintf(buffer, 42); 54 | 55 | test_fprintf(100, 200); 56 | 57 | return r1 == 6 ? 0 : 1; 58 | } 59 | -------------------------------------------------------------------------------- /amice-llvm/cpp/dominators_ffi.cc: -------------------------------------------------------------------------------- 1 | #include "llvm/IR/Dominators.h" 2 | #include "llvm/IR/Function.h" 3 | #include "llvm/IR/BasicBlock.h" 4 | #include "llvm/IR/Instruction.h" 5 | #include "llvm/IR/Value.h" 6 | #include "llvm/IR/Use.h" 7 | #include "llvm/ADT/Twine.h" 8 | #include 9 | 10 | extern "C" { 11 | 12 | // DominatorTree lifecycle management 13 | llvm::DominatorTree* llvm_dominator_tree_create() { 14 | auto* dt = new llvm::DominatorTree(); 15 | return dt; 16 | } 17 | 18 | llvm::DominatorTree* llvm_dominator_tree_create_from_function(llvm::Function* func) { 19 | if (!func) return nullptr; 20 | auto* dt = new llvm::DominatorTree(*func); 21 | return dt; 22 | } 23 | 24 | void llvm_dominator_tree_destroy(llvm::DominatorTree* dt) { 25 | if (dt) { 26 | delete dt; 27 | } 28 | } 29 | 30 | void llvm_dominator_tree_view_graph(llvm::DominatorTree* dt) { 31 | dt->viewGraph(); 32 | } 33 | 34 | bool llvm_dominator_tree_dominate_BU(llvm::DominatorTree* dt, llvm::BasicBlock* B, llvm::Use& U) { 35 | #if defined(LLVM_VERSION_MAJOR) && (LLVM_VERSION_MAJOR >= 12) 36 | //llvm::BasicBlock* BB = (llvm::BasicBlock*)&*U; 37 | return dt->dominates(B, U); 38 | #else 39 | const llvm::DomTreeNodeBase *NA = dt->getNode(B); 40 | const llvm::DomTreeNodeBase *NB = dt->getNode((llvm::BasicBlock*)&*U); 41 | if (!NA || !NB) return false; 42 | 43 | return dt->dominates(NA, NB); 44 | #endif 45 | } 46 | 47 | } // extern "C" -------------------------------------------------------------------------------- /tests/c/fixtures/edge_cases/large_function.c: -------------------------------------------------------------------------------- 1 | // Test large function handling (near the 4096 basic block limit) 2 | #define OBFUSCATE __attribute__((annotate("+flatten"))) 3 | 4 | // Function with many basic blocks 5 | OBFUSCATE 6 | int large_function(int x) { 7 | int result = 0; 8 | 9 | // Generate many if-else chains to create many basic blocks 10 | if (x == 0) result += 1; 11 | else if (x == 1) result += 2; 12 | else if (x == 2) result += 3; 13 | else if (x == 3) result += 4; 14 | else if (x == 4) result += 5; 15 | else if (x == 5) result += 6; 16 | else if (x == 6) result += 7; 17 | else if (x == 7) result += 8; 18 | else if (x == 8) result += 9; 19 | else if (x == 9) result += 10; 20 | else if (x == 10) result += 11; 21 | else if (x == 11) result += 12; 22 | else if (x == 12) result += 13; 23 | else if (x == 13) result += 14; 24 | else if (x == 14) result += 15; 25 | else if (x == 15) result += 16; 26 | else if (x == 16) result += 17; 27 | else if (x == 17) result += 18; 28 | else if (x == 18) result += 19; 29 | else if (x == 19) result += 20; 30 | else result += 100; 31 | 32 | // Add more branches 33 | for (int i = 0; i < 10; i++) { 34 | if (i % 2 == 0) { 35 | result += i; 36 | } else { 37 | result -= i; 38 | } 39 | } 40 | 41 | return result; 42 | } 43 | 44 | int main() { 45 | int r = large_function(5); 46 | return r == 1 ? 0 : 1; 47 | } 48 | -------------------------------------------------------------------------------- /amice-llvm/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use std::borrow::Cow; 4 | use std::ffi::{CStr, CString}; 5 | 6 | pub mod analysis; 7 | mod annotate; 8 | pub mod code_extractor; 9 | mod ffi; 10 | pub mod inkwell2; 11 | 12 | pub fn get_llvm_version_major() -> i32 { 13 | unsafe { ffi::amice_get_llvm_version_major() } 14 | } 15 | 16 | pub fn get_llvm_version_minor() -> i32 { 17 | unsafe { ffi::amice_get_llvm_version_minor() } 18 | } 19 | 20 | pub fn to_c_str(mut s: &str) -> Cow<'_, CStr> { 21 | if s.is_empty() { 22 | s = "\0"; 23 | } 24 | 25 | // Start from the end of the string as it's the most likely place to find a null byte 26 | if !s.chars().rev().any(|ch| ch == '\0') { 27 | return Cow::from(CString::new(s).expect("unreachable since null bytes are checked")); 28 | } 29 | 30 | unsafe { Cow::from(CStr::from_ptr(s.as_ptr() as *const _)) } 31 | } 32 | 33 | #[cfg(not(any( 34 | feature = "llvm17-0", 35 | feature = "llvm18-1", 36 | feature = "llvm19-1", 37 | feature = "llvm20-1" 38 | )))] 39 | #[macro_export] 40 | macro_rules! ptr_type { 41 | ($cx:ident, $ty:ident) => { 42 | $cx.$ty().ptr_type(AddressSpace::default()) 43 | }; 44 | } 45 | 46 | #[cfg(any( 47 | feature = "llvm17-0", 48 | feature = "llvm18-1", 49 | feature = "llvm19-1", 50 | feature = "llvm20-1" 51 | ))] 52 | #[macro_export] 53 | macro_rules! ptr_type { 54 | ($cx:ident, $ty:ident) => { 55 | $cx.ptr_type(AddressSpace::default()) 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /tests/c/fixtures/indirect_call/indirect_call.c: -------------------------------------------------------------------------------- 1 | // obfuscated_call_demo.c 2 | #include 3 | #include 4 | #include 5 | 6 | int add(int a, int b) { 7 | printf("Called: add(%d, %d)\n", a, b); 8 | return a + b; 9 | } 10 | 11 | int mul(int a, int b) { 12 | printf("Called: mul(%d, %b)\n", a, b); 13 | return a * b; 14 | } 15 | 16 | int sub(int a, int b) { 17 | printf("Called: sub(%d, %d)\n", a, b); 18 | return a - b; 19 | } 20 | 21 | typedef int (*func_ptr)(int, int); 22 | 23 | func_ptr func_table[] = { add, mul, sub }; 24 | #define TABLE_SIZE (sizeof(func_table) / sizeof(func_ptr)) 25 | 26 | int obfuscated_call(int func_id, int a, int b) { 27 | int decoded_id = func_id ^ 0x55; 28 | if (decoded_id >= 0 && decoded_id < TABLE_SIZE) { 29 | volatile int dummy = rand() % 100; 30 | (void)dummy; 31 | return func_table[decoded_id](a, b); 32 | } 33 | fprintf(stderr, "Invalid function ID!\n"); 34 | return -1; 35 | } 36 | 37 | // 主函数测试 38 | int main() { 39 | srand(time(NULL)); 40 | 41 | printf("=== Direct Calls (Clear) ===\n"); 42 | printf("Result: %d\n", add(10, 5)); 43 | printf("Result: %d\n", mul(10, 5)); 44 | printf("Result: %d\n", sub(10, 5)); 45 | 46 | printf("=== Obfuscated Indirect Calls ===\n"); 47 | 48 | printf("Result: %d\n", obfuscated_call(0 ^ 0x55, 20, 8)); // calls add 49 | printf("Result: %d\n", obfuscated_call(1 ^ 0x55, 20, 8)); // calls mul 50 | printf("Result: %d\n", obfuscated_call(2 ^ 0x55, 20, 8)); // calls sub 51 | 52 | return 0; 53 | } -------------------------------------------------------------------------------- /amice-llvm/cpp/verifier.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "llvm/IR/BasicBlock.h" 8 | #include "llvm/Transforms/Utils/ModuleUtils.h" 9 | #include "llvm/IR/Constants.h" 10 | #include "llvm/IR/AbstractCallSite.h" 11 | #include "llvm/IR/InstrTypes.h" 12 | #include "llvm/Transforms/Utils/Local.h" 13 | #include "llvm/Support/Casting.h" 14 | #include "llvm/Pass.h" 15 | #include "llvm/IR/CFG.h" 16 | #include "llvm/IR/IRBuilder.h" 17 | #include "llvm/ADT/SmallSet.h" 18 | #include "llvm/ADT/SmallVector.h" 19 | #include "llvm/Support/raw_ostream.h" 20 | #include "llvm/LinkAllPasses.h" 21 | #include "llvm/Transforms/Utils/Cloning.h" 22 | #include "llvm/IR/Verifier.h" 23 | #include "llvm/IR/Instructions.h" 24 | #include "llvm/ADT/Statistic.h" 25 | #include "llvm/Analysis/LoopInfo.h" 26 | #include "llvm/IR/Dominators.h" 27 | #include "llvm/IR/InstIterator.h" 28 | #include "llvm/Transforms/Scalar.h" 29 | #include "llvm/Transforms/Utils.h" 30 | #include "llvm/Transforms/Utils/BasicBlockUtils.h" 31 | 32 | extern "C" { 33 | 34 | int amice_verify_function(llvm::Function& F, char** errmsg) { 35 | std::string err; 36 | llvm::raw_string_ostream rso(err); 37 | bool broken = llvm::verifyFunction(F, &rso); 38 | rso.flush(); 39 | if (broken) { 40 | size_t n = err.length() + 1; 41 | char* p = (char*)malloc(n); 42 | if (p) memcpy(p, err.c_str(), n); 43 | *errmsg = p; 44 | } 45 | return broken; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /tests/indirect_call.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for indirect call obfuscation. 2 | 3 | mod common; 4 | 5 | use crate::common::Language; 6 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 7 | 8 | fn indirect_call_config() -> ObfuscationConfig { 9 | ObfuscationConfig { 10 | indirect_call: Some(true), 11 | ..ObfuscationConfig::disabled() 12 | } 13 | } 14 | 15 | #[test] 16 | fn test_indirect_call_basic() { 17 | ensure_plugin_built(); 18 | 19 | let result = CppCompileBuilder::new( 20 | fixture_path("indirect_call", "indirect_call.c", Language::C), 21 | "indirect_call_basic", 22 | ) 23 | .config(indirect_call_config()) 24 | .compile(); 25 | 26 | result.assert_success(); 27 | let run = result.run(); 28 | run.assert_success(); 29 | 30 | let lines = run.stdout_lines(); 31 | 32 | // Verify direct calls work 33 | assert!(lines.iter().any(|l| l.contains("=== Direct Calls"))); 34 | assert!(lines.iter().any(|l| l.contains("Called: add"))); 35 | assert!(lines.iter().any(|l| l.contains("Called: mul"))); 36 | assert!(lines.iter().any(|l| l.contains("Called: sub"))); 37 | 38 | // Verify obfuscated calls work 39 | assert!(lines.iter().any(|l| l.contains("=== Obfuscated Indirect Calls"))); 40 | } 41 | 42 | #[test] 43 | fn test_indirect_call_optimized() { 44 | ensure_plugin_built(); 45 | 46 | let result = CppCompileBuilder::new( 47 | fixture_path("indirect_call", "indirect_call.c", Language::C), 48 | "indirect_call_o2", 49 | ) 50 | .config(indirect_call_config()) 51 | .optimization("O2") 52 | .compile(); 53 | 54 | result.assert_success(); 55 | let run = result.run(); 56 | run.assert_success(); 57 | } 58 | -------------------------------------------------------------------------------- /src/config/clone_function.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | pub struct CloneFunctionConfig { 12 | pub enable: bool, 13 | } 14 | 15 | impl Default for CloneFunctionConfig { 16 | fn default() -> Self { 17 | Self { enable: false } 18 | } 19 | } 20 | 21 | impl EnvOverlay for CloneFunctionConfig { 22 | fn overlay_env(&mut self) { 23 | if std::env::var("AMICE_CLONE_FUNCTION").is_ok() { 24 | self.enable = bool_var("AMICE_CLONE_FUNCTION", self.enable); 25 | } 26 | } 27 | } 28 | 29 | impl FunctionAnnotationsOverlay for CloneFunctionConfig { 30 | type Config = CloneFunctionConfig; 31 | 32 | fn overlay_annotations<'a>( 33 | &self, 34 | module: &mut Module<'a>, 35 | function: FunctionValue<'a>, 36 | ) -> anyhow::Result { 37 | let mut cfg = self.clone(); 38 | let annotations_expr = module 39 | .read_function_annotate(function) 40 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 41 | .join(" "); 42 | 43 | let mut parser = EloquentConfigParser::new(); 44 | parser 45 | .parse(&annotations_expr) 46 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 47 | 48 | parser.get_bool("clone_function").map(|v| cfg.enable = v); 49 | 50 | Ok(cfg) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/config/param_aggregate.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | pub struct ParamAggregateConfig { 12 | pub enable: bool, 13 | } 14 | 15 | impl Default for ParamAggregateConfig { 16 | fn default() -> Self { 17 | Self { enable: false } 18 | } 19 | } 20 | 21 | impl EnvOverlay for ParamAggregateConfig { 22 | fn overlay_env(&mut self) { 23 | if std::env::var("AMICE_PARAM_AGGREGATE").is_ok() { 24 | self.enable = bool_var("AMICE_PARAM_AGGREGATE", self.enable); 25 | } 26 | } 27 | } 28 | 29 | impl FunctionAnnotationsOverlay for ParamAggregateConfig { 30 | type Config = ParamAggregateConfig; 31 | 32 | fn overlay_annotations<'a>( 33 | &self, 34 | module: &mut Module<'a>, 35 | function: FunctionValue<'a>, 36 | ) -> anyhow::Result { 37 | let mut cfg = self.clone(); 38 | let annotations_expr = module 39 | .read_function_annotate(function) 40 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 41 | .join(" "); 42 | 43 | let mut parser = EloquentConfigParser::new(); 44 | parser 45 | .parse(&annotations_expr) 46 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 47 | 48 | parser.get_bool("param_aggregate").map(|v| cfg.enable = v); 49 | 50 | Ok(cfg) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/c/fixtures/integration/ama.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct Inner { 5 | int16_t x; // 0 6 | uint8_t y; // 2 7 | uint8_t pad; // 3 (padding, but may be optimized away in IR) 8 | int32_t z; // 4 (due to alignment) 9 | }; 10 | 11 | struct Outer { 12 | int a; // 0 13 | struct Inner in; // 4 ... 11 14 | int arr[5]; // 12 ... 31 15 | unsigned char bytes[16]; // 32 ... 47 16 | }; 17 | 18 | struct S2 { 19 | int m[2][3]; // row-major: m[1][2] at offset 4 * (1*3 + 2) = 20 20 | }; 21 | 22 | typedef struct { int a; int b; } POD; 23 | 24 | // 1) 典型:多个不同字段/数组常量访问 25 | int sum_outer(struct Outer *o) { 26 | // 这些都是常量偏移:o->a、o->in.z、o->arr[3]、o->bytes[5] 27 | return o->a + o->in.z + o->arr[3] + o->bytes[5]; 28 | } 29 | 30 | // 2) 嵌套二维数组常量访问 31 | int get_s2(struct S2 *s) { 32 | return s->m[1][2]; // 常量偏移 33 | } 34 | 35 | // 3) 指针算术:常量步进(等价于常量偏移) 36 | int ptr_arith(struct Outer *o) { 37 | int *p = &o->arr[0]; 38 | return *(p + 4); // o->arr[4] 39 | } 40 | 41 | // 4) POD 结构体字段 42 | int pod_use(POD *p) { 43 | return p->b; // 常量偏移 44 | } 45 | 46 | // 5) 不应改写:非常量下标 47 | int nonconst_index(struct Outer *o, int i) { 48 | return o->arr[i]; // 索引非常量,应保持原样(我们的 pass 不改) 49 | } 50 | 51 | int main() { 52 | struct Outer o = {0}; 53 | o.a = 10; 54 | o.in.z = 7; 55 | o.arr[3] = 4; 56 | o.bytes[5] = 1; 57 | 58 | struct S2 s = { .m = {{1,2,3},{4,5,6}} }; 59 | POD pod = { .a = 11, .b = 22 }; 60 | 61 | int r = 0; 62 | r += sum_outer(&o); 63 | r += get_s2(&s); 64 | r += ptr_arith(&o); 65 | r += pod_use(&pod); 66 | r += nonconst_index(&o, 2); 67 | 68 | printf("%d\n", r); 69 | return 0; 70 | } 71 | // 72 | //Running test1... 73 | //50 74 | -------------------------------------------------------------------------------- /amice-llvm/src/code_extractor.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | use crate::inkwell2::LLVMValueRefExt; 3 | use inkwell::basic_block::BasicBlock; 4 | use inkwell::llvm_sys::prelude::{LLVMBasicBlockRef, LLVMValueRef}; 5 | use inkwell::values::{AsValueRef, FunctionValue}; 6 | use std::ptr; 7 | 8 | #[repr(C)] 9 | pub struct LLVMCodeExtractor { 10 | _private: [u8; 0], 11 | } 12 | 13 | pub type LLVMCodeExtractorRef = *mut LLVMCodeExtractor; 14 | 15 | pub struct CodeExtractor { 16 | ptr: LLVMCodeExtractorRef, 17 | } 18 | 19 | impl CodeExtractor { 20 | pub fn new(basic_blocks: &[BasicBlock]) -> Option { 21 | let mut basic_block_refs = vec![ptr::null(); basic_blocks.len()]; 22 | for (i, bb) in basic_blocks.iter().enumerate() { 23 | basic_block_refs[i] = bb.as_mut_ptr(); 24 | } 25 | let basic_block_refs = basic_block_refs.as_ptr() as *mut LLVMBasicBlockRef; 26 | let ptr = unsafe { ffi::amice_create_code_extractor(basic_block_refs, basic_blocks.len() as i32) }; 27 | if ptr.is_null() { 28 | None 29 | } else { 30 | Some(CodeExtractor { ptr }) 31 | } 32 | } 33 | 34 | pub fn is_eligible(&self) -> bool { 35 | unsafe { ffi::amice_code_extractor_is_eligible(self.ptr) } 36 | } 37 | 38 | pub fn extract_code_region<'a>(&self, function: FunctionValue<'a>) -> Option> { 39 | let generated_func = 40 | unsafe { ffi::amice_code_extractor_extract_code_region(self.ptr, function.as_value_ref() as LLVMValueRef) }; 41 | if generated_func.is_null() { 42 | None 43 | } else { 44 | generated_func.into_function_value() 45 | } 46 | } 47 | } 48 | 49 | impl Drop for CodeExtractor { 50 | fn drop(&mut self) { 51 | unsafe { 52 | ffi::amice_delete_code_extractor(self.ptr); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/config/custom_calling_conv.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | pub struct CustomCallingConvConfig { 12 | pub enable: bool, 13 | } 14 | 15 | impl Default for CustomCallingConvConfig { 16 | fn default() -> Self { 17 | Self { enable: true } 18 | } 19 | } 20 | 21 | impl EnvOverlay for CustomCallingConvConfig { 22 | fn overlay_env(&mut self) { 23 | if std::env::var("AMICE_CUSTOM_CALLING_CONV").is_ok() { 24 | self.enable = bool_var("AMICE_CUSTOM_CALLING_CONV", self.enable); 25 | } 26 | } 27 | } 28 | 29 | impl FunctionAnnotationsOverlay for CustomCallingConvConfig { 30 | type Config = CustomCallingConvConfig; 31 | 32 | fn overlay_annotations<'a>( 33 | &self, 34 | module: &mut Module<'a>, 35 | function: FunctionValue<'a>, 36 | ) -> anyhow::Result { 37 | let mut cfg = self.clone(); 38 | let annotations_expr = module 39 | .read_function_annotate(function) 40 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 41 | .join(" "); 42 | 43 | let mut parser = EloquentConfigParser::new(); 44 | parser 45 | .parse(&annotations_expr) 46 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 47 | 48 | parser 49 | .get_bool("custom_calling_conv") 50 | .or_else(|| parser.get_bool("custom_cc")) 51 | .map(|v| cfg.enable = v); 52 | 53 | Ok(cfg) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/c/fixtures/string_encryption/const_strings.c: -------------------------------------------------------------------------------- 1 | // 示例代码来自 NapXIN ! 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void print_bytes(const char *label, const char *data, size_t len) 8 | { 9 | printf("%s (bytes): ", label); 10 | for (size_t i = 0; i < len; ++i) 11 | printf("%02X ", (unsigned char)data[i]); 12 | printf("\n"); 13 | } 14 | 15 | void change(char **b) 16 | { 17 | char *bb = *b; 18 | bb[0] = 'c'; 19 | } 20 | 21 | void pp(char *n) 22 | { 23 | printf("1pu: %s\n", n); 24 | } 25 | 26 | static char *p = NULL; 27 | int main() 28 | { 29 | const char *test1 = "hello\0\0\x39\x05\0\0"; 30 | print_bytes("test1", test1, 5 + 2 + 2); 31 | printf("test1 string: %s\n", test1); 32 | int val = *(int *)(test1 + 7); 33 | printf("test1 int: %d\n", val); 34 | 35 | char test2[] = 36 | { 37 | 'h', 'e', 'l', 'l', 'o', 38 | '\0', '\0', 39 | 0x39, 0x05, 0x00, 0x00 40 | }; 41 | print_bytes("test2", test2, sizeof(test2)); 42 | printf("test2 string: %s\n", test2); 43 | val = *(int *)(test2 + 7); 44 | printf("test2 int: %d\n", val); 45 | 46 | printf("p1: %p\n", p); 47 | char name[] = "World"; 48 | p = name; 49 | char *name2 = "World"; 50 | printf("p2: %p\n", p); 51 | change(&p); 52 | pp(p); 53 | pp(name2); 54 | 55 | char *str = strdup("Hello world1"); 56 | *str = 'X'; 57 | printf("%s\n", str); 58 | free(str); 59 | 60 | char array[] = "Hello world2"; 61 | array[0] = 'X'; 62 | printf("%s\n", array); 63 | 64 | char *mallocStr = (char *)malloc(256); 65 | mallocStr = "Hello world3"; 66 | printf("%s\n", mallocStr); 67 | 68 | char *literal1 = "This is a literal."; 69 | char *literal2 = "This is a literal."; 70 | printf("%s %p\n", literal1, literal1); 71 | printf("%s %p\n", literal2, literal2); 72 | 73 | return 0; 74 | } -------------------------------------------------------------------------------- /src/config/pass_order.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{parse_kv_map, parse_list}; 2 | use crate::pass_registry::EnvOverlay; 3 | use log::warn; 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Default, Debug, Clone, Serialize, Deserialize)] 8 | #[serde(default)] 9 | pub struct PassOrderConfig { 10 | /// Explicit pass execution order; if None, sort by priority 11 | pub order: Option>, 12 | /// Override priorities for specific passes (higher values run first) 13 | pub priority_override: Option>, 14 | } 15 | 16 | impl EnvOverlay for PassOrderConfig { 17 | fn overlay_env(&mut self) { 18 | // 显式顺序:AMICE_PASS_ORDER="StringEncryption,SplitBasicBlock,ShuffleBlocks,IndirectBranch" 19 | if let Ok(v) = std::env::var("AMICE_PASS_ORDER") { 20 | let list = parse_list(&v); 21 | if !list.is_empty() { 22 | self.order = Some(list); 23 | } else { 24 | warn!("AMICE_PASS_ORDER is set but empty after parsing, ignoring"); 25 | } 26 | } 27 | 28 | // 覆盖优先级:AMICE_PASS_PRIORITY_OVERRIDE="StringEncryption=1200,IndirectBranch=500" 29 | if let Ok(v) = std::env::var("AMICE_PASS_PRIORITY_OVERRIDE") { 30 | let map = parse_kv_map(&v); 31 | if map.is_empty() { 32 | warn!("AMICE_PASS_PRIORITY_OVERRIDE is set but empty after parsing, ignoring"); 33 | } else { 34 | if self.priority_override.is_none() { 35 | self.priority_override = Some(HashMap::new()); 36 | } 37 | 38 | // 合并到已有覆盖表(环境变量优先) 39 | let priority_override = self.priority_override.as_mut().unwrap(); 40 | for (k, val) in map { 41 | priority_override.insert(k, val); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/config/vm_flatten.rs: -------------------------------------------------------------------------------- 1 | use super::{EnvOverlay, bool_var}; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::FunctionAnnotationsOverlay; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Debug, Clone, Serialize, Deserialize)] 8 | #[serde(default)] 9 | pub struct VmFlattenConfig { 10 | /// Whether to enable virtual machine based control flow flattening 11 | pub enable: bool, 12 | pub random_none_node_opcode: bool, 13 | } 14 | 15 | impl Default for VmFlattenConfig { 16 | fn default() -> Self { 17 | Self { 18 | enable: false, 19 | random_none_node_opcode: false, 20 | } 21 | } 22 | } 23 | 24 | impl EnvOverlay for VmFlattenConfig { 25 | fn overlay_env(&mut self) { 26 | if std::env::var("AMICE_VM_FLATTEN").is_ok() { 27 | self.enable = bool_var("AMICE_VM_FLATTEN", self.enable); 28 | } 29 | } 30 | } 31 | 32 | impl FunctionAnnotationsOverlay for VmFlattenConfig { 33 | type Config = VmFlattenConfig; 34 | 35 | fn overlay_annotations<'a>( 36 | &self, 37 | module: &mut llvm_plugin::inkwell::module::Module<'a>, 38 | function: llvm_plugin::inkwell::values::FunctionValue<'a>, 39 | ) -> anyhow::Result { 40 | let mut cfg = self.clone(); 41 | let annotations_expr = module 42 | .read_function_annotate(function) 43 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 44 | .join(" "); 45 | 46 | let mut parser = EloquentConfigParser::new(); 47 | parser 48 | .parse(&annotations_expr) 49 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 50 | 51 | parser 52 | .get_bool("vm_flatten") 53 | .or_else(|| parser.get_bool("vmf")) 54 | .map(|v| cfg.enable = v); 55 | 56 | Ok(cfg) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/PassOrder_zh_CN.md: -------------------------------------------------------------------------------- 1 | ### Pass 运行顺序与优先级覆盖能力说明 2 | 3 | 本文档介绍如何通过配置和环境变量控制 Pass 的运行顺序与优先级,以覆盖编译期写死的优先级。 4 | 5 | #### 背景与目标 6 | - 默认的运行顺序仅由编译期注解中的 `priority` 决定(数值越大越先执行)。 7 | - 运行时优先级配置: 8 | - 显式顺序:在配置文件中写出确切顺序,严格按列表运行,**未出现的不运行**。 9 | - 覆盖优先级:不改动源码注解,按名称为单个 **Pass** 提供新的优先级数值。 10 | 11 | > **显式顺序** > **覆盖优先级** >= **编译期注解优先级**,当显式顺序存在的时候,优先级完全失效,严格按照显式顺序运行,不在显式顺序中的不会运行。 12 | > 13 | > 未避免不可预知的问题,每个Pass只会运行一次! 14 | 15 | #### 配置结构 16 | ```rust 17 | #[derive(Default, Debug, Clone, Serialize, Deserialize)] 18 | #[serde(default)] 19 | pub struct PassOrderConfig { 20 | /// 显式运行顺序;若为 None,则按优先级排序 21 | pub order: Option>, 22 | /// 覆盖各 Pass 的优先级(越大越靠前) 23 | pub priority_override: HashMap, 24 | } 25 | ``` 26 | 27 | 环境变量覆盖实现: 28 | ```rust 29 | impl EnvOverlay for PassOrderConfig { 30 | fn overlay_env(&mut self) { 31 | // AMICE_PASS_ORDER="A,B,C" 或 "A;B;C" 32 | // AMICE_PASS_PRIORITY_OVERRIDE="A=1200,B=500" 或 "A=1200;B=500" 33 | } 34 | } 35 | ``` 36 | 37 | #### 生效优先级与规则 38 | 运行顺序采用如下规则: 39 | 1) 如果配置中提供了显式顺序 `pass_order.order`: 40 | - 列表中的 Pass 严格按给定顺序运行。 41 | - 未出现在列表中的 Pass,**不会运行**。 42 | - 若显式顺序与 `priority_override` 同时存在,`显式order`优先,`priority_override`不执行。 43 | 44 | 2) 如果未提供显式顺序,但提供了 `pass_order.priority_override`: 45 | - 按覆盖后的优先级从高到低排序;未覆盖的使用默认优先级。 46 | 47 | 3) 如果都未提供: 48 | - 回退为默认行为:按编译期优先级从高到低排序。 49 | 50 | #### 配置文件示例 51 | TOML 示例:TODO 52 | YAML 示例:TODO 53 | JSON 示例:TODO 54 | 55 | #### 环境变量覆盖 56 | 支持在不改动配置文件的情况下临时覆盖: 57 | - 显式顺序 58 | - AMICE_PASS_ORDER="StringEncryption,SplitBasicBlock,ShuffleBlocks,IndirectBranch,IndirectCall" 59 | - 分隔符可用逗号或分号:"A,B,C" 或 "A;B;C" 60 | - 覆盖优先级 61 | - AMICE_PASS_PRIORITY_OVERRIDE="StringEncryption=1200,IndirectBranch=500" 62 | - 同样支持分号作为分隔符:"A=1200;B=500" 63 | 64 | #### 不同Pass的名称与默认优先级 65 | 因为可能随着更新而改变名称或优先级的数值,wiki这里同步更新不够及时,这里给出一段源代码(如有需要改变运行顺序请自行翻阅源代码): 66 | ```rust 67 | #[amice(priority = 800, name = "IndirectBranch")] 68 | #[derive(Default)] 69 | pub struct IndirectBranch { 70 | enable: bool, 71 | flags: IndirectBranchFlags, 72 | xor_key: Option<[u32; 4]>, 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /src/config/split_basic_block.rs: -------------------------------------------------------------------------------- 1 | use super::{EnvOverlay, bool_var}; 2 | use crate::pass_registry::FunctionAnnotationsOverlay; 3 | use amice_llvm::inkwell2::ModuleExt; 4 | use llvm_plugin::inkwell::module::Module; 5 | use llvm_plugin::inkwell::values::FunctionValue; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Debug, Clone, Serialize, Deserialize)] 9 | #[serde(default)] 10 | pub struct SplitBasicBlockConfig { 11 | /// Whether to enable basic block splitting obfuscation 12 | pub enable: bool, 13 | /// Number of splits to perform on each basic block 14 | pub num: u32, 15 | } 16 | 17 | impl Default for SplitBasicBlockConfig { 18 | fn default() -> Self { 19 | Self { enable: false, num: 3 } 20 | } 21 | } 22 | 23 | impl EnvOverlay for SplitBasicBlockConfig { 24 | fn overlay_env(&mut self) { 25 | if std::env::var("AMICE_SPLIT_BASIC_BLOCK").is_ok() { 26 | self.enable = bool_var("AMICE_SPLIT_BASIC_BLOCK", self.enable); 27 | } 28 | if let Ok(v) = std::env::var("AMICE_SPLIT_BASIC_BLOCK_NUM") { 29 | self.num = v.parse::().unwrap_or(self.num); 30 | } 31 | } 32 | } 33 | 34 | impl FunctionAnnotationsOverlay for SplitBasicBlockConfig { 35 | type Config = Self; 36 | 37 | fn overlay_annotations<'a>( 38 | &self, 39 | module: &mut Module<'a>, 40 | function: FunctionValue<'a>, 41 | ) -> anyhow::Result { 42 | let mut cfg = self.clone(); 43 | let annotations_expr = module 44 | .read_function_annotate(function) 45 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 46 | .join(" "); 47 | 48 | let mut parser = crate::config::eloquent_config::EloquentConfigParser::new(); 49 | parser 50 | .parse(&annotations_expr) 51 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 52 | 53 | parser.get_bool("split_basic_block").map(|v| cfg.enable = v); 54 | parser.get_number::("split_basic_block_num").map(|v| cfg.num = v); 55 | 56 | Ok(cfg) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /amice-llvm/cpp/instructions.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "llvm/IR/BasicBlock.h" 5 | #include "llvm/Transforms/Utils/ModuleUtils.h" 6 | #include "llvm/IR/Constants.h" 7 | #include "llvm/IR/AbstractCallSite.h" 8 | #include "llvm/IR/InstrTypes.h" 9 | #include "llvm/IR/Attributes.h" 10 | #include "llvm/Transforms/Utils/Local.h" 11 | #include "llvm/Support/Casting.h" 12 | #include "llvm/Pass.h" 13 | #include "llvm/IR/CFG.h" 14 | #include "llvm/IR/IRBuilder.h" 15 | #include "llvm/ADT/SmallSet.h" 16 | #include "llvm/ADT/SmallVector.h" 17 | #include "llvm/Support/raw_ostream.h" 18 | #include "llvm/LinkAllPasses.h" 19 | #include "llvm/Transforms/Utils/Cloning.h" 20 | #include "llvm/IR/Verifier.h" 21 | #include "llvm/IR/Instructions.h" 22 | #include "llvm/ADT/Statistic.h" 23 | #include "llvm/Analysis/LoopInfo.h" 24 | #include "llvm/IR/Dominators.h" 25 | #include "llvm/IR/InstIterator.h" 26 | #include "llvm/Transforms/Scalar.h" 27 | #include "llvm/Transforms/Utils.h" 28 | #include "llvm/Transforms/Utils/BasicBlockUtils.h" 29 | #include "llvm/ADT/APInt.h" 30 | #include "llvm/IR/Module.h" 31 | 32 | extern "C" { 33 | 34 | llvm::ConstantInt* amice_switch_find_case_dest(llvm::SwitchInst* S, llvm::BasicBlock* B) { 35 | return S->findCaseDest (B); 36 | } 37 | 38 | bool amice_is_inline_marked_function(llvm::Function &F) { 39 | if (F.hasFnAttribute(llvm::Attribute::AlwaysInline)) { 40 | return true; 41 | } 42 | 43 | if (F.hasFnAttribute(llvm::Attribute::InlineHint)) { 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | bool amice_gep_accumulate_constant_offset(llvm::Instruction *I, llvm::Module *M, uint64_t *OutOffset) { 51 | if (auto *GEP = llvm::dyn_cast(I)) { 52 | const llvm::DataLayout &DL = M->getDataLayout(); 53 | llvm::APInt OffsetAI(DL.getIndexSizeInBits(/*AS=*/0), 0); 54 | bool result = GEP->accumulateConstantOffset(DL, OffsetAI); 55 | uint64_t Offset = OffsetAI.getZExtValue(); 56 | *OutOffset = Offset; 57 | return result; 58 | } 59 | return false; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "amice" 3 | version = "0.1.2" 4 | edition = "2024" 5 | authors = ["fuqiuluo", "ylarod"] 6 | 7 | [lib] 8 | name = "amice" 9 | crate-type = ["cdylib"] 10 | 11 | [features] 12 | default = ["llvm21-1"] 13 | 14 | android-ndk = ["amice-llvm/android-ndk"] 15 | 16 | # On Windows, llvm plugins require linking with either opt.lib, 17 | # or lld.lib. 18 | win-link-opt = ["llvm-plugin/win-link-opt", "amice-llvm/win-link-opt"] 19 | win-link-lld = ["llvm-plugin/win-link-lld", "amice-llvm/win-link-lld"] 20 | 21 | llvm11-0 = ["llvm-plugin/llvm11-0", "amice-llvm/llvm11-0"] 22 | llvm12-0 = ["llvm-plugin/llvm12-0", "amice-llvm/llvm12-0"] 23 | llvm13-0 = ["llvm-plugin/llvm13-0", "amice-llvm/llvm13-0"] 24 | llvm14-0 = ["llvm-plugin/llvm14-0", "amice-llvm/llvm14-0"] 25 | llvm15-0 = ["llvm-plugin/llvm15-0", "amice-llvm/llvm15-0"] 26 | llvm16-0 = ["llvm-plugin/llvm16-0", "amice-llvm/llvm16-0"] 27 | llvm17-0 = ["llvm-plugin/llvm17-0", "amice-llvm/llvm17-0"] 28 | llvm18-1 = ["llvm-plugin/llvm18-1", "amice-llvm/llvm18-1"] 29 | llvm19-1 = ["llvm-plugin/llvm19-1", "amice-llvm/llvm19-1"] 30 | llvm20-1 = ["llvm-plugin/llvm20-1", "amice-llvm/llvm20-1"] 31 | llvm21-1 = ["llvm-plugin/llvm21-1", "amice-llvm/llvm21-1"] 32 | 33 | [dependencies] 34 | amice-llvm = { path = "amice-llvm", default-features = false } 35 | amice-macro = { path = "amice-macro", default-features = false } 36 | llvm-plugin = { version = "0.6", features = ["default"] } 37 | log = "0.4" 38 | env_logger = "0.11.8" 39 | bitflags = { version = "2.10", features = ["serde"] } 40 | anyhow = "1.0.100" 41 | rand = { version = "0.9.2", features = ["default"] } 42 | hex = "0.4.3" 43 | num-traits = "0.2" 44 | lazy_static = "1" 45 | serde = { version = "1", features = ["derive"] } 46 | serde_yaml = "0.9" 47 | toml = "0.9.7" 48 | serde_json = "1" 49 | ctor = "0.6.3" 50 | 51 | [build-dependencies] 52 | cc = "1" 53 | lazy_static = "1" 54 | regex = "1" 55 | semver = "1" 56 | 57 | [dev-dependencies] 58 | serial_test = "3.2" 59 | 60 | [patch.crates-io] 61 | # inkwell = { git = "https://github.com/Ylarod/inkwell" } 62 | llvm-plugin = { git = "https://github.com/fuqiuluo/llvm-plugin-rs", rev = "fda28a6ccc6bf0f4be5ada3a94c12bade6421e93" } -------------------------------------------------------------------------------- /tests/c/fixtures/inline_asm/inline_asm_basic.c: -------------------------------------------------------------------------------- 1 | // Test inline assembly handling with obfuscation 2 | #define OBFUSCATE __attribute__((annotate("+flatten,+bcf"))) 3 | 4 | #if defined(__x86_64__) || defined(__i386__) 5 | 6 | // Function with inline assembly - should be skipped by obfuscation 7 | OBFUSCATE 8 | int function_with_inline_asm(int x, int y) { 9 | int result; 10 | 11 | #ifdef __x86_64__ 12 | __asm__ volatile ( 13 | "movl %1, %%eax\n\t" 14 | "addl %2, %%eax\n\t" 15 | "movl %%eax, %0\n\t" 16 | : "=r" (result) 17 | : "r" (x), "r" (y) 18 | : "%eax" 19 | ); 20 | #else 21 | result = x + y; // Fallback 22 | #endif 23 | 24 | return result; 25 | } 26 | 27 | // Function with inline asm and control flow 28 | OBFUSCATE 29 | int inline_asm_with_branches(int x) { 30 | int result; 31 | 32 | if (x > 0) { 33 | #ifdef __x86_64__ 34 | __asm__ volatile ( 35 | "movl %1, %%eax\n\t" 36 | "imull $2, %%eax\n\t" 37 | "movl %%eax, %0\n\t" 38 | : "=r" (result) 39 | : "r" (x) 40 | : "%eax" 41 | ); 42 | #else 43 | result = x * 2; 44 | #endif 45 | } else { 46 | result = 0; 47 | } 48 | 49 | return result; 50 | } 51 | 52 | #else 53 | 54 | // Non-x86 fallback 55 | int function_with_inline_asm(int x, int y) { 56 | return x + y; 57 | } 58 | 59 | int inline_asm_with_branches(int x) { 60 | return (x > 0) ? (x * 2) : 0; 61 | } 62 | 63 | #endif 64 | 65 | // Function without inline asm for comparison 66 | OBFUSCATE 67 | int normal_function(int x, int y) { 68 | if (x > y) { 69 | return x * 2; 70 | } else { 71 | return y * 2; 72 | } 73 | } 74 | 75 | int main() { 76 | int r1 = function_with_inline_asm(10, 20); // Should return 30 77 | int r2 = inline_asm_with_branches(5); // Should return 10 78 | int r3 = inline_asm_with_branches(-3); // Should return 0 79 | int r4 = normal_function(10, 5); // Should return 20 80 | 81 | if (r1 == 30 && r2 == 10 && r3 == 0 && r4 == 20) { 82 | return 0; 83 | } 84 | return 1; 85 | } 86 | -------------------------------------------------------------------------------- /src/config/indirect_call.rs: -------------------------------------------------------------------------------- 1 | use super::{EnvOverlay, bool_var}; 2 | use crate::pass_registry::FunctionAnnotationsOverlay; 3 | use amice_llvm::inkwell2::ModuleExt; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | #[serde(default)] 8 | pub struct IndirectCallConfig { 9 | /// Whether to enable indirect call obfuscation 10 | pub enable: bool, 11 | /// Optional XOR key for encrypting function pointers (None for random key) 12 | pub xor_key: Option, 13 | } 14 | 15 | impl Default for IndirectCallConfig { 16 | fn default() -> Self { 17 | Self { 18 | enable: false, 19 | xor_key: None, 20 | } 21 | } 22 | } 23 | 24 | impl EnvOverlay for IndirectCallConfig { 25 | fn overlay_env(&mut self) { 26 | if std::env::var("AMICE_INDIRECT_CALL").is_ok() { 27 | self.enable = bool_var("AMICE_INDIRECT_CALL", self.enable); 28 | } 29 | if let Ok(v) = std::env::var("AMICE_INDIRECT_CALL_XOR_KEY") { 30 | self.xor_key = v.parse::().ok(); 31 | } 32 | } 33 | } 34 | 35 | impl FunctionAnnotationsOverlay for IndirectCallConfig { 36 | type Config = Self; 37 | 38 | fn overlay_annotations<'a>( 39 | &self, 40 | module: &mut llvm_plugin::inkwell::module::Module<'a>, 41 | function: llvm_plugin::inkwell::values::FunctionValue<'a>, 42 | ) -> anyhow::Result { 43 | let mut cfg = self.clone(); 44 | let annotations_expr = module 45 | .read_function_annotate(function) 46 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 47 | .join(" "); 48 | 49 | let mut parser = crate::config::eloquent_config::EloquentConfigParser::new(); 50 | parser 51 | .parse(&annotations_expr) 52 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 53 | 54 | parser 55 | .get_bool("indirect_call") 56 | .or_else(|| parser.get_bool("icall")) 57 | .or_else(|| parser.get_bool("indirectcall")) 58 | .map(|v| cfg.enable = v); 59 | 60 | Ok(cfg) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/instruction/gep_inst.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi::amice_gep_accumulate_constant_offset; 2 | use inkwell::llvm_sys::core::LLVMGetNumIndices; 3 | use inkwell::llvm_sys::prelude::LLVMValueRef; 4 | use inkwell::module::Module; 5 | use inkwell::values::{AsValueRef, BasicValueEnum, InstructionValue}; 6 | use std::ops::{Deref, DerefMut}; 7 | 8 | #[derive(Debug, Copy, Clone)] 9 | pub struct GepInst<'ctx> { 10 | inst: InstructionValue<'ctx>, 11 | } 12 | 13 | impl<'ctx> GepInst<'ctx> { 14 | pub fn new(inst: InstructionValue<'ctx>) -> Self { 15 | assert_eq!(inst.get_opcode(), inkwell::values::InstructionOpcode::GetElementPtr); 16 | assert!(!inst.as_value_ref().is_null()); 17 | Self { inst } 18 | } 19 | 20 | pub fn get_num_indices(&self) -> u32 { 21 | unsafe { LLVMGetNumIndices(self.as_value_ref() as LLVMValueRef) } 22 | } 23 | 24 | pub fn get_indices(&self) -> Vec>> { 25 | (0..self.get_num_indices()) 26 | .map(|i| self.get_operand(i + 1).unwrap().value()) 27 | .collect() 28 | } 29 | 30 | pub fn accumulate_constant_offset(&self, module: &mut Module) -> Option { 31 | let mut offset = 0u64; 32 | if unsafe { 33 | amice_gep_accumulate_constant_offset( 34 | self.as_value_ref() as LLVMValueRef, 35 | module.as_mut_ptr() as _, 36 | &mut offset, 37 | ) 38 | } { 39 | return Some(offset); 40 | } 41 | None 42 | } 43 | 44 | pub fn get_pointer_operand(&self) -> Option> { 45 | assert!(self.get_num_operands() > 0); 46 | self.get_operand(0).unwrap().value() 47 | } 48 | } 49 | 50 | impl<'ctx> Deref for GepInst<'ctx> { 51 | type Target = InstructionValue<'ctx>; 52 | 53 | fn deref(&self) -> &Self::Target { 54 | &self.inst 55 | } 56 | } 57 | 58 | impl<'ctx> DerefMut for GepInst<'ctx> { 59 | fn deref_mut(&mut self) -> &mut Self::Target { 60 | &mut self.inst 61 | } 62 | } 63 | 64 | impl<'ctx> From> for GepInst<'ctx> { 65 | fn from(base: InstructionValue<'ctx>) -> Self { 66 | Self::new(base) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/function_wrapper.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for function wrapper obfuscation. 2 | 3 | mod common; 4 | 5 | use crate::common::Language; 6 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 7 | 8 | fn function_wrapper_config() -> ObfuscationConfig { 9 | ObfuscationConfig { 10 | function_wrapper: Some(true), 11 | ..ObfuscationConfig::disabled() 12 | } 13 | } 14 | 15 | #[test] 16 | fn test_function_wrapper_basic() { 17 | ensure_plugin_built(); 18 | 19 | let result = CppCompileBuilder::new( 20 | fixture_path("function_wrapper", "function_wrapper_test.c", Language::C), 21 | "function_wrapper_basic", 22 | ) 23 | .config(function_wrapper_config()) 24 | .compile(); 25 | 26 | result.assert_success(); 27 | let run = result.run(); 28 | run.assert_success(); 29 | 30 | let lines = run.stdout_lines(); 31 | 32 | // Verify expected output 33 | assert!(lines.iter().any(|l| l.contains("Testing function wrapper"))); 34 | assert!(lines.iter().any(|l| l.contains("In add function: 5 + 3"))); 35 | assert!(lines.iter().any(|l| l.contains("Result of add: 8"))); 36 | assert!(lines.iter().any(|l| l.contains("In multiply function: 4 * 7"))); 37 | assert!(lines.iter().any(|l| l.contains("Result of multiply: 28"))); 38 | assert!(lines.iter().any(|l| l.contains("Hello, Function Wrapper!"))); 39 | } 40 | 41 | #[test] 42 | fn test_function_wrapper_optimized() { 43 | ensure_plugin_built(); 44 | 45 | let result = CppCompileBuilder::new( 46 | fixture_path("function_wrapper", "function_wrapper_test.c", Language::C), 47 | "function_wrapper_o2", 48 | ) 49 | .config(function_wrapper_config()) 50 | .optimization("O2") 51 | .compile(); 52 | 53 | result.assert_success(); 54 | let run = result.run(); 55 | run.assert_success(); 56 | } 57 | 58 | #[test] 59 | fn test_clone_function() { 60 | ensure_plugin_built(); 61 | 62 | // clone_function.c tests constant argument specialization 63 | let result = CppCompileBuilder::new( 64 | fixture_path("function_wrapper", "clone_function.c", Language::C), 65 | "clone_function", 66 | ) 67 | .config(ObfuscationConfig::disabled()) 68 | .compile(); 69 | 70 | result.assert_success(); 71 | let run = result.run(); 72 | run.assert_success(); 73 | } 74 | -------------------------------------------------------------------------------- /src/config/lower_switch.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 3 | use amice_llvm::inkwell2::ModuleExt; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | #[serde(default)] 8 | #[derive(Default)] 9 | pub struct LowerSwitchConfig { 10 | /// Whether to enable switch statement lowering to if-else chains 11 | pub enable: bool, 12 | /// Append dummy code to obfuscate the lowered if-else structure 13 | pub append_dummy_code: bool, 14 | } 15 | 16 | impl EnvOverlay for LowerSwitchConfig { 17 | fn overlay_env(&mut self) { 18 | if std::env::var("AMICE_LOWER_SWITCH").is_ok() { 19 | self.enable = bool_var("AMICE_LOWER_SWITCH", self.enable); 20 | } 21 | 22 | if std::env::var("AMICE_LOWER_SWITCH_WITH_DUMMY_CODE").is_ok() { 23 | self.append_dummy_code = bool_var("AMICE_LOWER_SWITCH_WITH_DUMMY_CODE", self.append_dummy_code); 24 | } 25 | } 26 | } 27 | 28 | impl FunctionAnnotationsOverlay for LowerSwitchConfig { 29 | type Config = Self; 30 | 31 | fn overlay_annotations<'a>( 32 | &self, 33 | module: &mut llvm_plugin::inkwell::module::Module<'a>, 34 | function: llvm_plugin::inkwell::values::FunctionValue<'a>, 35 | ) -> anyhow::Result { 36 | let mut cfg = self.clone(); 37 | let annotations_expr = module 38 | .read_function_annotate(function) 39 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 40 | .join(" "); 41 | 42 | let mut parser = crate::config::eloquent_config::EloquentConfigParser::new(); 43 | parser 44 | .parse(&annotations_expr) 45 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 46 | 47 | parser 48 | .get_bool("lower_switch") 49 | .or_else(|| parser.get_bool("lowerswitch")) 50 | .or_else(|| parser.get_bool("switch_to_if")) 51 | .map(|v| cfg.enable = v); 52 | 53 | parser 54 | .get_bool("lower_switch_with_dummy_code") 55 | .or_else(|| parser.get_bool("lowerswitchwithdummycode")) 56 | .or_else(|| parser.get_bool("switch_to_if_with_dummy_code")) 57 | .map(|v| cfg.append_dummy_code = v); 58 | 59 | Ok(cfg) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/c/fixtures/exception_handling/cpp_exception_bcf.cpp: -------------------------------------------------------------------------------- 1 | // Test C++ exception handling with bogus control flow 2 | #include 3 | #include 4 | 5 | #define OBFUSCATE __attribute__((annotate("+bcf"))) 6 | 7 | // Function that throws exception - BCF should skip this 8 | OBFUSCATE 9 | int may_throw(int x) { 10 | if (x < 0) { 11 | throw std::runtime_error("Negative value"); 12 | } 13 | return x * 2; 14 | } 15 | 16 | // Function with try-catch - BCF should skip this 17 | OBFUSCATE 18 | int catch_exception(int x) { 19 | try { 20 | return may_throw(x); 21 | } catch (const std::exception& e) { 22 | printf("Caught: %s\n", e.what()); 23 | return -1; 24 | } 25 | } 26 | 27 | // Function with multiple catch blocks 28 | OBFUSCATE 29 | int multiple_catches(int x) { 30 | try { 31 | if (x == 0) { 32 | throw std::runtime_error("Runtime error"); 33 | } else if (x < 0) { 34 | throw std::invalid_argument("Invalid argument"); 35 | } 36 | return x; 37 | } catch (const std::runtime_error& e) { 38 | return -1; 39 | } catch (const std::invalid_argument& e) { 40 | return -2; 41 | } catch (...) { 42 | return -3; 43 | } 44 | } 45 | 46 | // Function with nested try-catch 47 | OBFUSCATE 48 | int nested_exception(int x) { 49 | try { 50 | try { 51 | if (x < 0) { 52 | throw std::runtime_error("Inner exception"); 53 | } 54 | return x; 55 | } catch (const std::runtime_error& e) { 56 | if (x < -10) { 57 | throw; // Re-throw 58 | } 59 | return 0; 60 | } 61 | } catch (...) { 62 | return -1; 63 | } 64 | } 65 | 66 | int main() { 67 | int r1 = catch_exception(10); // Should return 20 68 | int r2 = catch_exception(-5); // Should return -1 69 | int r3 = multiple_catches(5); // Should return 5 70 | int r4 = multiple_catches(0); // Should return -1 71 | int r5 = multiple_catches(-1); // Should return -2 72 | int r6 = nested_exception(5); // Should return 5 73 | int r7 = nested_exception(-5); // Should return 0 74 | int r8 = nested_exception(-15); // Should return -1 75 | 76 | if (r1 == 20 && r2 == -1 && r3 == 5 && r4 == -1 && 77 | r5 == -2 && r6 == 5 && r7 == 0 && r8 == -1) { 78 | return 0; 79 | } 80 | return 1; 81 | } 82 | -------------------------------------------------------------------------------- /src/config/basic_block_outlining.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | #[derive(Default)] 12 | pub struct BasicBlockOutliningConfig { 13 | pub enable: bool, 14 | pub max_extractor_size: usize, 15 | } 16 | 17 | impl EnvOverlay for BasicBlockOutliningConfig { 18 | fn overlay_env(&mut self) { 19 | if std::env::var("AMICE_BASIC_BLOCK_OUTLINING").is_ok() { 20 | self.enable = bool_var("AMICE_BASIC_BLOCK_OUTLINING", self.enable); 21 | } 22 | 23 | if std::env::var("AMICE_BASIC_BLOCK_OUTLINING_MAX_EXTRACTOR_SIZE").is_ok() { 24 | self.max_extractor_size = usize::from_str_radix( 25 | &std::env::var("AMICE_BASIC_BLOCK_OUTLINING_MAX_EXTRACTOR_SIZE").unwrap(), 26 | 10, 27 | ) 28 | .unwrap(); 29 | } else { 30 | self.max_extractor_size = 16; 31 | } 32 | } 33 | } 34 | 35 | impl FunctionAnnotationsOverlay for BasicBlockOutliningConfig { 36 | type Config = BasicBlockOutliningConfig; 37 | 38 | fn overlay_annotations<'a>( 39 | &self, 40 | module: &mut Module<'a>, 41 | function: FunctionValue<'a>, 42 | ) -> anyhow::Result { 43 | let mut cfg = self.clone(); 44 | let annotations_expr = module 45 | .read_function_annotate(function) 46 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 47 | .join(" "); 48 | 49 | let mut parser = EloquentConfigParser::new(); 50 | parser 51 | .parse(&annotations_expr) 52 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 53 | 54 | parser 55 | .get_bool("basic_block_outlining") 56 | .or_else(|| parser.get_bool("bb2func")) 57 | .map(|v| cfg.enable = v); 58 | 59 | parser 60 | .get_number::("basic_block_outlining_max_extractor_size") 61 | .or_else(|| parser.get_number::("bb2func_max_extractor_size")) 62 | .map(|v| cfg.max_extractor_size = v); 63 | 64 | Ok(cfg) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/config/delay_offset_loading.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | pub struct DelayOffsetLoadingConfig { 12 | pub enable: bool, 13 | pub xor_offset: bool, 14 | } 15 | 16 | impl Default for DelayOffsetLoadingConfig { 17 | fn default() -> Self { 18 | Self { 19 | enable: false, 20 | xor_offset: true, 21 | } 22 | } 23 | } 24 | 25 | impl EnvOverlay for DelayOffsetLoadingConfig { 26 | fn overlay_env(&mut self) { 27 | if std::env::var("AMICE_DELAY_OFFSET_LOADING").is_ok() { 28 | self.enable = bool_var("AMICE_DELAY_OFFSET_LOADING", self.enable); 29 | } 30 | 31 | if std::env::var("AMICE_DELAY_OFFSET_LOADING_XOR_OFFSET").is_ok() { 32 | self.xor_offset = bool_var("AMICE_DELAY_OFFSET_LOADING_XOR_OFFSET", self.xor_offset); 33 | } 34 | } 35 | } 36 | 37 | impl FunctionAnnotationsOverlay for DelayOffsetLoadingConfig { 38 | type Config = DelayOffsetLoadingConfig; 39 | 40 | fn overlay_annotations<'a>( 41 | &self, 42 | module: &mut Module<'a>, 43 | function: FunctionValue<'a>, 44 | ) -> anyhow::Result { 45 | let mut cfg = self.clone(); 46 | let annotations_expr = module 47 | .read_function_annotate(function) 48 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 49 | .join(" "); 50 | 51 | let mut parser = EloquentConfigParser::new(); 52 | parser 53 | .parse(&annotations_expr) 54 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 55 | 56 | parser 57 | .get_bool("delay_offset_loading") 58 | .or_else(|| parser.get_bool("ama")) 59 | .or_else(|| parser.get_bool("delay_offset_loading_xor_offset")) 60 | .map(|v| cfg.enable = v); 61 | 62 | parser 63 | .get_bool("delay_offset_loading_xor_offset") 64 | .or_else(|| parser.get_bool("ama_xor_offset")) 65 | .or_else(|| parser.get_bool("ama_xor")) 66 | .map(|v| cfg.xor_offset = v); 67 | 68 | Ok(cfg) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /amice-macro/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "amice-macro" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "ctor", 10 | "lazy_static", 11 | "proc-macro2", 12 | "quote", 13 | "syn", 14 | ] 15 | 16 | [[package]] 17 | name = "ctor" 18 | version = "0.5.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" 21 | dependencies = [ 22 | "ctor-proc-macro", 23 | "dtor", 24 | ] 25 | 26 | [[package]] 27 | name = "ctor-proc-macro" 28 | version = "0.0.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" 31 | 32 | [[package]] 33 | name = "dtor" 34 | version = "0.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" 37 | dependencies = [ 38 | "dtor-proc-macro", 39 | ] 40 | 41 | [[package]] 42 | name = "dtor-proc-macro" 43 | version = "0.0.6" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" 46 | 47 | [[package]] 48 | name = "lazy_static" 49 | version = "1.5.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 52 | 53 | [[package]] 54 | name = "proc-macro2" 55 | version = "1.0.95" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 58 | dependencies = [ 59 | "unicode-ident", 60 | ] 61 | 62 | [[package]] 63 | name = "quote" 64 | version = "1.0.40" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 67 | dependencies = [ 68 | "proc-macro2", 69 | ] 70 | 71 | [[package]] 72 | name = "syn" 73 | version = "2.0.104" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 76 | dependencies = [ 77 | "proc-macro2", 78 | "quote", 79 | "unicode-ident", 80 | ] 81 | 82 | [[package]] 83 | name = "unicode-ident" 84 | version = "1.0.18" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 87 | -------------------------------------------------------------------------------- /tests/c/fixtures/phi_nodes/phi_split_basic_block.c: -------------------------------------------------------------------------------- 1 | // Test PHI node handling with split_basic_block 2 | #define OBFUSCATE __attribute__((annotate("+split_basic_block"))) 3 | 4 | // Function with PHI nodes from if-else 5 | OBFUSCATE 6 | int phi_from_if_else(int x, int y) { 7 | int result; 8 | if (x > y) { 9 | result = x; 10 | } else { 11 | result = y; 12 | } 13 | return result * 2; 14 | } 15 | 16 | // Function with PHI nodes from loop 17 | OBFUSCATE 18 | int phi_from_loop(int n) { 19 | int sum = 0; 20 | for (int i = 0; i < n; i++) { 21 | sum += i; 22 | } 23 | return sum; 24 | } 25 | 26 | // Function with multiple PHI nodes 27 | OBFUSCATE 28 | int multiple_phi_nodes(int a, int b, int c) { 29 | int x, y; 30 | 31 | if (a > b) { 32 | x = a; 33 | y = b; 34 | } else { 35 | x = b; 36 | y = a; 37 | } 38 | 39 | int result; 40 | if (x > c) { 41 | result = x + y; 42 | } else { 43 | result = y + c; 44 | } 45 | 46 | return result; 47 | } 48 | 49 | // Function with nested control flow and PHI nodes 50 | OBFUSCATE 51 | int nested_phi(int n) { 52 | int result = 0; 53 | 54 | for (int i = 0; i < n; i++) { 55 | int temp; 56 | if (i % 2 == 0) { 57 | temp = i * 2; 58 | } else { 59 | temp = i + 1; 60 | } 61 | result += temp; 62 | } 63 | 64 | return result; 65 | } 66 | 67 | // Function with switch-like PHI pattern 68 | OBFUSCATE 69 | int switch_phi(int x) { 70 | int result; 71 | 72 | if (x == 0) { 73 | result = 1; 74 | } else if (x == 1) { 75 | result = 2; 76 | } else if (x == 2) { 77 | result = 4; 78 | } else { 79 | result = 0; 80 | } 81 | 82 | return result; 83 | } 84 | 85 | int main() { 86 | int r1 = phi_from_if_else(10, 5); // Should return 20 87 | int r2 = phi_from_if_else(3, 8); // Should return 16 88 | int r3 = phi_from_loop(5); // Should return 10 (0+1+2+3+4) 89 | int r4 = multiple_phi_nodes(5, 3, 2); // Should return 8 (5+3) 90 | int r5 = multiple_phi_nodes(2, 8, 10); // Should return 12 91 | int r6 = nested_phi(4); // Should return 10 (0+2+4+4) 92 | int r7 = switch_phi(1); // Should return 2 93 | 94 | if (r1 == 20 && r2 == 16 && r3 == 10 && r4 == 8 && 95 | r5 == 12 && r6 == 10 && r7 == 2) { 96 | return 0; 97 | } 98 | return 1; 99 | } 100 | -------------------------------------------------------------------------------- /tests/mba.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for Mixed Boolean Arithmetic (MBA) obfuscation. 2 | 3 | mod common; 4 | 5 | use crate::common::Language; 6 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 7 | 8 | fn mba_config() -> ObfuscationConfig { 9 | ObfuscationConfig { 10 | mba: Some(true), 11 | ..ObfuscationConfig::disabled() 12 | } 13 | } 14 | 15 | /// Get expected output from non-obfuscated baseline 16 | fn get_baseline_output(test_name: &str) -> String { 17 | let result = CppCompileBuilder::new( 18 | fixture_path("mba", "mba_constants_demo.c", Language::C), 19 | &format!("mba_baseline_{}", test_name), 20 | ) 21 | .without_plugin() 22 | .compile(); 23 | 24 | result.assert_success(); 25 | result.run().stdout() 26 | } 27 | 28 | #[test] 29 | fn test_mba_basic() { 30 | ensure_plugin_built(); 31 | 32 | let baseline = get_baseline_output("basic"); 33 | 34 | let result = CppCompileBuilder::new(fixture_path("mba", "mba_constants_demo.c", Language::C), "mba_basic") 35 | .config(mba_config()) 36 | .compile(); 37 | 38 | result.assert_success(); 39 | let run = result.run(); 40 | run.assert_success(); 41 | 42 | // MBA should not change program output 43 | assert_eq!(run.stdout(), baseline, "MBA obfuscation changed program output"); 44 | } 45 | 46 | #[test] 47 | fn test_mba_optimized() { 48 | ensure_plugin_built(); 49 | 50 | let baseline = get_baseline_output("optimized"); 51 | 52 | let result = CppCompileBuilder::new(fixture_path("mba", "mba_constants_demo.c", Language::C), "mba_o2") 53 | .config(mba_config()) 54 | .optimization("O2") 55 | .compile(); 56 | 57 | result.assert_success(); 58 | let run = result.run(); 59 | run.assert_success(); 60 | 61 | assert_eq!(run.stdout(), baseline, "MBA with O2 changed program output"); 62 | } 63 | 64 | #[test] 65 | fn test_mba_with_bcf() { 66 | ensure_plugin_built(); 67 | 68 | let baseline = get_baseline_output("with_bcf"); 69 | 70 | let config = ObfuscationConfig { 71 | mba: Some(true), 72 | bogus_control_flow: Some(true), 73 | ..ObfuscationConfig::disabled() 74 | }; 75 | 76 | let result = CppCompileBuilder::new(fixture_path("mba", "mba_constants_demo.c", Language::C), "mba_with_bcf") 77 | .config(config) 78 | .compile(); 79 | 80 | result.assert_success(); 81 | let run = result.run(); 82 | run.assert_success(); 83 | 84 | assert_eq!(run.stdout(), baseline, "MBA with BCF changed program output"); 85 | } 86 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/instruction/switch_inst.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | use crate::inkwell2::LLVMValueRefExt; 3 | use inkwell::basic_block::BasicBlock; 4 | use inkwell::llvm_sys::prelude::{LLVMBasicBlockRef, LLVMValueRef}; 5 | use inkwell::values::{AsValueRef, BasicValueEnum, InstructionOpcode, InstructionValue}; 6 | use std::ops::{Deref, DerefMut}; 7 | 8 | #[derive(Debug, Copy, Clone)] 9 | pub struct SwitchInst<'ctx> { 10 | inst: InstructionValue<'ctx>, 11 | } 12 | 13 | impl<'ctx> SwitchInst<'ctx> { 14 | pub fn new(inst: InstructionValue<'ctx>) -> Self { 15 | assert_eq!(inst.get_opcode(), InstructionOpcode::Switch); 16 | Self { inst } 17 | } 18 | 19 | pub fn get_case_num(&self) -> u32 { 20 | self.inst.get_num_operands() / 2 - 1 21 | } 22 | 23 | pub fn get_cases(&self) -> Vec<(BasicValueEnum<'ctx>, BasicBlock<'ctx>)> { 24 | let mut cases = Vec::new(); 25 | for i in (0..self.get_case_num()).step_by(1) { 26 | let case_value = self.inst.get_operand(i * 2 + 2); 27 | let case_block = self.inst.get_operand(i * 2 + 3); 28 | assert!(case_value.is_some()); 29 | assert!(case_block.is_some()); 30 | cases.push(( 31 | case_value.unwrap().value().unwrap(), 32 | case_block.unwrap().block().unwrap(), 33 | )); 34 | } 35 | cases 36 | } 37 | 38 | pub fn get_condition(&self) -> BasicValueEnum<'ctx> { 39 | self.inst.get_operand(0).unwrap().value().unwrap() 40 | } 41 | 42 | pub fn get_default_block(&self) -> BasicBlock<'ctx> { 43 | self.inst.get_operand(1).unwrap().block().unwrap() 44 | } 45 | 46 | pub fn find_case_dest<'a>(&self, basic_block: BasicBlock) -> Option> { 47 | let value_ref = unsafe { 48 | ffi::amice_switch_find_case_dest( 49 | self.inst.as_value_ref() as LLVMValueRef, 50 | basic_block.as_mut_ptr() as LLVMBasicBlockRef, 51 | ) 52 | }; 53 | if value_ref.is_null() { 54 | None 55 | } else { 56 | value_ref.into_basic_value_enum().into() 57 | } 58 | } 59 | } 60 | 61 | impl<'ctx> Deref for SwitchInst<'ctx> { 62 | type Target = InstructionValue<'ctx>; 63 | 64 | fn deref(&self) -> &Self::Target { 65 | &self.inst 66 | } 67 | } 68 | 69 | impl<'ctx> DerefMut for SwitchInst<'ctx> { 70 | fn deref_mut(&mut self) -> &mut Self::Target { 71 | &mut self.inst 72 | } 73 | } 74 | 75 | impl<'ctx> From> for SwitchInst<'ctx> { 76 | fn from(base: InstructionValue<'ctx>) -> Self { 77 | Self::new(base) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/aotu/alias_access/mod.rs: -------------------------------------------------------------------------------- 1 | mod pointer_chain; 2 | 3 | use crate::aotu::alias_access::pointer_chain::PointerChainAlgo; 4 | use crate::config::{AliasAccessConfig, AliasAccessMode, Config}; 5 | use crate::pass_registry::{AmiceFunctionPass, AmicePass, AmicePassFlag, AmicePassMetadata}; 6 | use amice_llvm::inkwell2::{FunctionExt, VerifyResult}; 7 | use amice_macro::amice; 8 | use llvm_plugin::inkwell::module::Module; 9 | use llvm_plugin::inkwell::values::FunctionValue; 10 | use llvm_plugin::{LlvmModulePass, ModuleAnalysisManager, PreservedAnalyses}; 11 | 12 | #[amice( 13 | priority = 1112, 14 | name = "AliasAccess", 15 | flag = AmicePassFlag::PipelineStart | AmicePassFlag::FunctionLevel, 16 | config = AliasAccessConfig, 17 | )] 18 | #[derive(Default)] 19 | pub struct AliasAccess {} 20 | 21 | impl AmicePass for AliasAccess { 22 | fn init(&mut self, cfg: &Config, _flag: AmicePassFlag) { 23 | self.default_config = cfg.alias_access.clone(); 24 | } 25 | 26 | fn do_pass(&self, module: &mut Module<'_>) -> anyhow::Result { 27 | let mut functions = Vec::new(); 28 | for function in module.get_functions() { 29 | if function.is_llvm_function() { 30 | continue; 31 | } 32 | 33 | let cfg = self.parse_function_annotations(module, function)?; 34 | if !cfg.enable { 35 | continue; 36 | } 37 | 38 | functions.push((function, cfg)); 39 | } 40 | 41 | if functions.is_empty() { 42 | return Ok(PreservedAnalyses::All); 43 | } 44 | 45 | for (function, cfg) in functions { 46 | let mut algo: Box = match cfg.mode { 47 | AliasAccessMode::PointerChain => Box::new(PointerChainAlgo::default()), 48 | }; 49 | 50 | if let Err(e) = algo.initialize(&cfg) { 51 | error!("failed to initialize: {}", e); 52 | return Ok(PreservedAnalyses::All); 53 | } 54 | 55 | if let Err(e) = algo.do_alias_access(&cfg, module, function) { 56 | error!("failed to do alias access: {}", e); 57 | return Ok(PreservedAnalyses::All); 58 | } 59 | 60 | if let VerifyResult::Broken(e) = function.verify_function() { 61 | warn!("function {:?} verify failed: {}", function.get_name(), e); 62 | } 63 | } 64 | 65 | Ok(PreservedAnalyses::None) 66 | } 67 | } 68 | 69 | pub(crate) trait AliasAccessAlgo { 70 | fn initialize(&mut self, pass: &AliasAccessConfig) -> anyhow::Result<()>; 71 | 72 | fn do_alias_access( 73 | &mut self, 74 | pass: &AliasAccessConfig, 75 | module: &Module<'_>, 76 | function: FunctionValue, 77 | ) -> anyhow::Result<()>; 78 | } 79 | -------------------------------------------------------------------------------- /tests/indirect_branch.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for indirect branch obfuscation. 2 | 3 | mod common; 4 | 5 | use crate::common::Language; 6 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 7 | 8 | fn indirect_branch_config() -> ObfuscationConfig { 9 | ObfuscationConfig { 10 | indirect_branch: Some(true), 11 | ..ObfuscationConfig::disabled() 12 | } 13 | } 14 | 15 | fn indirect_branch_config_chained() -> ObfuscationConfig { 16 | ObfuscationConfig { 17 | indirect_branch: Some(true), 18 | indirect_branch_flags: Some("chained_dummy_block".to_string()), 19 | ..ObfuscationConfig::disabled() 20 | } 21 | } 22 | 23 | #[test] 24 | fn test_indirect_branch_basic() { 25 | ensure_plugin_built(); 26 | 27 | let result = CppCompileBuilder::new( 28 | fixture_path("indirect_branch", "indirect_branch.c", Language::C), 29 | "indirect_branch_basic", 30 | ) 31 | .config(indirect_branch_config()) 32 | .compile(); 33 | 34 | result.assert_success(); 35 | let run = result.run(); 36 | run.assert_success(); 37 | 38 | let lines = run.stdout_lines(); 39 | assert_eq!(lines[0], "Running control flow test suite..."); 40 | assert_eq!(lines[1], "All tests completed. sink = 1"); 41 | } 42 | 43 | #[test] 44 | fn test_indirect_branch_chained() { 45 | ensure_plugin_built(); 46 | 47 | let result = CppCompileBuilder::new( 48 | fixture_path("indirect_branch", "indirect_branch.c", Language::C), 49 | "indirect_branch_chained", 50 | ) 51 | .config(indirect_branch_config_chained()) 52 | .compile(); 53 | 54 | result.assert_success(); 55 | let run = result.run(); 56 | run.assert_success(); 57 | 58 | let lines = run.stdout_lines(); 59 | assert_eq!(lines[0], "Running control flow test suite..."); 60 | assert_eq!(lines[1], "All tests completed. sink = 1"); 61 | } 62 | 63 | #[test] 64 | fn test_indirect_branch_with_string_encryption() { 65 | ensure_plugin_built(); 66 | 67 | let config = ObfuscationConfig { 68 | string_encryption: Some(true), 69 | string_algorithm: Some("xor".to_string()), 70 | string_decrypt_timing: Some("lazy".to_string()), 71 | string_stack_alloc: Some(true), 72 | indirect_branch: Some(true), 73 | ..ObfuscationConfig::disabled() 74 | }; 75 | 76 | let result = CppCompileBuilder::new( 77 | fixture_path("indirect_branch", "indirect_branch.c", Language::C), 78 | "indirect_branch_with_strings", 79 | ) 80 | .config(config) 81 | .compile(); 82 | 83 | result.assert_success(); 84 | let run = result.run(); 85 | run.assert_success(); 86 | 87 | let lines = run.stdout_lines(); 88 | assert_eq!(lines[0], "Running control flow test suite..."); 89 | assert_eq!(lines[1], "All tests completed. sink = 1"); 90 | } 91 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod aotu; 2 | pub(crate) mod config; 3 | pub(crate) mod pass_registry; 4 | 5 | use crate::config::CONFIG; 6 | use crate::pass_registry::AmicePassFlag; 7 | use llvm_plugin::PipelineParsing; 8 | use log::{error, info, warn}; 9 | 10 | #[llvm_plugin::plugin(name = "amice", version = "0.1.2")] 11 | fn plugin_registrar(builder: &mut llvm_plugin::PassBuilder) { 12 | if let Err(e) = env_logger::builder().try_init() { 13 | warn!("amice init logger failed: {e:?}"); 14 | return; 15 | } 16 | 17 | info!( 18 | "amice plugin initializing, version: {}, author: {}, llvm-sys: {}.{}", 19 | env!("CARGO_PKG_VERSION"), 20 | env!("CARGO_PKG_AUTHORS"), 21 | amice_llvm::get_llvm_version_major(), 22 | amice_llvm::get_llvm_version_minor() 23 | ); 24 | 25 | builder.add_module_pipeline_parsing_callback(|name, _manager| { 26 | error!("amice plugin module pipeline parsing callback, name: {}", name); 27 | 28 | PipelineParsing::NotParsed 29 | }); 30 | 31 | builder.add_pipeline_start_ep_callback(|manager, opt| { 32 | info!("amice plugin pipeline start callback, level: {opt:?}"); 33 | 34 | let cfg = &*CONFIG; 35 | pass_registry::install_all(cfg, manager, AmicePassFlag::PipelineStart); 36 | }); 37 | 38 | #[cfg(any( 39 | doc, 40 | feature = "llvm11-0", 41 | feature = "llvm12-0", 42 | feature = "llvm13-0", 43 | feature = "llvm14-0", 44 | feature = "llvm15-0", 45 | feature = "llvm16-0", 46 | feature = "llvm17-0", 47 | feature = "llvm18-1", 48 | feature = "llvm19-1", 49 | ))] 50 | builder.add_optimizer_last_ep_callback(|manager, opt| { 51 | info!("amice plugin optimizer last callback, level: {opt:?}"); 52 | let cfg = &*CONFIG; 53 | pass_registry::install_all(cfg, manager, AmicePassFlag::OptimizerLast); 54 | }); 55 | 56 | #[cfg(any(doc, feature = "llvm20-1"))] 57 | builder.add_optimizer_last_ep_callback(|manager, opt, phase| { 58 | info!("amice plugin optimizer last callback, level: {opt:?}, phase: {phase:?}"); 59 | let cfg = &*CONFIG; 60 | pass_registry::install_all(cfg, manager, AmicePassFlag::OptimizerLast); 61 | }); 62 | 63 | #[cfg(any( 64 | doc, 65 | feature = "llvm15-0", 66 | feature = "llvm16-0", 67 | feature = "llvm17-0", 68 | feature = "llvm18-1", 69 | feature = "llvm19-1", 70 | feature = "llvm20-1", 71 | feature = "llvm21-1", 72 | ))] 73 | builder.add_full_lto_last_ep_callback(|manager, opt| { 74 | info!("amice plugin full lto last callback, level: {opt:?}"); 75 | let cfg = &*CONFIG; 76 | pass_registry::install_all(cfg, manager, AmicePassFlag::FullLtoLast); 77 | }); 78 | 79 | info!("amice plugin registered"); 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/windwos-x64-link-lld-build.yml: -------------------------------------------------------------------------------- 1 | name: Windows X64 Build (lld-link) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | linux-build: 11 | name: "LLVM/OPT ${{ matrix.llvm-version[0] }} Windows" 12 | runs-on: windows-latest 13 | env: 14 | LLVM_INSTALL_PATH: C:\LLVM 15 | BUILD_OUTPUT_PATH: target\release 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | llvm-version: 20 | # - ["12", "12-0", "v12.0.1-rust-1.55/llvm-lld-12.0.1-rust-1.55-windows-x86_64.7z"] 21 | # - ["13", "13-0", "v13.0.0-rust-1.59/llvm-lld-13.0.0-rust-1.59-windows-x86_64.7z"] 22 | # - ["14", "14-0", "v14.0.6-rust-1.64/llvm-lld-14.0.6-rust-1.64-windows-x86_64.7z"] 23 | # - ["15", "15-0", "v15.0.0-rust-1.65/llvm-lld-15.0.0-rust-1.65-windows-x86_64.7z"] 24 | # - ["16", "16-0", "v16.0.2-rust-1.71/llvm-lld-16.0.2-rust-1.71-windows-x86_64.7z"] 25 | - ["17", "17-0", "v17.0.6-rust-1.75/llvm-lld-17.0.6-rust-1.75-windows-x86_64.7z"] 26 | - ["18", "18-1", "v18.1.2-rust-1.78/llvm-lld-18.1.2-rust-1.78-windows-x86_64.7z"] 27 | - ["19", "19-1", "v19.1.5-rust-1.84/llvm-lld-19.1.5-rust-1.84-windows-x86_64.7z"] 28 | - ["20", "20-1", "v20.1.1-rust-1.87/llvm-lld-20.1.1-rust-1.87-windows-x86_64.7z"] 29 | 30 | steps: 31 | - name: Checkout Repo 32 | uses: actions/checkout@v2 33 | 34 | - name: Setup LLVM Installation Path 35 | run: | 36 | mkdir ${{ env.LLVM_INSTALL_PATH }} 37 | echo ${{ env.LLVM_INSTALL_PATH }}\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 38 | 39 | - name: Check LLVM Artifacts In Cache 40 | id: cache-llvm 41 | uses: actions/cache@v3 42 | with: 43 | path: | 44 | ${{ env.LLVM_INSTALL_PATH }}\bin\llvm-config.exe 45 | ${{ env.LLVM_INSTALL_PATH }}\bin\opt.exe 46 | ${{ env.LLVM_INSTALL_PATH }}\bin\LLVM-C.dll 47 | ${{ env.LLVM_INSTALL_PATH }}\lib\opt.lib 48 | ${{ env.LLVM_INSTALL_PATH }}\lib\LLVM-C.lib 49 | ${{ env.LLVM_INSTALL_PATH }}\include 50 | key: ${{ runner.os }}-llvm-${{ matrix.llvm-version[0] }}-opt 51 | 52 | - name: Download LLVM Binaries 53 | if: steps.cache-llvm.outputs.cache-hit != 'true' 54 | run: | 55 | Invoke-WebRequest -UserAgent 'GithubCI' -outfile llvm.7z https://github.com/jamesmth/llvm-project/releases/download/${{ matrix.llvm-version[2] }} 56 | 7z x -oC: llvm.7z 57 | 58 | - name: Build amice.dll (Release) 59 | run: | 60 | cargo b --release --no-default-features --features llvm${{ matrix.llvm-version[1] }},win-link-lld 61 | 62 | - name: Upload amice.dll 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: libamice-llvm${{ matrix.llvm-version[0] }}-windows-link-lld 66 | path: ${{ env.BUILD_OUTPUT_PATH }}/amice.dll 67 | retention-days: 30 68 | -------------------------------------------------------------------------------- /.github/workflows/windwos-x64-link-opt-build.yml: -------------------------------------------------------------------------------- 1 | name: Windows X64 Build (opt-link) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | linux-build: 11 | name: "LLVM/OPT ${{ matrix.llvm-version[0] }} Windows" 12 | runs-on: windows-latest 13 | env: 14 | LLVM_INSTALL_PATH: C:\LLVM 15 | BUILD_OUTPUT_PATH: target\release 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | llvm-version: 20 | # - ["12", "12-0", "v12.0.1-rust-1.55/llvm-lld-12.0.1-rust-1.55-windows-x86_64.7z"] 21 | # - ["13", "13-0", "v13.0.0-rust-1.59/llvm-lld-13.0.0-rust-1.59-windows-x86_64.7z"] 22 | # - ["14", "14-0", "v14.0.6-rust-1.64/llvm-lld-14.0.6-rust-1.64-windows-x86_64.7z"] 23 | # - ["15", "15-0", "v15.0.0-rust-1.65/llvm-lld-15.0.0-rust-1.65-windows-x86_64.7z"] 24 | # - ["16", "16-0", "v16.0.2-rust-1.71/llvm-lld-16.0.2-rust-1.71-windows-x86_64.7z"] 25 | - ["17", "17-0", "v17.0.6-rust-1.75/llvm-lld-17.0.6-rust-1.75-windows-x86_64.7z"] 26 | - ["18", "18-1", "v18.1.2-rust-1.78/llvm-lld-18.1.2-rust-1.78-windows-x86_64.7z"] 27 | - ["19", "19-1", "v19.1.5-rust-1.84/llvm-lld-19.1.5-rust-1.84-windows-x86_64.7z"] 28 | - ["20", "20-1", "v20.1.1-rust-1.87/llvm-lld-20.1.1-rust-1.87-windows-x86_64.7z"] 29 | 30 | steps: 31 | - name: Checkout Repo 32 | uses: actions/checkout@v2 33 | 34 | - name: Setup LLVM Installation Path 35 | run: | 36 | mkdir ${{ env.LLVM_INSTALL_PATH }} 37 | echo ${{ env.LLVM_INSTALL_PATH }}\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 38 | 39 | - name: Check LLVM Artifacts In Cache 40 | id: cache-llvm 41 | uses: actions/cache@v3 42 | with: 43 | path: | 44 | ${{ env.LLVM_INSTALL_PATH }}\bin\llvm-config.exe 45 | ${{ env.LLVM_INSTALL_PATH }}\bin\opt.exe 46 | ${{ env.LLVM_INSTALL_PATH }}\bin\LLVM-C.dll 47 | ${{ env.LLVM_INSTALL_PATH }}\lib\opt.lib 48 | ${{ env.LLVM_INSTALL_PATH }}\lib\LLVM-C.lib 49 | ${{ env.LLVM_INSTALL_PATH }}\include 50 | key: ${{ runner.os }}-llvm-${{ matrix.llvm-version[0] }}-opt 51 | 52 | - name: Download LLVM Binaries 53 | if: steps.cache-llvm.outputs.cache-hit != 'true' 54 | run: | 55 | Invoke-WebRequest -UserAgent 'GithubCI' -outfile llvm.7z https://github.com/jamesmth/llvm-project/releases/download/${{ matrix.llvm-version[2] }} 56 | 7z x -oC: llvm.7z 57 | 58 | - name: Build amice.dll (Release) 59 | run: | 60 | cargo b --release --no-default-features --features llvm${{ matrix.llvm-version[1] }},win-link-opt 61 | 62 | - name: Upload amice.dll 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: libamice-llvm${{ matrix.llvm-version[0] }}-windows-link-opt 66 | path: ${{ env.BUILD_OUTPUT_PATH }}/amice.dll 67 | retention-days: 30 68 | -------------------------------------------------------------------------------- /src/aotu/bogus_control_flow/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic; 2 | mod polaris_primes; 3 | 4 | use crate::aotu::bogus_control_flow::basic::BogusControlFlowBasic; 5 | use crate::aotu::bogus_control_flow::polaris_primes::BogusControlFlowPolarisPrimes; 6 | use crate::config::{BogusControlFlowConfig, BogusControlFlowMode, Config}; 7 | use crate::pass_registry::{AmiceFunctionPass, AmicePass, AmicePassFlag}; 8 | use amice_llvm::inkwell2::{FunctionExt, VerifyResult}; 9 | use amice_macro::amice; 10 | use llvm_plugin::PreservedAnalyses; 11 | use llvm_plugin::inkwell::module::Module; 12 | use llvm_plugin::inkwell::values::FunctionValue; 13 | 14 | #[amice( 15 | priority = 950, 16 | name = "BogusControlFlow", 17 | flag = AmicePassFlag::PipelineStart | AmicePassFlag::FunctionLevel, 18 | config = BogusControlFlowConfig, 19 | )] 20 | #[derive(Default)] 21 | pub struct BogusControlFlow {} 22 | 23 | impl AmicePass for BogusControlFlow { 24 | fn init(&mut self, cfg: &Config, _flag: AmicePassFlag) { 25 | self.default_config = cfg.bogus_control_flow.clone(); 26 | } 27 | 28 | fn do_pass(&self, module: &mut Module<'_>) -> anyhow::Result { 29 | let mut executed = false; 30 | for function in module.get_functions() { 31 | if function.is_inline_marked() || function.is_llvm_function() || function.is_undef_function() { 32 | continue; 33 | } 34 | 35 | let cfg = self.parse_function_annotations(module, function)?; 36 | 37 | if !cfg.enable { 38 | continue; 39 | } 40 | 41 | let mut algo: Box = match cfg.mode { 42 | BogusControlFlowMode::Basic => Box::new(BogusControlFlowBasic::default()), 43 | BogusControlFlowMode::PolarisPrimes => Box::new(BogusControlFlowPolarisPrimes::default()), 44 | }; 45 | 46 | if let Err(err) = algo.initialize(&cfg, module) { 47 | error!("initialize failed: {}", err); 48 | continue; 49 | } 50 | 51 | if let Err(err) = algo.apply_bogus_control_flow(&cfg, module, function) { 52 | error!("apply failed: {}", err); 53 | continue; 54 | } 55 | 56 | executed = true; 57 | if let VerifyResult::Broken(msg) = function.verify_function() { 58 | error!("function {:?} is broken: {}", function.get_name(), msg); 59 | } 60 | } 61 | 62 | if !executed { 63 | return Ok(PreservedAnalyses::All); 64 | } 65 | 66 | Ok(PreservedAnalyses::None) 67 | } 68 | } 69 | 70 | pub(super) trait BogusControlFlowAlgo { 71 | fn initialize(&mut self, cfg: &BogusControlFlowConfig, module: &mut Module<'_>) -> anyhow::Result<()>; 72 | 73 | fn apply_bogus_control_flow( 74 | &mut self, 75 | cfg: &BogusControlFlowConfig, 76 | module: &mut Module<'_>, 77 | function: FunctionValue, 78 | ) -> anyhow::Result<()>; 79 | } 80 | -------------------------------------------------------------------------------- /src/config/function_wrapper.rs: -------------------------------------------------------------------------------- 1 | use super::{EnvOverlay, bool_var}; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::FunctionAnnotationsOverlay; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | pub struct FunctionWrapperConfig { 12 | /// Whether to enable function wrapper obfuscation 13 | pub enable: bool, 14 | /// Probability percentage for each call site to be obfuscated (0-100) 15 | pub probability: u32, 16 | /// Number of times to apply the wrapper transformation per call site 17 | pub times: u32, 18 | } 19 | 20 | impl Default for FunctionWrapperConfig { 21 | fn default() -> Self { 22 | Self { 23 | enable: false, 24 | probability: 70, 25 | times: 3, 26 | } 27 | } 28 | } 29 | 30 | impl EnvOverlay for FunctionWrapperConfig { 31 | fn overlay_env(&mut self) { 32 | if std::env::var("AMICE_FUNCTION_WRAPPER").is_ok() { 33 | self.enable = bool_var("AMICE_FUNCTION_WRAPPER", self.enable); 34 | } 35 | if let Ok(v) = std::env::var("AMICE_FUNCTION_WRAPPER_PROBABILITY") { 36 | if let Ok(prob) = v.parse::() { 37 | self.probability = prob.min(100); 38 | } 39 | } 40 | if let Ok(v) = std::env::var("AMICE_FUNCTION_WRAPPER_TIMES") { 41 | if let Ok(times) = v.parse::() { 42 | self.times = times.max(1); 43 | } 44 | } 45 | } 46 | } 47 | 48 | impl FunctionAnnotationsOverlay for FunctionWrapperConfig { 49 | type Config = Self; 50 | 51 | fn overlay_annotations<'a>( 52 | &self, 53 | module: &mut Module<'a>, 54 | function: FunctionValue<'a>, 55 | ) -> anyhow::Result { 56 | let mut cfg = self.clone(); 57 | let annotations_expr = module 58 | .read_function_annotate(function) 59 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 60 | .join(" "); 61 | 62 | let mut parser = EloquentConfigParser::new(); 63 | parser 64 | .parse(&annotations_expr) 65 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 66 | 67 | parser 68 | .get_bool("function_wrapper") 69 | .or_else(|| parser.get_bool("func_wrapper")) 70 | .map(|v| cfg.enable = v); 71 | 72 | parser 73 | .get_number::("function_wrapper_probability") 74 | .or_else(|| parser.get_number::("func_wrapper_probability")) 75 | .or_else(|| parser.get_number::("function_wrapper_prob")) 76 | .or_else(|| parser.get_number::("func_wrapper_prob")) 77 | .map(|v| cfg.probability = v.min(100)); 78 | 79 | Ok(cfg) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/LLVMSetup.md: -------------------------------------------------------------------------------- 1 | # LLVM Setup Guide 2 | 3 | ## Environment Variable Naming 4 | 5 | The environment variable format is `LLVM_SYS__PREFIX`: 6 | 7 | | LLVM Version | Environment Variable | 8 | |--------------|-----------------------| 9 | | 21.1 | `LLVM_SYS_211_PREFIX` | 10 | | 20.1 | `LLVM_SYS_201_PREFIX` | 11 | | 19.1 | `LLVM_SYS_191_PREFIX` | 12 | | 18.1 | `LLVM_SYS_181_PREFIX` | 13 | | 17.0 | `LLVM_SYS_170_PREFIX` | 14 | | 16.0 | `LLVM_SYS_160_PREFIX` | 15 | | 15.0 | `LLVM_SYS_150_PREFIX` | 16 | | 14.0 | `LLVM_SYS_140_PREFIX` | 17 | 18 | --- 19 | 20 | ## Linux (Fedora/RHEL/CentOS) 21 | 22 | ### Install 23 | 24 | ```bash 25 | # Search for available versions 26 | dnf search llvm 27 | 28 | # Install latest stable 29 | sudo dnf install llvm llvm-devel clang clang-devel 30 | 31 | # Or install specific version (e.g., LLVM 18) 32 | sudo dnf install llvm18 llvm18-devel clang18 clang18-devel 33 | ``` 34 | 35 | ### Verify 36 | 37 | ```bash 38 | which llvm-config 39 | llvm-config --version 40 | llvm-config --prefix 41 | ``` 42 | 43 | ### Set Environment Variable 44 | 45 | ```bash 46 | export LLVM_SYS_181_PREFIX=$(llvm-config --prefix) 47 | ``` 48 | 49 | --- 50 | 51 | ## Linux (Ubuntu/Debian) 52 | 53 | ### Install 54 | 55 | ```bash 56 | sudo apt update 57 | sudo apt install llvm llvm-dev clang libclang-dev 58 | 59 | # Or install specific version 60 | sudo apt install llvm-18 llvm-18-dev clang-18 libclang-18-dev 61 | ``` 62 | 63 | ### Verify 64 | 65 | ```bash 66 | llvm-config --version 67 | llvm-config --prefix 68 | ``` 69 | 70 | ### Set Environment Variable 71 | 72 | ```bash 73 | export LLVM_SYS_181_PREFIX=/usr/lib/llvm-18 74 | ``` 75 | 76 | --- 77 | 78 | ## macOS (Homebrew) 79 | 80 | ### Install 81 | 82 | ```bash 83 | # Install latest 84 | brew install llvm 85 | 86 | # Or install specific version 87 | brew install llvm@18 88 | ``` 89 | 90 | ### Set Environment Variable 91 | 92 | ```bash 93 | # Latest version 94 | export LLVM_SYS_181_PREFIX=$(brew --prefix llvm) 95 | 96 | # Specific version 97 | export LLVM_SYS_181_PREFIX=$(brew --prefix llvm@18) 98 | ``` 99 | 100 | ### Add to PATH (optional) 101 | 102 | ```bash 103 | export PATH="$(brew --prefix llvm)/bin:$PATH" 104 | ``` 105 | 106 | --- 107 | 108 | ## Windows 109 | 110 | ### Option 1: Pre-built LLVM (Recommended) 111 | 112 | Download pre-built LLVM from: https://github.com/jamesmth/llvm-project/releases 113 | 114 | ### Option 2: Official LLVM 115 | 116 | Download from: https://releases.llvm.org/ 117 | 118 | ### Set Environment Variable 119 | 120 | ```cmd 121 | setx LLVM_SYS_181_PREFIX "C:\Program Files\LLVM" 122 | ``` 123 | 124 | Or in PowerShell: 125 | 126 | ```powershell 127 | [Environment]::SetEnvironmentVariable("LLVM_SYS_181_PREFIX", "C:\Program Files\LLVM", "User") 128 | ``` 129 | 130 | ### Build Flags 131 | 132 | Windows builds require additional flags: 133 | 134 | ```bash 135 | cargo build --features win-link-opt 136 | # or 137 | cargo build --features win-link-lld 138 | ``` 139 | -------------------------------------------------------------------------------- /amice-llvm/src/analysis/dominators.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi::{ 2 | llvm_dominator_tree_create, llvm_dominator_tree_create_from_function, llvm_dominator_tree_destroy, 3 | llvm_dominator_tree_dominate_BU, llvm_dominator_tree_view_graph, 4 | }; 5 | use inkwell::basic_block::BasicBlock; 6 | use inkwell::llvm_sys::prelude::{LLVMBasicBlockRef, LLVMUseRef, LLVMValueRef}; 7 | use inkwell::values::{AsValueRef, FunctionValue}; 8 | 9 | #[repr(C)] 10 | pub struct LLVMDominatorTree { 11 | _private: [u8; 0], 12 | } 13 | 14 | pub type LLVMDominatorTreeRef = *mut LLVMDominatorTree; 15 | 16 | /// Safe Rust wrapper for LLVM DominatorTree 17 | pub struct DominatorTree { 18 | ptr: LLVMDominatorTreeRef, 19 | } 20 | 21 | impl DominatorTree { 22 | /// Create a new empty DominatorTree 23 | pub fn new() -> Result { 24 | let ptr = unsafe { llvm_dominator_tree_create() }; 25 | if ptr.is_null() { 26 | Err("Failed to create DominatorTree") 27 | } else { 28 | Ok(DominatorTree { ptr }) 29 | } 30 | } 31 | 32 | /// Create a DominatorTree from an LLVM Function 33 | pub fn from_function(func: FunctionValue) -> Result { 34 | if func.is_null() { 35 | return Err("Function pointer is null"); 36 | } 37 | 38 | let ptr = unsafe { llvm_dominator_tree_create_from_function(func.as_value_ref() as LLVMValueRef) }; 39 | if ptr.is_null() { 40 | Err("Failed to create DominatorTree from function") 41 | } else { 42 | Ok(DominatorTree { ptr }) 43 | } 44 | } 45 | 46 | pub fn from_function_ref(func: LLVMValueRef) -> Result { 47 | if func.is_null() { 48 | return Err("Function pointer is null"); 49 | } 50 | 51 | let ptr = unsafe { llvm_dominator_tree_create_from_function(func) }; 52 | if ptr.is_null() { 53 | Err("Failed to create DominatorTree from function") 54 | } else { 55 | Ok(DominatorTree { ptr }) 56 | } 57 | } 58 | 59 | pub fn view_graph(&self) { 60 | if self.ptr.is_null() { 61 | panic!("Cannot view graph of a null DominatorTree"); 62 | } 63 | unsafe { llvm_dominator_tree_view_graph(self.ptr) } 64 | } 65 | 66 | pub fn dominate(&self, b: BasicBlock, u: BasicBlock) -> bool { 67 | if self.ptr.is_null() { 68 | panic!("Cannot dominate of a null DominatorTree"); 69 | } 70 | 71 | unsafe { 72 | llvm_dominator_tree_dominate_BU( 73 | self.ptr, 74 | b.as_mut_ptr() as LLVMBasicBlockRef, 75 | u.as_mut_ptr() as LLVMUseRef, 76 | ) 77 | } 78 | } 79 | 80 | pub fn as_ptr(&self) -> LLVMDominatorTreeRef { 81 | self.ptr 82 | } 83 | } 84 | 85 | impl Drop for DominatorTree { 86 | fn drop(&mut self) { 87 | if !self.ptr.is_null() { 88 | unsafe { llvm_dominator_tree_destroy(self.ptr) } 89 | } 90 | } 91 | } 92 | 93 | unsafe impl Send for DominatorTree {} 94 | unsafe impl Sync for DominatorTree {} 95 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Auto-format Rust Code 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | 9 | concurrency: 10 | group: auto-format-rust-${{ github.ref }} 11 | cancel-in-progress: false 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | rustfmt: 18 | name: Format Rust Code 19 | runs-on: ubuntu-latest 20 | 21 | if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Setup Rust toolchain 30 | uses: dtolnay/rust-toolchain@stable 31 | with: 32 | components: rustfmt 33 | 34 | - name: Cache cargo registry 35 | uses: actions/cache@v4 36 | with: 37 | path: | 38 | ~/.cargo/registry 39 | ~/.cargo/git 40 | target 41 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 42 | restore-keys: | 43 | ${{ runner.os }}-cargo- 44 | 45 | - name: Check Rust formatting 46 | id: fmt-check 47 | run: | 48 | if ! cargo fmt --check; then 49 | echo "needs_formatting=true" >> $GITHUB_OUTPUT 50 | echo "Code needs formatting" 51 | else 52 | echo "needs_formatting=false" >> $GITHUB_OUTPUT 53 | echo "Code is already formatted correctly" 54 | fi 55 | 56 | - name: Format Rust code 57 | if: steps.fmt-check.outputs.needs_formatting == 'true' 58 | run: cargo fmt --verbose 59 | 60 | - name: Check for changes 61 | if: steps.fmt-check.outputs.needs_formatting == 'true' 62 | id: verify-changes 63 | run: | 64 | if git diff --quiet; then 65 | echo "has_changes=false" >> $GITHUB_OUTPUT 66 | echo "No changes after formatting" 67 | else 68 | echo "has_changes=true" >> $GITHUB_OUTPUT 69 | echo "Changes detected after formatting" 70 | echo "Changed files:" 71 | git diff --name-only 72 | fi 73 | 74 | - name: Commit and push formatted code 75 | if: steps.verify-changes.outputs.has_changes == 'true' 76 | uses: stefanzweifel/git-auto-commit-action@v5 77 | with: 78 | commit_message: 'chore(fmt): auto-format Rust code with rustfmt [skip ci]' 79 | branch: ${{ github.ref_name || github.head_ref }} 80 | 81 | - name: Summary 82 | if: always() 83 | run: | 84 | if [[ "${{ steps.fmt-check.outputs.needs_formatting }}" == "true" ]]; then 85 | if [[ "${{ steps.verify-changes.outputs.has_changes }}" == "true" ]]; then 86 | echo "[ok] Code was formatted and committed" 87 | else 88 | echo "[warn] Code was already formatted correctly" 89 | fi 90 | else 91 | echo "[ok] No formatting needed" 92 | fi 93 | -------------------------------------------------------------------------------- /.github/workflows/macos-arm64-build.yml: -------------------------------------------------------------------------------- 1 | name: MacOS Arm64 Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths: 7 | - '**/*.rs' 8 | - '**/Cargo.toml' 9 | - '**/Cargo.lock' 10 | - '**/build.rs' 11 | - 'rust-toolchain*' 12 | - '.github/workflows/macos-arm64-build.yml' 13 | pull_request: 14 | branches: [ master ] 15 | paths: 16 | - '**/*.rs' 17 | - '**/Cargo.toml' 18 | - '**/Cargo.lock' 19 | - '**/build.rs' 20 | - 'rust-toolchain*' 21 | - '.github/workflows/macos-arm64-build.yml' 22 | workflow_dispatch: 23 | 24 | env: 25 | CARGO_TERM_COLOR: always 26 | 27 | jobs: 28 | macos-build: 29 | name: "LLVM/OPT ${{ matrix.llvm-version[0] }} MacOS" 30 | runs-on: macos-latest 31 | env: 32 | BUILD_OUTPUT_PATH: target/release 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | llvm-version: 37 | - ["18", "18-1", "181"] 38 | - ["19", "19-1", "191"] 39 | - ["20", "20-1", "201"] 40 | - ["21", "21-1", "211"] 41 | 42 | steps: 43 | - name: Checkout Repo 44 | uses: actions/checkout@v2 45 | 46 | - name: Cache Homebrew downloads & Cellar 47 | uses: actions/cache@v4 48 | with: 49 | path: | 50 | /Users/runner/Library/Caches/Homebrew 51 | /opt/homebrew/Library/Caches/Homebrew 52 | /opt/homebrew/Cellar 53 | key: ${{ runner.os }}-homebrew-llvm-${{ matrix.llvm-version[0] }} 54 | restore-keys: | 55 | ${{ runner.os }}-homebrew-llvm-${{ matrix.llvm-version[0] }} 56 | 57 | - name: Install Homebrew LLVM (clang) and set env dynamically 58 | run: | 59 | set -euo pipefail 60 | major=${{ matrix.llvm-version[0] }} 61 | suffix=${{ matrix.llvm-version[2] }} 62 | echo "Installing llvm@${major} via Homebrew" 63 | brew update 64 | # Prefer bottled binaries to avoid building from source; fall back to normal install if bottles not available 65 | if ! brew install --force-bottle "llvm@${major}"; then 66 | brew install "llvm@${major}" 67 | fi 68 | brew link "llvm@${major}" 69 | # Get the Homebrew prefix for the installed llvm formula (usually /opt/homebrew/opt/llvm@) 70 | prefix=$(brew --prefix "llvm@${major}") 71 | echo "Found llvm prefix: ${prefix}" 72 | # Use suffix from matrix for the llvm-sys env var name (e.g. 201) 73 | env_name="LLVM_SYS_${suffix}_PREFIX" 74 | echo "${env_name}=${prefix}" >> $GITHUB_ENV 75 | # Ensure brewed clang is on PATH for subsequent steps 76 | echo "PATH=${prefix}/bin:$PATH" >> $GITHUB_ENV 77 | 78 | - name: Build libamice.dylib (Release) 79 | run: | 80 | cargo b --release --no-default-features --features llvm${{ matrix.llvm-version[1] }} 81 | 82 | - name: Upload libamice.dylib 83 | uses: actions/upload-artifact@v4 84 | with: 85 | name: libamice-llvm${{ matrix.llvm-version[0] }}-macos 86 | path: ${{ env.BUILD_OUTPUT_PATH }}/libamice.dylib 87 | retention-days: 30 88 | -------------------------------------------------------------------------------- /src/aotu/custom_calling_conv/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Config, CustomCallingConvConfig}; 2 | use crate::pass_registry::{AmiceFunctionPass, AmicePass, AmicePassFlag}; 3 | use amice_llvm::inkwell2::{FunctionExt, ModuleExt}; 4 | use amice_macro::amice; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::{AsValueRef, CallSiteValue, FunctionValue, InstructionOpcode}; 7 | use llvm_plugin::{LlvmModulePass, ModuleAnalysisManager, PreservedAnalyses}; 8 | 9 | #[amice( 10 | priority = 1121, 11 | name = "CustomCallingConv", 12 | flag = AmicePassFlag::PipelineStart | AmicePassFlag::FunctionLevel, 13 | config = CustomCallingConvConfig, 14 | )] 15 | #[derive(Default)] 16 | pub struct CustomCallingConv {} 17 | 18 | impl AmicePass for CustomCallingConv { 19 | fn init(&mut self, cfg: &Config, _flag: AmicePassFlag) { 20 | self.default_config = cfg.custom_calling_conv.clone(); 21 | } 22 | 23 | fn do_pass(&self, module: &mut Module<'_>) -> anyhow::Result { 24 | let mut executed = false; 25 | for function in module.get_functions() { 26 | if function.is_llvm_function() || function.is_undef_function() || function.is_inline_marked() { 27 | continue; 28 | } 29 | 30 | let cfg = self.parse_function_annotations(module, function)?; 31 | if !cfg.enable { 32 | continue; 33 | } 34 | 35 | if let Err(e) = do_random_calling_conv(module, function) { 36 | error!("failed to process function {:?}: {}", function.get_name(), e); 37 | continue; 38 | } 39 | 40 | executed = true; 41 | } 42 | 43 | if !executed { 44 | return Ok(PreservedAnalyses::All); 45 | } 46 | 47 | Ok(PreservedAnalyses::None) 48 | } 49 | } 50 | 51 | fn do_random_calling_conv<'a>(module: &mut Module<'a>, function: FunctionValue<'a>) -> anyhow::Result<()> { 52 | let annotates = module 53 | .read_function_annotate(function) 54 | .map_err(|e| anyhow::anyhow!("failed to read annotate: {}", e))?; 55 | 56 | if !annotates.iter().any(|annotate| { 57 | annotate == "+random_calling_conv" || annotate == "+custom_calling_conv" || annotate.contains("+customcc") 58 | }) { 59 | return Ok(()); 60 | } 61 | 62 | let obf_calling_conv = [0u32; 0]; // todo: 等待CodeGen部分实现hook以提供自定义CallingConv支持 63 | if obf_calling_conv.is_empty() { 64 | return Ok(()); 65 | } 66 | 67 | let random_calling_conv = obf_calling_conv[rand::random_range(0..obf_calling_conv.len())]; 68 | function.set_call_conventions(random_calling_conv); 69 | 70 | for func in module.get_functions() { 71 | for bb in func.get_basic_blocks() { 72 | for inst in bb.get_instructions() { 73 | unsafe { 74 | if inst.get_opcode() == InstructionOpcode::Call { 75 | let call_site = CallSiteValue::new(inst.as_value_ref()); 76 | call_site.set_call_convention(random_calling_conv) 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /tests/c/fixtures/indirect_branch/indirect_branch.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 防优化:防止编译器常量折叠或删除代码 4 | volatile int sink; 5 | 6 | void test_unconditional_br() { 7 | int a = 1; 8 | goto label1; 9 | a = 2; // dead code, but br will skip 10 | label1: 11 | a = 3; 12 | sink = a; 13 | } 14 | 15 | void test_conditional_br(int x) { 16 | int result; 17 | if (x > 0) { 18 | result = 10; 19 | } else if (x < 0) { 20 | result = -10; 21 | } else { 22 | result = 0; 23 | } 24 | sink = result; 25 | } 26 | 27 | void test_switch_br(int choice) { 28 | int value = 0; 29 | switch (choice) { 30 | case 1: 31 | value = 100; 32 | break; 33 | case 2: 34 | value = 200; 35 | break; 36 | case 3: 37 | value = 300; 38 | break; 39 | default: 40 | value = -1; 41 | break; 42 | } 43 | sink = value; 44 | } 45 | 46 | void test_loop_while(int n) { 47 | int sum = 0; 48 | while (n > 0) { 49 | sum += n; 50 | n--; 51 | } 52 | sink = sum; 53 | } 54 | 55 | void test_loop_for(int start, int end) { 56 | int count = 0; 57 | for (int i = start; i < end; i++) { 58 | if (i % 2 == 0) { 59 | count++; 60 | } 61 | } 62 | sink = count; 63 | } 64 | 65 | void test_nested_if_else(int a, int b, int c) { 66 | int result; 67 | if (a > 0) { 68 | if (b > 0) { 69 | result = 1; 70 | } else { 71 | if (c > 0) { 72 | result = 2; 73 | } else { 74 | result = 3; 75 | } 76 | } 77 | } else { 78 | result = 4; 79 | } 80 | sink = result; 81 | } 82 | 83 | void test_goto_based_control_flow(int flag) { 84 | int x = 0; 85 | 86 | if (flag == 1) goto branch1; 87 | if (flag == 2) goto branch2; 88 | goto default_branch; 89 | 90 | branch1: 91 | x = 11; 92 | goto end; 93 | 94 | branch2: 95 | x = 22; 96 | goto end; 97 | 98 | default_branch: 99 | x = 99; 100 | 101 | end: 102 | sink = x; 103 | } 104 | 105 | void test_function_call_and_return(int sel) { 106 | if (sel) { 107 | test_conditional_br(5); 108 | } else { 109 | test_switch_br(2); 110 | } 111 | sink = sel + 1; 112 | } 113 | 114 | int main() { 115 | printf("Running control flow test suite...\n"); 116 | 117 | test_unconditional_br(); 118 | 119 | test_conditional_br(1); 120 | test_conditional_br(-1); 121 | test_conditional_br(0); 122 | 123 | test_switch_br(1); 124 | test_switch_br(2); 125 | test_switch_br(3); 126 | test_switch_br(99); 127 | 128 | test_loop_while(5); 129 | test_loop_for(1, 10); 130 | 131 | test_nested_if_else(1, 1, 1); 132 | test_nested_if_else(1, 0, 1); 133 | test_nested_if_else(0, 0, 0); 134 | 135 | test_goto_based_control_flow(1); 136 | test_goto_based_control_flow(2); 137 | test_goto_based_control_flow(0); 138 | 139 | test_function_call_and_return(1); 140 | test_function_call_and_return(0); 141 | 142 | printf("All tests completed. sink = %d\n", sink); 143 | return 0; 144 | } -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/module.rs: -------------------------------------------------------------------------------- 1 | use crate::annotate::read_function_annotate; 2 | use crate::ffi; 3 | use crate::ffi::ArgReplacement; 4 | use crate::inkwell2::LLVMValueRefExt; 5 | use inkwell::llvm_sys::prelude::{LLVMModuleRef, LLVMValueRef}; 6 | use inkwell::module::Module; 7 | use inkwell::values::{AsValueRef, FunctionValue, GlobalValue}; 8 | 9 | pub trait ModuleExt<'ctx> { 10 | fn append_to_global_ctors(&mut self, function: FunctionValue, priority: i32); 11 | 12 | fn append_to_used(&mut self, value: GlobalValue); 13 | 14 | fn append_to_compiler_used(&mut self, value: GlobalValue); 15 | 16 | fn read_function_annotate(&mut self, func: FunctionValue<'ctx>) -> Result, &'static str>; 17 | 18 | #[cfg(not(feature = "android-ndk"))] 19 | unsafe fn specialize_function_by_args( 20 | &self, 21 | original_func: FunctionValue<'ctx>, 22 | args: &[(u32, LLVMValueRef)], 23 | ) -> Result, &'static str>; 24 | } 25 | 26 | impl<'ctx> ModuleExt<'ctx> for Module<'ctx> { 27 | fn append_to_global_ctors(&mut self, function: FunctionValue, priority: i32) { 28 | unsafe { 29 | ffi::amice_append_to_global_ctors( 30 | self.as_mut_ptr() as LLVMModuleRef, 31 | function.as_value_ref() as LLVMValueRef, 32 | priority, 33 | ); 34 | } 35 | } 36 | 37 | fn append_to_used(&mut self, value: GlobalValue) { 38 | unsafe { 39 | ffi::amice_append_to_used(self.as_mut_ptr() as LLVMModuleRef, value.as_value_ref() as LLVMValueRef); 40 | } 41 | } 42 | 43 | fn append_to_compiler_used(&mut self, value: GlobalValue) { 44 | unsafe { 45 | ffi::amice_append_to_compiler_used( 46 | self.as_mut_ptr() as LLVMModuleRef, 47 | value.as_value_ref() as LLVMValueRef, 48 | ); 49 | } 50 | } 51 | 52 | fn read_function_annotate(&mut self, func: FunctionValue<'ctx>) -> Result, &'static str> { 53 | read_function_annotate(self, func) 54 | } 55 | 56 | #[cfg(not(feature = "android-ndk"))] 57 | unsafe fn specialize_function_by_args( 58 | &self, 59 | original_func: FunctionValue<'ctx>, 60 | args: &[(u32, LLVMValueRef)], 61 | ) -> Result, &'static str> { 62 | if original_func.is_null() { 63 | return Err("Null pointer"); 64 | } 65 | 66 | let arg_replacements: Vec = args 67 | .iter() 68 | .map(|(index, constant)| ArgReplacement { 69 | index: *index, 70 | constant: *constant, 71 | }) 72 | .collect(); 73 | 74 | let result = unsafe { 75 | ffi::amice_specialize_function( 76 | original_func.as_value_ref() as LLVMValueRef, 77 | self.as_mut_ptr() as LLVMModuleRef, 78 | arg_replacements.as_ptr(), 79 | arg_replacements.len() as u32, 80 | ) 81 | }; 82 | 83 | if result.is_null() { 84 | Err("Specialization failed") 85 | } else { 86 | result 87 | .into_function_value() 88 | .ok_or("Specialization failed: invalid function value") 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/issue_61_cpp_fmt_strenc.rs: -------------------------------------------------------------------------------- 1 | //! Test case for issue #61: C++ fmt library string encryption failure 2 | //! 3 | //! This test tracks a known failure when obfuscating C++ code that uses the fmt library 4 | //! with string encryption enabled. 5 | //! 6 | //! Issue: https://github.com/fuqiuluo/amice/issues/61 7 | //! 8 | //! The test is marked as #[ignore] until the issue is resolved. 9 | 10 | mod common; 11 | 12 | use crate::common::Language; 13 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 14 | 15 | fn string_config_lazy_xor() -> ObfuscationConfig { 16 | ObfuscationConfig { 17 | string_encryption: Some(true), 18 | string_algorithm: Some("xor".to_string()), 19 | string_decrypt_timing: Some("lazy".to_string()), 20 | ..ObfuscationConfig::disabled() 21 | } 22 | } 23 | 24 | /// Test case for issue #61: fmt library string encryption 25 | /// 26 | /// This test attempts to compile a C++ program using the fmt library 27 | /// with string encryption enabled. This is currently known to fail. 28 | /// 29 | /// To run this test after fixing the issue: 30 | /// ```bash 31 | /// cargo test --release --no-default-features --features llvm18-1 test_issue_61_cpp_fmt_strenc -- --ignored 32 | /// ``` 33 | // #[test] 34 | #[ignore = "Known failure - issue #61: C++ fmt library string encryption"] 35 | fn test_issue_61_cpp_fmt_strenc() { 36 | use common::project_root; 37 | 38 | ensure_plugin_built(); 39 | 40 | let fmt_include = project_root().join("tests/fixtures/issues/issue_61_cpp_fmt_strenc/fmt/include"); 41 | 42 | let result = CppCompileBuilder::new( 43 | fixture_path("issues/issue_61_cpp_fmt_strenc", "main.cpp", Language::Cpp), 44 | "issue_61_cpp_fmt", 45 | ) 46 | .config(string_config_lazy_xor()) 47 | .optimization("O2") 48 | .std("c++17") 49 | .arg(&format!("-I{}", fmt_include.display())) 50 | .arg("-DFMT_HEADER_ONLY") 51 | .compile(); 52 | 53 | // When the issue is fixed, this should succeed 54 | result.assert_success(); 55 | 56 | let run = result.run(); 57 | run.assert_success(); 58 | 59 | let lines = run.stdout_lines(); 60 | assert!(!lines.is_empty(), "Expected output from fmt::format"); 61 | // The expected output should be "hello" formatted by fmt library 62 | assert!(lines[0].contains("hello"), "Expected 'hello' in output"); 63 | } 64 | 65 | /// Helper test to verify the fixture compiles without obfuscation 66 | /// 67 | /// This test ensures the C++ code itself is valid and can compile 68 | /// without the amice plugin. 69 | // #[test] 70 | fn test_issue_61_cpp_fmt_baseline() { 71 | use common::project_root; 72 | 73 | let fmt_include = project_root().join("tests/fixtures/issues/issue_61_cpp_fmt_strenc/fmt/include"); 74 | 75 | let result = CppCompileBuilder::new( 76 | fixture_path("issues/issue_61_cpp_fmt_strenc", "main.cpp", Language::Cpp), 77 | "issue_61_cpp_fmt_baseline", 78 | ) 79 | .without_plugin() 80 | .optimization("O2") 81 | .std("c++17") 82 | .arg(&format!("-I{}", fmt_include.display())) 83 | .arg("-DFMT_HEADER_ONLY") 84 | .compile(); 85 | 86 | result.assert_success(); 87 | 88 | let run = result.run(); 89 | run.assert_success(); 90 | 91 | let lines = run.stdout_lines(); 92 | assert!(!lines.is_empty(), "Expected output from fmt::format"); 93 | assert!(lines[0].contains("hello"), "Expected 'hello' in output"); 94 | } 95 | -------------------------------------------------------------------------------- /PROJECT_STRUCTURE.md: -------------------------------------------------------------------------------- 1 | # 项目结构 2 | 3 | - 仓库:amice 4 | - 生成时间:2025-08-31 11:07:28 UTC 5 | - 深度:3 6 | - 忽略:.git|target|node_modules|.idea|.vscode|dist|build 7 | 8 | ```text 9 | 10 | ├── .github/ 11 | │   ├── copilot-instructions.md 12 | │   └── workflows/ 13 | │   ├── generate-structure.yml 14 | │   ├── linux-x64-build-android-ndk.yml 15 | │   ├── linux-x64-build.yml 16 | │   ├── macos-arm64-build.yml 17 | │   ├── rustfmt.yml 18 | │   ├── windwos-x64-link-lld-build.yml 19 | │   └── windwos-x64-link-opt-build.yml 20 | ├── .gitignore 21 | ├── .rustfmt.toml 22 | ├── Cargo.lock 23 | ├── Cargo.toml 24 | ├── LICENSE 25 | ├── PROJECT_STRUCTURE.md 26 | ├── README.md 27 | ├── amice-llvm/ 28 | │   ├── Cargo.lock 29 | │   ├── Cargo.toml 30 | │   ├── build.rs 31 | │   ├── cpp/ 32 | │   │   ├── adt_ffi.cc 33 | │   │   ├── dominators_ffi.cc 34 | │   │   ├── ffi.cc 35 | │   │   ├── instructions.cc 36 | │   │   ├── utils.cc 37 | │   │   └── verifier.cc 38 | │   └── src/ 39 | │   ├── analysis/ 40 | │   ├── analysis.rs 41 | │   ├── annotate.rs 42 | │   ├── ffi.rs 43 | │   ├── inkwell2/ 44 | │   ├── inkwell2.rs 45 | │   └── lib.rs 46 | ├── amice-macro/ 47 | │   ├── Cargo.lock 48 | │   ├── Cargo.toml 49 | │   └── src/ 50 | │   └── lib.rs 51 | ├── build.rs 52 | ├── src/ 53 | │   ├── aotu/ 54 | │   │   ├── alias_access/ 55 | │   │   ├── bogus_control_flow/ 56 | │   │   ├── clone_function/ 57 | │   │   ├── custom_calling_conv/ 58 | │   │   ├── delay_offset_loading/ 59 | │   │   ├── flatten/ 60 | │   │   ├── function_wrapper/ 61 | │   │   ├── indirect_branch/ 62 | │   │   ├── indirect_call/ 63 | │   │   ├── lower_switch/ 64 | │   │   ├── mba/ 65 | │   │   ├── mod.rs 66 | │   │   ├── param_aggregate/ 67 | │   │   ├── shuffle_blocks/ 68 | │   │   ├── split_basic_block/ 69 | │   │   ├── string_encryption/ 70 | │   │   └── vm_flatten/ 71 | │   ├── config/ 72 | │   │   ├── alias_access.rs 73 | │   │   ├── bogus_control_flow.rs 74 | │   │   ├── clone_function.rs 75 | │   │   ├── custom_calling_conv.rs 76 | │   │   ├── delay_offset_loading.rs 77 | │   │   ├── flatten.rs 78 | │   │   ├── function_wrapper.rs 79 | │   │   ├── indirect_branch.rs 80 | │   │   ├── indirect_call.rs 81 | │   │   ├── lower_switch.rs 82 | │   │   ├── mba.rs 83 | │   │   ├── param_aggregate.rs 84 | │   │   ├── pass_order.rs 85 | │   │   ├── shuffle_blocks.rs 86 | │   │   ├── split_basic_block.rs 87 | │   │   ├── string_encryption.rs 88 | │   │   └── vm_flatten.rs 89 | │   ├── config.rs 90 | │   ├── lib.rs 91 | │   └── pass_registry.rs 92 | └── tests/ 93 | ├── .gitignore 94 | ├── ama.c 95 | ├── bogus_control_flow.c 96 | ├── clone_function.c 97 | ├── complex_switch_test.c 98 | ├── const_strings.c 99 | ├── const_strings.rs 100 | ├── custom_calling_conv.c 101 | ├── function_wrapper_test.c 102 | ├── function_wrapper_test.rs 103 | ├── indirect_branch.c 104 | ├── indirect_branch.rs 105 | ├── indirect_call.c 106 | ├── large_string.c 107 | ├── large_string_threshold.rs 108 | ├── mba_constants_demo.c 109 | ├── md5.c 110 | ├── md5.cc 111 | ├── md5.rs 112 | ├── repeated_strings.c 113 | ├── repeated_strings.rs 114 | ├── shuffle_blocks_test.rs 115 | ├── shuffle_test.c 116 | ├── test1.c 117 | ├── test_strings.c 118 | └── vm_flatten.c 119 | 120 | 30 directories, 80 files 121 | ``` 122 | 123 | > 本文件由 GitHub Actions 自动生成,请勿手动编辑。 124 | -------------------------------------------------------------------------------- /docs/PassOrder_en_US.md: -------------------------------------------------------------------------------- 1 | ### Pass's Execution Order and Priority Override 2 | 3 | This document describes how to control Pass execution order and priority through configuration and environment variables, overriding compile-time hardcoded priorities. 4 | 5 | #### Background and Objectives 6 | - Default execution order is determined solely by `priority` in compile-time annotations (higher values execute first). 7 | - Runtime priority configuration: 8 | - Explicit order: Define exact sequence in configuration file, run strictly by list, **unlisted passes will not run**. 9 | - Priority override: Provide new priority values for individual **Passes** by name without modifying source annotations. 10 | 11 | > **Explicit order** > **Priority override** >= **Compile-time annotation priority**. When explicit order exists, priority becomes completely ineffective, running strictly by explicit order, and passes not in the explicit order will not run. 12 | > 13 | > To avoid unpredictable issues, each Pass will only run once! 14 | 15 | #### Configuration Structure 16 | ```rust 17 | #[derive(Default, Debug, Clone, Serialize, Deserialize)] 18 | #[serde(default)] 19 | pub struct PassOrderConfig { 20 | /// Explicit execution order; if None, sort by priority 21 | pub order: Option>, 22 | /// Override priority for each Pass (higher values come first) 23 | pub priority_override: HashMap, 24 | } 25 | ``` 26 | 27 | Environment variable override implementation: 28 | ```rust 29 | impl EnvOverlay for PassOrderConfig { 30 | fn overlay_env(&mut self) { 31 | // AMICE_PASS_ORDER="A,B,C" or "A;B;C" 32 | // AMICE_PASS_PRIORITY_OVERRIDE="A=1200,B=500" or "A=1200;B=500" 33 | } 34 | } 35 | ``` 36 | 37 | #### Effective Priority and Rules 38 | Execution order follows these rules: 39 | 1) If explicit order `pass_order.order` is provided in configuration: 40 | - Passes in the list run strictly in the given order. 41 | - Passes not appearing in the list **will not run**. 42 | - If both explicit order and `priority_override` exist, `explicit order` takes precedence, `priority_override` is not executed. 43 | 44 | 2) If no explicit order is provided, but `pass_order.priority_override` is provided: 45 | - Sort by overridden priority from high to low; non-overridden passes use default priority. 46 | 47 | 3) If neither is provided: 48 | - Fall back to default behavior: sort by compile-time priority from high to low. 49 | 50 | #### Configuration File Examples 51 | TOML Example: TODO 52 | YAML Example: TODO 53 | JSON Example: TODO 54 | 55 | #### Environment Variable Override 56 | Supports temporary override without modifying configuration files: 57 | - Explicit order 58 | - AMICE_PASS_ORDER="StringEncryption,SplitBasicBlock,ShuffleBlocks,IndirectBranch,IndirectCall" 59 | - Delimiters can be comma or semicolon: "A,B,C" or "A;B;C" 60 | - Override priority 61 | - AMICE_PASS_PRIORITY_OVERRIDE="StringEncryption=1200,IndirectBranch=500" 62 | - Also supports semicolon as delimiter: "A=1200;B=500" 63 | 64 | #### Pass Names and Default Priorities 65 | Since names or priority values may change with updates, and wiki synchronization here may not be timely enough, here's a source code snippet (if you need to change execution order, please refer to the source code yourself): 66 | ```rust 67 | #[amice(priority = 800, name = "IndirectBranch")] 68 | #[derive(Default)] 69 | pub struct IndirectBranch { 70 | enable: bool, 71 | flags: IndirectBranchFlags, 72 | xor_key: Option<[u32; 4]>, 73 | } 74 | ``` -------------------------------------------------------------------------------- /tests/inline_asm.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for inline assembly handling. 2 | //! 3 | //! This module tests that obfuscation passes correctly handle: 4 | //! - Inline assembly blocks 5 | //! - CallBr instructions (indirect jumps to asm labels) 6 | //! - Functions mixing inline asm with control flow 7 | //! 8 | //! Critical: ALL obfuscation passes should detect inline asm and skip the function 9 | 10 | mod common; 11 | 12 | use crate::common::Language; 13 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 14 | 15 | #[test] 16 | fn test_inline_asm_with_flatten() { 17 | ensure_plugin_built(); 18 | 19 | // Inline asm detection test - should skip functions with inline asm 20 | let result = CppCompileBuilder::new( 21 | fixture_path("inline_asm", "inline_asm_basic.c", Language::C), 22 | "inline_asm_flatten", 23 | ) 24 | .config(ObfuscationConfig { 25 | flatten: Some(true), 26 | ..ObfuscationConfig::disabled() 27 | }) 28 | .compile(); 29 | 30 | result.assert_success(); 31 | let run = result.run(); 32 | run.assert_success(); 33 | } 34 | 35 | #[test] 36 | fn test_inline_asm_with_bcf() { 37 | ensure_plugin_built(); 38 | 39 | // BCF should detect inline asm (currently does NOT) 40 | let result = CppCompileBuilder::new( 41 | fixture_path("inline_asm", "inline_asm_basic.c", Language::C), 42 | "inline_asm_bcf", 43 | ) 44 | .config(ObfuscationConfig { 45 | bogus_control_flow: Some(true), 46 | ..ObfuscationConfig::disabled() 47 | }) 48 | .compile(); 49 | 50 | result.assert_success(); 51 | let run = result.run(); 52 | run.assert_success(); 53 | } 54 | 55 | #[test] 56 | fn test_inline_asm_with_indirect_branch() { 57 | ensure_plugin_built(); 58 | 59 | let result = CppCompileBuilder::new( 60 | fixture_path("inline_asm", "inline_asm_basic.c", Language::C), 61 | "inline_asm_indirect_branch", 62 | ) 63 | .config(ObfuscationConfig { 64 | indirect_branch: Some(true), 65 | ..ObfuscationConfig::disabled() 66 | }) 67 | .compile(); 68 | 69 | result.assert_success(); 70 | let run = result.run(); 71 | run.assert_success(); 72 | } 73 | 74 | #[test] 75 | fn test_inline_asm_optimized() { 76 | ensure_plugin_built(); 77 | 78 | let result = CppCompileBuilder::new( 79 | fixture_path("inline_asm", "inline_asm_basic.c", Language::C), 80 | "inline_asm_o2", 81 | ) 82 | .config(ObfuscationConfig { 83 | flatten: Some(true), 84 | bogus_control_flow: Some(true), 85 | ..ObfuscationConfig::disabled() 86 | }) 87 | .optimization("O2") 88 | .compile(); 89 | 90 | result.assert_success(); 91 | let run = result.run(); 92 | run.assert_success(); 93 | } 94 | 95 | #[test] 96 | fn test_inline_asm_combined() { 97 | ensure_plugin_built(); 98 | 99 | // Test all control flow passes with inline asm 100 | let result = CppCompileBuilder::new( 101 | fixture_path("inline_asm", "inline_asm_basic.c", Language::C), 102 | "inline_asm_combined", 103 | ) 104 | .config(ObfuscationConfig { 105 | flatten: Some(true), 106 | bogus_control_flow: Some(true), 107 | indirect_branch: Some(true), 108 | split_basic_block: Some(true), 109 | ..ObfuscationConfig::disabled() 110 | }) 111 | .compile(); 112 | 113 | result.assert_success(); 114 | let run = result.run(); 115 | run.assert_success(); 116 | } 117 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/function.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | use crate::inkwell2::LLVMBasicBlockRefExt; 3 | use inkwell::basic_block::BasicBlock; 4 | use inkwell::llvm_sys::core::{LLVMGetEntryBasicBlock, LLVMIsABasicBlock}; 5 | use inkwell::llvm_sys::prelude::LLVMValueRef; 6 | use inkwell::values::{AsValueRef, FunctionValue}; 7 | use std::ffi::{CStr, c_char}; 8 | 9 | pub trait FunctionExt<'ctx> { 10 | fn verify_function(self) -> VerifyResult; 11 | 12 | fn verify_function_bool(self) -> bool; 13 | 14 | fn get_entry_block(&self) -> Option>; 15 | 16 | fn is_inline_marked(&self) -> bool; 17 | 18 | fn is_llvm_function(&self) -> bool; 19 | 20 | fn is_undef_function(&self) -> bool; 21 | 22 | unsafe fn fix_stack(&self); 23 | 24 | unsafe fn fix_stack_at_terminator(&self); 25 | 26 | unsafe fn fix_stack_with_max_iterations(&self, max_iterations: usize); 27 | } 28 | 29 | #[derive(Debug, Clone, Eq, PartialEq)] 30 | pub enum VerifyResult { 31 | Broken(String), 32 | Ok, 33 | } 34 | 35 | impl<'ctx> FunctionExt<'ctx> for FunctionValue<'ctx> { 36 | fn verify_function(self) -> VerifyResult { 37 | let mut errmsg: *const c_char = std::ptr::null(); 38 | let broken = unsafe { 39 | ffi::amice_verify_function(self.as_value_ref() as LLVMValueRef, &mut errmsg as *mut *const c_char) == 1 40 | }; 41 | let result = if !errmsg.is_null() && broken { 42 | let c_errmsg = unsafe { CStr::from_ptr(errmsg) }; 43 | VerifyResult::Broken(c_errmsg.to_string_lossy().into_owned()) 44 | } else { 45 | VerifyResult::Ok 46 | }; 47 | unsafe { 48 | ffi::amice_free_msg(errmsg); 49 | } 50 | result 51 | } 52 | 53 | fn verify_function_bool(self) -> bool { 54 | match self.verify_function() { 55 | VerifyResult::Broken(_) => true, 56 | VerifyResult::Ok => false, 57 | } 58 | } 59 | 60 | fn get_entry_block(&self) -> Option> { 61 | unsafe { 62 | let basic_block = LLVMGetEntryBasicBlock(self.as_value_ref()); 63 | if LLVMIsABasicBlock(basic_block as LLVMValueRef).is_null() { 64 | return None; 65 | } 66 | basic_block.into_basic_block() 67 | } 68 | } 69 | 70 | fn is_inline_marked(&self) -> bool { 71 | unsafe { ffi::amice_is_inline_marked_function(self.as_value_ref() as LLVMValueRef) } 72 | } 73 | 74 | fn is_llvm_function(&self) -> bool { 75 | let name = self.get_name().to_str().unwrap_or(""); 76 | name.is_empty() 77 | || name.starts_with("llvm.") 78 | || name.starts_with("clang.") 79 | || name.starts_with("__") 80 | || name.starts_with("@") 81 | || self.get_intrinsic_id() != 0 82 | } 83 | 84 | fn is_undef_function(&self) -> bool { 85 | self.is_null() || self.is_undef() || self.count_basic_blocks() <= 0 || self.get_intrinsic_id() != 0 86 | } 87 | 88 | unsafe fn fix_stack(&self) { 89 | unsafe { ffi::amice_fix_stack(self.as_value_ref() as LLVMValueRef, 0, 0) } 90 | } 91 | 92 | unsafe fn fix_stack_at_terminator(&self) { 93 | unsafe { ffi::amice_fix_stack(self.as_value_ref() as LLVMValueRef, 1, 0) } 94 | } 95 | 96 | unsafe fn fix_stack_with_max_iterations(&self, max_iterations: usize) { 97 | unsafe { ffi::amice_fix_stack(self.as_value_ref() as LLVMValueRef, 0, max_iterations as i32) } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/config/mba.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | #[serde(default)] 11 | pub struct MbaConfig { 12 | /// Whether to enable Mixed Boolean Arithmetic (MBA) obfuscation 13 | pub enable: bool, 14 | /// Number of auxiliary parameters to use in MBA expressions 15 | pub aux_count: u32, 16 | /// Number of operations to rewrite with MBA expressions 17 | pub rewrite_ops: u32, 18 | /// Maximum depth of MBA expression rewriting 19 | pub rewrite_depth: u32, 20 | /// Allocate auxiliary parameters in global variables to prevent optimization 21 | /// When true, inserts global variables in expressions to resist LLVM optimizations 22 | pub alloc_aux_params_in_global: bool, 23 | /// Enable stack fixing to prevent crashes during MBA transformation 24 | pub fix_stack: bool, 25 | /// MBAFunction must not be optimized 26 | pub opt_none: bool, 27 | } 28 | 29 | impl Default for MbaConfig { 30 | fn default() -> Self { 31 | Self { 32 | enable: false, 33 | aux_count: 2, 34 | rewrite_ops: 24, 35 | rewrite_depth: 3, 36 | alloc_aux_params_in_global: false, 37 | fix_stack: false, 38 | opt_none: false, 39 | } 40 | } 41 | } 42 | 43 | impl EnvOverlay for MbaConfig { 44 | fn overlay_env(&mut self) { 45 | if std::env::var("AMICE_MBA").is_ok() { 46 | self.enable = bool_var("AMICE_MBA", self.enable); 47 | } 48 | 49 | if let Ok(v) = std::env::var("AMICE_MBA_AUX_COUNT") { 50 | self.aux_count = v.parse::().unwrap_or(self.aux_count); 51 | } 52 | 53 | if let Ok(v) = std::env::var("AMICE_MBA_REWRITE_OPS") { 54 | self.rewrite_ops = v.parse::().unwrap_or(self.rewrite_ops); 55 | } 56 | 57 | if let Ok(v) = std::env::var("AMICE_MBA_REWRITE_DEPTH") { 58 | self.rewrite_depth = v.parse::().unwrap_or(self.rewrite_depth); 59 | } 60 | 61 | if std::env::var("AMICE_MBA_ALLOC_AUX_PARAMS_IN_GLOBAL").is_ok() { 62 | self.alloc_aux_params_in_global = 63 | bool_var("AMICE_MBA_ALLOC_AUX_PARAMS_IN_GLOBAL", self.alloc_aux_params_in_global); 64 | } 65 | 66 | if std::env::var("AMICE_MBA_FIX_STACK").is_ok() { 67 | self.fix_stack = bool_var("AMICE_MBA_FIX_STACK", self.fix_stack); 68 | } 69 | 70 | if std::env::var("AMICE_MBA_OPT_NONE").is_ok() { 71 | self.opt_none = bool_var("AMICE_MBA_OPT_NONE", self.opt_none); 72 | } 73 | } 74 | } 75 | 76 | impl FunctionAnnotationsOverlay for MbaConfig { 77 | type Config = MbaConfig; 78 | 79 | fn overlay_annotations<'a>( 80 | &self, 81 | module: &mut Module<'a>, 82 | function: FunctionValue<'a>, 83 | ) -> anyhow::Result { 84 | let mut cfg = self.clone(); 85 | let annotations_expr = module 86 | .read_function_annotate(function) 87 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 88 | .join(" "); 89 | 90 | let mut parser = EloquentConfigParser::new(); 91 | parser 92 | .parse(&annotations_expr) 93 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 94 | 95 | parser 96 | .get_bool("mba") 97 | .or_else(|| parser.get_bool("linearmba")) 98 | .map(|v| cfg.enable = v); 99 | 100 | Ok(cfg) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /amice-llvm/src/ffi.rs: -------------------------------------------------------------------------------- 1 | use crate::analysis::dominators::LLVMDominatorTreeRef; 2 | use inkwell::llvm_sys::prelude::{LLVMBasicBlockRef, LLVMModuleRef, LLVMUseRef, LLVMValueRef}; 3 | use std::ffi::{c_char, c_void}; 4 | use crate::code_extractor::LLVMCodeExtractorRef; 5 | 6 | #[link(name = "amice-llvm-ffi")] 7 | unsafe extern "C" { 8 | #[cfg(any( 9 | feature = "llvm12-0", 10 | feature = "llvm13-0", 11 | feature = "llvm14-0", 12 | feature = "llvm15-0", 13 | feature = "llvm16-0", 14 | feature = "llvm17-0", 15 | feature = "llvm18-1", 16 | feature = "llvm19-1", 17 | feature = "llvm20-1", 18 | feature = "llvm21-1", 19 | ))] 20 | pub(crate) fn amice_append_to_global_ctors(module: LLVMModuleRef, function: LLVMValueRef, priority: i32); 21 | 22 | pub(crate) fn amice_append_to_used(module: LLVMModuleRef, value: LLVMValueRef); 23 | 24 | pub(crate) fn amice_append_to_compiler_used(module: LLVMModuleRef, value: LLVMValueRef); 25 | 26 | pub(crate) fn amice_fix_stack(function: LLVMValueRef, at_term: i32, max_iterations: i32); 27 | 28 | pub(crate) fn amice_verify_function(function: LLVMValueRef, errmsg: *mut *const c_char) -> i32; 29 | 30 | pub(crate) fn amice_free_msg(errmsg: *const c_char) -> i32; 31 | 32 | pub(crate) fn amice_split_basic_block( 33 | block: LLVMBasicBlockRef, 34 | inst: LLVMValueRef, 35 | name: *const c_char, 36 | before: i32, 37 | ) -> *mut c_void; 38 | 39 | pub(crate) fn amice_get_first_insertion_pt(block: LLVMBasicBlockRef) -> LLVMValueRef; 40 | 41 | pub(crate) fn llvm_dominator_tree_create() -> LLVMDominatorTreeRef; 42 | 43 | pub(crate) fn llvm_dominator_tree_create_from_function(func: LLVMValueRef) -> LLVMDominatorTreeRef; 44 | 45 | pub(crate) fn llvm_dominator_tree_destroy(dt: LLVMDominatorTreeRef); 46 | 47 | pub(crate) fn llvm_dominator_tree_view_graph(dt: LLVMDominatorTreeRef); 48 | 49 | pub(crate) fn llvm_dominator_tree_dominate_BU( 50 | dt: LLVMDominatorTreeRef, 51 | b: LLVMBasicBlockRef, 52 | u: LLVMUseRef, 53 | ) -> bool; 54 | 55 | pub(crate) fn amice_switch_find_case_dest(inst: LLVMValueRef, b: LLVMBasicBlockRef) -> LLVMValueRef; 56 | 57 | pub(crate) fn amice_is_inline_marked_function(function: LLVMValueRef) -> bool; 58 | 59 | pub(crate) fn amice_basic_block_remove_predecessor(basic_block: LLVMBasicBlockRef, predecessor: LLVMBasicBlockRef); 60 | 61 | pub(crate) fn amice_phi_node_remove_incoming_value(phi_node: LLVMValueRef, incoming: LLVMBasicBlockRef); 62 | 63 | pub(crate) fn amice_phi_node_replace_incoming_block_with( 64 | phi_node: LLVMValueRef, 65 | incoming: LLVMBasicBlockRef, 66 | new_block: LLVMBasicBlockRef, 67 | ); 68 | 69 | pub(crate) fn amice_specialize_function( 70 | original_func: LLVMValueRef, 71 | module: LLVMModuleRef, 72 | replacements: *const ArgReplacement, 73 | replacement_count: u32, 74 | ) -> LLVMValueRef; 75 | 76 | pub(crate) fn amice_gep_accumulate_constant_offset(gep: LLVMValueRef, module: LLVMModuleRef, offset: *mut u64) -> bool; 77 | 78 | pub(crate) fn amice_attribute_enum_kind_to_str(kind: u32) -> *const c_char; 79 | 80 | pub(crate) fn amice_create_code_extractor(bbs: *const LLVMBasicBlockRef, bb_len: i32) -> LLVMCodeExtractorRef; 81 | 82 | pub(crate) fn amice_delete_code_extractor(ce: LLVMCodeExtractorRef); 83 | 84 | pub(crate) fn amice_code_extractor_is_eligible(ce: LLVMCodeExtractorRef) -> bool; 85 | 86 | pub(crate) fn amice_code_extractor_extract_code_region(ce: LLVMCodeExtractorRef, function: LLVMValueRef) -> LLVMValueRef; 87 | 88 | pub(crate) fn amice_get_llvm_version_major() -> i32; 89 | 90 | pub(crate) fn amice_get_llvm_version_minor() -> i32; 91 | } 92 | 93 | #[repr(C)] 94 | pub(crate) struct ArgReplacement { 95 | pub index: u32, 96 | pub constant: LLVMValueRef, 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/config/alias_access.rs: -------------------------------------------------------------------------------- 1 | use crate::config::bool_var; 2 | use crate::config::eloquent_config::EloquentConfigParser; 3 | use crate::pass_registry::{EnvOverlay, FunctionAnnotationsOverlay}; 4 | use amice_llvm::inkwell2::ModuleExt; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use log::error; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 11 | #[serde(default)] 12 | pub struct AliasAccessConfig { 13 | pub enable: bool, 14 | pub mode: AliasAccessMode, 15 | 16 | /// Shuffle the RawBox order 17 | /// 18 | /// Parameters available only in `PointerChain` mode 19 | pub shuffle_raw_box: bool, 20 | /// Loose RawBox, the gap will fill the garbage 21 | /// 22 | /// Parameters available only in `PointerChain` mode 23 | pub loose_raw_box: bool, 24 | } 25 | 26 | #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] 27 | pub enum AliasAccessMode { 28 | #[serde(rename = "pointer_chain")] 29 | #[default] 30 | PointerChain, 31 | } 32 | 33 | fn parse_alias_access_mode(s: &str) -> Result { 34 | let s = s.to_lowercase(); 35 | match s.as_str() { 36 | "pointer_chain" | "basic" | "v1" => Ok(AliasAccessMode::PointerChain), 37 | _ => Err(format!("unknown alias access mode: {}", s)), 38 | } 39 | } 40 | 41 | impl EnvOverlay for AliasAccessConfig { 42 | fn overlay_env(&mut self) { 43 | if std::env::var("AMICE_ALIAS_ACCESS").is_ok() { 44 | self.enable = bool_var("AMICE_ALIAS_ACCESS", self.enable); 45 | } 46 | 47 | if std::env::var("AMICE_ALIAS_ACCESS_SHUFFLE_RAW_BOX").is_ok() { 48 | self.shuffle_raw_box = bool_var("AMICE_ALIAS_ACCESS_SHUFFLE_RAW_BOX", self.shuffle_raw_box); 49 | } 50 | 51 | if std::env::var("AMICE_ALIAS_ACCESS_LOOSE_RAW_BOX").is_ok() { 52 | self.loose_raw_box = bool_var("AMICE_ALIAS_ACCESS_LOOSE_RAW_BOX", self.loose_raw_box); 53 | } 54 | 55 | if let Ok(mode) = std::env::var("AMICE_ALIAS_ACCESS_MODE") { 56 | self.mode = parse_alias_access_mode(&mode).unwrap_or_else(|e| { 57 | error!("parse alias access mode failed: {}", e); 58 | AliasAccessMode::default() 59 | }) 60 | } 61 | } 62 | } 63 | 64 | impl FunctionAnnotationsOverlay for AliasAccessConfig { 65 | type Config = AliasAccessConfig; 66 | 67 | fn overlay_annotations<'a>(&self, module: &mut Module<'a>, function: FunctionValue<'a>) -> anyhow::Result { 68 | let mut cfg = self.clone(); 69 | let annotations_expr = module 70 | .read_function_annotate(function) 71 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 72 | .join(" "); 73 | 74 | let mut parser = EloquentConfigParser::new(); 75 | parser 76 | .parse(&annotations_expr) 77 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 78 | 79 | parser 80 | .get_bool("alias_access") 81 | .or_else(|| parser.get_bool("aliasaccess")) 82 | .or_else(|| parser.get_bool("alias")) // 兼容Polaris-Obfuscator 83 | .map(|v| cfg.enable = v); 84 | parser.get_string("alias_access_mode").map(|v| { 85 | cfg.mode = parse_alias_access_mode(&v).unwrap_or_else(|e| { 86 | error!("parse alias access mode failed: {}", e); 87 | AliasAccessMode::default() 88 | }) 89 | }); 90 | parser 91 | .get_bool("alias_access_shuffle_raw_box") 92 | .map(|v| cfg.shuffle_raw_box = v); 93 | parser 94 | .get_bool("alias_access_loose_raw_box") 95 | .map(|v| cfg.loose_raw_box = v); 96 | 97 | Ok(cfg) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/edge_cases.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for edge cases in obfuscation passes. 2 | //! 3 | //! This module tests: 4 | //! - Empty functions 5 | //! - Single basic block functions 6 | //! - Large functions 7 | //! - Boundary conditions for all obfuscation passes 8 | 9 | mod common; 10 | 11 | use crate::common::Language; 12 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 13 | 14 | fn all_passes_config() -> ObfuscationConfig { 15 | ObfuscationConfig { 16 | flatten: Some(true), 17 | bogus_control_flow: Some(true), 18 | mba: Some(true), 19 | indirect_branch: Some(true), 20 | ..ObfuscationConfig::disabled() 21 | } 22 | } 23 | 24 | #[test] 25 | fn test_empty_function() { 26 | ensure_plugin_built(); 27 | 28 | let result = CppCompileBuilder::new( 29 | fixture_path("edge_cases", "empty_function.c", Language::C), 30 | "empty_function", 31 | ) 32 | .config(all_passes_config()) 33 | .compile(); 34 | 35 | result.assert_success(); 36 | let run = result.run(); 37 | run.assert_success(); 38 | } 39 | 40 | #[test] 41 | fn test_empty_function_optimized() { 42 | ensure_plugin_built(); 43 | 44 | let result = CppCompileBuilder::new( 45 | fixture_path("edge_cases", "empty_function.c", Language::C), 46 | "empty_function_o2", 47 | ) 48 | .config(ObfuscationConfig { 49 | flatten: Some(false), 50 | bogus_control_flow: Some(true), 51 | mba: Some(false), 52 | indirect_branch: Some(true), 53 | ..ObfuscationConfig::disabled() 54 | }) 55 | .optimization("O2") 56 | .compile(); 57 | 58 | result.assert_success(); 59 | let run = result.run(); 60 | run.assert_success(); 61 | } 62 | 63 | #[test] 64 | fn test_large_function_flatten() { 65 | ensure_plugin_built(); 66 | 67 | let result = CppCompileBuilder::new( 68 | fixture_path("edge_cases", "large_function.c", Language::C), 69 | "large_function", 70 | ) 71 | .config(ObfuscationConfig { 72 | flatten: Some(true), 73 | ..ObfuscationConfig::disabled() 74 | }) 75 | .compile(); 76 | 77 | result.assert_success(); 78 | let run = result.run(); 79 | run.assert_success(); 80 | } 81 | 82 | #[test] 83 | fn test_single_block_with_bcf() { 84 | ensure_plugin_built(); 85 | 86 | let result = CppCompileBuilder::new( 87 | fixture_path("edge_cases", "empty_function.c", Language::C), 88 | "single_block_bcf", 89 | ) 90 | .config(ObfuscationConfig { 91 | bogus_control_flow: Some(true), 92 | ..ObfuscationConfig::disabled() 93 | }) 94 | .compile(); 95 | 96 | result.assert_success(); 97 | let run = result.run(); 98 | run.assert_success(); 99 | } 100 | 101 | #[test] 102 | fn test_single_block_with_flatten() { 103 | ensure_plugin_built(); 104 | 105 | let result = CppCompileBuilder::new( 106 | fixture_path("edge_cases", "empty_function.c", Language::C), 107 | "single_block_flatten", 108 | ) 109 | .config(ObfuscationConfig { 110 | flatten: Some(true), 111 | ..ObfuscationConfig::disabled() 112 | }) 113 | .compile(); 114 | 115 | result.assert_success(); 116 | let run = result.run(); 117 | run.assert_success(); 118 | } 119 | 120 | #[test] 121 | fn test_single_block_with_indirect_branch() { 122 | ensure_plugin_built(); 123 | 124 | let result = CppCompileBuilder::new( 125 | fixture_path("edge_cases", "empty_function.c", Language::C), 126 | "single_block_indirect_branch", 127 | ) 128 | .config(ObfuscationConfig { 129 | indirect_branch: Some(true), 130 | ..ObfuscationConfig::disabled() 131 | }) 132 | .compile(); 133 | 134 | result.assert_success(); 135 | let run = result.run(); 136 | run.assert_success(); 137 | } 138 | -------------------------------------------------------------------------------- /tests/shuffle_blocks.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for shuffle blocks obfuscation. 2 | //! 3 | //! Tests various shuffle modes: 4 | //! - Random shuffle 5 | //! - Reverse shuffle 6 | //! - Rotate shuffle 7 | //! - Combined modes 8 | 9 | mod common; 10 | 11 | use crate::common::Language; 12 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 13 | 14 | fn shuffle_config(flags: &str) -> ObfuscationConfig { 15 | ObfuscationConfig { 16 | shuffle_blocks: Some(true), 17 | shuffle_blocks_flags: Some(flags.to_string()), 18 | ..ObfuscationConfig::disabled() 19 | } 20 | } 21 | 22 | fn get_baseline_output(name: &str) -> String { 23 | let result = CppCompileBuilder::new( 24 | fixture_path("shuffle_blocks", "shuffle_test.c", Language::C), 25 | &format!("shuffle_baseline_{}", name), 26 | ) 27 | .without_plugin() 28 | .compile(); 29 | 30 | result.assert_success(); 31 | result.run().stdout() 32 | } 33 | 34 | #[test] 35 | fn test_shuffle_blocks_random() { 36 | ensure_plugin_built(); 37 | 38 | let baseline = get_baseline_output("random"); 39 | 40 | let result = CppCompileBuilder::new( 41 | fixture_path("shuffle_blocks", "shuffle_test.c", Language::C), 42 | "shuffle_random", 43 | ) 44 | .config(shuffle_config("random")) 45 | .compile(); 46 | 47 | result.assert_success(); 48 | let run = result.run(); 49 | run.assert_success(); 50 | 51 | // Output should be identical despite block shuffling 52 | assert_eq!(run.stdout(), baseline, "Random shuffle changed program behavior"); 53 | } 54 | 55 | #[test] 56 | fn test_shuffle_blocks_reverse() { 57 | ensure_plugin_built(); 58 | 59 | let baseline = get_baseline_output("reverse"); 60 | 61 | let result = CppCompileBuilder::new( 62 | fixture_path("shuffle_blocks", "shuffle_test.c", Language::C), 63 | "shuffle_reverse", 64 | ) 65 | .config(shuffle_config("reverse")) 66 | .compile(); 67 | 68 | result.assert_success(); 69 | let run = result.run(); 70 | run.assert_success(); 71 | 72 | assert_eq!(run.stdout(), baseline, "Reverse shuffle changed program behavior"); 73 | } 74 | 75 | #[test] 76 | fn test_shuffle_blocks_rotate() { 77 | ensure_plugin_built(); 78 | 79 | let baseline = get_baseline_output("rotate"); 80 | 81 | let result = CppCompileBuilder::new( 82 | fixture_path("shuffle_blocks", "shuffle_test.c", Language::C), 83 | "shuffle_rotate", 84 | ) 85 | .config(shuffle_config("rotate")) 86 | .compile(); 87 | 88 | result.assert_success(); 89 | let run = result.run(); 90 | run.assert_success(); 91 | 92 | assert_eq!(run.stdout(), baseline, "Rotate shuffle changed program behavior"); 93 | } 94 | 95 | #[test] 96 | fn test_shuffle_blocks_combined() { 97 | ensure_plugin_built(); 98 | 99 | let baseline = get_baseline_output("combined"); 100 | 101 | let result = CppCompileBuilder::new( 102 | fixture_path("shuffle_blocks", "shuffle_test.c", Language::C), 103 | "shuffle_combined", 104 | ) 105 | .config(shuffle_config("reverse,rotate")) 106 | .compile(); 107 | 108 | result.assert_success(); 109 | let run = result.run(); 110 | run.assert_success(); 111 | 112 | assert_eq!(run.stdout(), baseline, "Combined shuffle changed program behavior"); 113 | } 114 | 115 | #[test] 116 | fn test_shuffle_with_split_basic_block() { 117 | ensure_plugin_built(); 118 | 119 | let baseline = get_baseline_output("split"); 120 | 121 | let config = ObfuscationConfig { 122 | shuffle_blocks: Some(true), 123 | shuffle_blocks_flags: Some("random".to_string()), 124 | split_basic_block: Some(true), 125 | ..ObfuscationConfig::disabled() 126 | }; 127 | 128 | let result = CppCompileBuilder::new( 129 | fixture_path("shuffle_blocks", "shuffle_test.c", Language::C), 130 | "shuffle_with_split", 131 | ) 132 | .config(config) 133 | .compile(); 134 | 135 | result.assert_success(); 136 | let run = result.run(); 137 | run.assert_success(); 138 | 139 | assert_eq!(run.stdout(), baseline, "Shuffle with split changed program behavior"); 140 | } 141 | -------------------------------------------------------------------------------- /src/aotu/shuffle_blocks/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Config, ShuffleBlocksConfig, ShuffleBlocksFlags}; 2 | use crate::pass_registry::{AmiceFunctionPass, AmicePass, AmicePassFlag}; 3 | use amice_llvm::inkwell2::FunctionExt; 4 | use amice_macro::amice; 5 | use llvm_plugin::PreservedAnalyses; 6 | use llvm_plugin::inkwell::module::Module; 7 | use llvm_plugin::inkwell::values::FunctionValue; 8 | use log::{Level, log_enabled}; 9 | 10 | #[amice( 11 | priority = 970, 12 | name = "ShuffleBlocks", 13 | flag = AmicePassFlag::PipelineStart | AmicePassFlag::FunctionLevel, 14 | config = ShuffleBlocksConfig, 15 | )] 16 | #[derive(Default)] 17 | pub struct ShuffleBlocks {} 18 | 19 | impl AmicePass for ShuffleBlocks { 20 | fn init(&mut self, cfg: &Config, _flag: AmicePassFlag) { 21 | self.default_config = cfg.shuffle_blocks.clone(); 22 | } 23 | 24 | fn do_pass(&self, module: &mut Module<'_>) -> anyhow::Result { 25 | let mut executed = false; 26 | for function in module.get_functions() { 27 | if function.is_inline_marked() || function.is_llvm_function() || function.is_undef_function() { 28 | continue; 29 | } 30 | 31 | let cfg = self.parse_function_annotations(module, function)?; 32 | if !cfg.enable { 33 | continue; 34 | } 35 | 36 | if function.count_basic_blocks() <= 1 { 37 | continue; 38 | } 39 | 40 | if let Err(e) = handle_function(function, cfg.flags) { 41 | error!("failed to shuffle basic blocks: {}", e); 42 | } 43 | executed = true; 44 | } 45 | 46 | if !executed { 47 | return Ok(PreservedAnalyses::All); 48 | } 49 | 50 | Ok(PreservedAnalyses::None) 51 | } 52 | } 53 | 54 | fn handle_function(function: FunctionValue<'_>, flags: ShuffleBlocksFlags) -> anyhow::Result<()> { 55 | let mut blocks = function.get_basic_blocks(); 56 | if blocks.is_empty() || blocks.len() <= 3 { 57 | return Ok(()); 58 | } 59 | 60 | let entry_block = function 61 | .get_entry_block() 62 | .ok_or_else(|| anyhow::anyhow!("failed to get entry block"))?; 63 | blocks.retain(|block| block != &entry_block); 64 | 65 | if log_enabled!(Level::Debug) { 66 | debug!( 67 | "function {:?} has {:?} basic blocks: {:?}", 68 | function.get_name(), 69 | blocks.len(), 70 | flags 71 | ); 72 | } 73 | 74 | if flags.contains(ShuffleBlocksFlags::Random) { 75 | // Shuffle blocks by Ylarod 76 | for i in (1..blocks.len()).rev() { 77 | let j = rand::random_range(0..=i); 78 | if i != j { 79 | // Move block at index i after block at index j 80 | // Get the next block after blocks[j] to use as insertion point 81 | let insert_after = if j < blocks.len() - 1 { Some(blocks[j]) } else { None }; 82 | 83 | if let Some(after_block) = insert_after { 84 | let _ = blocks[i].move_after(after_block); 85 | } else { 86 | // Move to end if j is the last block 87 | let _ = blocks[i].move_after(blocks[j]); 88 | } 89 | } 90 | } 91 | } 92 | 93 | if flags.contains(ShuffleBlocksFlags::Reverse) { 94 | // Reverse the order of blocks (excluding entry block which is already removed from blocks) 95 | // Move blocks from back to front to reverse the order 96 | for i in (1..blocks.len()).rev() { 97 | let first_block = blocks[0]; 98 | let _ = blocks[i].move_before(first_block); 99 | } 100 | } 101 | 102 | if flags.contains(ShuffleBlocksFlags::Rotate) { 103 | // Rotate blocks left by 1 (move first block to end) 104 | if !blocks.is_empty() { 105 | let first_block = blocks[0]; 106 | let last_block = blocks[blocks.len() - 1]; 107 | let _ = first_block.move_after(last_block); 108 | } 109 | } 110 | 111 | if function.verify_function_bool() { 112 | warn!("function {:?} is not verified", function.get_name()); 113 | } 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /tests/rust_string_encryption.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use common::{ObfuscationConfig, RustCompileBuilder}; 4 | use serial_test::serial; 5 | use std::fs; 6 | 7 | #[test] 8 | #[serial] 9 | fn test_rust_string_encryption_basic() { 10 | common::ensure_plugin_built(); 11 | 12 | let project_dir = common::project_root() 13 | .join("tests") 14 | .join("rust") 15 | .join("string_encryption"); 16 | 17 | // Test with obfuscation enabled 18 | let mut config = ObfuscationConfig::default(); 19 | config.string_encryption = Some(true); 20 | config.string_only_llvm_string = Some(false); 21 | config.string_algorithm = Some("xor".to_string()); 22 | 23 | let result = RustCompileBuilder::new(&project_dir, "string_encryption_test") 24 | .config(config) 25 | .compile(); 26 | 27 | result.assert_success(); 28 | 29 | let run_result = result.run(); 30 | run_result.assert_success(); 31 | 32 | let output = run_result.stdout(); 33 | //fs::write("aaa.log", &run_result.output.stdout).unwrap(); 34 | 35 | println!("Output:\n{}", output); 36 | 37 | assert!(output.contains("Hello, World!")); 38 | assert!(output.contains("Message length: 24")); 39 | assert!(output.contains("Welcome, User!")); 40 | assert!(output.contains("Obfuscated String")); 41 | assert!(output.contains("你好,世界!🦀")); 42 | assert!(output.contains("All tests completed!")); 43 | } 44 | // 45 | // #[test] 46 | // #[serial] 47 | // fn test_rust_string_encryption_simd() { 48 | // common::ensure_plugin_built(); 49 | // 50 | // let project_dir = common::project_root() 51 | // .join("tests") 52 | // .join("rust") 53 | // .join("string_encryption"); 54 | // 55 | // // Test with obfuscation enabled 56 | // let mut config = ObfuscationConfig::default(); 57 | // config.string_encryption = Some(true); 58 | // config.string_only_llvm_string = Some(false); 59 | // config.string_algorithm = Some("simd_xor".to_string()); 60 | // 61 | // let result = RustCompileBuilder::new(&project_dir, "string_encryption_test") 62 | // .config(config) 63 | // .compile(); 64 | // 65 | // result.assert_success(); 66 | // 67 | // let run_result = result.run(); 68 | // run_result.assert_success(); 69 | // 70 | // let output = run_result.stdout(); 71 | // assert!(output.contains("Hello, World!")); 72 | // assert!(output.contains("Message length: 24")); 73 | // assert!(output.contains("Welcome, User!")); 74 | // assert!(output.contains("Obfuscated String")); 75 | // assert!(output.contains("你好,世界!🦀")); 76 | // assert!(output.contains("All tests completed!")); 77 | // } 78 | // 79 | // #[test] 80 | // #[serial] 81 | // fn test_rust_string_encryption_vs_baseline() { 82 | // common::ensure_plugin_built(); 83 | // 84 | // let project_dir = common::project_root() 85 | // .join("tests") 86 | // .join("rust") 87 | // .join("string_encryption"); 88 | // 89 | // // Compile without obfuscation (baseline) 90 | // let baseline_result = RustCompileBuilder::new(&project_dir, "string_encryption_test") 91 | // .without_plugin() 92 | // .use_stable() 93 | // .compile(); 94 | // 95 | // baseline_result.assert_success(); 96 | // let baseline_run = baseline_result.run(); 97 | // baseline_run.assert_success(); 98 | // let baseline_output = baseline_run.stdout(); 99 | // 100 | // // Compile with obfuscation 101 | // let mut config = ObfuscationConfig::default(); 102 | // config.string_encryption = Some(true); 103 | // config.string_only_llvm_string = Some(false); 104 | // config.string_algorithm = Some("xor".to_string()); 105 | // 106 | // let obfuscated_result = RustCompileBuilder::new(&project_dir, "string_encryption_test") 107 | // .config(config) 108 | // .compile(); 109 | // 110 | // obfuscated_result.assert_success(); 111 | // let obfuscated_run = obfuscated_result.run(); 112 | // obfuscated_run.assert_success(); 113 | // let obfuscated_output = obfuscated_run.stdout(); 114 | // 115 | // // Both versions should produce identical output 116 | // assert_eq!( 117 | // baseline_output, obfuscated_output, 118 | // "Baseline and obfuscated outputs differ" 119 | // ); 120 | // } 121 | -------------------------------------------------------------------------------- /tests/varargs.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for varargs function handling. 2 | //! 3 | //! This module tests that obfuscation passes correctly handle: 4 | //! - printf, sprintf, fprintf (standard varargs functions) 5 | //! - Custom varargs functions 6 | //! - Functions that call varargs functions 7 | //! 8 | //! Critical tests: 9 | //! - indirect_call should not break varargs calls 10 | //! - function_wrapper should not wrap varargs functions 11 | //! - clone_function should not clone varargs functions 12 | 13 | mod common; 14 | 15 | use crate::common::Language; 16 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 17 | 18 | #[test] 19 | fn test_varargs_with_indirect_call() { 20 | ensure_plugin_built(); 21 | 22 | // This is a CRITICAL test - indirect_call currently does NOT properly handle varargs 23 | // This test will likely FAIL or produce incorrect printf output 24 | let result = CppCompileBuilder::new( 25 | fixture_path("varargs", "varargs_indirect_call.c", Language::C), 26 | "varargs_indirect_call", 27 | ) 28 | .config(ObfuscationConfig { 29 | indirect_call: Some(true), 30 | ..ObfuscationConfig::disabled() 31 | }) 32 | .compile(); 33 | 34 | result.assert_success(); 35 | let run = result.run(); 36 | run.assert_success(); 37 | } 38 | 39 | #[test] 40 | fn test_varargs_with_indirect_call_optimized() { 41 | ensure_plugin_built(); 42 | 43 | let result = CppCompileBuilder::new( 44 | fixture_path("varargs", "varargs_indirect_call.c", Language::C), 45 | "varargs_indirect_call_o2", 46 | ) 47 | .config(ObfuscationConfig { 48 | indirect_call: Some(true), 49 | ..ObfuscationConfig::disabled() 50 | }) 51 | .optimization("O2") 52 | .compile(); 53 | 54 | result.assert_success(); 55 | let run = result.run(); 56 | run.assert_success(); 57 | } 58 | 59 | #[test] 60 | fn test_varargs_with_function_wrapper() { 61 | ensure_plugin_built(); 62 | 63 | // function_wrapper should detect varargs and skip wrapping 64 | let result = CppCompileBuilder::new( 65 | fixture_path("varargs", "varargs_function_wrapper.c", Language::C), 66 | "varargs_function_wrapper", 67 | ) 68 | .config(ObfuscationConfig { 69 | function_wrapper: Some(true), 70 | ..ObfuscationConfig::disabled() 71 | }) 72 | .compile(); 73 | 74 | result.assert_success(); 75 | let run = result.run(); 76 | run.assert_success(); 77 | } 78 | 79 | #[test] 80 | fn test_varargs_with_function_wrapper_optimized() { 81 | ensure_plugin_built(); 82 | 83 | let result = CppCompileBuilder::new( 84 | fixture_path("varargs", "varargs_function_wrapper.c", Language::C), 85 | "varargs_function_wrapper_o2", 86 | ) 87 | .config(ObfuscationConfig { 88 | function_wrapper: Some(true), 89 | ..ObfuscationConfig::disabled() 90 | }) 91 | .optimization("O2") 92 | .compile(); 93 | 94 | result.assert_success(); 95 | let run = result.run(); 96 | run.assert_success(); 97 | } 98 | 99 | #[test] 100 | fn test_varargs_with_clone_function() { 101 | ensure_plugin_built(); 102 | 103 | // clone_function should detect varargs and skip cloning 104 | let result = CppCompileBuilder::new( 105 | fixture_path("varargs", "varargs_clone_function.c", Language::C), 106 | "varargs_clone_function", 107 | ) 108 | .config(ObfuscationConfig { 109 | clone_function: Some(true), 110 | ..ObfuscationConfig::disabled() 111 | }) 112 | .compile(); 113 | 114 | result.assert_success(); 115 | let run = result.run(); 116 | run.assert_success(); 117 | } 118 | 119 | #[test] 120 | fn test_varargs_combined_passes() { 121 | ensure_plugin_built(); 122 | 123 | // Test multiple passes with varargs 124 | let result = CppCompileBuilder::new( 125 | fixture_path("varargs", "varargs_indirect_call.c", Language::C), 126 | "varargs_combined", 127 | ) 128 | .config(ObfuscationConfig { 129 | indirect_call: Some(true), 130 | function_wrapper: Some(true), 131 | flatten: Some(true), 132 | ..ObfuscationConfig::disabled() 133 | }) 134 | .compile(); 135 | 136 | result.assert_success(); 137 | let run = result.run(); 138 | run.assert_success(); 139 | } 140 | -------------------------------------------------------------------------------- /tests/exception_handling.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests for C++ exception handling with obfuscation. 2 | //! 3 | //! This module tests that obfuscation passes correctly handle: 4 | //! - C++ exceptions (throw/catch) 5 | //! - Invoke instructions 6 | //! - Landing pads 7 | //! - Multiple catch blocks 8 | //! - Nested exceptions 9 | //! 10 | //! Critical test: bogus_control_flow should skip functions with exception handling 11 | 12 | mod common; 13 | 14 | use crate::common::Language; 15 | use common::{CppCompileBuilder, ObfuscationConfig, ensure_plugin_built, fixture_path}; 16 | 17 | #[test] 18 | fn test_cpp_exception_with_bcf() { 19 | ensure_plugin_built(); 20 | 21 | // This is a CRITICAL test - BCF currently does NOT check for exception handling 22 | // This test will likely FAIL or produce incorrect results 23 | let result = CppCompileBuilder::new( 24 | fixture_path("exception_handling", "cpp_exception_bcf.cpp", Language::Cpp), 25 | "cpp_exception_bcf", 26 | ) 27 | .config(ObfuscationConfig { 28 | bogus_control_flow: Some(true), 29 | ..ObfuscationConfig::disabled() 30 | }) 31 | .compile(); 32 | 33 | result.assert_success(); 34 | let run = result.run(); 35 | run.assert_success(); 36 | } 37 | 38 | #[test] 39 | fn test_cpp_exception_with_bcf_optimized() { 40 | ensure_plugin_built(); 41 | 42 | let result = CppCompileBuilder::new( 43 | fixture_path("exception_handling", "cpp_exception_bcf.cpp", Language::Cpp), 44 | "cpp_exception_bcf_o2", 45 | ) 46 | .config(ObfuscationConfig { 47 | bogus_control_flow: Some(true), 48 | ..ObfuscationConfig::disabled() 49 | }) 50 | .optimization("O2") 51 | .compile(); 52 | 53 | result.assert_success(); 54 | let run = result.run(); 55 | run.assert_success(); 56 | } 57 | 58 | #[test] 59 | fn test_cpp_exception_with_flatten() { 60 | ensure_plugin_built(); 61 | 62 | // Flatten should properly detect exception handling and skip the function 63 | let result = CppCompileBuilder::new( 64 | fixture_path("exception_handling", "cpp_exception_flatten.cpp", Language::Cpp), 65 | "cpp_exception_flatten", 66 | ) 67 | .config(ObfuscationConfig { 68 | flatten: Some(true), 69 | ..ObfuscationConfig::disabled() 70 | }) 71 | .compile(); 72 | 73 | result.assert_success(); 74 | let run = result.run(); 75 | run.assert_success(); 76 | } 77 | 78 | #[test] 79 | fn test_cpp_exception_with_flatten_optimized() { 80 | ensure_plugin_built(); 81 | 82 | let result = CppCompileBuilder::new( 83 | fixture_path("exception_handling", "cpp_exception_flatten.cpp", Language::Cpp), 84 | "cpp_exception_flatten_o2", 85 | ) 86 | .config(ObfuscationConfig { 87 | flatten: Some(true), 88 | ..ObfuscationConfig::disabled() 89 | }) 90 | .optimization("O2") 91 | .compile(); 92 | 93 | result.assert_success(); 94 | let run = result.run(); 95 | run.assert_success(); 96 | } 97 | 98 | #[test] 99 | fn test_cpp_exception_with_indirect_branch() { 100 | ensure_plugin_built(); 101 | 102 | // Indirect branch has partial EH detection - should handle this correctly 103 | let result = CppCompileBuilder::new( 104 | fixture_path("exception_handling", "cpp_exception_indirect_branch.cpp", Language::Cpp), 105 | "cpp_exception_indirect_branch", 106 | ) 107 | .config(ObfuscationConfig { 108 | indirect_branch: Some(true), 109 | ..ObfuscationConfig::disabled() 110 | }) 111 | .compile(); 112 | 113 | result.assert_success(); 114 | let run = result.run(); 115 | run.assert_success(); 116 | } 117 | 118 | #[test] 119 | fn test_cpp_exception_combined() { 120 | ensure_plugin_built(); 121 | 122 | // Test multiple passes with exception handling 123 | let result = CppCompileBuilder::new( 124 | fixture_path("exception_handling", "cpp_exception_bcf.cpp", Language::Cpp), 125 | "cpp_exception_combined", 126 | ) 127 | .config(ObfuscationConfig { 128 | flatten: Some(true), 129 | bogus_control_flow: Some(true), 130 | indirect_branch: Some(true), 131 | ..ObfuscationConfig::disabled() 132 | }) 133 | .compile(); 134 | 135 | result.assert_success(); 136 | let run = result.run(); 137 | run.assert_success(); 138 | } 139 | -------------------------------------------------------------------------------- /amice-llvm/src/inkwell2/basic_block.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi::{amice_get_first_insertion_pt, amice_phi_node_replace_incoming_block_with, amice_split_basic_block}; 2 | use crate::inkwell2::{InstructionExt, LLVMBasicBlockRefExt, LLVMValueRefExt}; 3 | use crate::to_c_str; 4 | use inkwell::basic_block::BasicBlock; 5 | use inkwell::llvm_sys::core::LLVMAddIncoming; 6 | use inkwell::llvm_sys::prelude::{LLVMBasicBlockRef, LLVMValueRef}; 7 | use inkwell::values::{AsValueRef, InstructionOpcode, InstructionValue, PhiValue}; 8 | 9 | pub trait BasicBlockExt<'ctx> { 10 | fn split_basic_block(&self, inst: InstructionValue<'ctx>, name: &str, before: bool) -> Option>; 11 | 12 | fn get_first_insertion_pt(&self) -> InstructionValue<'ctx>; 13 | 14 | #[deprecated(since = "0.1.0", note = "no tested")] 15 | fn remove_predecessor(&self, pred: BasicBlock<'ctx>); 16 | 17 | fn fix_phi_node(&self, old_pred: BasicBlock<'ctx>, new_pred: BasicBlock<'ctx>); 18 | 19 | #[deprecated(since = "0.1.0", note = "no tested")] 20 | fn replace_phi_node(&self, old_pred: BasicBlock<'ctx>, new_pred: BasicBlock<'ctx>); 21 | } 22 | 23 | impl<'ctx> BasicBlockExt<'ctx> for BasicBlock<'ctx> { 24 | fn split_basic_block(&self, inst: InstructionValue<'ctx>, name: &str, before: bool) -> Option> { 25 | let c_str_name = to_c_str(name); 26 | let new_block = unsafe { 27 | amice_split_basic_block( 28 | self.as_mut_ptr() as LLVMBasicBlockRef, 29 | inst.as_value_ref() as LLVMValueRef, 30 | c_str_name.as_ptr(), 31 | if before { 1 } else { 0 }, 32 | ) 33 | }; 34 | let value = new_block as LLVMBasicBlockRef; 35 | value.into_basic_block() 36 | } 37 | 38 | fn get_first_insertion_pt(&self) -> InstructionValue<'ctx> { 39 | (unsafe { amice_get_first_insertion_pt(self.as_mut_ptr() as LLVMBasicBlockRef) } as LLVMValueRef) 40 | .into_instruction_value() 41 | } 42 | 43 | fn remove_predecessor(&self, pred: BasicBlock<'ctx>) { 44 | unsafe { 45 | crate::ffi::amice_basic_block_remove_predecessor( 46 | self.as_mut_ptr() as LLVMBasicBlockRef, 47 | pred.as_mut_ptr() as LLVMBasicBlockRef, 48 | ) 49 | } 50 | } 51 | 52 | fn fix_phi_node(&self, old_pred: BasicBlock<'ctx>, new_pred: BasicBlock<'ctx>) { 53 | for phi in self.get_first_instruction().iter() { 54 | if phi.get_opcode() != InstructionOpcode::Phi { 55 | continue; 56 | } 57 | 58 | let phi = unsafe { PhiValue::new(phi.as_value_ref()) }; 59 | let incoming_vec = phi 60 | .get_incomings() 61 | .filter_map(|(value, pred)| { 62 | if pred == old_pred { 63 | (value, new_pred).into() 64 | } else { 65 | None 66 | } 67 | }) 68 | .collect::>(); 69 | 70 | let (mut values, mut basic_blocks): (Vec, Vec) = { 71 | incoming_vec 72 | .iter() 73 | .map(|&(v, bb)| (v.as_value_ref() as LLVMValueRef, bb.as_mut_ptr() as LLVMBasicBlockRef)) 74 | .unzip() 75 | }; 76 | 77 | unsafe { 78 | LLVMAddIncoming( 79 | phi.as_value_ref(), 80 | values.as_mut_ptr(), 81 | basic_blocks.as_mut_ptr(), 82 | incoming_vec.len() as u32, 83 | ); 84 | } 85 | } 86 | } 87 | 88 | fn replace_phi_node(&self, old_pred: BasicBlock<'ctx>, new_pred: BasicBlock<'ctx>) { 89 | for phi in self.get_first_instruction().iter() { 90 | if phi.get_opcode() != InstructionOpcode::Phi { 91 | continue; 92 | } 93 | 94 | let phi = phi.into_phi_inst(); 95 | unsafe { 96 | amice_phi_node_replace_incoming_block_with( 97 | phi.as_value_ref() as LLVMValueRef, 98 | old_pred.as_mut_ptr() as LLVMBasicBlockRef, 99 | new_pred.as_mut_ptr() as LLVMBasicBlockRef, 100 | ) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amice 2 | 3 | Amice 是一个基于 **llvm-plugin-rs** 构建的 LLVM Pass 插件项目,可通过 `clang -fpass-plugin` 方式注入到编译流程中。 4 | 5 | --- 6 | 7 | ## 快速上手 8 | 9 | 1. **构建插件** 10 | 11 | ```bash 12 | # 如需调试日志请解除注释 13 | # export RUST_LOG=debug 14 | cargo build --release 15 | # 生成的动态库位于 target/release/libamice.so 16 | ``` 17 | 18 | 2. **编译并注入 Pass** 19 | 20 | ```bash 21 | clang -fpass-plugin=libamice.so your_source.c -o your_source 22 | ``` 23 | 24 | --- 25 | 26 | ## 支持的混淆 27 | 28 | | 混淆 | C/C++ | Rust | ObjC | 29 | |:---------------|:-----:|:----:|:----:| 30 | | 字符串加密 | ✅ | ⏳ | ⏳ | 31 | | 间接调用混淆 | ✅ | ⏳ | ❌ | 32 | | 间接跳转混淆 | ✅ | ⏳ | ❌ | 33 | | 切割基本块 | ✅ | ⏳ | ❌ | 34 | | switch 降级 | ✅ | ⏳ | ❌ | 35 | | 扁平化控制流 (VM) | ✅ | ⏳ | ❌ | 36 | | 控制流平坦化 | ✅ | ⏳ | ❌ | 37 | | MBA 算术混淆 | ✅ | ⏳ | ❌ | 38 | | 虚假控制流混淆 | ✅ | ⏳ | ❌ | 39 | | 函数包装 | ✅ | ⏳ | ❌ | 40 | | 常参特化克隆混淆 | ✅ | ⏳ | ❌ | 41 | | 别名访问混淆 | ✅ | ⏳ | ❌ | 42 | | 自定义调用约定 | ⏳ | ⏳ | ❌ | 43 | | 延时偏移加载 (AMA) | ✅ | ⏳ | ❌ | 44 | | 反Class导出 | ❌ | ❌ | ⏳ | 45 | | 参数结构化混淆 (PAO) | ✅ | ⏳ | ❌ | 46 | | 指令虚拟化 | ⏳ | ⏳ | ❌ | 47 | | 函数分片 (BB2FUNC) | ✅ | ⏳ | ❌ | 48 | 49 | > 说明: 50 | > - ✅ 已支持 51 | > - ⏳ 进行中 / 计划中 / 未测试 52 | > - ❌ 暂未规划 53 | 54 | ## 运行时环境变量 55 | 56 | 详细说明请参阅: 57 | 58 | 59 | --- 60 | 61 | ## 构建指南 62 | 63 | ### 1. Linux / macOS 64 | 65 | > 要求 LLVM 工具链支持 **动态链接** LLVM 库。 66 | > 推荐使用系统包管理器安装。 67 | 68 | - Debian / Ubuntu 69 | 70 | ```bash 71 | sudo apt install llvm-14 72 | ``` 73 | 74 | - Homebrew 75 | 76 | ```bash 77 | brew install llvm@14 78 | ``` 79 | 80 | 如使用自编译或自解压版本,请手动配置路径: 81 | 82 | ```bash 83 | # 假设 LLVM 安装在 ~/llvm 84 | export PATH="$PATH:$HOME/llvm/bin" 85 | # 或者 86 | export LLVM_SYS_140_PREFIX="$HOME/llvm" 87 | ``` 88 | 89 | #### [问题排查](docs/Troubleshooting.md) | [LLVM 安装指南](docs/LLVMSetup.md) 90 | 91 | ### 2. Windows 92 | 93 | 官方预编译的 LLVM 无法启用动态插件,需**自行编译**或使用社区版本: 94 | 95 | 96 | ```powershell 97 | # 假设 LLVM 安装在 C:\llvm 98 | setx PATH "%PATH%;C:\llvm\bin" 99 | rem 或者 100 | setx LLVM_SYS_140_PREFIX "C:\llvm" 101 | ``` 102 | 103 | ### 3. Android NDK 104 | 105 | Android 自带 clang 支持动态加载 Pass,但缺少 `opt`。可采用“未精简版 clang”方案,参考: 106 | [Ylarod:NDK 加载 LLVM Pass](https://xtuly.cn/article/ndk-load-llvm-pass-plugin) 107 | 108 | ```bash 109 | # 以下示例基于 r522817 (NDK 25c) 110 | export CXX="/path/to/unstripped-clang/bin/clang++" 111 | export CXXFLAGS="-stdlib=libc++ -I/path/to/unstripped-clang/include/c++/v1" 112 | export LDFLAGS="-stdlib=libc++ -L/path/to/unstripped-clang/lib" 113 | 114 | # llvm-plugin-rs 18.1,对应 NDK clang 18.0 115 | export LLVM_SYS_181_PREFIX=/path/to/unstripped-clang 116 | 117 | # cargo build --release 118 | # ndk 25c is llvm-18-1 119 | cargo b --release --no-default-features --features llvm-18-1 120 | 121 | # 如遇找不到 libLLVM.so,可指定 LD_LIBRARY_PATH 122 | export LD_LIBRARY_PATH=/path/to/unstripped-clang/lib 123 | 124 | /path/to/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ 125 | -fpass-plugin=../target/release/libamice.so \ 126 | -Xclang -load -Xclang ../target/release/libamice.so \ 127 | luo.c -o luo 128 | ``` 129 | 130 | Download: [android-ndk-r25c Linux X64](https://github.com/fuqiuluo/amice/releases/tag/android-ndk-r25c) 131 | 132 | --- 133 | 134 | ## TODO 135 | 136 | - [ ] 内联模式(Inline)支持 137 | - [ ] 更多 Pass 示例 138 | - [ ] CI / CD 139 | 140 | --- 141 | 142 | ## 鸣谢 143 | 144 | - LLVM Project – 145 | - llvm-plugin-rs 146 | - 147 | - 148 | - Obfuscator-LLVM - 149 | - SsagePass – 150 | - Polaris-Obfuscator – 151 | - YANSOllvm - 152 | - 相关文章 153 | - MBA – 154 | - LLVM PassManager 变更及动态注册 – 155 | 156 | --- 157 | 158 | > © 2025-2026 Fuqiuluo & Contributors. 159 | > 使用遵循本仓库 LICENSE。 160 | -------------------------------------------------------------------------------- /src/aotu/basic_block_outlining/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::BasicBlockOutliningConfig; 2 | use crate::config::Config; 3 | use crate::pass_registry::{AmiceFunctionPass, AmicePass, AmicePassFlag}; 4 | use amice_llvm::code_extractor::CodeExtractor; 5 | use amice_llvm::inkwell2::{FunctionExt, VerifyResult}; 6 | use amice_macro::amice; 7 | use llvm_plugin::PreservedAnalyses; 8 | use llvm_plugin::inkwell::attributes::{Attribute, AttributeLoc}; 9 | use llvm_plugin::inkwell::module::Module; 10 | use llvm_plugin::inkwell::values::FunctionValue; 11 | use log::Level; 12 | 13 | #[amice( 14 | priority = 979, 15 | name = "BasicBlockOutlining", 16 | flag = AmicePassFlag::PipelineStart | AmicePassFlag::FunctionLevel, 17 | config = BasicBlockOutliningConfig, 18 | )] 19 | #[derive(Default)] 20 | pub struct BasicBlockOutlining {} 21 | 22 | impl AmicePass for BasicBlockOutlining { 23 | fn init(&mut self, cfg: &Config, _flag: AmicePassFlag) { 24 | self.default_config = cfg.basic_block_outlining.clone(); 25 | } 26 | 27 | fn do_pass(&self, module: &mut Module<'_>) -> anyhow::Result { 28 | let mut functions = Vec::new(); 29 | for function in module.get_functions() { 30 | if function.is_undef_function() || function.is_llvm_function() && function.is_inline_marked() { 31 | continue; 32 | } 33 | 34 | let cfg = self.parse_function_annotations(module, function)?; 35 | if !cfg.enable { 36 | continue; 37 | } 38 | 39 | let mut inst_count = 0; 40 | for bb in function.get_basic_blocks() { 41 | inst_count += bb.get_instructions().count(); 42 | } 43 | 44 | if inst_count <= 8 { 45 | continue; 46 | } 47 | 48 | functions.push((function, cfg)); 49 | } 50 | 51 | if functions.is_empty() { 52 | return Ok(PreservedAnalyses::All); 53 | } 54 | 55 | for (function, cfg) in functions { 56 | if let Err(e) = do_outline(module, function, cfg.max_extractor_size) { 57 | error!("outline func {:?} failed: {}", function.get_name(), e); 58 | } 59 | 60 | if let VerifyResult::Broken(e) = function.verify_function() { 61 | error!("function {:?} verify failed: {}", function.get_name(), e); 62 | } 63 | } 64 | 65 | Ok(PreservedAnalyses::None) 66 | } 67 | } 68 | 69 | fn do_outline<'a>( 70 | module: &mut Module<'_>, 71 | function: FunctionValue<'a>, 72 | max_extractor_size: usize, 73 | ) -> anyhow::Result<()> { 74 | let mut bbs = function 75 | .get_basic_blocks() 76 | .iter() 77 | .map(|bb| *bb) 78 | .map(|bb| (bb.get_instructions().count(), bb)) 79 | .filter(|bb| bb.0 > 4) 80 | .filter(|bb| { 81 | let tmp_bbs = vec![bb.1]; 82 | let Some(ce) = CodeExtractor::new(&tmp_bbs) else { 83 | return false; 84 | }; 85 | ce.is_eligible() 86 | }) 87 | .collect::>(); 88 | 89 | if bbs.is_empty() { 90 | return Ok(()); 91 | } 92 | 93 | bbs.sort_unstable_by_key(|bb| std::cmp::Reverse(bb.0)); 94 | if bbs.len() > max_extractor_size { 95 | // 保留最大的前 max_extractor_size 个 96 | bbs.truncate(max_extractor_size); 97 | } 98 | 99 | if log::log_enabled!(Level::Debug) { 100 | debug!( 101 | "{:?} bbs to outline: {:?}", 102 | function.get_name(), 103 | bbs.iter().map(|bb| bb.0).collect::>() 104 | ); 105 | } 106 | 107 | for (_, bb) in bbs { 108 | let tmp_bbs = vec![bb]; 109 | let Some(ce) = CodeExtractor::new(&tmp_bbs) else { 110 | continue; 111 | }; 112 | 113 | if !ce.is_eligible() { 114 | continue; 115 | } 116 | 117 | if let Some(new_function) = ce.extract_code_region(function) { 118 | let ctx = module.get_context(); 119 | let noinline_attr = ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("noinline"), 0); 120 | new_function.add_attribute(AttributeLoc::Function, noinline_attr); 121 | } else { 122 | warn!("failed to extract code region from function {:?}", function.get_name()); 123 | } 124 | } 125 | 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /amice-llvm/src/annotate.rs: -------------------------------------------------------------------------------- 1 | use inkwell::llvm_sys::core::{LLVMGetAsString, LLVMGetNumOperands, LLVMGetOperand, LLVMGetSection}; 2 | use inkwell::llvm_sys::prelude::LLVMValueRef; 3 | use inkwell::values::{AsValueRef, BasicValueEnum, GlobalValue, StructValue}; 4 | use inkwell::{ 5 | module::Module, 6 | values::FunctionValue, 7 | }; 8 | use std::ffi::{CStr, CString, c_uint}; 9 | 10 | /// 读取给定函数在 llvm.global.annotations 里的注解 11 | pub(crate) fn read_function_annotate<'ctx>(module: &Module<'ctx>, func: FunctionValue<'ctx>) -> Result, &'static str> { 12 | let mut out = Vec::new(); 13 | 14 | let Some(global) = module.get_global("llvm.global.annotations") else { 15 | return Ok(out); 16 | }; 17 | 18 | let Some(ca) = global.get_initializer() else { 19 | return Ok(out); 20 | }; 21 | let ca_ref = ca.as_value_ref(); 22 | 23 | unsafe { 24 | let num_operands = LLVMGetNumOperands(ca_ref as LLVMValueRef); 25 | for i in 0..num_operands { 26 | let elem = LLVMGetOperand(ca_ref as LLVMValueRef, i as c_uint); 27 | if elem.is_null() { 28 | continue; 29 | } 30 | 31 | let constant_struct = StructValue::new(elem); 32 | if constant_struct.is_null() || constant_struct.count_fields() < 2 { 33 | continue; 34 | } 35 | 36 | if let Some(first_field) = constant_struct.get_field_at_index(0) 37 | && first_field.is_pointer_value() 38 | && first_field.as_value_ref() == func.as_value_ref() 39 | { 40 | for j in 1..constant_struct.count_fields() { 41 | let Some(field) = constant_struct.get_field_at_index(j) else { 42 | continue; 43 | }; 44 | 45 | if !field.is_pointer_value() || field.into_pointer_value().is_null() { 46 | continue; 47 | } 48 | 49 | let section = CStr::from_ptr(LLVMGetSection(field.as_value_ref() as LLVMValueRef)); 50 | let section = section.to_str().unwrap().to_string().to_lowercase(); 51 | 52 | if section != "llvm.metadata" { 53 | continue; 54 | } 55 | 56 | let global_string = GlobalValue::new(field.as_value_ref()); 57 | let Some(str_arr) = (match global_string 58 | .get_initializer() 59 | .ok_or("Invalid string field: initializer needed")? 60 | { 61 | BasicValueEnum::ArrayValue(arr) => Some(arr), 62 | BasicValueEnum::StructValue(stru) if stru.count_fields() <= 1 => { 63 | match stru.get_field_at_index(0).ok_or("Invalid string field")? { 64 | BasicValueEnum::ArrayValue(arr) => Some(arr), 65 | _ => None, 66 | } 67 | }, 68 | _ => None, 69 | }) else { 70 | eprintln!("Invalid string field: {:?}", field); 71 | continue; 72 | }; 73 | 74 | let mut len = 0; 75 | let ptr = LLVMGetAsString(str_arr.as_value_ref(), &mut len); 76 | if ptr.is_null() { 77 | continue; 78 | } 79 | let arr = std::slice::from_raw_parts::(ptr.cast(), len - 1); 80 | let c_str = CString::new(arr).unwrap(); 81 | out.push(c_str.to_string_lossy().into_owned()); 82 | } 83 | } else { 84 | continue; 85 | } 86 | } 87 | } 88 | 89 | Ok(out) 90 | } 91 | 92 | // @.str = private unnamed_addr constant [18 x i8] c"add(10, 20) = %d\0A\00", align 1 93 | // @.str.1 = private unnamed_addr constant [21 x i8] c"multiply(5, 6) = %d\0A\00", align 1 94 | // @.str.2 = private unnamed_addr constant [20 x i8] c"custom_calling_conv\00", section "llvm.metadata" 95 | // @.str.3 = private unnamed_addr constant [8 x i8] c"test1.c\00", section "llvm.metadata" 96 | // @llvm.global.annotations = appending global [2 x { ptr, ptr, ptr, i32, ptr }] [{ ptr, ptr, ptr, i32, ptr } { ptr @add, ptr @.str.2, ptr @.str.3, i32 6, ptr null }, { ptr, ptr, ptr, i32, ptr } { ptr @multiply, ptr @.str.2, ptr @.str.3, i32 11, ptr null }], section "llvm.metadata" 97 | -------------------------------------------------------------------------------- /docs/AndroidNDKSupport_zh_CN.md: -------------------------------------------------------------------------------- 1 | # Android NDK 支持 2 | 3 | ## 背景说明 4 | 5 | Android NDK 和上游的 LLVM Clang 版本存在不一致的问题。为了正确构建和使用 AMICE 插件,需要使用与 Android NDK 版本匹配的完整版 Clang。 6 | 7 | ## 查看 Android NDK 信息 8 | 9 | 首先,查看当前 Android NDK 使用的 LLVM 版本信息: 10 | 11 | ```bash 12 | cat $ANDROID_HOME/ndk/25.2.9519653/toolchains/llvm/prebuilt/linux-x86_64/AndroidVersion.txt 13 | ``` 14 | 15 | 输出内容示例: 16 | 17 | ``` 18 | 14.0.7 19 | based on r450784d1 20 | for additional information on LLVM revision and cherry-picks, see clang_source_info.md 21 | ``` 22 | 23 | ## 获取匹配的完整版 Clang 24 | 25 | 根据版本信息中的 `r450784d1`,访问 Google 的预构建 Clang 仓库找到对应分支: 26 | 27 | 🔗 [https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+log/refs/heads/master/clang-r450784d](https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+log/refs/heads/master/clang-r450784d) 28 | 29 | **详细下载教程**: [https://xtuly.cn/article/ndk-load-llvm-pass-plugin](https://xtuly.cn/article/ndk-load-llvm-pass-plugin) 30 | 31 | 下载完整版(未精简)的 Clang,然后使用该版本编译 AMICE,以获得与当前 Android NDK (Clang) 兼容的 AMICE 插件库文件。 32 | 33 | ## 构建脚本 34 | 35 | 以下是构建 AMICE 的示例脚本: 36 | 37 | ```bash 38 | # r522817是llvm18-1 39 | export LLVM_SYS_181_PREFIX=/home/fuqiuluo/下载/linux-x86-refs_heads_main-clang-r522817 40 | 41 | #cargo clean 42 | export CXX="/home/fuqiuluo/下载/linux-x86-refs_heads_main-clang-r522817/bin/clang++" 43 | export CXXFLAGS="-stdlib=libc++ -I/home/fuqiuluo/下载/linux-x86-refs_heads_main-clang-r522817/include/c++/v1" 44 | export LDFLAGS="-stdlib=libc++ -L/home/fuqiuluo/下载/linux-x86-refs_heads_main-clang-r522817/lib" 45 | 46 | cargo b --release --no-default-features --features llvm18-1,android-ndk 47 | ``` 48 | 49 | ## 编译使用方式 50 | 51 | ### 使用完整版 Clang 编译 52 | 53 | 构建成功后,可以直接使用完整版 Clang 编译源文件: 54 | 55 | ```bash 56 | # 设置库依赖路径,因为插件依赖 libLLVM.so 57 | export LD_LIBRARY_PATH="/home/fuqiuluo/下载/linux-x86-refs_heads_main-clang-r522817/lib" 58 | /home/fuqiuluo/下载/linux-x86-refs_heads_main-clang-r522817/bin/clang \ 59 | -fpass-plugin=../target/release/libamice.so \ 60 | test1.c -o test1 61 | ``` 62 | 63 | ### 使用 Android NDK Toolchain 编译 64 | 65 | 也可以直接使用 Android NDK toolchain 中的 Clang 进行编译: 66 | 67 | ```bash 68 | /home/fuqiuluo/android-kernel/android-ndk-r25c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \ 69 | -fpass-plugin=../target/release/libamice.so \ 70 | test1.c -o test_ndk 71 | ``` 72 | 73 | ## 配套资源 74 | 75 | **Android NDK r25c 配套版本**: [https://github.com/fuqiuluo/amice/releases/tag/android-ndk-r25c](https://github.com/fuqiuluo/amice/releases/tag/android-ndk-r25c) 76 | 77 | 配套构建命令: 78 | ```bash 79 | cargo b --release --no-default-features --features llvm18-1 80 | ``` 81 | 82 | ## 常见问题及解决方案 83 | 84 | ### 符号未定义错误 85 | 86 | 如果在载入时出现以下错误: 87 | ``` 88 | error: unable to load plugin './target/release/libamice.so': './target/release/libamice.so: undefined symbol: _ZTIN4llvm10CallbackVHE' 89 | ``` 90 | 91 | 尝试添加新的 feature: 92 | ```bash 93 | cargo b --release --no-default-features --features llvm18-1,android-ndk 94 | ``` 95 | 96 | ### 版本不匹配错误 97 | 98 | 如果出现类似错误: 99 | ``` 100 | error: unable to load plugin '/home/who/amice/target/release/libamice.so': 'Could not load library '/home/who/amice/target/release/libamice.so': /usr/lib/llvm-18/lib/libLLVM-18.so: version `LLVM_18' not found (required by /home/who/amice/target/release/libamice.so)' 101 | ``` 102 | 103 | **解决步骤:** 104 | 105 | 1. 检查 Clang 版本: 106 | ```bash 107 | $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --version 108 | ``` 109 | 110 | 2. 设置完整版 Clang 库路径: 111 | ```bash 112 | export LD_LIBRARY_PATH=/path/to/unstripped-clang/lib:$LD_LIBRARY_PATH 113 | ``` 114 | 115 | ## 集成到构建系统 116 | 117 | ### CMake 集成 118 | 119 | 在 CMake 中使用插件: 120 | 121 | ```cmake 122 | target_compile_options(${PROJECT_NAME} PRIVATE 123 | -fpass-plugin=${PLUGIN_PATH} 124 | -Xclang -load -Xclang ${PLUGIN_PATH} 125 | ) 126 | ``` 127 | 128 | ### Gradle 集成 129 | 130 | 配合 Gradle 使用: 131 | 132 | ```gradle 133 | externalNativeBuild { 134 | cmake { 135 | arguments( 136 | "-DCMAKE_VERBOSE_MAKEFILE=ON", 137 | "-DPLUGIN_PATH=/home/who/amice/target/release/libamice.so" 138 | ) 139 | targets += "[your target name]" 140 | } 141 | } 142 | ``` 143 | 144 | ## 调试和日志 145 | 146 | 构建成功运行后,可以启用日志查看详细信息: 147 | 148 | ```bash 149 | export RUST_LOG=info 150 | ``` 151 | 152 | ## 更多信息 153 | 154 | 更多详细信息请参考:[https://github.com/fuqiuluo/amice/wiki](https://github.com/fuqiuluo/amice/wiki) 155 | 156 | > 感谢 [Android1500](https://github.com/Android1500) 在 https://github.com/fuqiuluo/amice/discussions/55 的讨论与研究。 -------------------------------------------------------------------------------- /src/config/shuffle_blocks.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{EnvOverlay, bool_var}; 2 | use crate::pass_registry::FunctionAnnotationsOverlay; 3 | use amice_llvm::inkwell2::ModuleExt; 4 | use bitflags::bitflags; 5 | use llvm_plugin::inkwell::module::Module; 6 | use llvm_plugin::inkwell::values::FunctionValue; 7 | use log::warn; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | bitflags! { 11 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 12 | pub struct ShuffleBlocksFlags: u32 { 13 | /// Reverse the order of basic blocks 14 | const Reverse = 0b0000_0001; 15 | /// Randomly shuffle the order of basic blocks 16 | const Random = 0b0000_0010; 17 | /// Rotate basic blocks (left rotate by 1) 18 | const Rotate = 0b0000_0100; 19 | } 20 | } 21 | 22 | #[derive(Debug, Clone, Serialize, Deserialize)] 23 | #[serde(default)] 24 | pub struct ShuffleBlocksConfig { 25 | /// Whether to enable basic block shuffling obfuscation 26 | pub enable: bool, 27 | /// Configuration flags for different shuffling techniques 28 | #[serde(deserialize_with = "deserialize_shuffle_blocks_flags")] 29 | pub flags: ShuffleBlocksFlags, 30 | } 31 | 32 | impl Default for ShuffleBlocksConfig { 33 | fn default() -> Self { 34 | Self { 35 | enable: false, 36 | flags: ShuffleBlocksFlags::empty(), 37 | } 38 | } 39 | } 40 | 41 | impl EnvOverlay for ShuffleBlocksConfig { 42 | fn overlay_env(&mut self) { 43 | if std::env::var("AMICE_SHUFFLE_BLOCKS").is_ok() { 44 | self.enable = bool_var("AMICE_SHUFFLE_BLOCKS", self.enable); 45 | } 46 | if let Ok(v) = std::env::var("AMICE_SHUFFLE_BLOCKS_FLAGS") { 47 | self.flags |= parse_shuffle_blocks_flags(&v); 48 | } 49 | } 50 | } 51 | 52 | fn parse_shuffle_blocks_flags(value: &str) -> ShuffleBlocksFlags { 53 | let mut flags = ShuffleBlocksFlags::empty(); 54 | for x in value.split(',') { 55 | let x = x.trim().to_lowercase(); 56 | if x.is_empty() { 57 | continue; 58 | } 59 | match x.as_str() { 60 | "reverse" | "flip" => flags |= ShuffleBlocksFlags::Reverse, 61 | "random" | "shuffle" => flags |= ShuffleBlocksFlags::Random, 62 | "rotate" | "rotate_left" => flags |= ShuffleBlocksFlags::Rotate, 63 | _ => warn!("Unknown AMICE_SHUFFLE_BLOCKS_FLAGS: \"{x}\" , ignoring"), 64 | } 65 | } 66 | flags 67 | } 68 | 69 | #[derive(Deserialize)] 70 | #[serde(untagged)] 71 | enum ShuffleBlocksFlagsRepr { 72 | Bits(u32), 73 | One(String), 74 | Many(Vec), 75 | } 76 | 77 | pub(crate) fn deserialize_shuffle_blocks_flags<'de, D>(deserializer: D) -> Result 78 | where 79 | D: serde::Deserializer<'de>, 80 | { 81 | let repr = ShuffleBlocksFlagsRepr::deserialize(deserializer)?; 82 | let flags = match repr { 83 | ShuffleBlocksFlagsRepr::Bits(bits) => ShuffleBlocksFlags::from_bits_truncate(bits), 84 | ShuffleBlocksFlagsRepr::One(s) => parse_shuffle_blocks_flags(&s), 85 | ShuffleBlocksFlagsRepr::Many(arr) => { 86 | let mut all = ShuffleBlocksFlags::empty(); 87 | for s in arr { 88 | all |= parse_shuffle_blocks_flags(&s); 89 | } 90 | all 91 | }, 92 | }; 93 | Ok(flags) 94 | } 95 | 96 | impl FunctionAnnotationsOverlay for ShuffleBlocksConfig { 97 | type Config = Self; 98 | 99 | fn overlay_annotations<'a>( 100 | &self, 101 | module: &mut Module<'a>, 102 | function: FunctionValue<'a>, 103 | ) -> anyhow::Result { 104 | let mut cfg = self.clone(); 105 | let annotations_expr = module 106 | .read_function_annotate(function) 107 | .map_err(|e| anyhow::anyhow!("read function annotations failed: {}", e))? 108 | .join(" "); 109 | 110 | let mut parser = crate::config::eloquent_config::EloquentConfigParser::new(); 111 | parser 112 | .parse(&annotations_expr) 113 | .map_err(|e| anyhow::anyhow!("parse function annotations failed: {}", e))?; 114 | 115 | parser.get_bool("shuffle_blocks").map(|v| cfg.enable = v); 116 | parser.get_number("shuffle_blocks_flags").map(|v| { 117 | cfg.flags |= ShuffleBlocksFlags::from_bits_truncate(v); 118 | }); 119 | parser.get_string("shuffle_blocks_flags").map(|v| { 120 | cfg.flags |= parse_shuffle_blocks_flags(&v); 121 | }); 122 | 123 | Ok(cfg) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/pass_registry.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use bitflags::bitflags; 3 | use lazy_static::lazy_static; 4 | use llvm_plugin::inkwell::module::Module; 5 | use llvm_plugin::inkwell::values::FunctionValue; 6 | use llvm_plugin::{ModulePassManager, PreservedAnalyses}; 7 | use log::info; 8 | use serde::{Deserialize, Serialize}; 9 | use std::collections::HashMap; 10 | use std::sync::Mutex; 11 | 12 | lazy_static! { 13 | static ref REGISTRY: Mutex> = Mutex::new(Vec::new()); 14 | } 15 | 16 | pub trait AmicePassMetadata { 17 | fn name() -> &'static str; 18 | 19 | fn flag() -> AmicePassFlag; 20 | } 21 | 22 | pub trait AmicePass { 23 | #[allow(unused_variables)] 24 | fn init(&mut self, cfg: &Config, flag: AmicePassFlag); 25 | 26 | #[allow(unused_variables)] 27 | fn do_pass(&self, module: &mut Module<'_>) -> anyhow::Result; 28 | } 29 | 30 | pub trait AmiceFunctionPass { 31 | type Config; 32 | 33 | #[allow(unused_variables)] 34 | fn parse_function_annotations<'a>( 35 | &self, 36 | module: &mut Module<'a>, 37 | function: FunctionValue<'a>, 38 | ) -> anyhow::Result; 39 | } 40 | 41 | pub trait FunctionAnnotationsOverlay { 42 | type Config; 43 | 44 | fn overlay_annotations<'a>( 45 | &self, 46 | module: &mut Module<'a>, 47 | function: FunctionValue<'a>, 48 | ) -> anyhow::Result; 49 | } 50 | 51 | pub trait EnvOverlay { 52 | fn overlay_env(&mut self); 53 | } 54 | 55 | bitflags! { 56 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 57 | pub struct AmicePassFlag: u32 { 58 | /// add_pipeline_start_ep_callback 59 | const PipelineStart = 0b00001; 60 | /// add_optimizer_ep_callback 61 | const OptimizerLast = 0b00010; 62 | /// add_full_lto_ep_callback 63 | const FullLtoLast = 0b00100; 64 | /// Function Level Pass 65 | const FunctionLevel = 0b01000; 66 | /// Module Level Pass 67 | const ModuleLevel = 0b10000; 68 | } 69 | } 70 | 71 | #[derive(Clone, Copy)] 72 | pub struct PassEntry { 73 | pub name: &'static str, 74 | pub priority: i32, // 优先级越大越先执行 75 | pub add: fn(&Config, &mut ModulePassManager, AmicePassFlag) -> bool, 76 | } 77 | 78 | /// 供宏生成的注册函数调用 79 | pub fn register(entry: PassEntry) { 80 | let mut reg = REGISTRY.lock().expect("pass_registry: lock poisoned"); 81 | reg.push(entry); 82 | } 83 | 84 | /// 安装全部已注册的 pass:按优先级从高到低排序后依次调用 add 85 | pub fn install_all(cfg: &Config, manager: &mut ModulePassManager, flag: AmicePassFlag) { 86 | // 拷贝一份快照,避免持锁执行用户代码 87 | let mut entries = { 88 | let reg = REGISTRY.lock().expect("pass_registry: lock poisoned"); 89 | reg.clone() 90 | }; 91 | 92 | // 如果提供了显式顺序 pass_order,则按该顺序优先 93 | if let Some(order) = &cfg.pass_order.order { 94 | // name -> index 95 | let mut idx = HashMap::with_capacity(order.len()); 96 | for (i, name) in order.iter().enumerate() { 97 | idx.insert(name.as_str(), i as i32); 98 | } 99 | 100 | // 不运行不在显示顺序内的模块 101 | entries.retain(|e| idx.contains_key(e.name)); 102 | entries.sort_by(|a, b| { 103 | let a_idx = idx.get(a.name).unwrap_or(&i32::MAX); 104 | let b_idx = idx.get(b.name).unwrap_or(&i32::MAX); 105 | a_idx.cmp(b_idx) 106 | }); 107 | } else if let Some(priority_override) = &cfg.pass_order.priority_override { 108 | entries.sort_by_key(|e| { 109 | -if let Some(priority) = priority_override.get(e.name) { 110 | *priority 111 | } else { 112 | e.priority 113 | } 114 | }); 115 | } else { 116 | // priority 越大越先安装 117 | entries.sort_by_key(|e| -e.priority); 118 | } 119 | 120 | for e in entries { 121 | if (e.add)(cfg, manager, flag) { 122 | //info!("pass_registry: install pass: {} \twith {:?}", e.name, flag); 123 | } 124 | } 125 | } 126 | 127 | /// 打印注册表所有的Pass名称 128 | #[allow(dead_code)] 129 | pub fn print_all_registry() { 130 | let mut passes = REGISTRY 131 | .lock() 132 | .expect("pass_registry: lock poisoned") 133 | .iter() 134 | .map(|e| (e.name, e.priority)) 135 | .collect::>(); 136 | 137 | passes.sort_by_key(|e| -e.1); 138 | 139 | passes 140 | .iter() 141 | .for_each(|(name, priority)| info!("pass_registry: {} (priority: {})", name, priority)) 142 | } 143 | 144 | /// 清空注册表 145 | #[allow(dead_code)] 146 | pub fn clear() { 147 | let mut reg = REGISTRY.lock().expect("pass_registry: lock poisoned"); 148 | reg.clear(); 149 | } 150 | --------------------------------------------------------------------------------