├── .gitignore
├── .vscode_config
├── c_cpp_properties.json
└── settings.json
├── CMakeLists.txt
├── COPYING
├── CallObfuscatorHelpers
├── CMakeLists.txt
├── headers
│ ├── callDispatcher
│ │ ├── asm
│ │ │ └── callDispatcher.x64.hasm
│ │ └── callDispatcher.h
│ ├── common
│ │ ├── asm
│ │ │ └── common.x64.hasm
│ │ ├── common.h
│ │ ├── commonUtils.h
│ │ ├── debug.h
│ │ └── wintypes
│ │ │ └── typedefs.h
│ ├── pe
│ │ ├── peUtils.h
│ │ └── unwind
│ │ │ └── unwindUtils.h
│ ├── stackSpoof
│ │ ├── asm
│ │ │ └── stackSpoofHelpers.x64.hasm
│ │ └── stackSpoof.h
│ └── syscalls
│ │ └── syscalls.h
└── source
│ ├── callDispatcher
│ ├── asm
│ │ └── callDispatcher.x64.asm
│ └── callDispatcher.c
│ ├── common
│ └── commonUtils.c
│ ├── pe
│ ├── peUtils.c
│ └── unwind
│ │ └── unwindUtils.c
│ ├── stackSpoof
│ ├── asm
│ │ └── stackSpoofHelper.x64.asm
│ └── stackSpoof.c
│ └── syscalls
│ └── syscalls.c
├── CallObfuscatorPlugin
├── CMakeLists.txt
├── headers
│ ├── CallObfuscator.h
│ └── CallObfuscatorPass.h
└── source
│ ├── CallObfuscator.cpp
│ ├── CallObfuscatorPass.cpp
│ └── CallObfuscatorPluginRegister.cpp
├── README.md
└── example
├── callobfuscator.conf
├── headers
└── utils.h
├── makefile_example
└── source
├── main.c
└── utils.c
/.gitignore:
--------------------------------------------------------------------------------
1 | #Folders:
2 | **/internal-tests
3 | **/build/**
4 | **/.vscode
5 | **/.VSCodeCounter
6 |
7 |
8 | #C/C++
9 | # Compiled Object files
10 | *.slo
11 | *.lo
12 | *.o
13 | *.obj
14 |
15 | # Precompiled Headers
16 | *.gch
17 | *.pch
18 |
19 | # Compiled Dynamic libraries
20 | *.so
21 | *.dylib
22 | *.dll
23 |
24 | # Fortran module files
25 | *.mod
26 | *.smod
27 |
28 | # Compiled Static libraries
29 | *.lai
30 | *.la
31 | *.a
32 | *.lib
33 |
34 | # Executables
35 | *.exe
36 | *.out
37 | *.app
38 |
39 | # Cmake:
40 | CMakeLists.txt.user
41 | CMakeCache.txt
42 | CMakeFiles
43 | CMakeScripts
44 | Testing
45 | Makefile
46 | cmake_install.cmake
47 | install_manifest.txt
48 | compile_commands.json
49 | CTestTestfile.cmake
50 | _deps
--------------------------------------------------------------------------------
/.vscode_config/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Win32",
5 | "includePath": [
6 | "${workspaceFolder}/**",
7 | "${workspaceFolder}/CallObfuscatorPlugin/headers"
8 | ],
9 | "defines": [],
10 | "cStandard": "c17",
11 | "compilerPath": "",
12 | "cppStandard": "c++17",
13 | "intelliSenseMode": "windows-clang-x64",
14 | "configurationProvider": "ms-vscode.cmake-tools"
15 | }
16 | ],
17 | "version": 4
18 | }
--------------------------------------------------------------------------------
/.vscode_config/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cmake.configureOnOpen": false,
3 | "cmake.buildDirectory": "${workspaceFolder}/build",
4 | "cmake.installPrefix": "${userHome}/llvm-plugins",
5 | "cmake.configureArgs": [
6 | "-DCMAKE_VERBOSE_MAKEFILE=ON"
7 | ],
8 | "files.associations": {
9 | "typeinfo": "cpp",
10 | "*.hpp": "cpp",
11 | "*.h": "c",
12 | "*.hasm": "asm-intel-x86-generic",
13 | "*.cpp": "cpp",
14 | "*.c": "c",
15 | "*.asm": "asm-intel-x86-generic",
16 | "*.in": "cpp",
17 | "cstdlib": "c",
18 | "random": "c",
19 | "chrono": "c",
20 | "variant": "c",
21 | "format": "c"
22 | },
23 | "VSCodeCounter.exclude": [
24 | "**/.gitignore",
25 | "**/.vscode/**",
26 | "**/node_modules/**",
27 | "**/build/**",
28 | "**/*.json",
29 | "**/*.cmake",
30 | "**/.txt",
31 | "**/*.md"
32 | ],
33 | "VSCodeCounter.include": [
34 | "CallDispatcher/**",
35 | "CallObfuscatorPlugin/**"
36 | ]
37 | }
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.20.0)
2 |
3 | # I know this is not a "best practice", but CallDispatcher requires Clang to be compiled because of the use of builtins, also,
4 | # this only works for windows, and x64, so I dont see a problem on forcing some options.
5 | set(CMAKE_SYSTEM_NAME Windows)
6 | set(CMAKE_SYSTEM_PROCESSOR AMD64)
7 | set(CMAKE_C_COMPILER clang)
8 | set(CMAKE_CXX_COMPILER clang++)
9 | #TODO: Generate errors on bad arch/SO
10 |
11 | project(llvm-yx-callobfuscator LANGUAGES C CXX ASM_NASM VERSION 0.1.0)
12 |
13 | find_package(LLVM REQUIRED CONFIG)
14 |
15 | message(STATUS "Using install prefix ${CMAKE_INSTALL_PREFIX}")
16 | message(STATUS "Using LLVM ${LLVM_PACKAGE_VERSION}")
17 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
18 |
19 | add_definitions(${LLVM_DEFINITIONS})
20 | include_directories(${LLVM_INCLUDE_DIRS})
21 |
22 |
23 |
24 | add_subdirectory(CallObfuscatorPlugin)
25 | add_subdirectory(CallObfuscatorHelpers)
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set(CLANG_DEBUG_OPTIONS "-D__CALLOBF_DEBUG")
2 | # This migth be wrong, I only want to apply this options to the target defined here,
3 | # but Im applying them to the hole project...
4 | set(CLANG_RELEASE_OPTIONS "-O3 -nostdlib -ffunction-sections -Wall -Wextra -fno-ident")
5 |
6 | set(NASM_DEBUG_OPTIONS "")
7 | set(NASM_RELEASE_OPTIONS "")
8 |
9 | #find ./source -type f -name "*.c"
10 | #find ./source -type f -name "*.asm"
11 | add_library(CallObfuscatorHelpers STATIC
12 | source/callDispatcher/callDispatcher.c
13 | source/common/commonUtils.c
14 | source/pe/peUtils.c
15 | source/pe/unwind/unwindUtils.c
16 | source/stackSpoof/stackSpoof.c
17 | source/syscalls/syscalls.c
18 | source/callDispatcher/asm/callDispatcher.x64.asm
19 | source/stackSpoof/asm/stackSpoofHelper.x64.asm
20 | )
21 |
22 | target_include_directories(CallObfuscatorHelpers PRIVATE headers)
23 |
24 | set_target_properties(CallObfuscatorHelpers PROPERTIES PREFIX "lib")
25 | set_target_properties(CallObfuscatorHelpers PROPERTIES CXX_STANDARD 17)
26 |
27 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${CLANG_DEBUG_OPTIONS}")
28 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${CLANG_RELEASE_OPTIONS}")
29 | set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${CLANG_RELEASE_OPTIONS}")
30 |
31 | set(CMAKE_NASM_FLAGS_DEBUG "${CMAKE_NASM_FLAGS_DEBUG} ${NASM_DEBUG_OPTIONS}")
32 | set(CMAKE_NASM_FLAGS_RELEASE "${CMAKE_NASM_FLAGS_RELEASE} ${NASM_RELEASE_OPTIONS}")
33 | set(CMAKE_NASM_FLAGS_MINSIZEREL "${CMAKE_NASM_FLAGS_MINSIZEREL} ${NASM_RELEASE_OPTIONS}")
34 |
35 | install(TARGETS CallObfuscatorHelpers DESTINATION ${PROJECT_NAME}/plugin-helpers)
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/callDispatcher/asm/callDispatcher.x64.hasm:
--------------------------------------------------------------------------------
1 | ; @file callDispatcher.x64.hasm
2 | ; @author Alejandro González (@httpyxel)
3 | ; @brief Assembly utilities to dispatch windows native calls.
4 | ; @version 0.1
5 | ; @date 2024-01-14
6 | ;
7 | ; @copyright
8 | ; Copyright (C) 2024 Alejandro González
9 | ;
10 | ; This program is free software: you can redistribute it and/or modify
11 | ; it under the terms of the GNU General Public License as published by
12 | ; the Free Software Foundation, either version 3 of the License, or
13 | ; (at your option) any later version.
14 | ;
15 | ; This program is distributed in the hope that it will be useful,
16 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ; GNU General Public License for more details.
19 | ;
20 | ; You should have received a copy of the GNU General Public License
21 | ; along with this program. If not, see .
22 |
23 | %ifndef __CALL_DISPATCHER_ASM__
24 | %define __CALL_DISPATCHER_ASM__
25 |
26 | %include "common/asm/common.x64.hasm"
27 |
28 | [BITS 64]
29 |
30 | ; ============== Define symbols =================
31 | ; Exported functions
32 | GLOBAL __callobf_doCall
33 |
34 | EXTERN __callobf_setLastError
35 | EXTERN __callobf_buildSpoofedCallStack
36 |
37 | ; ============ Define struct types ==============
38 | struc DLL_ENTRY
39 | .name: resw 4
40 | alignb 4
41 | .address: resw 4
42 | alignb 4
43 | endstruc
44 |
45 | struc FUN_ENTRY
46 | .hash: resw 2
47 | alignb 4
48 | .moduleIndex: resw 2
49 | alignb 4
50 | .argCount: resw 2
51 | alignb 4
52 | .ssn: resw 2
53 | alignb 4
54 | .functionPtr: resw 4
55 | alignb 4
56 | endstruc
57 |
58 | struc DLL_TABLE
59 | .count: resw 2
60 | alignb 4
61 | .__padding: resw 2
62 | alignb 4
63 | .entries: resb DLL_ENTRY_size ; _size is defined for every struct
64 | alignb 4
65 | endstruc
66 |
67 | struc FUN_TABLE
68 | .count: resw 2
69 | alignb 4
70 | .__padding: resw 2
71 | alignb 4
72 | .entries: resb FUN_ENTRY_size
73 | alignb 4
74 | endstruc
75 |
76 | %endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/callDispatcher/callDispatcher.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file callDispatcher.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Funcionality to invoke windows native functions applying obfuscation.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _CALL_DISPATCHER_H_
26 | #define _CALL_DISPATCHER_H_
27 |
28 | #include "common/common.h"
29 | #include "common/wintypes/typedefs.h"
30 | #include "stackSpoof/stackSpoof.h"
31 |
32 | // ==============================================================================
33 | // ============================ STRUCT DEFINITIONS ==============================
34 |
35 | #pragma pack(push, 1)
36 | typedef struct _DLL_TABLE_ENTRY
37 | {
38 | PCHAR name;
39 | PVOID handle;
40 | } DLL_TABLE_ENTRY, *PDLL_TABLE_ENTRY;
41 |
42 | typedef struct _FUNCTION_TABLE_ENTRY
43 | {
44 | DWORD hash;
45 | DWORD moduleIndex;
46 | DWORD argCount;
47 | DWORD ssn; // As any other time that ssn is defined as a 4byte value, the
48 | // lower bytes are set to either 0xFF or 0x00, and it means if
49 | // it is actually a valid ssn. So we can check if the function
50 | // is a syscall by checking if any of thos bits are set to 1.
51 | PVOID functionPtr;
52 | } FUNCTION_TABLE_ENTRY, *PFUNCTION_TABLE_ENTRY;
53 |
54 | typedef struct _DLL_TABLE
55 | {
56 | DWORD count;
57 | DWORD __padding;
58 | DLL_TABLE_ENTRY entries[];
59 | } DLL_TABLE, *PDLL_TABLE;
60 |
61 | typedef struct _FUNCTION_TABLE
62 | {
63 | DWORD count;
64 | DWORD __padding;
65 | FUNCTION_TABLE_ENTRY entries[];
66 | } FUNCTION_TABLE, *PFUNCTION_TABLE;
67 | #pragma pack(pop)
68 |
69 | // ==============================================================================
70 | // =============================== GLOBALS ======================================
71 |
72 | // ==============================================================================
73 | // =========================== EXTERNAL GLOBALS =================================
74 |
75 | extern DLL_TABLE __callobf_dllTable;
76 | extern FUNCTION_TABLE __callobf_functionTable;
77 |
78 | // ==============================================================================
79 | // =========================== EXTERNAL FUNCTIONS ===============================
80 |
81 | /**
82 | * @brief Given all the information about a function call, generates an obfuscated
83 | * stack, and "applies indirect syscalling" if possible to the call.
84 | *
85 | * Note: Returning errors is still something that is not resolved.
86 | *
87 | * @param p_function Pointer to function to be called, in case of syscall, this is
88 | * the syscall instruction pointer.
89 | * @param ssn SSN of the syscall, in case it is.
90 | * @param isSyscall If it is a syscall.
91 | * @param argCount Number of arguments taken by the function to be called.
92 | * @param p_args Pointer to arguments to be passed to the function.
93 | * @param p_returnAddress Address containing the return address of the entry point
94 | * for the current thread.
95 | * @param p_globalFrameTable Pointer to stack spoof info to build the spoofed
96 | * stack with.
97 | * @return void* Return value of the called function.
98 | */
99 | extern void *__callobf_doCall(
100 | PVOID p_function,
101 | WORD ssn,
102 | DWORD isSyscall,
103 | DWORD argCount,
104 | PVOID p_args,
105 | PVOID p_returnAddress,
106 | PSTACK_SPOOF_INFO p_globalFrameTable);
107 |
108 | // ==============================================================================
109 | // ============================ PUBLIC FUNCTIONS ===============================
110 |
111 | /**
112 | * @brief Main function of the pass. This function can replace tha call to any
113 | * native windows x64 function, by giving it an index to the function
114 | * table containing the info about the function being replaced. All the
115 | * arguments of the function being replaced, must be passed after the index.
116 | *
117 | * @param index Index to the function table.
118 | * @param ... Function call arguments.
119 | * @return void* Return value of the replaced function.
120 | */
121 | void *__callobf_callDispatcher(DWORD32 index, ...);
122 |
123 | // ==============================================================================
124 | // =========================== PRIVATE FUNCTIONS ===============================
125 |
126 | /**
127 | * @brief Given a function entry, loads all it non initialized fields.
128 | *
129 | * @param p_fEntry Pointer to a function table entry.
130 | * @return void* Pointer to function, or NULL.
131 | */
132 | void *__callobf_loadFunction(PFUNCTION_TABLE_ENTRY p_fEntry);
133 |
134 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/common/asm/common.x64.hasm:
--------------------------------------------------------------------------------
1 |
2 | ; @file common.x64.hasm
3 | ; @author Alejandro González (@httpyxel)
4 | ; @brief Common utilities to work with windows x64.
5 | ; @version 0.1
6 | ; @date 2024-01-14
7 | ;
8 | ; @copyright
9 | ; Copyright (C) 2024 Alejandro González
10 | ;
11 | ; This program is free software: you can redistribute it and/or modify
12 | ; it under the terms of the GNU General Public License as published by
13 | ; the Free Software Foundation, either version 3 of the License, or
14 | ; (at your option) any later version.
15 | ;
16 | ; This program is distributed in the hope that it will be useful,
17 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ; GNU General Public License for more details.
20 | ;
21 | ; You should have received a copy of the GNU General Public License
22 | ; along with this program. If not, see .
23 |
24 | %ifndef _COMMON_HASM_
25 | %define _COMMON_HASM_
26 |
27 | ; ==================================================================
28 | ; Do not modify, required by future macros.
29 |
30 | %assign __OPEN_OPTS 1
31 | %assign __NEW_OPTS 0
32 |
33 | ; ==================================================================
34 | ; Generates compile error if format does not match
35 | ; Params:
36 | ; format: desired format
37 | %macro ENFORCE_FORMAT 1
38 | %ifnidn __OUTPUT_FORMAT__, %1
39 | %error "This file must be compiled for win64"
40 | %endif
41 | %endmacro
42 |
43 | ; ==================================================================
44 | ; C like builtins to obtain the size of an struct by its type name
45 |
46 | %define sizeof(type) type%+_size
47 | %define sizeoflist(type, count) sizeof(type)*count
48 |
49 | ; ==================================================================
50 | ; Translates to the pointer of the field for the given object
51 | ; Params:
52 | ; type: object type
53 | ; basePtr: pointer to the base of the object
54 | ; field: field to get the pointer to
55 | %define fieldPtr(type, basePtr, field) basePtr + type%+.%+field
56 |
57 | ; ==================================================================
58 | ; Sets options for function, use along end_opts.
59 | ; Params:
60 | ; option: One of the following
61 | ; > stored_regs: comma separated list of registers to be stored.
62 | ; > arg_count: number of arguments this function receives.
63 | ; > local_var_space: space to be reserved in the stack for local use.
64 | ; > max_callee_arg_count: number of arguments of the called function
65 | ; with the most arguments (if that makes any sense).
66 | ; > store_args: Boolean to set if we want arguments on rcx, rdx... to
67 | ; be stored in to the stack, this allows using
68 | ; stored_arg(n) to access params from 1 to 4.
69 | ; value: value to be used for the option.
70 | ;
71 | ; Example:
72 | ; set_opt stored_regs , rbp, rsi, rdi
73 | ; set_opt arg_count , 6
74 | ; set_opt local_var_space , 0x10
75 | ; set_opt max_callee_arg_count , 1
76 | ; set_opt store_args , true
77 | ;
78 | ; end_opts (see end_opts)
79 |
80 | %macro set_opt 1-*
81 | %ifn __OPEN_OPTS
82 | %error "Unexpected use of set_opt, make sure previous functions are correctly formed"
83 | %endif
84 |
85 | %ifn __NEW_OPTS
86 | __set_defaults
87 | %endif
88 |
89 | %assign __NEW_OPTS 1
90 |
91 | %ifidn %1, max_callee_arg_count
92 | __max_callee_arg_count_opt_parse %{2:-1}
93 | %exitmacro
94 | %endif
95 |
96 | %ifidn %1, local_var_space
97 | __local_var_space_opt_parse %{2:-1}
98 | %exitmacro
99 | %endif
100 |
101 | %ifidn %1, stored_regs
102 | __stored_regs_opt_parse %{2:-1}
103 | %exitmacro
104 | %endif
105 |
106 | %ifidn %1, store_args
107 | __store_args_opt_parse %{2:-1}
108 | %exitmacro
109 | %endif
110 |
111 | %ifidn %1, arg_count
112 | __arg_count_opt_parse %{2:-1}
113 | %exitmacro
114 | %endif
115 |
116 | %error "Unknown option"
117 | %endmacro
118 |
119 | ; ===================================================================
120 | ; Internal: Sets function option defaults
121 | %macro __set_defaults 0
122 | %define __stored_regs
123 | %assign __arg_count 0
124 | %assign __max_callee_arg_count 0
125 | %assign __local_var_space 0
126 | %define __store_args true
127 |
128 | %assign __stored_regs_stack_use 0
129 | %assign __local_var_required_size 0
130 | %assign __callee_required_stack_space 0
131 | %assign __required_stack_space 0
132 |
133 | %endmacro
134 |
135 | ; ===================================================================
136 | ; Applies options set previously, and marks the start of the function code
137 | %macro end_opts 0
138 | %ifn __OPEN_OPTS
139 | %error "Unexpected use of end_opts"
140 | %endif
141 |
142 | %ifn __NEW_OPTS
143 | %warning "end_opts found, but not new options were set"
144 | %exitmacro
145 | %endif
146 |
147 | %assign __NEW_OPTS 0
148 | %assign __OPEN_OPTS 0
149 |
150 | ; This options are undefined at end of function
151 | %assign __frame_size 0
152 | %assign __args_stored 0
153 |
154 | %assign __required_stack_space __local_var_required_size + __callee_required_stack_space
155 |
156 | ; Why +0x8? Because we have to remember the ret addr!!
157 | %if ((__required_stack_space + __stored_regs_stack_use + 0x8) % 0x10) != 0x0
158 | %assign __required_stack_space __required_stack_space + 0x8
159 | %endif
160 |
161 | %assign __frame_size __required_stack_space
162 | sub rsp, __frame_size
163 |
164 | %ifidn __store_args, true
165 | __store_args_to_stack __arg_count
166 | %assign __args_stored %cond(__arg_count < 4, __arg_count, 4)
167 | %endif
168 |
169 | %undef __arg_count
170 | %undef __local_var_space
171 | %undef __store_args
172 |
173 | %undef __local_var_required_size
174 | %undef __callee_required_stack_space
175 | %undef __required_stack_space
176 |
177 | %endmacro
178 |
179 | ; ===================================================================
180 | ; Restores the stack and saved registers, then returns, or if given a target as first parameter, jums to it
181 | ; Usage: end_function
182 | ; Usage: end_function destination
183 | ; Usage: end_function new_rsp destination
184 | ;TODO: Add checks
185 | %macro end_function 0-1
186 | %assign __OPEN_OPTS 1
187 |
188 | __restore_stack
189 | __restore_regs
190 |
191 | %if %0 == 2
192 | mov rsp, %2
193 | jmp %1
194 | %elif %0 == 1
195 | jmp %1
196 | %else
197 | ret
198 | %endif
199 |
200 | %undef __frame_size
201 | %undef __args_stored
202 |
203 | %undef __max_callee_arg_count
204 | %undef __stored_regs
205 | %undef __stored_regs_stack_use
206 | %endmacro
207 |
208 | ; ====================================================================
209 | ; argPtr: points to the base of stored args, or 0 if not stored, growing upwards
210 | ; basePtr: points to the base of local variables, growing downwards
211 |
212 | %define argPtr %cond(__args_stored > 0, rsp + %eval(__frame_size + __stored_regs_stack_use + 0x8), 0)
213 | %define basePtr (rsp + __frame_size)
214 |
215 | ; TODO: write this as a macro, and check that the index doesnt go out of bounds.
216 | %define local_var(index) basePtr + %eval(0x8*index)
217 | %define stored_arg(index) %cond(__args_stored > 0, argPtr + %eval(0x8*index), 0)
218 |
219 |
220 |
221 | ; ====================================================================
222 | ; Sets software breakpoint
223 | ; Usage: breakpoint
224 |
225 | %define breakpoint int3
226 |
227 | ; ====================================================================
228 | ; Jumps to label if first param is 0
229 | ; Usage: checkNull var label
230 | ; Params:
231 | ; var: 64 bit register to ckeck
232 | ; labbel: destination if 0
233 |
234 | %macro checkNull 2
235 | test %1, %1
236 | jz %2
237 | %endmacro
238 |
239 | ; ====================================================================
240 | ; Gets a single byte from a 64 bit register
241 | ; Usage: pickByte outByte inValue byteIndex
242 | ; Params:
243 | ; outByte: 64 bit register to save the byte to
244 | ; inValue: 64 bit register containing the value to pick the byte from
245 | ; byteIndex: 0 starting index of the byte to read
246 |
247 | %imacro pickByte 3
248 | %if %3 >= 8
249 | mov %1, 0
250 | %else
251 | mov %1, %2
252 | shr %1, (0x8 * %3)
253 | and %1, 0xff
254 | %endif
255 | %endmacro
256 |
257 | ; ====================================================================
258 | ; Dont use this
259 | %macro mod 2
260 | xor rdx, rdx
261 | mov eax, %1
262 | div %2
263 | mov %1, edx
264 | %endmacro
265 |
266 | ; ====================================================================
267 | ; Win64 fastcall helper
268 | ; Usage: fastcall_win64 function, args...
269 | ; Params:
270 | ; function: 64 bit register or label pointing to desired function
271 | ; args: comma separated list of arguments: 64 bit registers, labels or constants
272 |
273 | %macro fastcall_win64 1-*
274 | %ifndef __max_callee_arg_count
275 | %error "fastcall_win64 expects the use of set_max_callee_argcount to check stack usage"
276 | %endif
277 |
278 | %assign ARG_COUNT %0 -1
279 |
280 | %if ARG_COUNT > __max_callee_arg_count
281 | %error "set_max_callee_argcount specifies an smaller maximun callee argument counter"
282 | %endif
283 |
284 | %if ARG_COUNT >= 1
285 | mov rcx, %2
286 | %endif
287 |
288 | %if ARG_COUNT >= 2
289 | mov rdx, %3
290 | %endif
291 |
292 | %if ARG_COUNT >= 3
293 | mov r8, %4
294 | %endif
295 |
296 | %if ARG_COUNT >= 4
297 | mov r9, %5
298 | %endif
299 |
300 | %define TMP_CALL_TARGET %1
301 |
302 | %if ARG_COUNT > 4
303 | %assign STACK_ARG_COUNT ARG_COUNT -4
304 | %assign STACK_RUNNER 0x0
305 | %rotate 5
306 |
307 | %rep STACK_ARG_COUNT
308 | __mov_to_mem_helper [rsp + 0x20 + STACK_RUNNER], %1, rax
309 |
310 | %rotate 1
311 | %assign STACK_RUNNER STACK_RUNNER + 0x8
312 | %endrep
313 | %undef STACK_RUNNER
314 | %endif
315 |
316 | call TMP_CALL_TARGET
317 |
318 | %undef ARG_COUNT
319 | %undef TMP_CALL_TARGET
320 | %endmacro
321 |
322 | %macro __mov_to_mem_helper 3
323 | %assign is_reg 0
324 |
325 | %ifstr %2
326 | %error "Passing an string as fastcall argument is not supported, please store \
327 | it in readable memory and pass it as a label or pointer"
328 | %endif
329 |
330 | %ifnum %2
331 | mov QWORD %1, %2
332 | %exitmacro
333 | %endif
334 |
335 | %ifid %2
336 | %ifndef %2_reg
337 | mov %3, %2
338 | mov QWORD %1, %3
339 | %else
340 | mov QWORD %1, %2
341 | %endif
342 | %exitmacro
343 | %endif
344 |
345 | mov %3, QWORD %2
346 | mov QWORD %1, %3
347 | %endmacro
348 |
349 | ; ==================================================================
350 | ; Used to check if an arg is a register
351 | ; NOTE: Nasm %ifid will return true both for registers and labels,
352 | ; so if we want to check wether a parameter is a register or a label,
353 | ; we can define all registers as follows, then check if _reg is defined
354 |
355 | %define rax_reg
356 | %define rcx_reg
357 | %define rdx_reg
358 | %define rbx_reg
359 | %define rsi_reg
360 | %define rdi_reg
361 | %define rsp_reg
362 | %define rbp_reg
363 | %define r8_reg
364 | %define r9_reg
365 | %define r10_reg
366 | %define r11_reg
367 | %define r12_reg
368 | %define r13_reg
369 | %define r14_reg
370 | %define r15_reg
371 |
372 | %define as_reg(label) label%+_reg
373 |
374 | ; Exmple usage:
375 | ; Here, memory starting at label exampleLabel should look like
376 | ; noReg <0x0> isReg <0x0>
377 | ;
378 | ; exampleLabel:
379 | ; %ifdef as_reg(exampleLabel)
380 | ; db "isReg", 0
381 | ; %else
382 | ; db "noReg", 0
383 | ; %endif
384 | ; %ifdef as_reg(rax)
385 | ; db "isReg", 0
386 | ; %else
387 | ; db "noReg", 0
388 | ; %endif
389 |
390 | ; ====================================================================;
391 | ; Reserve space for callees
392 | ; Usage: __max_callee_arg_count_opt_parse argument_count
393 | ; Params:
394 | ; argument_count: Number of arguments of the callee with the most arguments
395 |
396 | %macro __max_callee_arg_count_opt_parse 1
397 | %ifnnum %1
398 | %error "max_callee_arg_count value must be numeric"
399 | %endif
400 |
401 | %assign __max_callee_arg_count %1
402 |
403 | %assign __callee_stack_arg_count %cond(__max_callee_arg_count > 4, __max_callee_arg_count - 4, 0)
404 |
405 | %assign __callee_required_stack_space (0x20 + (__callee_stack_arg_count * 0x8))
406 |
407 | %undef __callee_stack_arg_count
408 | %endmacro
409 |
410 | ; ====================================================================
411 | ; Reserve space for local variables
412 | ; Usage: __local_var_space_opt_parse size
413 | ; Params:
414 | ; size: Number of bytes to reserve, must be divisible by 8
415 | %macro __local_var_space_opt_parse 1
416 | %ifnnum %1
417 | %error "local_var_space value must be numeric"
418 | %endif
419 |
420 | %if (%1 % 8) != 0x0
421 | %error "local_var_space must be multiple of 0x8"
422 | %endif
423 |
424 | %assign __local_var_required_size %1
425 | %endmacro
426 |
427 | ; ==================================================================
428 | ; Register saving
429 | ; Usage: __stored_regs_opt_parse reg_1, reg_2 ... reg_n
430 | ; Params:
431 | ; registers: comma separated list of all registers to save
432 | %macro __stored_regs_opt_parse 1-*
433 | %rep %0
434 | push %1
435 | %rotate 1
436 | %endrep
437 |
438 | %define __stored_regs %{-1:1}
439 | %assign __stored_regs_stack_use 0x8 * %0
440 | %endmacro
441 |
442 | ; ===================================================================
443 | ; Register restoring, pops all registers specified with the last call to store_regs
444 | ; Usage: restore_regs
445 |
446 | %macro __restore_regs 0
447 | %ifndef __stored_regs
448 | %error "Unexpected use of __restore_regs"
449 | %endif
450 | __restore_regs_helper __stored_regs
451 | %define __stored_regs
452 | %endmacro
453 |
454 | %macro __restore_regs_helper 1-*
455 | %rep %0
456 | pop %1
457 | %rotate 1
458 | %endrep
459 | %endmacro
460 |
461 | ; ===================================================================
462 | %macro __store_args_opt_parse 1
463 | %ifidn %1, true
464 | %ifidn %1, false
465 | %error "store_args option must be either true or false"
466 | %endif
467 | %endif
468 |
469 | %define __store_args %1
470 | %endmacro
471 | ; ===================================================================
472 |
473 | %macro __arg_count_opt_parse 1
474 | %ifnnum %1
475 | %error "arg_count value must be numeric"
476 | %endif
477 |
478 | %assign __arg_count %1
479 | %endmacro
480 |
481 | ; ===================================================================
482 | %macro __store_args_to_stack 1
483 | %ifnnum %1
484 | %error "__store_args_to_stack expects a numeric first paramater"
485 | %endif
486 |
487 | %xdefine __arg_ptr %cond(%isdef(__frame_size), (rsp + %eval(__frame_size + __stored_regs_stack_use + 0x8)), (rsp + %eval(__stored_regs_stack_use + 0x8)))
488 |
489 | %if (%1 >= 1)
490 | mov [__arg_ptr], rcx
491 | %endif
492 |
493 | %if (%1 >= 2)
494 | mov [__arg_ptr + 0x8], rdx
495 | %endif
496 |
497 | %if (%1 >= 3)
498 | mov [__arg_ptr + 0x10], r8
499 | %endif
500 |
501 | %if (%1 >= 4)
502 | mov [__arg_ptr + 0x18], r9
503 | %endif
504 |
505 | %undef __arg_ptr
506 | %endmacro
507 |
508 | ; ====================================================================
509 | ; Restores stack to its address rigth after saving all registers (inverse of apply_stack_changes)
510 | ; Usage restore_stack
511 |
512 | %macro __restore_stack 0
513 | add rsp, __frame_size
514 | %assign __frame_size 0
515 | %endmacro
516 |
517 | %endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/common/common.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file common.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Common definitions used across the project.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _COMMON_H_
26 | #define _COMMON_H_
27 |
28 | #define WIN32_LEAN_AND_MEAN
29 | #include
30 |
31 | // ==============================================================================
32 | // ============================= MACRO DEFINITIONS ==============================
33 |
34 | // Some hashes:
35 |
36 | #define NTDLL_HASH 0xE59C2120
37 | #define KERNEL32_HASH 0x70B456D4
38 | #define KERNELBASE_HASH 0xBD145C12
39 |
40 | #define MESSAGEBOXA_HASH 0x35FC1883
41 | #define LOADLIBRARYA_HASH 0x15E7E6C2
42 | #define BASETRHEADINITTHUNK_HASH 0xA2EC03A5
43 | #define RTLUSERTHREADSTART_HASH 0x362F51AF
44 |
45 | #define PE_MAGIC 0x5A4D
46 |
47 | // Constants:
48 | #define RET 0xc3 // One byte, no conversion needed
49 |
50 | // Utils:
51 | #define POSITIVE_OR_ZERO(a) ((signed)a > 0 ? a : 0)
52 | #define UNUSED(x) (void)(x)
53 | #define COUNT(x) (sizeof(x) / sizeof(*x))
54 |
55 | /** A compile time assertion check.
56 | *
57 | * Validate at compile time that the predicate is true without
58 | * generating code. This can be used at any point in a source file
59 | * where typedef is legal.
60 | *
61 | * @param predicate The predicate to test. It must evaluate to
62 | * something that can be coerced to a normal C boolean.
63 | */
64 | #define CASSERT(predicate) _impl_CASSERT_LINE(predicate, __LINE__, __FILE__)
65 |
66 | #define _impl_PASTE(a, b) a##b
67 | #define _impl_CASSERT_LINE(predicate, line, file) \
68 | NOWARN("-Wunused-local-typedef", \
69 | typedef char _impl_PASTE(assertion_failed_##file##_, line)[2 * !!(predicate)-1];)
70 |
71 | #define DO_PRAGMA(x) _Pragma(#x)
72 | #define NOWARN(warnoption, ...) \
73 | DO_PRAGMA(GCC diagnostic push) \
74 | DO_PRAGMA(GCC diagnostic ignored warnoption) \
75 | __VA_ARGS__ \
76 | DO_PRAGMA(GCC diagnostic pop)
77 |
78 | #define BitVal(data, y) ((data >> y) & 1)
79 |
80 | #define BitChainInfo(data) BitVal(data, 2)
81 | #define BitUHandler(data) BitVal(data, 1)
82 | #define BitEHandler(data) BitVal((data), 0)
83 | #define Version(data) BitVal(data, 4) * 2 + BitVal(data, 3)
84 |
85 | #define DISABLE_OPTIMIZATIONS __attribute__((optnone))
86 |
87 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/common/commonUtils.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file commonUtils.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Common functionality used across the project.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _UTILS_H_
26 | #define _UTILS_H_
27 |
28 | #include "common/common.h"
29 | #include "common/wintypes/typedefs.h"
30 |
31 | // ==============================================================================
32 | // ============================= MACRO DEFINITIONS ==============================
33 |
34 | // Constants:
35 | #define HASH_MULTIPLIER 37
36 | #define RAND_SEED 123456
37 |
38 | // Utils:
39 | #define MID_POINT_ADDR(begin, end) ((end >= begin) ? ((PVOID)((DWORD_PTR)begin + (((DWORD_PTR)end - (DWORD_PTR)begin) / 2))) : 0)
40 |
41 | // ==============================================================================
42 | // ============================ PUBLIC FUNCTIONS ===============================
43 |
44 | /**
45 | * @brief Initializes the context using seed.
46 | *
47 | * @param p_ctx Context to be used for pseudo rng.
48 | * @param seed Seed to set for the context.
49 | * @return BOOL Success.
50 | */
51 | BOOL __callobf_srand(PDWORD p_ctx, DWORD seed);
52 |
53 | /**
54 | * @brief Generates next pesudo random number. Updates context for next call.
55 | *
56 | * @param p_ctx Context to be used for pseudo rng.
57 | * @return DWORD Next pseudo random number.
58 | */
59 | DWORD __callobf_rand(PDWORD p_ctx);
60 |
61 | /**
62 | * @brief Generates a ciclic sequence using lfsr.
63 | *
64 | * @param prevVal The last value used.
65 | * @return DWORD32 Next ciclic value.
66 | */
67 | DWORD32 __callobf_lfsrXorShift32(DWORD32 prevVal);
68 |
69 | /**
70 | * @brief Generates 32 bit value representing the string in ascii form.
71 | *
72 | * @param p_str String to be hashed.
73 | * @return UINT32 32 bit value representing the string.
74 | */
75 | UINT32 __callobf_hashA(const PCHAR p_str);
76 |
77 | /**
78 | * @brief Generates 32 bit value representing the string on its wide char form.
79 | *
80 | * @param p_str String to be hashed.
81 | * @return UINT32 32 bit value representing the string.
82 | */
83 | UINT32 __callobf_hashW(const PWCHAR p_str);
84 |
85 | /**
86 | * @brief Generates 32 bit value representing the string on its unicode form.
87 | *
88 | * @param p_str String to be hashed.
89 | * @return UINT32 32 bit value representing the string.
90 | */
91 | UINT32 __callobf_hashU(const PUNICODE_STRING p_str);
92 |
93 | /**
94 | * @brief Find the given pattern between startAddr and endAddr.
95 | *
96 | * @param startAddr Base address of the search space.
97 | * @param endAddr Top address of the search space.
98 | * @param bytes Patter to search for.
99 | * @param mask Mask to apply to the bytes beoing searched.
100 | * @param byteCount Size of the pattern.
101 | * @return PVOID Addres containing byteCount bytes matching the patter.
102 | */
103 | PVOID __callobf_findBytes(
104 | const PVOID startAddr,
105 | const PVOID endAddr,
106 | const PBYTE bytes,
107 | const PBYTE mask,
108 | const DWORD byteCount);
109 |
110 | /**
111 | * @brief Return base of the stack for the current thread.
112 | *
113 | * @return PVOID Stack base.
114 | */
115 | PVOID __callobf_getStackBase();
116 |
117 | /**
118 | * @brief Return top of the stack for the current thread.
119 | *
120 | * @return PVOID Stack limit.
121 | */
122 | PVOID __callobf_getStackLimit();
123 |
124 | /**
125 | * @brief Sets last error to given value.
126 | *
127 | * @param error 0 for success, 1 for error.
128 | * @return VOID
129 | */
130 | VOID __callobf_setLastError(DWORD32 error);
131 |
132 | /**
133 | * @brief Returns the las value set by __callobf_setLastError
134 | * 0 means success, eny other case means error.
135 | * @return DWORD32 error
136 | */
137 | DWORD32 __callobf_getLastError();
138 |
139 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/common/debug.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file debug.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Debug definitions.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _DEBUG_H_
26 | #define _DEBUG_H_
27 |
28 | #include "common/common.h"
29 |
30 | #ifdef __CALLOBF_DEBUG
31 |
32 | // ==============================================================================
33 | // =========================== EXTERNAL FUNCTIONS ===============================
34 | #ifndef printf
35 | extern int printf(const char *format, ...);
36 | #endif
37 |
38 | // ==============================================================================
39 | // ============================= MACRO DEFINITIONS ==============================
40 |
41 | #define DEBUG_PRINT(fmt, ...) \
42 | do \
43 | { \
44 | printf("[+] DEBUG: %s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__ __VA_OPT__(, ) __VA_ARGS__); \
45 | } while (0);
46 |
47 | #define DEBUG_PRINT_PTR(ptr) DEBUG_PRINT("PTR %s: %p", #ptr, ptr);
48 |
49 | // Basic idea here: I want to be able to run this without a debugger while
50 | // testing, and still keep my breakpoints in place.
51 | #define BREAKPOINT() \
52 | do \
53 | { \
54 | if (NtCurrentTeb()->ProcessEnvironmentBlock->BeingDebugged) \
55 | __debugbreak(); \
56 | } while (0);
57 | #else
58 | #define DEBUG_PRINT(...) \
59 | do \
60 | { \
61 | } while (0);
62 | #define DEBUG_PRINT_PTR(...) \
63 | do \
64 | { \
65 | } while (0);
66 | #define BREAKPOINT() \
67 | do \
68 | { \
69 | } while (0);
70 | #endif
71 |
72 | #endif
73 |
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/common/wintypes/typedefs.h:
--------------------------------------------------------------------------------
1 | #ifndef _TYPESDEF_H_
2 | #define _TYPESDEF_H_
3 |
4 | #define WIN32_LEAN_AND_MEAN
5 | #include
6 |
7 | typedef HMODULE (*PLOADLIBRARYA)(LPCSTR lpLibFileName);
8 |
9 | #ifndef _NTDEF_
10 | typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
11 | typedef NTSTATUS *PNTSTATUS;
12 | #endif
13 |
14 | #ifndef InitializeObjectAttributes
15 | #define InitializeObjectAttributes(p, n, a, r, s) \
16 | { \
17 | (p)->Length = sizeof(OBJECT_ATTRIBUTES); \
18 | (p)->RootDirectory = r; \
19 | (p)->Attributes = a; \
20 | (p)->ObjectName = n; \
21 | (p)->SecurityDescriptor = s; \
22 | (p)->SecurityQualityOfService = NULL; \
23 | }
24 | #endif
25 |
26 | typedef UCHAR UBYTE;
27 |
28 | typedef struct _IO_STATUS_BLOCK
29 | {
30 | union
31 | {
32 | NTSTATUS Status;
33 | PVOID Pointer;
34 | };
35 | ULONG_PTR Information;
36 | } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
37 |
38 | typedef void *PPS_POST_PROCESS_INIT_ROUTINE;
39 |
40 | typedef struct _LSA_UNICODE_STRING
41 | {
42 | USHORT Length;
43 | USHORT MaximumLength;
44 | PWSTR Buffer;
45 | } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
46 |
47 | typedef struct _OBJECT_ATTRIBUTES
48 | {
49 | ULONG Length;
50 | HANDLE RootDirectory;
51 | PUNICODE_STRING ObjectName;
52 | ULONG Attributes;
53 | PVOID SecurityDescriptor;
54 | PVOID SecurityQualityOfService;
55 | } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
56 |
57 | typedef struct _STRING
58 | {
59 | USHORT Length;
60 | USHORT MaximumLength;
61 | PCHAR Buffer;
62 | } STRING, *PSTRING, ANSI_STRING, *PANSI_STRING;
63 |
64 | typedef struct _RTL_USER_PROCESS_PARAMETERS
65 | {
66 | BYTE Reserved1[16];
67 | PVOID Reserved2[10];
68 | UNICODE_STRING ImagePathName;
69 | UNICODE_STRING CommandLine;
70 | } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
71 |
72 | // PEB defined by rewolf
73 | // http://blog.rewolf.pl/blog/?p=573
74 | typedef struct _PEB_LDR_DATA
75 | {
76 | ULONG Length;
77 | BOOL Initialized;
78 | LPVOID SsHandle;
79 | LIST_ENTRY InLoadOrderModuleList;
80 | LIST_ENTRY InMemoryOrderModuleList;
81 | LIST_ENTRY InInitializationOrderModuleList;
82 | } PEB_LDR_DATA, *PPEB_LDR_DATA;
83 |
84 | typedef struct _LDR_DATA_TABLE_ENTRY
85 | {
86 | LIST_ENTRY InLoadOrderLinks;
87 | LIST_ENTRY InMemoryOrderLinks;
88 | LIST_ENTRY InInitializationOrderLinks;
89 | LPVOID DllBase;
90 | LPVOID EntryPoint;
91 | ULONG SizeOfImage;
92 | UNICODE_STRING FullDllName;
93 | UNICODE_STRING BaseDllName;
94 | } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
95 |
96 | typedef struct _PEB
97 | {
98 | BYTE InheritedAddressSpace;
99 | BYTE ReadImageFileExecOptions;
100 | BYTE BeingDebugged;
101 | BYTE _SYSTEM_DEPENDENT_01;
102 |
103 | LPVOID Mutant;
104 | LPVOID ImageBaseAddress;
105 |
106 | PPEB_LDR_DATA Ldr;
107 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
108 | LPVOID SubSystemData;
109 | LPVOID ProcessHeap;
110 | LPVOID FastPebLock;
111 | LPVOID _SYSTEM_DEPENDENT_02;
112 | LPVOID _SYSTEM_DEPENDENT_03;
113 | LPVOID _SYSTEM_DEPENDENT_04;
114 | union
115 | {
116 | LPVOID KernelCallbackTable;
117 | LPVOID UserSharedInfoPtr;
118 | };
119 | DWORD SystemReserved;
120 | DWORD _SYSTEM_DEPENDENT_05;
121 | LPVOID _SYSTEM_DEPENDENT_06;
122 | LPVOID TlsExpansionCounter;
123 | LPVOID TlsBitmap;
124 | DWORD TlsBitmapBits[2];
125 | LPVOID ReadOnlySharedMemoryBase;
126 | LPVOID _SYSTEM_DEPENDENT_07;
127 | LPVOID ReadOnlyStaticServerData;
128 | LPVOID AnsiCodePageData;
129 | LPVOID OemCodePageData;
130 | LPVOID UnicodeCaseTableData;
131 | DWORD NumberOfProcessors;
132 | union
133 | {
134 | DWORD NtGlobalFlag;
135 | LPVOID dummy02;
136 | };
137 | LARGE_INTEGER CriticalSectionTimeout;
138 | LPVOID HeapSegmentReserve;
139 | LPVOID HeapSegmentCommit;
140 | LPVOID HeapDeCommitTotalFreeThreshold;
141 | LPVOID HeapDeCommitFreeBlockThreshold;
142 | DWORD NumberOfHeaps;
143 | DWORD MaximumNumberOfHeaps;
144 | LPVOID ProcessHeaps;
145 | LPVOID GdiSharedHandleTable;
146 | LPVOID ProcessStarterHelper;
147 | LPVOID GdiDCAttributeList;
148 | LPVOID LoaderLock;
149 | DWORD OSMajorVersion;
150 | DWORD OSMinorVersion;
151 | WORD OSBuildNumber;
152 | WORD OSCSDVersion;
153 | DWORD OSPlatformId;
154 | DWORD ImageSubsystem;
155 | DWORD ImageSubsystemMajorVersion;
156 | LPVOID ImageSubsystemMinorVersion;
157 | union
158 | {
159 | LPVOID ImageProcessAffinityMask;
160 | LPVOID ActiveProcessAffinityMask;
161 | };
162 | #ifdef _WIN64
163 | LPVOID GdiHandleBuffer[64];
164 | #else
165 | LPVOID GdiHandleBuffer[32];
166 | #endif
167 | LPVOID PostProcessInitRoutine;
168 | LPVOID TlsExpansionBitmap;
169 | DWORD TlsExpansionBitmapBits[32];
170 | LPVOID SessionId;
171 | ULARGE_INTEGER AppCompatFlags;
172 | ULARGE_INTEGER AppCompatFlagsUser;
173 | LPVOID pShimData;
174 | LPVOID AppCompatInfo;
175 | PUNICODE_STRING CSDVersion;
176 | LPVOID ActivationContextData;
177 | LPVOID ProcessAssemblyStorageMap;
178 | LPVOID SystemDefaultActivationContextData;
179 | LPVOID SystemAssemblyStorageMap;
180 | LPVOID MinimumStackCommit;
181 | } PEB, *PPEB;
182 |
183 | typedef struct _CLIENT_ID
184 | {
185 | HANDLE UniqueProcess;
186 | HANDLE UniqueThread;
187 | } CLIENT_ID, *PCLIENT_ID;
188 |
189 | #define GDI_BATCH_BUFFER_SIZE 310
190 | typedef struct _GDI_TEB_BATCH
191 | {
192 | ULONG Offset;
193 | ULONG_PTR HDC;
194 | ULONG Buffer[GDI_BATCH_BUFFER_SIZE];
195 | } GDI_TEB_BATCH, *PGDI_TEB_BATCH;
196 |
197 | typedef struct _TEB_ACTIVE_FRAME_CONTEXT
198 | {
199 | ULONG Flags;
200 | PSTR FrameName;
201 | } TEB_ACTIVE_FRAME_CONTEXT, *PTEB_ACTIVE_FRAME_CONTEXT;
202 |
203 | typedef struct _TEB_ACTIVE_FRAME
204 | {
205 | ULONG Flags;
206 | struct _TEB_ACTIVE_FRAME *Previous;
207 | PTEB_ACTIVE_FRAME_CONTEXT Context;
208 | } TEB_ACTIVE_FRAME, *PTEB_ACTIVE_FRAME;
209 |
210 | typedef struct _TEB
211 | {
212 | NT_TIB NtTib;
213 |
214 | PVOID EnvironmentPointer;
215 | CLIENT_ID ClientId;
216 | PVOID ActiveRpcHandle;
217 | PVOID ThreadLocalStoragePointer;
218 | PPEB ProcessEnvironmentBlock;
219 |
220 | ULONG LastErrorValue;
221 | ULONG CountOfOwnedCriticalSections;
222 | PVOID CsrClientThread;
223 | PVOID Win32ThreadInfo;
224 | ULONG User32Reserved[26];
225 | ULONG UserReserved[5];
226 | PVOID WOW32Reserved;
227 | LCID CurrentLocale;
228 | ULONG FpSoftwareStatusRegister;
229 | PVOID SystemReserved1[54];
230 | NTSTATUS ExceptionCode;
231 | PVOID ActivationContextStackPointer;
232 | #ifdef _M_X64
233 | UCHAR SpareBytes[24];
234 | #else
235 | UCHAR SpareBytes[36];
236 | #endif
237 | ULONG TxFsContext;
238 |
239 | GDI_TEB_BATCH GdiTebBatch;
240 | CLIENT_ID RealClientId;
241 | HANDLE GdiCachedProcessHandle;
242 | ULONG GdiClientPID;
243 | ULONG GdiClientTID;
244 | PVOID GdiThreadLocalInfo;
245 | ULONG_PTR Win32ClientInfo[62];
246 | PVOID glDispatchTable[233];
247 | ULONG_PTR glReserved1[29];
248 | PVOID glReserved2;
249 | PVOID glSectionInfo;
250 | PVOID glSection;
251 | PVOID glTable;
252 | PVOID glCurrentRC;
253 | PVOID glContext;
254 |
255 | NTSTATUS LastStatusValue;
256 | UNICODE_STRING StaticUnicodeString;
257 | WCHAR StaticUnicodeBuffer[261];
258 |
259 | PVOID DeallocationStack;
260 | PVOID TlsSlots[64];
261 | LIST_ENTRY TlsLinks;
262 |
263 | PVOID Vdm;
264 | PVOID ReservedForNtRpc;
265 | PVOID DbgSsReserved[2];
266 |
267 | ULONG HardErrorMode;
268 | #ifdef _M_X64
269 | PVOID Instrumentation[11];
270 | #else
271 | PVOID Instrumentation[9];
272 | #endif
273 | GUID ActivityId;
274 |
275 | PVOID SubProcessTag;
276 | PVOID EtwLocalData;
277 | PVOID EtwTraceData;
278 | PVOID WinSockData;
279 | ULONG GdiBatchCount;
280 |
281 | union
282 | {
283 | PROCESSOR_NUMBER CurrentIdealProcessor;
284 | ULONG IdealProcessorValue;
285 | struct
286 | {
287 | UCHAR ReservedPad0;
288 | UCHAR ReservedPad1;
289 | UCHAR ReservedPad2;
290 | UCHAR IdealProcessor;
291 | };
292 | };
293 |
294 | ULONG GuaranteedStackBytes;
295 | PVOID ReservedForPerf;
296 | PVOID ReservedForOle;
297 | ULONG WaitingOnLoaderLock;
298 | PVOID SavedPriorityState;
299 | ULONG_PTR SoftPatchPtr1;
300 | PVOID ThreadPoolData;
301 | PVOID *TlsExpansionSlots;
302 | #ifdef _M_X64
303 | PVOID DeallocationBStore;
304 | PVOID BStoreLimit;
305 | #endif
306 | ULONG MuiGeneration;
307 | ULONG IsImpersonating;
308 | PVOID NlsCache;
309 | PVOID pShimData;
310 | ULONG HeapVirtualAffinity;
311 | HANDLE CurrentTransactionHandle;
312 | PTEB_ACTIVE_FRAME ActiveFrame;
313 | PVOID FlsData;
314 |
315 | PVOID PreferredLanguages;
316 | PVOID UserPrefLanguages;
317 | PVOID MergedPrefLanguages;
318 | ULONG MuiImpersonation;
319 |
320 | union
321 | {
322 | USHORT CrossTebFlags;
323 | USHORT SpareCrossTebBits : 16;
324 | };
325 | union
326 | {
327 | USHORT SameTebFlags;
328 | struct
329 | {
330 | USHORT SafeThunkCall : 1;
331 | USHORT InDebugPrint : 1;
332 | USHORT HasFiberData : 1;
333 | USHORT SkipThreadAttach : 1;
334 | USHORT WerInShipAssertCode : 1;
335 | USHORT RanProcessInit : 1;
336 | USHORT ClonedThread : 1;
337 | USHORT SuppressDebugMsg : 1;
338 | USHORT DisableUserStackWalk : 1;
339 | USHORT RtlExceptionAttached : 1;
340 | USHORT InitialThread : 1;
341 | USHORT SessionAware : 1;
342 | USHORT SpareSameTebBits : 4;
343 | };
344 | };
345 |
346 | PVOID TxnScopeEnterCallback;
347 | PVOID TxnScopeExitCallback;
348 | PVOID TxnScopeContext;
349 | ULONG LockCount;
350 | ULONG SpareUlong0;
351 | PVOID ResourceRetValue;
352 | PVOID ReservedForWdf;
353 | } TEB, *PTEB;
354 |
355 | typedef enum _UNWIND_OP_CODES
356 | {
357 | // x86_64. https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
358 | UWOP_PUSH_NONVOL = 0,
359 | UWOP_ALLOC_LARGE, // 1
360 | UWOP_ALLOC_SMALL, // 2
361 | UWOP_SET_FPREG, // 3
362 | UWOP_SAVE_NONVOL, // 4
363 | UWOP_SAVE_NONVOL_BIG, // 5
364 | UWOP_EPILOG, // 6
365 | UWOP_SPARE_CODE, // 7
366 | UWOP_SAVE_XMM128, // 8
367 | UWOP_SAVE_XMM128BIG, // 9
368 | UWOP_PUSH_MACH_FRAME, // 10
369 |
370 | // ARM64. https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling
371 | UWOP_ALLOC_MEDIUM,
372 | UWOP_SAVE_R19R20X,
373 | UWOP_SAVE_FPLRX,
374 | UWOP_SAVE_FPLR,
375 | UWOP_SAVE_REG,
376 | UWOP_SAVE_REGX,
377 | UWOP_SAVE_REGP,
378 | UWOP_SAVE_REGPX,
379 | UWOP_SAVE_LRPAIR,
380 | UWOP_SAVE_FREG,
381 | UWOP_SAVE_FREGX,
382 | UWOP_SAVE_FREGP,
383 | UWOP_SAVE_FREGPX,
384 | UWOP_SET_FP,
385 | UWOP_ADD_FP,
386 | UWOP_NOP,
387 | UWOP_END,
388 | UWOP_SAVE_NEXT,
389 | UWOP_TRAP_FRAME,
390 | UWOP_CONTEXT,
391 | UWOP_CLEAR_UNWOUND_TO_CALL,
392 | // ARM: https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling
393 |
394 | UWOP_ALLOC_HUGE,
395 | UWOP_WIDE_ALLOC_MEDIUM,
396 | UWOP_WIDE_ALLOC_LARGE,
397 | UWOP_WIDE_ALLOC_HUGE,
398 |
399 | UWOP_WIDE_SAVE_REG_MASK,
400 | UWOP_WIDE_SAVE_SP,
401 | UWOP_SAVE_REGS_R4R7LR,
402 | UWOP_WIDE_SAVE_REGS_R4R11LR,
403 | UWOP_SAVE_FREG_D8D15,
404 | UWOP_SAVE_REG_MASK,
405 | UWOP_SAVE_LR,
406 | UWOP_SAVE_FREG_D0D15,
407 | UWOP_SAVE_FREG_D16D31,
408 | UWOP_WIDE_NOP, // UWOP_NOP
409 | UWOP_END_NOP, // UWOP_END
410 | UWOP_WIDE_END_NOP,
411 | // Custom implementation opcodes (implementation specific).
412 | UWOP_CUSTOM,
413 | } UNWIND_OP_CODES;
414 |
415 | typedef union _UNWIND_CODE
416 | {
417 | struct
418 | {
419 | UBYTE CodeOffset; // 0xFF00
420 | UBYTE UnwindOp : 4; // 0x000f OPCODE
421 | UBYTE OpInfo : 4; // 0x00f0
422 | };
423 | USHORT FrameOffset;
424 | } UNWIND_CODE, *PUNWIND_CODE;
425 |
426 | typedef struct _UNWIND_INFO
427 | {
428 | UBYTE Version : 3;
429 | UBYTE Flags : 5; // 4 bytes
430 | UBYTE SizeOfProlog; // 4 bytes
431 | UBYTE CountOfCodes; // 4 bytes
432 | UBYTE FrameRegister : 4;
433 | UBYTE FrameOffset : 4; // 4bytes
434 | UNWIND_CODE UnwindCode[1];
435 | union
436 | {
437 | OPTIONAL ULONG ExceptionHandler;
438 | OPTIONAL ULONG FunctionEntry;
439 | };
440 | OPTIONAL ULONG ExceptionData[];
441 | } UNWIND_INFO, *PUNWIND_INFO;
442 |
443 | typedef enum _REGISTERS
444 | {
445 | RAX = 0,
446 | RCX,
447 | RDX,
448 | RBX,
449 | RSP,
450 | RBP,
451 | RSI,
452 | RDI,
453 | R8,
454 | R9,
455 | R10,
456 | R11,
457 | R12,
458 | R13,
459 | R14,
460 | R15
461 | } REGISTERS;
462 |
463 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/pe/peUtils.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file peUtils.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Utilities to manipulate and work with in-memory PEs.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _PE_UTILS_H_
26 | #define _PE_UTILS_H_
27 |
28 | #include "common/common.h"
29 | #include "common/wintypes/typedefs.h"
30 |
31 | #ifndef NtCurrentTeb
32 | extern PTEB NtCurrentTeb(void);
33 | #endif
34 |
35 | // ==============================================================================
36 | // ============================ PUBLIC FUNCTIONS ===============================
37 |
38 | /**
39 | * @brief Finds pointer to a module loaded in memory from its name.
40 | *
41 | * @param p_moduleName Module name.
42 | * @return PVOID Pointer to module base if found, else NULL.
43 | */
44 | PVOID __callobf_getModuleAddrA(const PCHAR p_moduleName);
45 |
46 | /**
47 | * @brief Finds pointer to a module loaded in memory from its name in wide char
48 | * form.
49 | *
50 | * @param p_moduleName Module name.
51 | * @return PVOID Pointer to module base if found, else NULL.
52 | */
53 | PVOID __callobf_getModuleAddrW(const PWCHAR p_moduleName);
54 |
55 | /**
56 | * @brief Finds pointer to a module loaded in memory from its name hash.
57 | *
58 | * @param moduleHash Module hash.
59 | * @return PVOID Pointer to module base if found, else NULL.
60 | */
61 | PVOID __callobf_getModuleAddrH(const UINT32 moduleHash);
62 |
63 | /**
64 | * @brief Given a module pointer, find the given function name between its
65 | * exported functions, then returns its pointer.
66 | *
67 | * @param p_module Pointer to module to search in.
68 | * @param p_functionName Function name.
69 | * @return PVOID Pointer to function if found, else NULL.
70 | */
71 | PVOID __callobf_getFunctionAddrA(
72 | const PVOID p_module,
73 | const PCHAR p_functionName);
74 |
75 | /**
76 | * @brief Given a module pointer, find the given function name in wide char form
77 | * between its exported functions, then returns its pointer.
78 | *
79 | * @param p_module Pointer to module to search in.
80 | * @param p_functionName Function name.
81 | * @return PVOID Pointer to function if found, else NULL.
82 | */
83 | PVOID __callobf_getFunctionAddrW(
84 | const PVOID p_module,
85 | const PWCHAR p_functionName);
86 |
87 | /**
88 | * @brief Given a module pointer, find the given function hash between its exported
89 | * functions, then returns its pointer.
90 | *
91 | * @param p_module Pointer to module to search in.
92 | * @param funtionHash Function hash.
93 | * @return PVOID Pointer to function if found, else NULL.
94 | */
95 | PVOID __callobf_getFunctionAddrH(
96 | const PVOID p_module,
97 | const UINT32 funtionHash);
98 |
99 | /**
100 | * @brief Returns the exception directory address if any.
101 | *
102 | * @param p_module Pointer to module to return the exception dir address from.
103 | * @param p_size Retruns size of the exception directory.
104 | * @return PVOID Pointer exception directory, or NULL.
105 | */
106 | PVOID __callobf_getExceptionDirectoryAddress(
107 | const PVOID p_module,
108 | PDWORD p_size);
109 |
110 | /**
111 | * @brief Gets boundaries for the first executable section found in a module.
112 | *
113 | * @param p_module Pointer to module to search in.
114 | * @param pp_base Returns pointer to base of code section.
115 | * @param pp_top Returns pointer to top of code section.
116 | * @return BOOL Success.
117 | */
118 | BOOL __callobf_getCodeBoundaries(
119 | PVOID p_module,
120 | PVOID *pp_base,
121 | PVOID *pp_top);
122 |
123 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/pe/unwind/unwindUtils.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file unwindUtils.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Utilities to manipulate and work with PE unwind information.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _UNWIND_UTILS_H_
26 | #define _UNWIND_UTILS_H_
27 |
28 | #include "common/common.h"
29 | #include "common/wintypes/typedefs.h"
30 | #include "common/debug.h"
31 |
32 | // ==============================================================================
33 | // ============================ STRUCT DEFINITIONS ==============================
34 |
35 | typedef struct _UWOP_ITERATOR_CONTEXT
36 | {
37 | HMODULE p_moduleBase;
38 | PUNWIND_INFO p_unwindInfo;
39 | DWORD nextIndex;
40 | BOOL ended;
41 | } UWOP_ITERATOR_CONTEXT, *PUWOP_ITERATOR_CONTEXT;
42 |
43 | typedef struct _UNWIND_INFO_ITERATOR_CONTEXT
44 | {
45 | HMODULE p_moduleBase;
46 | DWORD nextIndex;
47 | BOOL ended;
48 | PIMAGE_RUNTIME_FUNCTION_ENTRY p_runtimeFunctionTable;
49 | DWORD entryCount;
50 | } UNWIND_INFO_ITERATOR_CONTEXT, *PUNWIND_INFO_ITERATOR_CONTEXT;
51 |
52 | // ==============================================================================
53 | // ============================ PUBLIC FUNCTIONS ===============================
54 |
55 | /**
56 | * @brief Initializes or resets the given iterator context.
57 | *
58 | * @param p_ctx Pointer to iterator context.
59 | * @param p_moduleBase Pointer to module from where unwind info will be read.
60 | * @return BOOL Success.
61 | */
62 | BOOL __callobf_createOrResetUnwindInfoIterator(
63 | _Out_ PUNWIND_INFO_ITERATOR_CONTEXT p_ctx,
64 | _In_ HMODULE p_moduleBase);
65 |
66 | /**
67 | * @brief Given an unwind info iterator context, gets the next unwind info
68 | * and modifies the ctx so the next call to this function returns the
69 | * following unwind info. If there is no more unwind entries remaining
70 | * or an error occurs, returns NULL.
71 | *
72 | * @param p_ctx Pointer to iterator context.
73 | * @return PUNWIND_INFO Pointer to next unwind info, or NULL.
74 | */
75 | PUNWIND_INFO __callobf_getNextUnwindInfo(
76 | PUNWIND_INFO_ITERATOR_CONTEXT p_ctx);
77 |
78 | /**
79 | * @brief Initializes or resets the given iterator context.
80 | *
81 | * @param p_ctx Pointer to iterator context.
82 | * @param p_moduleBase Pointer to module from where unwind operations will be read.
83 | * @param p_unwindInfoAddress Pointer to unwind info from where unwind operations
84 | * will be read.
85 | * @return BOOL Success.
86 | */
87 | BOOL __callobf_createOrResetUwopIterator(
88 | _Out_ PUWOP_ITERATOR_CONTEXT p_ctx,
89 | _In_ HMODULE p_moduleBase,
90 | _In_ PUNWIND_INFO p_unwindInfoAddress);
91 |
92 | /**
93 | * @brief Given an unwind operation iterator context, gets the next uwop and
94 | * modifies the ctx so the next call to this function returns the following
95 | * uwops. If there is no more uwop entries remaining or an error occurs,
96 | * returns NULL.
97 | *
98 | * @param p_ctx Pointer to iterator context.
99 | * @return PUNWIND_CODE Pointer to next uwop, or NULL.
100 | */
101 | PUNWIND_CODE __callobf_getNextUwop(
102 | PUWOP_ITERATOR_CONTEXT p_ctx);
103 |
104 | /**
105 | * @brief Return the BeginAddress and EndAddress of the last unwind info read with
106 | * the given context. If the context was never used to read a value, this
107 | * returns FALSE.
108 | *
109 | * @param p_ctx Pointer to an unwind info iterator context.
110 | * @param pp_beginAddress Returns BeginAddress of the unwind info of the last entry
111 | * read from p_ctx.
112 | * @param pp_endAddress Returns EndAddress of the unwind info of the last entry read
113 | * from p_ctx.
114 | * @return BOOL Success.
115 | */
116 | BOOL __callobf_getCodeBoundariesLastUnwindInfo(
117 | PUNWIND_INFO_ITERATOR_CONTEXT p_ctx,
118 | PVOID *pp_beginAddress,
119 | PVOID *pp_endAddress);
120 |
121 | /**
122 | * @brief Calculates frame size modification applied by the given uwop.
123 | *
124 | * Note: the difference between frame size and stack size,
125 | * is that stack size represents the actual modifications
126 | * to rsp, while frame size applies to the frame, but is
127 | * not backed by rsp
128 | *
129 | * @param p_unwindInfo Unwind info containing the given uwop.
130 | * @param p_unwindCode Unwind code to get the frame modification from.
131 | * @return LONG Frame size modification, or 0 on bad arguments.
132 | */
133 | LONG __callobf_getFrameSizeModification(
134 | PUNWIND_INFO p_unwindInfo,
135 | PUNWIND_CODE p_unwindCode);
136 |
137 | /**
138 | * @brief Calculates stack size modification applied by the given uwop.
139 | *
140 | * Note: the difference between frame size and stack size,
141 | * is that stack size represents the actual modifications
142 | * to rsp, while frame size applies to the frame, but is
143 | * not backed by rsp
144 | *
145 | * @param p_unwindCode Unwind code to get the stack modification from.
146 | * @return LONG Frame size modification, or 0 on bad arguments.
147 | */
148 | LONG __callobf_getStackSizeModification(
149 | PUNWIND_CODE p_unwindCode);
150 |
151 | /**
152 | * @brief Find the address containing the return address for the entry
153 | * point of thread executing this code.
154 | *
155 | * @param p_ntdll Pointer to ntdll.
156 | * @param p_kernel32 Pointer to kernel32.
157 | * @return PVOID Pointer containing return addr of current thread, or NULL.
158 | */
159 | PVOID __callobf_findEntryAddressOfReturnAddress(
160 | PVOID p_ntdll,
161 | PVOID p_kernel32);
162 |
163 | /**
164 | * @brief Given a pointer to a code section in a known module, finds the unwind info
165 | * for said address. Optionally return the begin address and end address for
166 | * the found unwind info.
167 | *
168 | * @param p_module Pointer to module containing the code pointer.
169 | * @param p_code Pointer to code.
170 | * @param pp_functionStart [optional] Pointer code block start.
171 | * @param p_functionEnd [optional] Pointer code block end.
172 | * @return PUNWIND_INFO Pointer to unwind info containing the code pointer, or NULL.
173 | */
174 | PUNWIND_INFO __callobf_getUnwindInfoForCodePtr(
175 | PVOID p_module,
176 | PVOID p_code,
177 | PVOID *pp_functionStart,
178 | PVOID *pp_functionEnd);
179 |
180 | /**
181 | * @brief Returns the count of nodes used by a given unwind code.
182 | *
183 | * @param p_unwindCode Unwind code to get the node count from.
184 | * @return DWORD Count of nodes, or 0 on error.
185 | */
186 | DWORD __callobf_getNodesUsed(
187 | PUNWIND_CODE p_unwindCode);
188 |
189 | /**
190 | * @brief For a given unwind info and a register, finds the offset where the register is saved.
191 | * Returns -1 if the register is not saved or error found.
192 | *
193 | * @param p_module Module containing given unwind info.
194 | * @param p_unwindInfo Unwind info to find offset from.
195 | * @param reg Register to find ofset for.
196 | * @return DWORD64 Offset where register is saved or -1.
197 | */
198 | LONG64 __callobf_getOffsetWhereRegSaved(
199 | PVOID p_module,
200 | PUNWIND_INFO p_unwindInfo,
201 | REGISTERS reg);
202 |
203 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/stackSpoof/asm/stackSpoofHelpers.x64.hasm:
--------------------------------------------------------------------------------
1 | ; @file stackSpoofHelper.x64.hasm
2 | ; @author Alejandro González (@httpyxel)
3 | ; @brief Assembly utilities to build spoofed stacks for windows x64 native function.
4 | ; @version 0.1
5 | ; @date 2024-01-14
6 | ;
7 | ; @copyright
8 | ; Copyright (C) 2024 Alejandro González
9 | ;
10 | ; This program is free software: you can redistribute it and/or modify
11 | ; it under the terms of the GNU General Public License as published by
12 | ; the Free Software Foundation, either version 3 of the License, or
13 | ; (at your option) any later version.
14 | ;
15 | ; This program is distributed in the hope that it will be useful,
16 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ; GNU General Public License for more details.
19 | ;
20 | ; You should have received a copy of the GNU General Public License
21 | ; along with this program. If not, see .
22 |
23 |
24 | %ifndef _DYN_STACK_SPOOF_HASM_
25 | %define _DYN_STACK_SPOOF_HASM_
26 |
27 | %include "common/asm/common.x64.hasm"
28 |
29 | [BITS 64]
30 |
31 | ; Macros
32 | %define MAX_ENTRIES_PER_LIST 10
33 |
34 | %define arg(basePtr, n) basePtr + 0x8 + (n * 0x8)
35 | %define var(basePtr, n) basePtr - 0x8 + (n * 0x8)
36 |
37 | %imacro mul2 1
38 | shl %1, 1
39 | %endmacro
40 |
41 | %imacro defTest 0
42 | %define test__
43 | %endmacro
44 |
45 | ; Defined strucs
46 | struc FRAME_INFO
47 | .frameSize resq 1
48 | .p_entryAddr resq 1
49 | endstruc
50 |
51 | struc SAVE_RBP_FRAME_INFO
52 | .frameSize resq 1
53 | .p_entryAddr resq 1
54 | .rbpOffset resq 1
55 | endstruc
56 |
57 | struc STACK_SPOOF_INFO
58 | .initialized resq 1 ; 0x0
59 | .entryCountPerList resq 1 ; 0x8
60 |
61 | .nextCiclicValue resb 4 ; 0x10
62 | .__seedPadding resd 1 ; 0x14
63 |
64 | .p_entryRetAddr resq 1 ; 0x18
65 |
66 | .addRspCount resq 1 ; 0x20
67 | .addRspList resb sizeoflist(FRAME_INFO, MAX_ENTRIES_PER_LIST)
68 |
69 | .jmpRbxCount resq 1
70 | .jmpRbxList resb sizeoflist(FRAME_INFO, MAX_ENTRIES_PER_LIST)
71 |
72 | .setFpRegCount resq 1
73 | .setFpRegList resb sizeoflist(FRAME_INFO, MAX_ENTRIES_PER_LIST)
74 |
75 | .saveRbpCount resq 1
76 | .saveRbpList resb sizeoflist(SAVE_RBP_FRAME_INFO, MAX_ENTRIES_PER_LIST)
77 | endstruc
78 |
79 | %endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/stackSpoof/stackSpoof.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file stackSpoof.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Functionality to apply dynamic stack spoofing in windows x64 enviroments.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | // Many thanks to the project showing this idea: https://github.com/klezVirus/klezVirus.github.io
26 |
27 | #ifndef _DYN_STACK_SPOOF_H_
28 | #define _DYN_STACK_SPOOF_H_
29 |
30 | #include "common/common.h"
31 |
32 | // ==============================================================================
33 | // ============================= MACRO DEFINITIONS ==============================
34 |
35 | // Constants:
36 | #define MAX_ENTRIES_PER_LIST 10
37 |
38 | #define JUMP_RBX_GADGET "\xff\x23"
39 | #define JUMP_RBX_GADGET_MASK "\xff\xff"
40 | #define JUMP_RBX_GADGET_SIZE sizeof(JUMP_RBX_GADGET) - 1
41 |
42 | #define ADD_RSP_GADGET "\x48\x83\xC4\x90\xC3"
43 | #define ADD_RSP_GADGET_MASK "\xff\xff\xff\x00\xff"
44 | #define ADD_RSP_GADGET_SIZE sizeof(ADD_RSP_GADGET) - 1
45 |
46 | // TODO: Allow to search for multiple ranges, like, 50% of the entries with minimun stack size of
47 | // TODO: and the other 50% with minimun of , so we have multiple options to choose from
48 | #define MIN_ADD_RSP_FRAME_SIZE ((0x8 * 15) + 0x20) // This sets how many args we will be able to store, in this case 15 args
49 |
50 | // Utils:
51 | #define REQUIRED_STACK(NARGS) (((POSITIVE_OR_ZERO(NARGS - 4) * 8) + 0x20) | 8)
52 |
53 | // ==============================================================================
54 | // ============================ STRUCT DEFINITIONS ==============================
55 |
56 | typedef struct _FRAME_INFO
57 | {
58 | SIZE_T frameSize;
59 | PVOID p_entryAddr;
60 | } FRAME_INFO, *PFRAME_INFO;
61 |
62 | typedef struct _SAVE_RBP_FRAME_INFO
63 | {
64 | SIZE_T frameSize;
65 | PVOID p_entryAddr;
66 | DWORD64 rbpOffset;
67 | } SAVE_RBP_FRAME_INFO, *PSAVE_RBP_FRAME_INFO;
68 |
69 | #pragma pack(push, 1)
70 | typedef struct _FRAME_TABLE
71 | {
72 | DWORD64 initialized;
73 | DWORD64 entryCountPerList;
74 |
75 | DWORD32 nextCiclicValue;
76 | DWORD32 __seedPadding;
77 |
78 | PVOID *p_entryRetAddr;
79 |
80 | DWORD64 addRspCount;
81 | FRAME_INFO addRspList[MAX_ENTRIES_PER_LIST];
82 |
83 | DWORD64 jmpRbxCount;
84 | FRAME_INFO jmpRbxList[MAX_ENTRIES_PER_LIST];
85 |
86 | DWORD64 setFpRegCount;
87 | FRAME_INFO setFpRegList[MAX_ENTRIES_PER_LIST];
88 |
89 | DWORD64 saveRbpCount;
90 | SAVE_RBP_FRAME_INFO saveRbpList[MAX_ENTRIES_PER_LIST];
91 | } STACK_SPOOF_INFO, *PSTACK_SPOOF_INFO;
92 |
93 | #pragma pack(pop)
94 |
95 | // ==============================================================================
96 | // =========================== EXTERNAL GLOBALS =================================
97 |
98 | extern STACK_SPOOF_INFO __callobf_globalFrameTable;
99 |
100 | // ==============================================================================
101 | // =========================== EXTERNAL FUNCTIONS ===============================
102 |
103 | /**
104 | * @brief Given stack spoof info, build a new spoofed stack picking different
105 | * entries each time. It will build the spoofed stach after its stack
106 | * frame ended, that means that is save to use the stack after a call
107 | * __callobf_buildSpoofedCallStack, if only we dont call more functions.
108 | * Even if its possible, this function should not be used from C.
109 | *
110 | * @return PVOID Pointer to start of the spoofed stack.
111 | */
112 | extern PVOID __callobf_buildSpoofedCallStack(PSTACK_SPOOF_INFO);
113 |
114 | // ==============================================================================
115 | // ============================ PUBLIC FUNCTIONS ===============================
116 |
117 | /**
118 | * @brief Given a module, finds the gadgets specified by the convination
119 | * of p_gadgetBytes, p_mask and gadgetSize. For every found gadget,
120 | * writes its frame info into the list given in p_entryList, until
121 | * no more gagets or the number of gadgets found equals maxEntries.
122 | *
123 | * @param p_module Module to search gadgets from.
124 | * @param p_entryList List of frame info to add the gadgets to.
125 | * @param maxEntries Maximun number of entries to add.
126 | * @param p_gadgetBytes Byte pattern of the gadget.
127 | * @param p_mask Mask to apply to read bytes before checking.
128 | * @param gadgetSize Size of p_gadgetBytes buffer.
129 | * @return DWORD64 Number of added entries to the list.
130 | */
131 | DWORD64 __callobf_fillGadgetTable(
132 | PVOID p_module,
133 | PFRAME_INFO p_entryList,
134 | DWORD64 maxEntries,
135 | PBYTE p_gadgetBytes,
136 | PBYTE p_mask,
137 | SIZE_T gadgetSize);
138 |
139 | /**
140 | * @brief Given a module, finds frames containing UWOP_SET_FPREG. For every found
141 | * gadget, writes its frame info into the list given in p_entryList, until
142 | * no more frames are or the number of frames found equals maxEntries.
143 | *
144 | * @param p_module Module to search frames from.
145 | * @param p_entryList List of frame info to add the frames to.
146 | * @param maxEntries Maximun number of entries to add.
147 | * @return DWORD64 Number of added entries to the list.
148 | */
149 | DWORD64 __callobf_fillFpRegFrameTable(
150 | PVOID p_module,
151 | PFRAME_INFO p_entryList,
152 | DWORD64 maxEntries);
153 |
154 | /**
155 | * @brief Given a module, finds frames containing a save of rsp to the stack. For every
156 | * found gadget, writes its frame info (including the offset where rbp is save)
157 | * into the list given in p_entryList, until no more frames are or the number of
158 | * frames found equals maxEntries.
159 | *
160 | * @param p_module Module to search frames from.
161 | * @param p_entryList List of frame info to add the frames to.
162 | * @param maxEntries Maximun number of entries to add.
163 | * @return DWORD64 Number of added entries to the list.
164 | */
165 | DWORD64 __callobf_fillSaveRbpFrameTable(
166 | PVOID p_module,
167 | PSAVE_RBP_FRAME_INFO p_entryList,
168 | DWORD64 maxEntries);
169 |
170 | /**
171 | * @brief Given a partially initialized stack spoof info, fills the four lists of
172 | * frames needed at runtime.
173 | *
174 | * @param p_stackSpoofInfo Pointer to spoof info containing the lists to be fille.
175 | * @param p_module Module to search frames from.
176 | * @return BOOL Success.
177 | */
178 | BOOL __callobf_fillStackSpoofTables(
179 | PSTACK_SPOOF_INFO p_stackSpoofInfo,
180 | PVOID p_module);
181 |
182 | /**
183 | * @brief Given an unitialized stack spoof info, initialized its basic fields.
184 | *
185 | * @param p_stackSpoofInfo Pointer to spoof to initialize.
186 | * @return BOOL Success.
187 | */
188 | BOOL __callobf_initializeSpoofInfo(
189 | PSTACK_SPOOF_INFO p_stackSpoofInfo);
190 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/headers/syscalls/syscalls.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file syscalls.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Utilities to work with windows x64 syscalls.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _SYSCALLS_H_
26 | #define _SYSCALLS_H_
27 |
28 | #include "common/common.h"
29 |
30 | // ==============================================================================
31 | // ============================ STRUCT DEFINITIONS ==============================
32 |
33 | typedef struct _SYSCALL_ITER_CTX
34 | {
35 | PVOID p_ntdll;
36 | PIMAGE_EXPORT_DIRECTORY p_expDir;
37 | DWORD64 lastEntry;
38 | } SYSCALL_ITER_CTX, *PSYSCALL_ITER_CTX;
39 |
40 | // ==============================================================================
41 | // ============================ PUBLIC FUNCTIONS ===============================
42 |
43 | /**
44 | * @brief Given the address of ntdll, initializes a syscall iterator context.
45 | *
46 | * @param p_ctx Pointer to the iterator context.
47 | * @param p_ntdll Pointer to ntdll.
48 | * @return BOOL Success.
49 | */
50 | BOOL __callobf_initSyscallIter(
51 | PSYSCALL_ITER_CTX p_ctx,
52 | PVOID p_ntdll);
53 |
54 | /**
55 | * @brief Given a syscall iterator context, gets the next syscalls address and stub
56 | * name, and modifies the ctx so the next call to this function returns the
57 | * following stub. The function names are always the Zw version of the function.
58 | * If there is no more stubs remaining or an error occurs, return FALSE.
59 | *
60 | * @param p_ctx Pointer to the iterator context.
61 | * @param pp_name Double pointer to return the function name.
62 | * @param pp_functionAddr Double pointer to return function address.
63 | * @return BOOL TRUE if next stub found, FALSE in any other case.
64 | */
65 | BOOL __callobf_getNextSyscall(
66 | PSYSCALL_ITER_CTX p_ctx,
67 | PCHAR *pp_name,
68 | PVOID *pp_functionAddr);
69 |
70 | /**
71 | * @brief Given a function name hash and a pointer to ntdll, checks if the function
72 | * is a syscall, if it is, returns its ssn and syscall pointer.
73 | *
74 | * @param nameHash 32 bit hash of the function name.
75 | * @param p_ntdll Pointer to ntdll.
76 | * @param p_ssn Pointer to return SSN if found.
77 | * @param pp_function Pointer to return the syscall address if found.
78 | * @return BOOL TRUE if is a syscall and both SSN and syscall address was found.
79 | */
80 | BOOL __callobf_loadSyscall(
81 | DWORD32 nameHash,
82 | PVOID p_ntdll,
83 | PUSHORT p_ssn,
84 | PVOID *pp_function);
85 |
86 | /**
87 | * @brief Given a pointer, searches for the closest syscall ret instruction pair
88 | * between p_lowBoundary and p_highBoundary.
89 | *
90 | * @param p_function Pointer to start search from.
91 | * @param p_lowBoundary Lowest possible address to search.
92 | * @param p_highBoundary Highest possible address to search.
93 | * @return PVOID Pointer to closest syscall ret, or NULL if not found.
94 | */
95 | PVOID __callobf_getSyscallAddr(
96 | PVOID p_function,
97 | PVOID p_lowBoundary,
98 | PVOID p_highBoundary);
99 |
100 | /**
101 | * @brief Given a function names, calculates its 32 bit representation using
102 | * the first two chars as "Zw"
103 | *
104 | * @param p_str Pointer to function name.
105 | * @return UINT32 32 bit representation of function name.
106 | */
107 | UINT32 __callobf_hashSyscallAsZw(PCHAR p_str);
108 |
109 | /**
110 | * @brief Given a function names, calculates its 32 bit representation using
111 | * the first two chars as "Nt"
112 | *
113 | * @param p_str Pointer to function name.
114 | * @return UINT32 32 bit representation of function name.
115 | */
116 | UINT32 __callobf_hashSyscallAsNt(PCHAR p_str);
117 |
118 | /**
119 | * @brief Check if the given function name matches the given hash in any of its
120 | * Zw or Nt forms. So ZwClose would match with the hash of NtClose.
121 | *
122 | * @param p_functionName Pointer to function name to check.
123 | * @param hash Hash of the function we are comparing to.
124 | * @return BOOL TRUE if the hash matches in any of its Zw or Nt form
125 | */
126 | BOOL __callobf_checkHashSyscallA(
127 | PCHAR p_functionName,
128 | DWORD32 hash);
129 |
130 | #endif
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/callDispatcher/asm/callDispatcher.x64.asm:
--------------------------------------------------------------------------------
1 | ; @file callDispatcher.x64.asm
2 | ; @author Alejandro González (@httpyxel)
3 | ; @brief Assembly utilities to dispatch windows native calls.
4 | ; @version 0.1
5 | ; @date 2024-01-14
6 | ;
7 | ; @copyright
8 | ; Copyright (C) 2024 Alejandro González
9 | ;
10 | ; This program is free software: you can redistribute it and/or modify
11 | ; it under the terms of the GNU General Public License as published by
12 | ; the Free Software Foundation, either version 3 of the License, or
13 | ; (at your option) any later version.
14 | ;
15 | ; This program is distributed in the hope that it will be useful,
16 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ; GNU General Public License for more details.
19 | ;
20 | ; You should have received a copy of the GNU General Public License
21 | ; along with this program. If not, see .
22 |
23 | %include "callDispatcher/asm/callDispatcher.x64.hasm"
24 |
25 | ENFORCE_FORMAT win64
26 | [BITS 64]
27 |
28 | ;void* __callobf_doCall(void* p_function, unsigned short ssn, unsigned long isSyscall, unsigned lon argCount, void* p_args, p_returnAddress, void* p_globalFramTable)
29 | __callobf_doCall:
30 | set_opt stored_regs , rbp, rsi, rdi
31 | set_opt arg_count , 6
32 | set_opt local_var_space , 0x10
33 | set_opt max_callee_arg_count , 1
34 | set_opt store_args , true
35 |
36 | end_opts
37 |
38 | ; ===================== Define args =======================
39 |
40 | %define pp_function stored_arg(0)
41 | %define p_ssn stored_arg(1)
42 | %define p_isSyscall stored_arg(2)
43 | %define p_argCount stored_arg(3)
44 | %define pp_args stored_arg(4)
45 | %define pp_returnAddress stored_arg(5)
46 | %define pp_globalFrameTable stored_arg(6)
47 |
48 | %define pp_newRsp local_var(0)
49 | %define pp_restore local_var(1)
50 |
51 | ; ======================= Code ============================
52 | mov rbp, rsp
53 |
54 | fastcall_win64 __callobf_buildSpoofedCallStack, [pp_globalFrameTable]
55 | checkNull rax, error
56 |
57 | mov [pp_newRsp], rax
58 |
59 | ; Store reference to restore subroutine
60 | lea rax, [rel restore]
61 | mov [pp_restore], rax
62 |
63 | ; Move arguments 4+
64 | mov rcx, [p_argCount]
65 | sub rcx, 4
66 | jle lessThanFiveArgs
67 |
68 | mov rsi, [pp_args]
69 | add rsi, 0x20
70 |
71 | mov rdi, [pp_newRsp]
72 | add rdi, 0x28
73 |
74 | rep movsq
75 |
76 | lessThanFiveArgs:
77 |
78 | ; Move arguments 0-3
79 | mov rsi, [pp_args]
80 | mov rcx, [rsi]
81 | mov rdx, [rsi + 0x8]
82 | mov r8, [rsi + 0x10]
83 | mov r9, [rsi + 0x18]
84 |
85 | mov r10, [stored_arg(2)]
86 |
87 | ; Set ssn and first arg if syscall
88 | checkNull r10, notSyscall
89 | mov r10, rcx
90 | mov rax, [stored_arg(1)]
91 | notSyscall:
92 |
93 | ; Set rbx to point to the reference to restore
94 | lea rbx, [pp_restore]
95 |
96 | ; Take any variable we need from the stack before changing rsp
97 | mov r11, [pp_function]
98 |
99 | mov rsp, [pp_newRsp]
100 | jmp r11
101 |
102 | error:
103 | fastcall_win64 __callobf_setLastError, 1
104 | xor rax, rax
105 | restore:
106 | mov rsp, rbp
107 | end_function
108 |
109 |
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/callDispatcher/callDispatcher.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file callDispatcher.c
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Funcionality to invoke windows native functions applying obfuscation.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "callDispatcher/callDispatcher.h"
26 | #include "common/commonUtils.h"
27 | #include "pe/peUtils.h"
28 | #include "syscalls/syscalls.h"
29 | #include "common/debug.h"
30 |
31 | HMODULE __callobf_loadLibrary(PCHAR p_dllName)
32 | {
33 | DWORD loadLibraryIndex = -1;
34 |
35 | if (!p_dllName)
36 | return NULL;
37 |
38 | for (DWORD i = 0; i < __callobf_functionTable.count; i++)
39 | if (__callobf_functionTable.entries[i].hash == LOADLIBRARYA_HASH)
40 | loadLibraryIndex = i;
41 |
42 | if (loadLibraryIndex == (DWORD)-1)
43 | return NULL;
44 |
45 | return __callobf_callDispatcher(loadLibraryIndex, p_dllName);
46 | }
47 |
48 | void *__callobf_loadFunction(PFUNCTION_TABLE_ENTRY p_fEntry)
49 | {
50 | PDLL_TABLE_ENTRY p_dllEntry = NULL;
51 | DWORD32 moduleHash = 0;
52 | USHORT ssn = 0;
53 | PVOID p_function = NULL;
54 | BOOL isSyscall = FALSE;
55 |
56 | if (!p_fEntry)
57 | return NULL;
58 |
59 | p_dllEntry = &__callobf_dllTable.entries[p_fEntry->moduleIndex];
60 | moduleHash = __callobf_hashA(p_dllEntry->name);
61 |
62 | DEBUG_PRINT("Loading function: %lX", p_fEntry->hash);
63 |
64 | if (!p_dllEntry->handle)
65 | {
66 | DEBUG_PRINT("Loading module: %s", p_dllEntry->name);
67 | p_dllEntry->handle = __callobf_getModuleAddrH(moduleHash);
68 | if (!p_dllEntry->handle)
69 | p_dllEntry->handle = __callobf_loadLibrary(p_dllEntry->name);
70 | }
71 |
72 | if (!p_dllEntry->handle)
73 | {
74 | DEBUG_PRINT("Error, couldnt load dll");
75 | return NULL;
76 | }
77 |
78 | p_fEntry->ssn = 0;
79 | if (moduleHash == NTDLL_HASH)
80 | {
81 | DEBUG_PRINT("Is ntdll");
82 | if (__callobf_loadSyscall(p_fEntry->hash, p_dllEntry->handle, &ssn, &p_function))
83 | {
84 | isSyscall = TRUE;
85 |
86 | p_fEntry->ssn = (((DWORD32)ssn) | 0xFFFF0000);
87 | p_fEntry->functionPtr = p_function;
88 |
89 | DEBUG_PRINT("Loaded function 0x%08lX as syscall with ssn 0x%04X at %p", p_fEntry->hash, ssn, p_function);
90 | }
91 | };
92 |
93 | if (!isSyscall)
94 | p_fEntry->functionPtr = __callobf_getFunctionAddrH(p_dllEntry->handle, p_fEntry->hash);
95 |
96 | if (!p_fEntry->functionPtr)
97 | {
98 | DEBUG_PRINT("Error loading function");
99 | return NULL;
100 | }
101 |
102 | return p_fEntry->functionPtr;
103 | }
104 |
105 | void *__callobf_callDispatcher(DWORD32 index, ...)
106 | {
107 | PVOID p_returnAddress = __builtin_frame_address(0) - 0x8;
108 | PVOID p_function = NULL;
109 | USHORT ssn = 0;
110 | BOOL isSyscall = FALSE;
111 | DWORD32 argCount = 0;
112 | PFUNCTION_TABLE_ENTRY p_fEntry = NULL;
113 |
114 | __callobf_setLastError(0);
115 |
116 | #ifdef __clang__
117 | __builtin_ms_va_list p_args;
118 | #else
119 | __builtin_va_list p_args;
120 | #endif
121 |
122 | __builtin_ms_va_start(p_args, index);
123 |
124 | if (index > __callobf_functionTable.count)
125 | {
126 | __callobf_setLastError(1);
127 | return NULL;
128 | }
129 |
130 | p_fEntry = &(__callobf_functionTable.entries[index]);
131 |
132 | if (!(p_function = p_fEntry->functionPtr))
133 | {
134 | if (!(p_function = __callobf_loadFunction(p_fEntry)))
135 | {
136 | __callobf_setLastError(1);
137 | return NULL;
138 | }
139 | }
140 |
141 | argCount = p_fEntry->argCount;
142 |
143 | if ((p_fEntry->ssn & 0xFFFF0000))
144 | {
145 | p_fEntry->ssn &= 0x0000FFFF;
146 | ssn = (USHORT)(p_fEntry->ssn);
147 | isSyscall = TRUE;
148 | }
149 |
150 | DEBUG_PRINT("Dispatching index %u", index);
151 | BREAKPOINT();
152 | return __callobf_doCall(p_function, ssn, isSyscall, argCount, p_args, p_returnAddress, &__callobf_globalFrameTable);
153 | }
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/common/commonUtils.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file commonUtils.c
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Common functionality used across the project.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "common/commonUtils.h"
26 | #include "common/debug.h"
27 |
28 | // ==============================================================================
29 | // ================================= GLOBALS ====================================
30 |
31 | unsigned long __callobf_lastError = 0;
32 |
33 | BOOL __callobf_srand(PDWORD p_ctx, DWORD seed)
34 | {
35 | if (!p_ctx)
36 | return FALSE;
37 |
38 | *p_ctx = seed;
39 |
40 | return TRUE;
41 | }
42 |
43 | DWORD __callobf_rand(PDWORD p_ctx) // RAND_MAX assumed as 256 + 20
44 | {
45 | if (!p_ctx)
46 | return 0;
47 | *p_ctx = *p_ctx * 1103515245 + 12345;
48 | return ((DWORD)(*p_ctx / 65536) % 0x7f) + 0x20;
49 | }
50 |
51 | /**
52 | * @brief Simple hash function to produce 32 bit values from NULL terminated strings.
53 | * Any other 32 bit hash like djb2 would work.
54 | *
55 | * @param p_str Pointer to NULL terminated string.
56 | * @return UINT32 32 bit value representing the string.
57 | */
58 | UINT32 __callobf_hashA(const PCHAR p_str)
59 | {
60 | UINT h;
61 | PCHAR p;
62 | CHAR c;
63 |
64 | if (!p_str)
65 | return 0;
66 |
67 | h = 0;
68 | for (p = p_str; *p != '\0'; p++)
69 | {
70 | c = (*p >= 65 && *p <= 90) ? *p + 32 : *p;
71 | h = HASH_MULTIPLIER * h + c;
72 | }
73 | return h;
74 | }
75 |
76 | UINT32 __callobf_hashW(const PWCHAR p_str)
77 | {
78 | UINT h;
79 | PWCHAR p;
80 | WCHAR c;
81 |
82 | if (!p_str)
83 | return 0;
84 |
85 | h = 0;
86 | for (p = p_str; *p != 0; p++)
87 | {
88 | c = (*p >= 65 && *p <= 90) ? *p + 32 : *p;
89 | h = HASH_MULTIPLIER * h + c;
90 | }
91 | return h;
92 | }
93 |
94 | UINT32 __callobf_hashU(const PUNICODE_STRING p_str)
95 | {
96 | UINT32 h;
97 | PWCHAR p;
98 | WCHAR c;
99 |
100 | if (!p_str)
101 | return 0;
102 |
103 | DWORD_PTR p_end = ((DWORD_PTR)p_str->Buffer) + p_str->Length;
104 |
105 | h = 0;
106 | for (p = p_str->Buffer; (DWORD_PTR)p < p_end; p++)
107 | {
108 | c = (*p >= 65 && *p <= 90) ? *p + 32 : *p;
109 | h = HASH_MULTIPLIER * h + c;
110 | }
111 | return h;
112 | }
113 |
114 | PVOID __callobf_findBytes(
115 | _In_ const PVOID startAddr,
116 | _In_ const PVOID endAddr,
117 | _In_ const PBYTE bytes,
118 | _In_ const PBYTE mask,
119 | _In_ const DWORD byteCount)
120 | {
121 | BYTE checkByte = 0;
122 | BYTE maskByte = 0;
123 | DWORD matches = 0;
124 |
125 | if (!bytes || !mask)
126 | return NULL;
127 |
128 | for (DWORD_PTR j = (DWORD_PTR)startAddr; j < ((DWORD_PTR)endAddr - byteCount); j++)
129 | {
130 | checkByte = *(PBYTE)((DWORD_PTR)bytes + matches);
131 | maskByte = *(PBYTE)((DWORD_PTR)mask + matches);
132 | if (checkByte == *(PBYTE)j || maskByte == 0)
133 | {
134 | if (++matches == byteCount)
135 | return (PVOID)(j - (matches - 1));
136 | }
137 | else
138 | {
139 | matches = 0;
140 | }
141 | }
142 | return NULL;
143 | }
144 |
145 | PVOID __callobf_getStackBase()
146 | {
147 | #pragma GCC diagnostic push
148 | #pragma GCC diagnostic ignored "-Warray-bounds"
149 | return ((PNT_TIB)NtCurrentTeb())->StackBase;
150 | #pragma GCC diagnostic pop
151 | }
152 |
153 | PVOID __callobf_getStackLimit()
154 | {
155 | #pragma GCC diagnostic push
156 | #pragma GCC diagnostic ignored "-Warray-bounds"
157 | return ((PNT_TIB)NtCurrentTeb())->StackLimit;
158 | #pragma GCC diagnostic pop
159 | }
160 |
161 | DWORD32 __callobf_lfsrXorShift32(DWORD32 prevVal)
162 | {
163 | prevVal ^= prevVal >> 13;
164 | prevVal ^= prevVal << 17;
165 | prevVal ^= prevVal >> 5;
166 | return prevVal;
167 | }
168 |
169 | DWORD32 __callobf_getLastError()
170 | {
171 | return __callobf_lastError;
172 | }
173 |
174 | VOID __callobf_setLastError(DWORD32 error)
175 | {
176 | __callobf_lastError = error;
177 | }
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/pe/peUtils.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file peUtils.c
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Utilities to manipulate and work with in-memory PEs.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "pe/peUtils.h"
26 | #include "common/commonUtils.h"
27 |
28 | PVOID __callobf_getModuleAddrA(const PCHAR p_moduleName)
29 | {
30 | return __callobf_getModuleAddrH(__callobf_hashA(p_moduleName));
31 | }
32 |
33 | PVOID __callobf_getModuleAddrW(const PWCHAR p_moduleName)
34 | {
35 | return __callobf_getModuleAddrH(__callobf_hashW(p_moduleName));
36 | }
37 |
38 | PVOID __callobf_getModuleAddrH(const UINT32 moduleHash)
39 | {
40 | PPEB peb;
41 | PPEB_LDR_DATA ldr;
42 | PLDR_DATA_TABLE_ENTRY dte;
43 | PLIST_ENTRY ent;
44 | PLIST_ENTRY hdr;
45 |
46 | NOWARN("-Warray-bounds",
47 | peb = NtCurrentTeb()->ProcessEnvironmentBlock;)
48 |
49 | ldr = peb->Ldr;
50 | hdr = &ldr->InLoadOrderModuleList;
51 | ent = hdr->Flink;
52 |
53 | for (; hdr != ent; ent = ent->Flink)
54 | {
55 | dte = (PLDR_DATA_TABLE_ENTRY)ent;
56 | if (moduleHash == __callobf_hashU(&dte->BaseDllName))
57 | {
58 | return (PVOID)dte->DllBase;
59 | }
60 | }
61 | return NULL;
62 | }
63 |
64 | PVOID __callobf_getFunctionAddrA(const PVOID p_module, const PCHAR p_functionName)
65 | {
66 | return __callobf_getFunctionAddrH(p_module, __callobf_hashA(p_functionName));
67 | }
68 |
69 | PVOID __callobf_getFunctionAddrW(const PVOID p_module, const PWCHAR p_functionName)
70 | {
71 | return __callobf_getFunctionAddrH(p_module, __callobf_hashW(p_functionName));
72 | }
73 |
74 | PVOID __callobf_getFunctionAddrH(const PVOID p_module, const UINT32 funtionHash)
75 | {
76 | PIMAGE_DOS_HEADER dos;
77 | PIMAGE_NT_HEADERS nth;
78 | PIMAGE_DATA_DIRECTORY dir;
79 | PIMAGE_EXPORT_DIRECTORY exp;
80 | PDWORD aof;
81 | PDWORD aon;
82 | PUSHORT ano;
83 | PCHAR str;
84 | DWORD cnt;
85 |
86 | if (p_module == NULL)
87 | return NULL;
88 |
89 | if (*(PSHORT)p_module != PE_MAGIC)
90 | return NULL;
91 |
92 | dos = p_module;
93 | nth = (PVOID)((DWORD_PTR)dos + dos->e_lfanew);
94 | dir = (PVOID)(&nth->OptionalHeader.DataDirectory[0]);
95 |
96 | if (dir->VirtualAddress)
97 | {
98 | exp = (PVOID)((DWORD_PTR)dos + dir->VirtualAddress);
99 | aof = (PVOID)((DWORD_PTR)dos + exp->AddressOfFunctions);
100 | aon = (PVOID)((DWORD_PTR)dos + exp->AddressOfNames);
101 | ano = (PVOID)((DWORD_PTR)dos + exp->AddressOfNameOrdinals);
102 |
103 | for (cnt = 0; cnt < exp->NumberOfNames; cnt++)
104 | {
105 | str = (PVOID)((DWORD_PTR)dos + aon[cnt]);
106 | if (funtionHash == __callobf_hashA(str))
107 | {
108 | return (PVOID)((DWORD_PTR)dos + aof[ano[cnt]]);
109 | };
110 | };
111 | };
112 | return NULL;
113 | };
114 |
115 | PVOID __callobf_getExceptionDirectoryAddress(const PVOID p_module, PDWORD p_size)
116 | {
117 | if (p_module == NULL)
118 | return NULL;
119 |
120 | if (*(PSHORT)p_module != PE_MAGIC)
121 | return NULL;
122 |
123 | PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)p_module;
124 | PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)p_module + dosHeader->e_lfanew);
125 |
126 | DWORD exceptionDirectoryRVA = ntHeader->OptionalHeader.DataDirectory[3].VirtualAddress;
127 | if (exceptionDirectoryRVA == 0)
128 | return NULL;
129 |
130 | *p_size = ntHeader->OptionalHeader.DataDirectory[3].Size;
131 | return (PVOID)((DWORD_PTR)p_module + exceptionDirectoryRVA);
132 | }
133 |
134 | BOOL __callobf_getCodeBoundaries(PVOID p_module, PVOID *pp_base, PVOID *pp_top)
135 | {
136 | PIMAGE_SECTION_HEADER codeHeader = NULL;
137 |
138 | if (!p_module || !pp_base || !pp_top)
139 | return FALSE;
140 |
141 | // Get boundaries for gadget search
142 | PIMAGE_DOS_HEADER dos = p_module;
143 | PIMAGE_NT_HEADERS nth = (PVOID)((DWORD_PTR)dos + dos->e_lfanew);
144 | PIMAGE_SECTION_HEADER sectionHeaders = IMAGE_FIRST_SECTION(nth);
145 |
146 | for (WORD i = 0; i < nth->FileHeader.NumberOfSections; i++)
147 | {
148 | if (sectionHeaders[i].Characteristics & IMAGE_SCN_CNT_CODE)
149 | {
150 | codeHeader = §ionHeaders[i];
151 | break;
152 | }
153 | }
154 |
155 | if (!codeHeader)
156 | return FALSE;
157 |
158 | if (!codeHeader->VirtualAddress || !codeHeader->SizeOfRawData)
159 | return FALSE;
160 |
161 | *pp_base = (PVOID)((DWORD_PTR)dos + codeHeader->VirtualAddress);
162 | *pp_top = (PVOID)((DWORD_PTR)*pp_base + codeHeader->SizeOfRawData);
163 | return TRUE;
164 | }
165 |
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/pe/unwind/unwindUtils.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file unwindUtils.c
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Utilities to manipulate and work with PE unwind information.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "pe/unwind/unwindUtils.h"
26 | #include "common/commonUtils.h"
27 | #include "pe/peUtils.h"
28 |
29 | BOOL __callobf_createOrResetUnwindInfoIterator(
30 | _Out_ PUNWIND_INFO_ITERATOR_CONTEXT p_ctx,
31 | _In_ HMODULE p_moduleBase)
32 | {
33 | DWORD runtimeFunctionTableSize = 0;
34 |
35 | if (!p_ctx || !p_moduleBase)
36 | return FALSE;
37 |
38 | p_ctx->p_moduleBase = p_moduleBase;
39 | p_ctx->nextIndex = 0;
40 | p_ctx->ended = FALSE;
41 |
42 | p_ctx->p_runtimeFunctionTable = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(__callobf_getExceptionDirectoryAddress(p_moduleBase, &runtimeFunctionTableSize));
43 | p_ctx->entryCount = (DWORD)(runtimeFunctionTableSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY));
44 | return TRUE;
45 | }
46 |
47 | PUNWIND_INFO __callobf_getNextUnwindInfo(
48 | PUNWIND_INFO_ITERATOR_CONTEXT p_ctx)
49 | {
50 | if (!p_ctx)
51 | return NULL;
52 |
53 | PIMAGE_RUNTIME_FUNCTION_ENTRY p_runtimeFunctionTable = p_ctx->p_runtimeFunctionTable;
54 | DWORD currentIndex = p_ctx->nextIndex;
55 |
56 | if (currentIndex >= p_ctx->entryCount)
57 | {
58 | p_ctx->ended = TRUE;
59 | return NULL;
60 | }
61 |
62 | p_ctx->nextIndex++;
63 | return (PUNWIND_INFO)((UINT64)p_ctx->p_moduleBase + (DWORD)p_runtimeFunctionTable[currentIndex].UnwindData);
64 | }
65 |
66 | BOOL __callobf_createOrResetUwopIterator(
67 | _Out_ PUWOP_ITERATOR_CONTEXT p_ctx,
68 | _In_ HMODULE p_moduleBase,
69 | _In_ PUNWIND_INFO p_unwindInfoAddress)
70 | {
71 | if (!p_ctx || !p_moduleBase || !p_unwindInfoAddress)
72 | return FALSE;
73 |
74 | p_ctx->p_moduleBase = p_moduleBase;
75 | p_ctx->p_unwindInfo = p_unwindInfoAddress;
76 | p_ctx->nextIndex = 0;
77 | p_ctx->ended = FALSE;
78 | return TRUE;
79 | }
80 |
81 | PUNWIND_CODE __callobf_getNextUwop(
82 | _Inout_ PUWOP_ITERATOR_CONTEXT p_ctx)
83 | {
84 | PRUNTIME_FUNCTION p_chainedFunction;
85 | PUNWIND_CODE p_unwindCode = (PUNWIND_CODE)p_ctx->p_unwindInfo->UnwindCode;
86 | PUNWIND_CODE p_nextUnwindCode = NULL;
87 |
88 | DWORD countOfCodes = p_ctx->p_unwindInfo->CountOfCodes;
89 |
90 | if (p_ctx->ended)
91 | return NULL;
92 |
93 | if (p_ctx->nextIndex >= countOfCodes)
94 | {
95 | if (BitChainInfo(p_ctx->p_unwindInfo->Flags))
96 | {
97 | PUNWIND_INFO p_unwindInfoBackup = p_ctx->p_unwindInfo;
98 | PUNWIND_CODE tmpUwop = NULL;
99 |
100 | p_chainedFunction = (PRUNTIME_FUNCTION)(&p_unwindCode[(countOfCodes + 1) & ~1]);
101 |
102 | p_ctx->nextIndex -= countOfCodes;
103 | p_ctx->p_unwindInfo = (PUNWIND_INFO)((UINT64)p_ctx->p_moduleBase + (DWORD)p_chainedFunction->UnwindData);
104 | tmpUwop = __callobf_getNextUwop(p_ctx);
105 | p_ctx->p_unwindInfo = p_unwindInfoBackup;
106 | p_ctx->nextIndex += countOfCodes;
107 |
108 | return tmpUwop;
109 | }
110 | p_ctx->ended = TRUE;
111 | return NULL;
112 | }
113 | p_nextUnwindCode = &p_unwindCode[p_ctx->nextIndex];
114 |
115 | p_ctx->nextIndex += __callobf_getNodesUsed(&p_unwindCode[p_ctx->nextIndex]);
116 |
117 | return p_nextUnwindCode;
118 | }
119 |
120 | DWORD __callobf_getNodesUsed(PUNWIND_CODE p_unwindCode)
121 | {
122 | DWORD nodeCount = 0;
123 |
124 | if (!p_unwindCode)
125 | return 0;
126 |
127 | switch (p_unwindCode->UnwindOp)
128 | {
129 | case UWOP_PUSH_NONVOL: // 0
130 | case UWOP_ALLOC_SMALL: // 2
131 | case UWOP_SET_FPREG: // 3
132 | case UWOP_PUSH_MACH_FRAME: // 10
133 | break;
134 |
135 | case UWOP_SAVE_NONVOL: // 4
136 | case UWOP_EPILOG: // 6
137 | case UWOP_SAVE_XMM128: // 8
138 | nodeCount++;
139 | break;
140 |
141 | case UWOP_ALLOC_LARGE: // 1
142 | nodeCount++;
143 | if (p_unwindCode->OpInfo != 0)
144 | nodeCount++;
145 | break;
146 |
147 | case UWOP_SAVE_NONVOL_BIG: // 5
148 | case UWOP_SPARE_CODE: // 7
149 | case UWOP_SAVE_XMM128BIG: // 9
150 | nodeCount += 2;
151 | break;
152 | }
153 | return (nodeCount + 1);
154 | }
155 |
156 | LONG __callobf_getStackSizeModification(PUNWIND_CODE p_unwindCode)
157 | {
158 | LONG stackSizeMod = 0;
159 |
160 | if (!p_unwindCode)
161 | return 0;
162 |
163 | switch (p_unwindCode->UnwindOp)
164 | {
165 | case UWOP_PUSH_NONVOL: // 0
166 | stackSizeMod = 8;
167 | break;
168 | case UWOP_ALLOC_SMALL: // 2
169 | stackSizeMod = 8 * (p_unwindCode->OpInfo + 1);
170 | break;
171 | case UWOP_SET_FPREG: // 3
172 | break;
173 | case UWOP_PUSH_MACH_FRAME: // 10
174 | if (p_unwindCode->OpInfo == 0)
175 | {
176 | stackSizeMod = 0x40;
177 | break;
178 | }
179 | stackSizeMod = 0x48;
180 | break;
181 |
182 | case UWOP_SAVE_NONVOL: // 4
183 | case UWOP_EPILOG: // 6
184 | case UWOP_SAVE_XMM128: // 8
185 | break;
186 |
187 | case UWOP_ALLOC_LARGE: // 1
188 | stackSizeMod = p_unwindCode[1].FrameOffset;
189 |
190 | if (p_unwindCode->OpInfo == 0)
191 | {
192 | stackSizeMod *= 8;
193 | }
194 | else
195 | {
196 | stackSizeMod += p_unwindCode[2].FrameOffset << 16;
197 | }
198 | break;
199 |
200 | case UWOP_SAVE_NONVOL_BIG: // 5
201 | case UWOP_SPARE_CODE: // 7
202 | case UWOP_SAVE_XMM128BIG: // 9
203 | break;
204 | }
205 | return stackSizeMod;
206 | }
207 |
208 | LONG __callobf_getFrameSizeModification(PUNWIND_INFO p_unwindInfo, PUNWIND_CODE p_unwindCode)
209 | {
210 | LONG frameSizeMod = 0;
211 | if (!p_unwindInfo || !p_unwindCode)
212 | return 0;
213 |
214 | switch (p_unwindCode->UnwindOp)
215 | {
216 | case UWOP_SET_FPREG: // 3
217 | frameSizeMod = -0x10 * (p_unwindInfo->FrameOffset);
218 | break;
219 | default:
220 | frameSizeMod = __callobf_getStackSizeModification(p_unwindCode);
221 | }
222 | return frameSizeMod;
223 | }
224 |
225 | PUNWIND_INFO __callobf_getUnwindInfoForCodePtr(PVOID p_module, PVOID p_code, PVOID *pp_functionStart, PVOID *pp_functionEnd)
226 | {
227 | DWORD_PTR startAddress = 0;
228 | DWORD_PTR endAddress = 0;
229 |
230 | DWORD runtimeFunctionTableSize = 0;
231 | DWORD rtEntryCount = 0;
232 | PIMAGE_RUNTIME_FUNCTION_ENTRY p_runtimeFunctionTable = NULL;
233 |
234 | if (!p_module || !p_code)
235 | return NULL;
236 |
237 | p_runtimeFunctionTable = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(__callobf_getExceptionDirectoryAddress(p_module, &runtimeFunctionTableSize));
238 |
239 | if (!p_runtimeFunctionTable || !runtimeFunctionTableSize)
240 | return NULL;
241 |
242 | rtEntryCount = (DWORD)(runtimeFunctionTableSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY));
243 |
244 | for (DWORD i = 0; i < rtEntryCount; i++)
245 | {
246 | startAddress = (DWORD_PTR)p_module + p_runtimeFunctionTable[i].BeginAddress;
247 | endAddress = (DWORD_PTR)p_module + p_runtimeFunctionTable[i].EndAddress;
248 |
249 | if ((DWORD_PTR)p_code >= startAddress && (DWORD_PTR)p_code < endAddress)
250 | {
251 | if (pp_functionStart)
252 | *pp_functionStart = (PVOID)startAddress;
253 |
254 | if (pp_functionEnd)
255 | *pp_functionEnd = (PVOID)endAddress;
256 |
257 | return (PUNWIND_INFO)((UINT64)p_module + (DWORD)p_runtimeFunctionTable[i].UnwindData);
258 | }
259 | }
260 | return NULL;
261 | }
262 |
263 | PVOID __callobf_findEntryAddressOfReturnAddress(PVOID p_ntdll, PVOID p_kernel32)
264 | {
265 | PVOID p_BaseThreadInitThunk = NULL;
266 | PVOID p_RtlUserThreadStart = NULL;
267 |
268 | PVOID p_BaseThreadInitThunkEnd = NULL;
269 | PVOID p_RtlUserThreadStartEnd = NULL;
270 |
271 | PUNWIND_INFO p_unwindInfoBtit = NULL;
272 | PUNWIND_INFO p_unwindInfoRuts = NULL;
273 |
274 | SIZE_T frameSizeBtit = 0;
275 | SIZE_T frameSizeRuts = 0;
276 |
277 | UWOP_ITERATOR_CONTEXT ctx = {0};
278 | PUNWIND_CODE p_uwop = NULL;
279 |
280 | if (!p_ntdll || !p_kernel32)
281 | return NULL;
282 |
283 | p_BaseThreadInitThunk = __callobf_getFunctionAddrH(p_kernel32, BASETRHEADINITTHUNK_HASH);
284 | p_RtlUserThreadStart = __callobf_getFunctionAddrH(p_ntdll, RTLUSERTHREADSTART_HASH);
285 |
286 | if (!p_BaseThreadInitThunk || !p_RtlUserThreadStart)
287 | return NULL;
288 |
289 | p_unwindInfoBtit = __callobf_getUnwindInfoForCodePtr(p_kernel32, p_BaseThreadInitThunk, NULL, &p_BaseThreadInitThunkEnd);
290 | p_unwindInfoRuts = __callobf_getUnwindInfoForCodePtr(p_ntdll, p_RtlUserThreadStart, NULL, &p_RtlUserThreadStartEnd);
291 |
292 | if (!p_unwindInfoBtit || !p_unwindInfoRuts)
293 | return NULL;
294 |
295 | __callobf_createOrResetUwopIterator(&ctx, p_kernel32, p_unwindInfoBtit);
296 | while ((p_uwop = __callobf_getNextUwop(&ctx)))
297 | frameSizeBtit += __callobf_getFrameSizeModification(p_unwindInfoBtit, p_uwop);
298 |
299 | __callobf_createOrResetUwopIterator(&ctx, p_ntdll, p_unwindInfoRuts);
300 | while ((p_uwop = __callobf_getNextUwop(&ctx)))
301 | frameSizeRuts += __callobf_getFrameSizeModification(p_unwindInfoRuts, p_uwop);
302 |
303 | // We start searching from the stack base and go downwards (or upwards, however u want to see it xd)
304 | // We search for a configuration like:
305 | // 0xN : value >= p_BaseThreadInitThunk && value < p_BaseThreadInitThunkEnd
306 | // 0xN + frameSizeBtit + 0x8 : value >= p_RtlUserThreadStart && value < p_RtlUserThreadStartEnd
307 | // 0xN + frameSizeRuts + 0x8 : value == 0
308 |
309 | PDWORD64 stackBase = __callobf_getStackBase();
310 | PDWORD64 stackLimit = __callobf_getStackLimit();
311 | DWORD_PTR value = 0;
312 |
313 | // Rember stack base is the lower limit, but it is not included, so the first readable quadword
314 | // is found at stackBase - 0x8
315 |
316 | // Also, we know the block of addresses have a certain size, so we can discard the starting addresses
317 | // where it can not be, so we dont need to check for access violations
318 |
319 | stackBase = (PDWORD64)((DWORD_PTR)stackBase - 8);
320 | stackBase = (PDWORD64)((DWORD_PTR)stackBase - (frameSizeRuts + frameSizeBtit + 0x10));
321 | for (PDWORD64 runner = stackBase; runner >= stackLimit; runner--)
322 | {
323 | value = *runner;
324 | if (value >= (DWORD_PTR)p_BaseThreadInitThunk && value < (DWORD_PTR)p_BaseThreadInitThunkEnd)
325 | {
326 | value = *(PDWORD64)((DWORD_PTR)runner + frameSizeBtit + 0x8);
327 | if (value >= (DWORD_PTR)p_RtlUserThreadStart && value < (DWORD_PTR)p_RtlUserThreadStartEnd)
328 | {
329 |
330 | value = *(PDWORD64)((DWORD_PTR)runner + frameSizeBtit + 0x8 + frameSizeRuts + 0x8);
331 | if (value == 0)
332 | return (PVOID)runner;
333 | }
334 | }
335 | }
336 |
337 | return NULL;
338 | }
339 |
340 | BOOL __callobf_getCodeBoundariesLastUnwindInfo(
341 | PUNWIND_INFO_ITERATOR_CONTEXT p_ctx,
342 | PVOID *pp_beginAddress,
343 | PVOID *pp_endAddress)
344 | {
345 | PIMAGE_RUNTIME_FUNCTION_ENTRY p_runtimeFunctionTable = p_ctx->p_runtimeFunctionTable;
346 |
347 | if (!p_ctx || !pp_beginAddress || !pp_endAddress)
348 | return FALSE;
349 |
350 | if (p_ctx->nextIndex <= 0)
351 | return FALSE;
352 |
353 | DWORD currentIndex = p_ctx->nextIndex - 1;
354 |
355 | if (currentIndex >= p_ctx->entryCount)
356 | return FALSE;
357 |
358 | if (pp_beginAddress)
359 | *pp_beginAddress = (PVOID)((UINT64)p_ctx->p_moduleBase + (DWORD)p_runtimeFunctionTable[currentIndex].BeginAddress);
360 |
361 | if (pp_endAddress)
362 | *pp_endAddress = (PVOID)((UINT64)p_ctx->p_moduleBase + (DWORD)p_runtimeFunctionTable[currentIndex].EndAddress);
363 |
364 | return TRUE;
365 | }
366 |
367 | LONG64 __callobf_getOffsetWhereRegSaved(PVOID p_module, PUNWIND_INFO p_unwindInfo, REGISTERS reg)
368 | {
369 | UWOP_ITERATOR_CONTEXT uwopCtx = {0};
370 | PUNWIND_CODE p_uwop = NULL;
371 | LONG stackSize = 0;
372 |
373 | if (!p_module || !p_unwindInfo)
374 | return -1;
375 |
376 | if (!__callobf_createOrResetUwopIterator(&uwopCtx, p_module, p_unwindInfo))
377 | return -1;
378 |
379 | while ((p_uwop = __callobf_getNextUwop(&uwopCtx)))
380 | {
381 | if (p_uwop->OpInfo == reg)
382 | {
383 | switch (p_uwop->UnwindOp)
384 | {
385 | case UWOP_PUSH_NONVOL: // 0
386 | return stackSize;
387 | case UWOP_SAVE_NONVOL: // 0
388 | return (p_uwop[1].FrameOffset * 8);
389 |
390 | case UWOP_SAVE_NONVOL_BIG: // 0
391 | return (p_uwop[2].FrameOffset << 16 | p_uwop[1].FrameOffset);
392 | default:
393 | break;
394 | }
395 | }
396 | stackSize += __callobf_getStackSizeModification(p_uwop);
397 | }
398 | return -1;
399 | }
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/stackSpoof/asm/stackSpoofHelper.x64.asm:
--------------------------------------------------------------------------------
1 | ; @file stackSpoofHelper.x64.asm
2 | ; @author Alejandro González (@httpyxel)
3 | ; @brief Assembly utilities to build spoofed stacks for windows x64 native function.
4 | ; @version 0.1
5 | ; @date 2024-01-14
6 | ;
7 | ; @copyright
8 | ; Copyright (C) 2024 Alejandro González
9 | ;
10 | ; This program is free software: you can redistribute it and/or modify
11 | ; it under the terms of the GNU General Public License as published by
12 | ; the Free Software Foundation, either version 3 of the License, or
13 | ; (at your option) any later version.
14 | ;
15 | ; This program is distributed in the hope that it will be useful,
16 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ; GNU General Public License for more details.
19 | ;
20 | ; You should have received a copy of the GNU General Public License
21 | ; along with this program. If not, see .
22 |
23 | %include "stackSpoof/asm/stackSpoofHelpers.x64.hasm"
24 |
25 | ENFORCE_FORMAT win64
26 | [BITS 64]
27 |
28 | ; Imported functions
29 | EXTERN __callobf_lfsrXorShift32
30 | EXTERN __callobf_initializeSpoofInfo
31 |
32 | ; Exported functions
33 | GLOBAL __callobf_buildSpoofedCallStack
34 |
35 | [SECTION .text]
36 |
37 | ; This function should only be used from other assembly code, where we have full control of the stack
38 |
39 | ; void* __callobf_buildSpoofedCallStack(PSTACK_SPOOF_INFO)
40 | __callobf_buildSpoofedCallStack:
41 | set_opt stored_regs , rbp, rbx, rsi, r12, r13, r14, r15
42 | set_opt arg_count , 1
43 | set_opt local_var_space , 0x0
44 | set_opt max_callee_arg_count , 1
45 | set_opt store_args , false
46 |
47 | end_opts
48 |
49 | ; ======================================================================================================================
50 |
51 |
52 | ; PSTACK_SPOOF_INFO p_info
53 | %define p_info rcx
54 |
55 | %define listEntryCount rsi
56 | %define entry32 ebx
57 | %define entry64 rbx
58 |
59 | %define p_setFpRegFrameInfo r10
60 | %define p_saveRbpFrameInfo r11
61 | %define p_jmpRbxFrameInfo r12
62 | %define p_addRspFrameInfo r13
63 | %define desiredRbpValue r14
64 | %define ciclicValue32 r15d
65 |
66 | ; ======================================================================================================================
67 | ; TODO: This hole thing looks ugly...
68 | checkNull p_info, error
69 | mov rbp, rsp
70 | mov rbx, rcx ; Temporary save rcx
71 |
72 | ; Initialize spoof info if not already initialized
73 | mov rax, [fieldPtr(STACK_SPOOF_INFO, p_info, initialized)]
74 | test rax, rax
75 | jnz alreadyInitialized
76 |
77 | fastcall_win64 __callobf_initializeSpoofInfo, p_info
78 | checkNull rax, error
79 |
80 | mov rcx, rbx
81 | alreadyInitialized:
82 | ; Obtain a new value to get the indexes in every list
83 | fastcall_win64 __callobf_lfsrXorShift32, [fieldPtr(STACK_SPOOF_INFO, p_info, nextCiclicValue)]
84 | mov rcx, rbx
85 |
86 | ; Save it
87 | mov DWORD [fieldPtr(STACK_SPOOF_INFO, p_info, nextCiclicValue)], eax
88 |
89 | ; We need rax to be used in the mod macro (div inside)
90 | mov ciclicValue32, eax
91 |
92 | ; For every list, pick a byte, put the value in range of each list entry count, and get the entry
93 | pickByte entry32, ciclicValue32, 0
94 | mod entry32, DWORD [fieldPtr(STACK_SPOOF_INFO, p_info, setFpRegCount)]
95 | shl entry64, 4 ; Multiply by 16 (sizeof(FRAME_INFO))
96 | lea p_setFpRegFrameInfo, [fieldPtr(STACK_SPOOF_INFO, p_info, setFpRegList) + entry64]
97 |
98 | pickByte entry32, ciclicValue32, 1
99 | mod entry32, DWORD [fieldPtr(STACK_SPOOF_INFO, p_info, jmpRbxCount)]
100 | shl entry64, 4 ; Multiply by 16 (sizeof(FRAME_INFO))
101 | lea p_jmpRbxFrameInfo, [fieldPtr(STACK_SPOOF_INFO, p_info, jmpRbxList) + entry64]
102 |
103 | pickByte entry32, ciclicValue32, 2
104 | mod entry32, DWORD [fieldPtr(STACK_SPOOF_INFO, p_info, addRspCount)]
105 | shl entry64, 4 ; Multiply by 16 (sizeof(FRAME_INFO))
106 | lea p_addRspFrameInfo, [fieldPtr(STACK_SPOOF_INFO, p_info, addRspList) + entry64]
107 |
108 | pickByte entry32, ciclicValue32, 3
109 | mod entry32, DWORD [fieldPtr(STACK_SPOOF_INFO, p_info, saveRbpCount)]
110 |
111 | ; Since size of SAVE_RBP_FRAME_INFO is 24 bytes, means rax * 24
112 | mov r11, entry64 ; tmp holder
113 |
114 | ; Multiply by 24 (sizeof(SAVE_RBP_FRAME_INFO))
115 | ; rax * 24 = 8 * ((rax * 2) + rax)
116 | shl entry64, 1 ; (rax * 2)
117 | add entry64, r11 ; + "rax"
118 | shl entry64, 3 ; rax * 8
119 |
120 | lea p_saveRbpFrameInfo, [fieldPtr(STACK_SPOOF_INFO, p_info, saveRbpList) + entry64]
121 |
122 | ; Calculate the rbp value that will be "saved" by saveRbpFrame
123 | mov desiredRbpValue, [fieldPtr(STACK_SPOOF_INFO, p_info, p_entryRetAddr)]
124 | sub desiredRbpValue, [fieldPtr(FRAME_INFO, p_setFpRegFrameInfo, frameSize)]
125 |
126 | ; Set startting frame, used to set an arbitrary rsp value, based on rbp
127 | mov rax, [fieldPtr(FRAME_INFO, p_setFpRegFrameInfo, p_entryAddr)]
128 | checkNull rax, error
129 | push rax
130 |
131 | ; Set frame to load arbitrary rbp value
132 | sub rsp, [fieldPtr(SAVE_RBP_FRAME_INFO, p_saveRbpFrameInfo, frameSize)]
133 | mov rax, [fieldPtr(SAVE_RBP_FRAME_INFO, p_saveRbpFrameInfo, p_entryAddr)]
134 | checkNull rax, error
135 | push rax
136 |
137 | ; Store desiredRbpValue at the address where it should have been saved ?¿
138 | mov rax, [fieldPtr(SAVE_RBP_FRAME_INFO, p_saveRbpFrameInfo, rbpOffset)]
139 | add rax, 0x8
140 | mov [rax + rsp], desiredRbpValue
141 |
142 | ; Set frame to redirect execution to the restore routine, it will need to be stored in rbx before execution begins, caller job
143 | sub rsp, [fieldPtr(FRAME_INFO, p_jmpRbxFrameInfo, frameSize)]
144 | mov rax, [fieldPtr(FRAME_INFO, p_jmpRbxFrameInfo, p_entryAddr)]
145 | checkNull rax, error
146 | push rax
147 |
148 | ; Set frame to add some stack space so we can store args, shadow space, etc.
149 | sub rsp, [fieldPtr(FRAME_INFO, p_addRspFrameInfo, frameSize)]
150 | mov rax, [fieldPtr(FRAME_INFO, p_addRspFrameInfo, p_entryAddr)]
151 | checkNull rax, error
152 | push rax
153 |
154 | ; Return value of spoofed stack start
155 | mov rax, rsp
156 |
157 | success:
158 | jmp end
159 |
160 | error:
161 | xor rax, rax
162 |
163 | end:
164 | mov rsp, rbp
165 | end_function
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/stackSpoof/stackSpoof.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file stackSpoof.c
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Functionality to apply dynamic stack spoofing in windows x64 enviroments.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | // Many thanks to the project showing this idea: https://github.com/klezVirus/klezVirus.github.io
26 |
27 | #include "stackSpoof/stackSpoof.h"
28 |
29 | #include "common/commonUtils.h"
30 | #include "pe/peUtils.h"
31 | #include "pe/unwind/unwindUtils.h"
32 |
33 | // TODO: Check if this can me moved to a header file
34 | STACK_SPOOF_INFO __callobf_globalFrameTable = {
35 | .entryCountPerList = MAX_ENTRIES_PER_LIST,
36 | .nextCiclicValue = 0x89235482,
37 | .initialized = 0,
38 | .p_entryRetAddr = NULL,
39 |
40 | .addRspCount = 0,
41 | .addRspList = {0},
42 |
43 | .jmpRbxCount = 0,
44 | .jmpRbxList = {0},
45 |
46 | .setFpRegList = {0},
47 | .saveRbpList = {0}};
48 |
49 | DWORD64 __callobf_fillGadgetTable(
50 | PVOID p_module,
51 | PFRAME_INFO p_entryList,
52 | DWORD64 maxEntries,
53 | PBYTE p_gadgetBytes,
54 | PBYTE p_mask,
55 | SIZE_T gadgetSize)
56 | {
57 | UWOP_ITERATOR_CONTEXT uwopCtx = {0};
58 |
59 | PUNWIND_INFO p_unwindInfo = NULL;
60 | PUNWIND_CODE p_uwop = NULL;
61 | PVOID p_gadget = NULL;
62 |
63 | SIZE_T frameSize = 0;
64 |
65 | PVOID gadgetLowerSearch = NULL;
66 | PVOID gadgetHigherSearch = NULL;
67 |
68 | DWORD64 entryCount = 0;
69 | BOOL valid = TRUE;
70 |
71 | if (!p_module || !p_entryList || !maxEntries || !p_gadgetBytes || !p_mask || !gadgetSize)
72 | return entryCount;
73 |
74 | if (!__callobf_getCodeBoundaries(p_module, &gadgetLowerSearch, &gadgetHigherSearch))
75 | return entryCount;
76 |
77 | while ((p_gadget = __callobf_findBytes(gadgetLowerSearch, gadgetHigherSearch, p_gadgetBytes, p_mask, gadgetSize)))
78 | {
79 | gadgetLowerSearch = (PVOID)((DWORD_PTR)p_gadget + gadgetSize + 0x1);
80 |
81 | // TODO: This incredibly horrible, find a better way to define this function, so it includes the
82 | // case of gadgets with variable values
83 |
84 | // Values over 0x7F, change the compilation, so add rsp, 0x75 will be our bigger possible
85 | // value wich means 15 args
86 | // This could be done by a pattern like 0x78C48348, but I want to eventually build a more robust system capable
87 | // of selecting different sizes.
88 | if (*(PDWORD32)p_gadgetBytes == 0x90C48348) // ADD_RSP_GADGET
89 | {
90 | DWORD32 delta = ((*(PDWORD32)p_gadget) >> 24) & 0xFF;
91 | if (delta != 0x78)
92 | continue;
93 | }
94 |
95 | if (entryCount >= maxEntries)
96 | break;
97 |
98 | p_unwindInfo = __callobf_getUnwindInfoForCodePtr(p_module, p_gadget, NULL, NULL);
99 | if (!p_unwindInfo)
100 | continue;
101 |
102 | if (!__callobf_createOrResetUwopIterator(&uwopCtx, p_module, p_unwindInfo))
103 | return entryCount;
104 | frameSize = 0;
105 | valid = TRUE;
106 |
107 | while ((p_uwop = __callobf_getNextUwop(&uwopCtx)))
108 | {
109 | switch (p_uwop->UnwindOp)
110 | {
111 | case UWOP_PUSH_NONVOL:
112 | case UWOP_SAVE_NONVOL:
113 | case UWOP_SAVE_NONVOL_BIG:
114 | if (p_uwop->OpInfo == RSP)
115 | valid = FALSE;
116 | break;
117 | case UWOP_SET_FPREG:
118 | valid = FALSE;
119 | break;
120 | default:
121 | break;
122 | }
123 | if (!valid)
124 | break;
125 | frameSize += __callobf_getFrameSizeModification(p_unwindInfo, p_uwop);
126 | }
127 | if (valid)
128 | {
129 | p_entryList[entryCount].p_entryAddr = p_gadget;
130 | p_entryList[entryCount].frameSize = frameSize;
131 | entryCount++;
132 | }
133 | }
134 |
135 | return entryCount;
136 | }
137 |
138 | DWORD64 __callobf_fillFpRegFrameTable(
139 | PVOID p_module,
140 | PFRAME_INFO p_entryList,
141 | DWORD64 maxEntries)
142 | {
143 | UWOP_ITERATOR_CONTEXT uwopCtx = {0};
144 | UNWIND_INFO_ITERATOR_CONTEXT unwindInfoCtx = {0};
145 |
146 | PUNWIND_INFO p_unwindInfo = NULL;
147 | PUNWIND_CODE p_uwop = NULL;
148 |
149 | SIZE_T frameSize = 0;
150 |
151 | DWORD64 entryCount = 0;
152 | BOOL valid = TRUE;
153 | BOOL foundFpRegOp = FALSE;
154 |
155 | if (!p_module || !p_entryList || !maxEntries)
156 | return entryCount;
157 |
158 | if (!__callobf_createOrResetUnwindInfoIterator(&unwindInfoCtx, p_module))
159 | return entryCount;
160 |
161 | while ((p_unwindInfo = __callobf_getNextUnwindInfo(&unwindInfoCtx)))
162 | {
163 | if (entryCount >= maxEntries)
164 | break;
165 |
166 | if (!__callobf_createOrResetUwopIterator(&uwopCtx, p_module, p_unwindInfo))
167 | return entryCount;
168 |
169 | frameSize = 0;
170 | valid = TRUE;
171 | foundFpRegOp = FALSE;
172 |
173 | while ((p_uwop = __callobf_getNextUwop(&uwopCtx)))
174 | {
175 | switch (p_uwop->UnwindOp)
176 | {
177 | case UWOP_PUSH_NONVOL: // 0
178 | if (p_uwop->OpInfo == RSP && !foundFpRegOp)
179 | valid = FALSE;
180 | break;
181 | case UWOP_SAVE_NONVOL: // 0
182 | case UWOP_SAVE_NONVOL_BIG: // 0
183 | if (p_uwop->OpInfo == RSP || p_uwop->OpInfo == RBP)
184 | valid = FALSE;
185 | break;
186 | case UWOP_SET_FPREG:
187 | foundFpRegOp = TRUE;
188 | break;
189 | default:
190 | break;
191 | }
192 | if (!valid)
193 | break;
194 | frameSize += __callobf_getFrameSizeModification(p_unwindInfo, p_uwop);
195 | }
196 | if (valid && foundFpRegOp)
197 | {
198 | PVOID p_begin = NULL;
199 | PVOID p_end = NULL;
200 |
201 | if (!__callobf_getCodeBoundariesLastUnwindInfo(&unwindInfoCtx, &p_begin, &p_end))
202 | return entryCount;
203 |
204 | p_entryList[entryCount].p_entryAddr = MID_POINT_ADDR(p_begin, p_end); // random ptr inside function
205 | p_entryList[entryCount].frameSize = frameSize;
206 | entryCount++;
207 | }
208 | }
209 |
210 | return entryCount;
211 | }
212 |
213 | DWORD64 __callobf_fillSaveRbpFrameTable(
214 | PVOID p_module,
215 | PSAVE_RBP_FRAME_INFO p_entryList,
216 | DWORD64 maxEntries)
217 | {
218 | UWOP_ITERATOR_CONTEXT uwopCtx = {0};
219 | UNWIND_INFO_ITERATOR_CONTEXT unwindInfoCtx = {0};
220 |
221 | PUNWIND_INFO p_unwindInfo = NULL;
222 | PUNWIND_CODE p_uwop = NULL;
223 |
224 | SIZE_T frameSize = 0;
225 |
226 | DWORD64 entryCount = 0;
227 | BOOL valid = TRUE;
228 | BOOL foundSaveRbpOp = FALSE;
229 |
230 | if (!p_module || !p_entryList || !maxEntries)
231 | return entryCount;
232 |
233 | if (!__callobf_createOrResetUnwindInfoIterator(&unwindInfoCtx, p_module))
234 | return FALSE;
235 |
236 | while ((p_unwindInfo = __callobf_getNextUnwindInfo(&unwindInfoCtx)))
237 | {
238 | if (entryCount >= maxEntries)
239 | break;
240 |
241 | if (!__callobf_createOrResetUwopIterator(&uwopCtx, p_module, p_unwindInfo))
242 | return FALSE;
243 |
244 | frameSize = 0;
245 | valid = TRUE;
246 | foundSaveRbpOp = FALSE;
247 | while ((p_uwop = __callobf_getNextUwop(&uwopCtx)))
248 | {
249 | switch (p_uwop->UnwindOp)
250 | {
251 | case UWOP_PUSH_NONVOL: // 0
252 | case UWOP_SAVE_NONVOL: // 0
253 | case UWOP_SAVE_NONVOL_BIG: // 0
254 |
255 | if (p_uwop->OpInfo == RSP)
256 | valid = FALSE;
257 |
258 | if (p_uwop->OpInfo == RBP)
259 | {
260 | if (foundSaveRbpOp)
261 | {
262 | valid = FALSE;
263 | break;
264 | }
265 | foundSaveRbpOp = TRUE;
266 | }
267 | break;
268 | case UWOP_SET_FPREG:
269 | valid = FALSE;
270 | break;
271 | default:
272 | break;
273 | }
274 | if (!valid)
275 | break;
276 | frameSize += __callobf_getFrameSizeModification(p_unwindInfo, p_uwop);
277 | }
278 | if (valid && foundSaveRbpOp)
279 | {
280 | PVOID p_begin = NULL;
281 | PVOID p_end = NULL;
282 | LONG64 saveOffset = -1;
283 |
284 | if (!__callobf_getCodeBoundariesLastUnwindInfo(&unwindInfoCtx, &p_begin, &p_end))
285 | return entryCount;
286 |
287 | if (!(saveOffset = __callobf_getOffsetWhereRegSaved(p_module, p_unwindInfo, RBP)))
288 | return entryCount;
289 |
290 | p_entryList[entryCount].p_entryAddr = MID_POINT_ADDR(p_begin, p_end); // random ptr inside function
291 | p_entryList[entryCount].frameSize = frameSize;
292 | p_entryList[entryCount].rbpOffset = saveOffset;
293 |
294 | entryCount++;
295 | }
296 | }
297 | return entryCount;
298 | }
299 |
300 | BOOL __callobf_fillStackSpoofTables(PSTACK_SPOOF_INFO p_stackSpoofInfo, PVOID p_module)
301 | {
302 |
303 | CASSERT((JUMP_RBX_GADGET_SIZE != 0));
304 | CASSERT((ADD_RSP_GADGET_SIZE != 0));
305 |
306 | CASSERT((sizeof(JUMP_RBX_GADGET) == sizeof(JUMP_RBX_GADGET_MASK)));
307 | CASSERT((sizeof(ADD_RSP_GADGET) == sizeof(ADD_RSP_GADGET_MASK)));
308 |
309 | if (!p_stackSpoofInfo || !p_module)
310 | return FALSE;
311 |
312 | if (!(p_stackSpoofInfo->addRspCount = __callobf_fillGadgetTable(
313 | p_module,
314 | p_stackSpoofInfo->addRspList,
315 | p_stackSpoofInfo->entryCountPerList,
316 | (PBYTE)ADD_RSP_GADGET,
317 | (PBYTE)ADD_RSP_GADGET_MASK,
318 | ADD_RSP_GADGET_SIZE)))
319 | return FALSE;
320 |
321 | if (!(p_stackSpoofInfo->jmpRbxCount = __callobf_fillGadgetTable(
322 | p_module,
323 | p_stackSpoofInfo->jmpRbxList,
324 | p_stackSpoofInfo->entryCountPerList,
325 | (PBYTE)JUMP_RBX_GADGET,
326 | (PBYTE)JUMP_RBX_GADGET_MASK,
327 | JUMP_RBX_GADGET_SIZE)))
328 | return FALSE;
329 |
330 | if (!(p_stackSpoofInfo->setFpRegCount = __callobf_fillFpRegFrameTable(
331 | p_module,
332 | p_stackSpoofInfo->setFpRegList,
333 | p_stackSpoofInfo->entryCountPerList)))
334 | return FALSE;
335 |
336 | if (!(p_stackSpoofInfo->saveRbpCount = __callobf_fillSaveRbpFrameTable(
337 | p_module,
338 | p_stackSpoofInfo->saveRbpList,
339 | p_stackSpoofInfo->entryCountPerList)))
340 | return FALSE;
341 | DEBUG_PRINT("\n");
342 | DEBUG_PRINT("Correctly initialized table");
343 | DEBUG_PRINT("addRspCount: %llu", p_stackSpoofInfo->addRspCount);
344 | DEBUG_PRINT("jmpRbxCount: %llu", p_stackSpoofInfo->jmpRbxCount);
345 | DEBUG_PRINT("setFpRegCount: %llu", p_stackSpoofInfo->setFpRegCount);
346 | DEBUG_PRINT("saveRbpCount: %llu\n", p_stackSpoofInfo->saveRbpCount);
347 |
348 | return TRUE;
349 | }
350 |
351 | BOOL __callobf_initializeSpoofInfo(PSTACK_SPOOF_INFO p_stackSpoofInfo)
352 | {
353 | PVOID p_kernelBase = __callobf_getModuleAddrH(KERNELBASE_HASH);
354 | if (!p_kernelBase)
355 | return FALSE;
356 |
357 | PVOID p_kernel32 = __callobf_getModuleAddrH(KERNEL32_HASH);
358 | if (!p_kernelBase)
359 | return FALSE;
360 |
361 | PVOID p_ntdll = __callobf_getModuleAddrH(NTDLL_HASH);
362 | if (!p_ntdll)
363 | return FALSE;
364 |
365 | DEBUG_PRINT("Filling spoof tables");
366 | if (!__callobf_fillStackSpoofTables(p_stackSpoofInfo, p_kernelBase))
367 | return FALSE;
368 |
369 | if (!(p_stackSpoofInfo->p_entryRetAddr = __callobf_findEntryAddressOfReturnAddress(p_ntdll, p_kernel32)))
370 | return FALSE;
371 |
372 | p_stackSpoofInfo->initialized = TRUE;
373 |
374 | BREAKPOINT();
375 |
376 | return TRUE;
377 | }
--------------------------------------------------------------------------------
/CallObfuscatorHelpers/source/syscalls/syscalls.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file syscalls.c
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Utilities to work with windows x64 syscalls.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "syscalls/syscalls.h"
26 | #include "common/commonUtils.h"
27 | #include "common/debug.h"
28 |
29 | BOOL __callobf_initSyscallIter(PSYSCALL_ITER_CTX p_ctx, PVOID p_ntdll)
30 | {
31 | if (!p_ctx || !p_ntdll)
32 | return FALSE;
33 |
34 | PIMAGE_DOS_HEADER p_dosHeaders = (PIMAGE_DOS_HEADER)p_ntdll;
35 | PIMAGE_NT_HEADERS p_ntHeaders = (PVOID)((DWORD_PTR)p_dosHeaders + p_dosHeaders->e_lfanew);
36 | PIMAGE_DATA_DIRECTORY p_dataDir = (PVOID)(&p_ntHeaders->OptionalHeader.DataDirectory[0]);
37 |
38 | if (!p_dataDir->VirtualAddress)
39 | {
40 | DEBUG_PRINT("No data directory virtual address");
41 | return FALSE;
42 | }
43 |
44 | if (!(p_ctx->p_expDir = (PVOID)((DWORD_PTR)p_dosHeaders + p_dataDir->VirtualAddress)))
45 | return FALSE;
46 |
47 | p_ctx->p_ntdll = p_ntdll;
48 | p_ctx->lastEntry = 0;
49 | return TRUE;
50 | }
51 |
52 | BOOL __callobf_getNextSyscall(PSYSCALL_ITER_CTX p_ctx, PCHAR *pp_name, PVOID *pp_functionAddr)
53 | {
54 | PIMAGE_DOS_HEADER p_dosHeaders = (PIMAGE_DOS_HEADER)p_ctx->p_ntdll;
55 | PIMAGE_EXPORT_DIRECTORY p_expDir = p_ctx->p_expDir;
56 |
57 | if (!p_ctx || !pp_name || !pp_functionAddr)
58 | return FALSE;
59 |
60 | if (!p_dosHeaders || !p_expDir)
61 | return FALSE;
62 |
63 | PDWORD aof = (PVOID)((DWORD_PTR)p_dosHeaders + p_expDir->AddressOfFunctions);
64 | PDWORD aon = (PVOID)((DWORD_PTR)p_dosHeaders + p_expDir->AddressOfNames);
65 | PUSHORT ano = (PVOID)((DWORD_PTR)p_dosHeaders + p_expDir->AddressOfNameOrdinals);
66 |
67 | if (p_ctx->lastEntry >= p_expDir->NumberOfNames)
68 | {
69 | DEBUG_PRINT("Iterator already ended");
70 | return FALSE;
71 | }
72 |
73 | for (DWORD cnt = p_ctx->lastEntry; cnt < p_expDir->NumberOfNames; cnt++)
74 | {
75 | PCHAR p_name = (PVOID)((DWORD_PTR)p_dosHeaders + aon[cnt]);
76 |
77 | if (p_name[0] == 'Z' && p_name[1] == 'w')
78 | {
79 | // p_name[0] = 'N';
80 | // p_name[1] = 't';
81 |
82 | *pp_name = p_name;
83 | *pp_functionAddr = (PVOID)((DWORD_PTR)p_dosHeaders + aof[ano[cnt]]);
84 | p_ctx->lastEntry = ++cnt;
85 | return TRUE;
86 | }
87 | }
88 | return FALSE;
89 | }
90 |
91 | // TODO: Improve this
92 | UINT32 __callobf_hashSyscallAsZw(PCHAR p_str)
93 | {
94 | UINT h = 0;
95 | PCHAR p;
96 | CHAR c;
97 |
98 | if (!p_str)
99 | return 0;
100 |
101 | if (p_str[0] == 0 || p_str[1] == 0)
102 | return 0;
103 |
104 | h = HASH_MULTIPLIER * h + 'z';
105 | h = HASH_MULTIPLIER * h + 'w';
106 | p_str += 2;
107 |
108 | for (p = p_str; *p != '\0'; p++)
109 | {
110 | c = (*p >= 65 && *p <= 90) ? *p + 32 : *p;
111 | h = HASH_MULTIPLIER * h + c;
112 | }
113 | return h;
114 | }
115 |
116 | UINT32 __callobf_hashSyscallAsNt(PCHAR p_str)
117 | {
118 | UINT h = 0;
119 | PCHAR p;
120 | CHAR c;
121 |
122 | if (!p_str)
123 | return 0;
124 |
125 | if (p_str[0] == 0 || p_str[1] == 0)
126 | return 0;
127 |
128 | h = HASH_MULTIPLIER * h + 'n';
129 | h = HASH_MULTIPLIER * h + 't';
130 | p_str += 2;
131 |
132 | for (p = p_str; *p != '\0'; p++)
133 | {
134 | c = (*p >= 65 && *p <= 90) ? *p + 32 : *p;
135 | h = HASH_MULTIPLIER * h + c;
136 | }
137 | return h;
138 | }
139 |
140 | BOOL __callobf_checkHashSyscallA(PCHAR p_functionName, DWORD32 hash)
141 | {
142 | if (!p_functionName)
143 | return FALSE;
144 |
145 | DWORD32 hashAsNt = __callobf_hashSyscallAsNt(p_functionName);
146 | DWORD32 hashAsZw = __callobf_hashSyscallAsZw(p_functionName);
147 |
148 | return (BOOL)(hash == hashAsZw || hash == hashAsNt);
149 | }
150 |
151 | PVOID __callobf_getSyscallAddr(PVOID p_function, PVOID p_lowBoundary, PVOID p_highBoundary)
152 | {
153 | PBYTE lowerRunner = (PBYTE)p_function;
154 | PBYTE higherRunner = (PBYTE)p_function;
155 | USHORT tmpVal = 0x0;
156 |
157 | lowerRunner += 0x15;
158 | higherRunner += 0x15;
159 |
160 | // Is this overkill?
161 | while (lowerRunner >= (PBYTE)p_lowBoundary || higherRunner < (PBYTE)p_highBoundary - 0x3) // 3 because 0f05C3 aka syscallret
162 | {
163 | if (higherRunner < (PBYTE)p_highBoundary - 0x3)
164 | {
165 | // Here we are just picking the lower 3 bytes of *higherRunner, the xoring with a random value
166 | // 0xde6edba2 is the result of 0xc3050f (syscall ret (little endian)) xor 0xdeaddead.
167 | // Then (A ^ C == B ^ C) if (A == B)
168 | // The idea is to remove 0xc3050f from code
169 | // TODO: Check if the compiler is optimizing this
170 | tmpVal = ((*(PUSHORT)higherRunner) & 0x00FFFFFF) ^ 0xdeaddead;
171 | if (tmpVal == (USHORT)0xde6edba2)
172 | return higherRunner;
173 |
174 | higherRunner++;
175 | }
176 |
177 | if (lowerRunner >= (PBYTE)p_lowBoundary)
178 | {
179 | tmpVal = ((*(PUSHORT)lowerRunner) & 0x00FFFFFF) ^ 0xdeaddead;
180 | if (tmpVal == (USHORT)0xde6edba2)
181 | return lowerRunner;
182 |
183 | lowerRunner--;
184 | }
185 | }
186 | return NULL;
187 | }
188 |
189 | BOOL __callobf_loadSyscall(
190 | DWORD32 nameHash,
191 | PVOID p_ntdll,
192 | PUSHORT p_ssn,
193 | PVOID *pp_function)
194 | {
195 | SYSCALL_ITER_CTX iterCtx = {0};
196 | USHORT ssn = 0;
197 |
198 | PCHAR p_stubNameTmp = NULL;
199 | PVOID p_functionTmp = NULL;
200 |
201 | PVOID p_function = NULL;
202 |
203 | BOOL found = FALSE;
204 |
205 | if (!p_ntdll || !p_ssn || !pp_function)
206 | return FALSE;
207 |
208 | if (!__callobf_initSyscallIter(&iterCtx, p_ntdll))
209 | return FALSE;
210 |
211 | // Remember: p_stubNameTmp is always the Zw version
212 | while (__callobf_getNextSyscall(&iterCtx, &p_stubNameTmp, &p_functionTmp))
213 | if ((found = __callobf_checkHashSyscallA(p_stubNameTmp, nameHash)))
214 | break;
215 |
216 | if (!found)
217 | return FALSE;
218 |
219 | p_function = p_functionTmp;
220 |
221 | if (!__callobf_initSyscallIter(&iterCtx, p_ntdll))
222 | return FALSE;
223 |
224 | while (__callobf_getNextSyscall(&iterCtx, &p_stubNameTmp, &p_functionTmp))
225 | if (p_functionTmp < p_function)
226 | ssn++;
227 |
228 | PIMAGE_NT_HEADERS p_ntHeaders = (PIMAGE_NT_HEADERS)(p_ntdll + ((PIMAGE_DOS_HEADER)p_ntdll)->e_lfanew);
229 | PIMAGE_SECTION_HEADER p_sectionHeaders = IMAGE_FIRST_SECTION(p_ntHeaders);
230 | PVOID p_sectionStart = NULL;
231 | PVOID p_sectionEnd = NULL;
232 |
233 | found = FALSE;
234 |
235 | for (WORD sectionIndex = 0; sectionIndex < p_ntHeaders->FileHeader.NumberOfSections; sectionIndex++)
236 | {
237 | PIMAGE_SECTION_HEADER p_sectionHeader = &p_sectionHeaders[sectionIndex];
238 | p_sectionStart = p_ntdll + p_sectionHeader->VirtualAddress;
239 | p_sectionEnd = p_sectionStart + p_sectionHeader->SizeOfRawData;
240 |
241 | if (((DWORD_PTR)p_sectionStart <= (DWORD_PTR)p_function) && ((DWORD_PTR)p_function < (DWORD_PTR)p_sectionEnd))
242 | {
243 | found = TRUE;
244 | break;
245 | }
246 | }
247 |
248 | if (!found)
249 | return FALSE;
250 |
251 | if (!(p_functionTmp = __callobf_getSyscallAddr(p_function, p_sectionStart, p_sectionEnd)))
252 | return FALSE;
253 |
254 | *p_ssn = ssn;
255 | *pp_function = p_functionTmp;
256 |
257 | return TRUE;
258 | }
--------------------------------------------------------------------------------
/CallObfuscatorPlugin/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_library(CallObfuscatorPlugin MODULE
2 | source/CallObfuscatorPass.cpp
3 | source/CallObfuscatorPluginRegister.cpp
4 | source/CallObfuscator.cpp)
5 |
6 | llvm_map_components_to_libnames(llvm_libs core linker)
7 | target_link_libraries(CallObfuscatorPlugin ${llvm_libs})
8 | target_include_directories(CallObfuscatorPlugin PRIVATE headers)
9 |
10 | set_target_properties(CallObfuscatorPlugin PROPERTIES PREFIX "")
11 | set_target_properties(CallObfuscatorPlugin PROPERTIES CXX_STANDARD 17)
12 |
13 | install(TARGETS CallObfuscatorPlugin DESTINATION ${PROJECT_NAME})
14 |
--------------------------------------------------------------------------------
/CallObfuscatorPlugin/headers/CallObfuscator.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file CallObfuscator.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Includes the logic needed to transparently apply call obfucation at compile time.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _CALL_OBFUSCATOR_H_
26 | #define _CALL_OBFUSCATOR_H_
27 |
28 | #include "llvm/IR/PassManager.h"
29 |
30 | using namespace std;
31 | using namespace llvm;
32 |
33 | namespace callobfuscator
34 | {
35 | struct FunctionInfo
36 | {
37 | Function &function;
38 | StringRef dllName;
39 | bool isSyscall;
40 | unsigned int ssn;
41 | unsigned long modIndex = 0;
42 | unsigned long argCount = 0;
43 | };
44 |
45 | class CallObfuscator
46 | {
47 | private:
48 | Module &mod; // We are ensured the ref (Module given by the pass manager) outlives any object of this class... probably, i guess O_o
49 | Module *tmpMod; // We are ensured the ref (Module given by the pass manager) outlives any object of this class... probably, i guess O_o
50 |
51 | bool __changedModule;
52 | bool __locked; // Once finalized, cant get more hooks
53 | vector functionList;
54 |
55 | vector dllNames;
56 | vector dllHashes;
57 |
58 | FunctionCallee callDispatcher;
59 |
60 | public:
61 | CallObfuscator(Module &module);
62 |
63 | /**
64 | * @brief Inserts given function to list of functions that will be hooked on finalize.
65 | *
66 | * @param functionInfo Information about the function to be hooked.
67 | * @return true Success.
68 | */
69 | bool addHook(const FunctionInfo &functionInfo);
70 |
71 | /**
72 | * @brief Applies applies hooks, inserts definitions, inserts tables, and briefly
73 | * makes every change to the module. Prior to the execution of this function,
74 | * the module stays unmodified.
75 | *
76 | * @return true Success.
77 | */
78 | bool finalize();
79 |
80 | /**
81 | * @return true Module changed.
82 | * @return false Module didnt change.
83 | */
84 | bool changedModule();
85 |
86 | private:
87 | bool insertTables(vector dllNames, vector functionInfo);
88 | /**
89 | * @brief Inserts the definition of the call dispatcher, need to
90 | * replace hooked functions.
91 | *
92 | * @return true Success.
93 | */
94 | bool insertCallDispatcherDef();
95 |
96 | /**
97 | * @brief Replaces the use of a function, by an equivalent use to the call dispatcher.
98 | * NOTE: In LLVM, Uses represents reads, calls...
99 | *
100 | * @param use Use to be replaced.
101 | * @param functionTableIndex Index in the function table for the function to be replaced.
102 | * @return true Success.
103 | */
104 | bool replaceUse(Use &use, int functionTableIndex);
105 |
106 | /**
107 | * @brief Replaces a call instruction, by a call istruction to the call dispatcher.
108 | *
109 | * @param callInstruction Instruction to be replaced.
110 | * @param functionTableIndex Index in the function table for the function to be replaced.
111 | * @return true Success.
112 | */
113 | bool replaceCall(CallInst *callInstruction, int functionTableIndex);
114 |
115 | /**
116 | * @brief Creates an array of objects of type _FUNCTION_TABLE_ENTRY, and partially initializes it.
117 | *
118 | * @param ctx Module context.
119 | * @param pp_functionTableEntryStruct [OUT] Returns the array definition indicating entry type and size.
120 | * @param functionInfo Function information to partially initialize array.
121 | * @return Constant* Value containing the array.
122 | * NOTE: In LLVM, Values represents functions, variables...
123 | */
124 | static Constant *createFunctionTableArray(LLVMContext &ctx, ArrayType **pp_functionTableEntryStruct, vector functionInfo);
125 |
126 | /**
127 | * @brief Creates an array of objects of type _DLL_TABLE_ENTRY, and partially initializes it.
128 | *
129 | * @param ctx Module context.
130 | * @param pp_dllTableEntryStruct [OUT] Returns the array definition indicating entry type and size.
131 | * @param dlls Dll information to partially initialize table
132 | * @return Constant* Value containing the array.
133 | * NOTE: In LLVM, Values represents functions, variables...
134 | */
135 | static Constant *createDllTableArray(LLVMContext &ctx, ArrayType **pp_dllTableEntryStruct, vector dlls);
136 |
137 | /**
138 | * @brief Creates an objects of type _FUNCTION_TABLE, and partially initializes it.
139 | *
140 | * @param ctx Module context.
141 | * @param pp_functionTableStruct [OUT] Returns object definition.
142 | * @param functionInfo Function information to partially initialize table.
143 | * @return Constant* Value containing the object.
144 | * NOTE: In LLVM, Values represents functions, variables...
145 | */
146 | static Constant *createFunctionTable(LLVMContext &ctx, StructType **pp_functionTableStruct, vector functionInfo);
147 |
148 | /**
149 | * @brief Creates an objects of type _DLL_TABLE, and partially initializes it.
150 | *
151 | * @param ctx Module context.
152 | * @param pp_dllTableEntryStruct [OUT] Returns object definition.
153 | * @param dlls Dll information to partially initialize table.
154 | * @return Constant* Value containing the object.
155 | * NOTE: In LLVM, Values represents functions, variables...
156 | */
157 | static Constant *createDllTable(LLVMContext &ctx, StructType **pp_dllTableEntryStruct, vector dlls);
158 | };
159 |
160 | }
161 |
162 | #endif
163 |
--------------------------------------------------------------------------------
/CallObfuscatorPlugin/headers/CallObfuscatorPass.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file CallObfucatorPass.h
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Initalization and managment of the obfuscator pass.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #ifndef _CALL_OBFUSCATOR_PASS_H_
26 | #define _CALL_OBFUSCATOR_PASS_H_
27 |
28 | #include "llvm/IR/PassManager.h"
29 | #include "llvm/Support/JSON.h"
30 |
31 | #define LLVM_CALL_OBF_CONFIG_PATH "LLVM_OBF_FUNCTIONS"
32 |
33 | #define DLL_HOOKS_KEY "dll_hooks"
34 | #define DLL_NAME_KEY "dll_name"
35 | #define FUNCTION_HOOKS_KEY "hooked_functions"
36 |
37 | #define DO_PRAGMA(x) _Pragma(#x)
38 | #define NOWARN(warnoption, ...) \
39 | DO_PRAGMA(GCC diagnostic push) \
40 | DO_PRAGMA(GCC diagnostic ignored warnoption) \
41 | __VA_ARGS__ \
42 | DO_PRAGMA(GCC diagnostic pop)
43 |
44 | using namespace llvm;
45 | using namespace json;
46 |
47 | namespace callobfuscatorpass
48 | {
49 | class CallObfuscatorPass : public PassInfoMixin
50 | {
51 | public:
52 | /**
53 | * @brief Function invoked by opt for each module given.
54 | *
55 | * @param M Module being optimized.
56 | * @param AM Analisys Manager for current optimization
57 | * @return PreservedAnalyses Wich analyses can be preserved.
58 | */
59 | PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
60 |
61 | private:
62 | bool configLoaded;
63 | bool fileMalformed;
64 | Object jsonConfig;
65 |
66 | /**
67 | * @brief Check if the given function is indicated as hooked in the configuration file.
68 | *
69 | * @param argFunctionName Name of the function to check.
70 | * @param argDllName [OUT] Returns dll name, if hooked.
71 | * @return true Function is hooked.
72 | */
73 | bool isFunctionHooked(StringRef argFunctionName, StringRef &argDllName);
74 |
75 | /**
76 | * @brief Reads config file from LLVM_OBF_FUNCTIONS env variable.
77 | *
78 | * @param jsonConfig [OUT] Returns json config.
79 | * @return true Success.
80 | */
81 | bool readConfig(Object &jsonConfig);
82 | };
83 |
84 | }
85 |
86 | #endif
87 |
--------------------------------------------------------------------------------
/CallObfuscatorPlugin/source/CallObfuscator.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * @file CallObfuscator.cpp
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Includes the logic needed to transparently apply call obfucation at compile time.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "CallObfuscator.h"
26 |
27 | #include
28 |
29 | #include "llvm/Support/CommandLine.h"
30 |
31 | #include "llvm/IR/Constants.h"
32 | #include "llvm/IR/IRBuilder.h"
33 |
34 | #include "llvm/Support/JSON.h"
35 | #include "llvm/Support/MemoryBuffer.h"
36 | #include "llvm/IR/LLVMContext.h"
37 |
38 | #include "llvm/Linker/Linker.h"
39 |
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 |
46 | using namespace std;
47 | using namespace llvm;
48 |
49 | namespace callobfuscator
50 | {
51 | CallObfuscator::CallObfuscator(Module &module) : mod(module)
52 | {
53 | __changedModule = false;
54 | __locked = false;
55 | functionList = std::vector();
56 |
57 | tmpMod = new Module("__callobf_tmpMod", module.getContext());
58 | tmpMod->setDataLayout(module.getDataLayout());
59 | }
60 |
61 | unsigned long long hashStr(StringRef str)
62 | {
63 | unsigned long long h;
64 | char c;
65 |
66 | h = 0;
67 | for (char p : str)
68 | {
69 | c = (p >= 65 && p <= 90) ? p + 32 : p;
70 | h = 37 * h + c;
71 | }
72 | return h;
73 | }
74 |
75 | bool CallObfuscator::addHook(const FunctionInfo &functionInfo)
76 | {
77 | // TODO: Add checks <- What I was thinking about when wrote this? Ive no idea
78 | if (__locked)
79 | return false;
80 |
81 | FunctionInfo info = functionInfo;
82 | bool found = false;
83 | for (auto entry : functionList) // This thing looks so bad
84 | {
85 | if (entry.function.getName().equals(info.function.getName()))
86 | {
87 | found = true;
88 | break;
89 | }
90 | }
91 |
92 | if (!found)
93 | {
94 | unsigned long dllHash = hashStr(info.dllName);
95 | unsigned long functionHash = hashStr(info.dllName);
96 |
97 | info.modIndex = find(dllHashes.begin(), dllHashes.end(), dllHash) - dllHashes.begin();
98 |
99 | if (info.modIndex >= dllHashes.size())
100 | {
101 | info.modIndex = dllHashes.size();
102 | dllNames.push_back(info.dllName);
103 | dllHashes.push_back(dllHash);
104 | }
105 |
106 | info.argCount = info.function.arg_size();
107 | functionList.push_back(info);
108 | }
109 |
110 | return true;
111 | }
112 |
113 | Constant *CallObfuscator::createFunctionTableArray(LLVMContext &ctx, ArrayType **pp_functionTableEntryStruct, vector functionInfo)
114 | {
115 |
116 | // _FUNCTION_TABLE_ENTRY (24 bytes -> 64bits; 20 bytes -> 32bits)
117 | // > u_int32 hash
118 | // > u_int32 moduleIndex
119 | // > u_int32 argCount
120 | // > u_int32 ssn
121 | // > void *functionPtr
122 | StructType *p_functionTableEntryStruct = StructType::create(ctx, "_FUNCTION_TABLE_ENTRY");
123 |
124 | p_functionTableEntryStruct->setBody(
125 | {IntegerType::get(ctx, 32),
126 | IntegerType::get(ctx, 32),
127 | IntegerType::get(ctx, 32),
128 | IntegerType::get(ctx, 32),
129 | PointerType::get(ctx, 0)},
130 | true);
131 |
132 | vector functionTableEntries;
133 | for (FunctionInfo info : functionInfo)
134 | {
135 | functionTableEntries.push_back(ConstantStruct::get(
136 | p_functionTableEntryStruct,
137 | {ConstantInt::get(IntegerType::get(ctx, 32), hashStr(info.function.getName())),
138 | ConstantInt::get(IntegerType::get(ctx, 32), info.modIndex),
139 | ConstantInt::get(IntegerType::get(ctx, 32), info.argCount),
140 | ConstantInt::get(IntegerType::get(ctx, 32), info.ssn),
141 | ConstantPointerNull::get(PointerType::get(ctx, 0))}));
142 | }
143 |
144 | *pp_functionTableEntryStruct = ArrayType::get(p_functionTableEntryStruct, functionTableEntries.size());
145 |
146 | return ConstantArray::get(*pp_functionTableEntryStruct,
147 | ArrayRef(functionTableEntries)); // Im assuming get makes a copy, of the array, or at least of its contents, if not, this is passing a reference to a vector defined in the current scope :/
148 | // TODO: check this by modifying the vector after a call to get
149 | }
150 |
151 | Constant *CallObfuscator::createFunctionTable(LLVMContext &ctx, StructType **pp_functionTableStruct, vector functionInfo)
152 | {
153 | // _FUNCTION_TABLE
154 | // > u_int32 entryCount
155 | // > u_int32 padding
156 | // > _FUNCTION_TABLE_ENTRY[] entries
157 | StructType *p_functionTableStruct = StructType::create(ctx, "_FUNCTION_TABLE");
158 |
159 | ArrayType *p_functionTableArrayDef;
160 | Constant *p_functionTableArray = createFunctionTableArray(ctx, &p_functionTableArrayDef, functionInfo);
161 |
162 | p_functionTableStruct->setBody(
163 | {IntegerType::get(ctx, 32),
164 | IntegerType::get(ctx, 32),
165 | p_functionTableArrayDef},
166 | true);
167 |
168 | *pp_functionTableStruct = p_functionTableStruct; // This doesnt seem rigth xd, expecting that the lifetime of p_functionTableStruct is enough :P
169 |
170 | outs() << "[INFO] Number of elements in funcion table: " << p_functionTableArrayDef->getNumElements()
171 | << "\n";
172 |
173 | return ConstantStruct::get(p_functionTableStruct,
174 | {ConstantInt::get(IntegerType::get(ctx, 32), p_functionTableArrayDef->getNumElements()),
175 | ConstantInt::get(IntegerType::get(ctx, 32), 0),
176 | p_functionTableArray});
177 | }
178 |
179 | Constant *CallObfuscator::createDllTableArray(LLVMContext &ctx, ArrayType **pp_dllTableEntryStruct, vector dlls)
180 | {
181 | vector dllTableEntries;
182 | // _DLL_TABLE_ENTRY (16 bytes -> 64bits; 6 bytes-> 32bits)
183 | // > char* name
184 | // > void* handle
185 | StructType *p_dllTableEntryStruct = StructType::create(ctx, "_DLL_TABLE_ENTRY");
186 |
187 | p_dllTableEntryStruct->setBody(
188 | {PointerType::get(ctx, 0),
189 | PointerType::get(ctx, 0)},
190 | true);
191 |
192 | for (Constant *dllName : dlls)
193 | {
194 | dllTableEntries.push_back(ConstantStruct::get(
195 | p_dllTableEntryStruct,
196 | {dllName,
197 | ConstantPointerNull::get(PointerType::get(ctx, 0))}));
198 | }
199 |
200 | *pp_dllTableEntryStruct = ArrayType::get(p_dllTableEntryStruct, dllTableEntries.size());
201 |
202 | return ConstantArray::get(*pp_dllTableEntryStruct,
203 | ArrayRef(dllTableEntries));
204 | }
205 |
206 | Constant *CallObfuscator::createDllTable(LLVMContext &ctx, StructType **pp_dllTableEntryStruct, vector dlls)
207 | {
208 | // _DLL_TABLE
209 | // > u_int32 entryCount
210 | // > u_int32 padding
211 | // > _DLL_TABLE_ENTRY[] entries
212 | StructType *p_dllTableStruct = StructType::create(ctx, "_DLL_TABLE");
213 |
214 | ArrayType *p_dllTableArrayDef;
215 | Constant *p_dllTableArray = createDllTableArray(ctx, &p_dllTableArrayDef, dlls);
216 |
217 | p_dllTableStruct->setBody(
218 | {IntegerType::get(ctx, 32),
219 | IntegerType::get(ctx, 32),
220 | p_dllTableArrayDef},
221 | true);
222 |
223 | *pp_dllTableEntryStruct = p_dllTableStruct; // This doesnt seem rigth xd, expecting that the lifetime of p_dllTableStruct is enough :P
224 |
225 | return ConstantStruct::get(p_dllTableStruct,
226 | {ConstantInt::get(IntegerType::get(ctx, 32), p_dllTableArrayDef->getNumElements()),
227 | ConstantInt::get(IntegerType::get(ctx, 32), 0),
228 | p_dllTableArray});
229 | }
230 |
231 | vector createDllNames(Module &M, vector dllNames)
232 | {
233 | LLVMContext &ctx = M.getContext();
234 | vector dllNamesAsCt;
235 | for (StringRef name : dllNames)
236 | {
237 | Constant *stringAsCt = ConstantDataArray::getString(ctx, name, true);
238 | Constant *stringGlobalAsCt = M.getOrInsertGlobal((".str.__callobfuscator." + name).str(), stringAsCt->getType()); // TODO: Check size
239 | GlobalVariable *stringGlobalAsGv = cast(stringGlobalAsCt);
240 |
241 | stringGlobalAsGv->setConstant(true);
242 | stringGlobalAsGv->setLinkage(GlobalValue::InternalLinkage);
243 | stringGlobalAsGv->setInitializer(stringAsCt);
244 | dllNamesAsCt.push_back(stringGlobalAsCt);
245 | }
246 |
247 | return dllNamesAsCt;
248 | }
249 |
250 | bool CallObfuscator::insertTables(vector dllNames, vector functionInfo)
251 | {
252 | LLVMContext &ctx = tmpMod->getContext();
253 |
254 | // ============================= Declare tables =============================
255 | StructType *p_functionTableDef;
256 | StructType *p_dllTableDef;
257 |
258 | if (!dllNames.size())
259 | {
260 | outs() << "[ERROR] No dlls to insert to tables, aborting";
261 | return false;
262 | }
263 | if (!functionInfo.size())
264 | {
265 | outs() << "[ERROR] No functions to insert to tables, aborting";
266 | return false;
267 | }
268 |
269 | vector dllNamesAsCt = createDllNames(*tmpMod, dllNames);
270 |
271 | Constant *p_functionTable = CallObfuscator::createFunctionTable(ctx, &p_functionTableDef, functionInfo);
272 | Constant *p_dllTable = CallObfuscator::createDllTable(ctx, &p_dllTableDef, dllNamesAsCt);
273 |
274 | // ================= Create global tables ===============
275 |
276 | GlobalVariable *functionTableGlobal = cast(tmpMod->getOrInsertGlobal("__callobf_functionTable", p_functionTableDef));
277 | GlobalVariable *dllTableGlobal = cast(tmpMod->getOrInsertGlobal("__callobf_dllTable", p_dllTableDef));
278 |
279 | functionTableGlobal->setConstant(false);
280 | functionTableGlobal->setLinkage(GlobalValue::ExternalLinkage);
281 | functionTableGlobal->setInitializer(p_functionTable);
282 |
283 | dllTableGlobal->setConstant(false);
284 | dllTableGlobal->setLinkage(GlobalValue::ExternalLinkage);
285 | dllTableGlobal->setInitializer(p_dllTable);
286 |
287 | return true;
288 | }
289 |
290 | bool CallObfuscator::replaceCall(CallInst *p_callInstruction, int functionTableIndex)
291 | {
292 | LLVMContext &ctx = mod.getContext();
293 | if (!p_callInstruction)
294 | return false;
295 |
296 | FunctionType *funcType = callDispatcher.getFunctionType();
297 | Value *funcValue = callDispatcher.getCallee();
298 |
299 | vector funcArgsVec;
300 | funcArgsVec.push_back(ConstantInt::get(IntegerType::get(ctx, 32), functionTableIndex));
301 | p_callInstruction->getReturnedArgOperand();
302 |
303 | for (int i = 0; i < p_callInstruction->arg_size(); i++)
304 | {
305 | funcArgsVec.push_back(p_callInstruction->getArgOperand(i));
306 | }
307 |
308 | CallInst *p_callReplacement = CallInst::Create(funcType, funcValue, ArrayRef(funcArgsVec));
309 | p_callReplacement->insertBefore(p_callInstruction);
310 |
311 | int originalRetSize = 0;
312 | if (!p_callInstruction->getType()->isVoidTy())
313 | originalRetSize = mod.getDataLayout().getTypeSizeInBits(p_callInstruction->getType());
314 |
315 | int newRetSize = mod.getDataLayout().getTypeSizeInBits(p_callReplacement->getType());
316 |
317 | if (originalRetSize != newRetSize && originalRetSize != 0)
318 | {
319 | // originalRetSize should never be bigger than newRetSize
320 | assert(newRetSize > originalRetSize);
321 |
322 | TruncInst *p_truncInstruction = new TruncInst(p_callReplacement, p_callInstruction->getType());
323 | p_truncInstruction->insertAfter(p_callReplacement);
324 | p_callInstruction->replaceAllUsesWith(p_truncInstruction);
325 | }
326 | else
327 | {
328 | p_callInstruction->replaceAllUsesWith(p_callReplacement);
329 | }
330 |
331 | // TODO: Add checks if its safe to delete (It should ¿?)
332 | p_callInstruction->eraseFromParent();
333 | return true;
334 | }
335 |
336 | bool CallObfuscator::replaceUse(Use &use, int functionTableIndex)
337 | {
338 | // TODO: Handle invoke instructions and exception stuff (should not happen in C but...)
339 | // TODO: Handle indirect calls
340 | // TODO: Handle address reads
341 | if (Instruction *p_callInstruction = dyn_cast(use.getUser()))
342 | {
343 | if (p_callInstruction->getOpcode() == Instruction::OtherOps::Call)
344 | return replaceCall(cast(p_callInstruction), functionTableIndex);
345 | }
346 | return false;
347 | }
348 |
349 | bool CallObfuscator::insertCallDispatcherDef()
350 | {
351 | LLVMContext &ctx = mod.getContext();
352 |
353 | FunctionType *p_dispatcherType = FunctionType::get(
354 | IntegerType::get(ctx, 64),
355 | {IntegerType::get(ctx, 32)},
356 | true);
357 |
358 | if (mod.getFunction("__callobf_callDispatcher"))
359 | return false;
360 |
361 | callDispatcher = mod.getOrInsertFunction("__callobf_callDispatcher", p_dispatcherType);
362 | return true;
363 | }
364 |
365 | bool CallObfuscator::finalize()
366 | {
367 | if (__locked)
368 | return false;
369 |
370 | if (!insertTables(dllNames, functionList))
371 | return false;
372 |
373 | outs() << "[INFO] Inserted tables\n";
374 | __locked = true;
375 | if (Linker::linkModules(mod, std::unique_ptr(tmpMod)))
376 | return false;
377 |
378 | outs() << "[INFO] Linked modules\n";
379 |
380 | if (!insertCallDispatcherDef())
381 | return false;
382 |
383 | outs() << "[INFO] Inserted dispatcher definition\n";
384 |
385 | int functionTableIndex = 0;
386 |
387 | for (FunctionInfo &info : functionList)
388 | {
389 | outs() << "[INFO] Hooking calls to " << info.function.getName() << " using index " << functionTableIndex << " \n";
390 | for (auto &use : info.function.uses())
391 | if (!replaceUse(use, functionTableIndex))
392 | {
393 | outs() << "[ERROR] Unsuported use, code may break, aborting"
394 | << "\n";
395 |
396 | return false;
397 | }
398 | functionTableIndex++;
399 | }
400 |
401 | __changedModule = true;
402 | return true;
403 | }
404 |
405 | bool CallObfuscator::changedModule()
406 | {
407 | return __changedModule;
408 | }
409 | }
--------------------------------------------------------------------------------
/CallObfuscatorPlugin/source/CallObfuscatorPass.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * @file CallObfucatorPass.cpp
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Initalization and managment of the obfuscator pass.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "CallObfuscatorPass.h"
26 | #include "CallObfuscator.h"
27 |
28 | #include
29 |
30 | #include "llvm/Support/CommandLine.h"
31 |
32 | #include "llvm/IR/Constants.h"
33 | #include "llvm/IR/IRBuilder.h"
34 |
35 | #include "llvm/Support/JSON.h"
36 | #include "llvm/Support/MemoryBuffer.h"
37 |
38 | using namespace std;
39 | using namespace llvm;
40 | using namespace json;
41 |
42 | namespace callobfuscatorpass
43 | {
44 | bool CallObfuscatorPass::readConfig(Object &jsonConfig)
45 | {
46 |
47 | StringRef config = getenv(LLVM_CALL_OBF_CONFIG_PATH);
48 |
49 | if (config.empty())
50 | {
51 | errs() << "[ERROR] " LLVM_CALL_OBF_CONFIG_PATH " env variable not set"
52 | << "\n";
53 | return false;
54 | }
55 |
56 | config = config.trim(" \t\n\v\f\r\"");
57 |
58 | ErrorOr> result =
59 | MemoryBuffer::getFile(config);
60 |
61 | error_code ec = result.getError();
62 |
63 | if (ec)
64 | {
65 | errs() << "[ERROR] Config file could not be read: " << ec.message() << "\n";
66 | return false;
67 | }
68 |
69 | Expected ParseResult =
70 | parse(result.get().get()->getBuffer());
71 |
72 | if (Error E = ParseResult.takeError())
73 | {
74 | errs() << "[ERROR] Config file could not be parsed\n";
75 | errs() << E << "\n";
76 | consumeError(std::move(E));
77 | return false;
78 | }
79 |
80 | outs() << "[INFO] Using config: " << config << "\n";
81 |
82 | jsonConfig = *ParseResult.get().getAsObject();
83 | return true;
84 | }
85 |
86 | bool CallObfuscatorPass::isFunctionHooked(StringRef argFunctionName, StringRef &argDllName)
87 | {
88 | if (fileMalformed)
89 | return false;
90 |
91 | const json::Array *dllHooks = jsonConfig.getArray(DLL_HOOKS_KEY);
92 |
93 | if (!dllHooks)
94 | {
95 | errs() << "[ERROR] Config file malformed, cant find \"" DLL_HOOKS_KEY "\" key\n";
96 | fileMalformed = true;
97 | return false;
98 | }
99 | int entryCount = 0; // Used for error reporting
100 | for (auto &entry : *dllHooks)
101 | {
102 | const Object *dllInfo = entry.getAsObject();
103 |
104 | if (!dllInfo)
105 | {
106 | errs() << "[ERROR] Config file malformed at entry " << entryCount << ", not an object\n";
107 | fileMalformed = true;
108 | return false;
109 | }
110 |
111 | std::optional dllName = (*dllInfo).getString(DLL_NAME_KEY);
112 | if (!dllName.has_value())
113 | {
114 | errs() << "[ERROR] Config file malformed at entry " << entryCount << ", cant find \"" DLL_NAME_KEY "\" key, or is not a string\n";
115 | fileMalformed = true;
116 | return false;
117 | }
118 |
119 | const json::Array *functionNames = (*dllInfo).getArray(FUNCTION_HOOKS_KEY);
120 | if (!functionNames)
121 | {
122 | errs() << "[ERROR] Config file malformed at entry " << entryCount << ", cant find \"" FUNCTION_HOOKS_KEY "\" key, or is not a list\n";
123 | fileMalformed = true;
124 | return false;
125 | }
126 |
127 | for (auto &functionEntry : *functionNames)
128 | {
129 | std::optional functionName = functionEntry.getAsString();
130 | if (!functionName.has_value())
131 | {
132 | errs() << "[ERROR] Config file malformed at entry " << entryCount << ", function list contains errors\n";
133 | fileMalformed = true;
134 | return false;
135 | }
136 |
137 | if (functionName.value().equals(argFunctionName))
138 | {
139 |
140 | argDllName = dllName.value();
141 | return true;
142 | }
143 | }
144 |
145 | entryCount++;
146 | }
147 | return false;
148 | }
149 |
150 | // CallObfuscatorPass implementations:
151 | PreservedAnalyses CallObfuscatorPass::run(Module &M,
152 | ModuleAnalysisManager &AM)
153 | {
154 | LLVMContext &ctx = M.getContext();
155 | if (!configLoaded)
156 | {
157 | configLoaded = true; // No matter the result, try only once
158 | bool result = readConfig(jsonConfig);
159 | if (!result)
160 | return PreservedAnalyses::all();
161 | }
162 |
163 | outs() << "[INFO] Analyzing module: " << M.getName() << "\n";
164 |
165 | callobfuscator::CallObfuscator obf = callobfuscator::CallObfuscator(M);
166 |
167 | FunctionType *p_loadLibraryType = FunctionType::get(
168 | PointerType::get(ctx, 0),
169 | {PointerType::get(ctx, 0)},
170 | false);
171 |
172 | FunctionCallee c = M.getOrInsertFunction("LoadLibraryA", p_loadLibraryType);
173 | Function &loadLibrary = cast(*c.getCallee());
174 | obf.addHook({loadLibrary, "kernel32.dll", false, 0});
175 |
176 | for (Function &F : M)
177 | {
178 | StringRef dllName;
179 |
180 | if (isFunctionHooked(F.getName(), dllName))
181 | {
182 | if (!obf.addHook({F, dllName, false, 0}))
183 | {
184 | outs() << "[INFO] Something went wrong while preparing the hooks... "
185 | << "\n";
186 | return PreservedAnalyses::all();
187 | }
188 | }
189 | }
190 |
191 | if (!obf.finalize())
192 | outs() << "[ERROR] Something went wrong\n";
193 |
194 | // Im not really sure wich passes should invalidate, so better to rerun all,
195 | // but since we run it as a single pass with opt, doesnt really matter.
196 | if (obf.changedModule())
197 | return PreservedAnalyses::none();
198 |
199 | outs() << "[INFO] Module not modified"
200 | << "\n";
201 |
202 | return PreservedAnalyses::all();
203 | }
204 | }
--------------------------------------------------------------------------------
/CallObfuscatorPlugin/source/CallObfuscatorPluginRegister.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * @file CallObfuscatorPluginRegister.cpp
3 | * @author Alejandro González (@httpyxel)
4 | * @brief Plugin registration.
5 | * @version 0.1
6 | * @date 2024-01-14
7 | *
8 | * @copyright
9 | * Copyright (C) 2024 Alejandro González
10 | *
11 | * This program is free software: you can redistribute it and/or modify
12 | * it under the terms of the GNU General Public License as published by
13 | * the Free Software Foundation, either version 3 of the License, or
14 | * (at your option) any later version.
15 | *
16 | * This program is distributed in the hope that it will be useful,
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | * GNU General Public License for more details.
20 | *
21 | * You should have received a copy of the GNU General Public License
22 | * along with this program. If not, see .
23 | */
24 |
25 | #include "llvm/Passes/PassPlugin.h"
26 | #include "llvm/Passes/PassBuilder.h"
27 |
28 | #include "CallObfuscatorPass.h"
29 | NOWARN(
30 | "-Wdll-attribute-on-redeclaration",
31 | __declspec(dllexport) extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK
32 | llvmGetPassPluginInfo() {
33 | return {
34 | LLVM_PLUGIN_API_VERSION,
35 | "CallObfuscatorPlugin", "v0.1",
36 | [](PassBuilder &PB)
37 | {
38 | // Allows to run the pass alone, only works with opt.exe
39 | // Enables: opt -load-pass-plugin="/LLVMBasePlugin.dll" -passes="base-plugin-pass"
40 | PB.registerPipelineParsingCallback(
41 | [](StringRef Name, ModulePassManager &MPM,
42 | ArrayRef)
43 | {
44 | if(Name == "callobfuscator-pass"){
45 | MPM.addPass(callobfuscatorpass::CallObfuscatorPass());
46 | return true;
47 | }
48 | return false; });
49 |
50 | // This is on test...
51 | // Allows to run the pass as part of the defaults optimization passes
52 | // Enables: clang -O0 -Xclang -disable-O0-optnone -fpass-plugin="/LLVMBasePlugin.dll" ...
53 | // Enables: opt -O0 -load-pass-plugin="\LLVMBasePlugin.dll" ...
54 |
55 | // This extension point allows adding optimization passes after most of the
56 | // main optimizations, but before the last cleanup-ish optimizations.
57 |
58 | /*PB.registerScalarOptimizerLateEPCallback(
59 | [](FunctionPassManager &FPM, OptimizationLevel opt)
60 | {
61 | FPM.addPass(baseplugin::CallObfuscatorPass());
62 | }); */
63 | PB.registerFullLinkTimeOptimizationEarlyEPCallback(
64 | [](ModulePassManager &MPM, OptimizationLevel opt)
65 | {
66 | MPM.addPass(callobfuscatorpass::CallObfuscatorPass());
67 | });
68 | }};
69 | })
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LLVM-YX-CALLOBFUSCATOR
2 |
3 |
4 | LLVM plugin to transparently apply stack spoofing and indirect syscalls to Windows x64 native calls at compile time.
5 |
6 |
7 | ## "I've 5 mins, what is this?"
8 | This project is a plugin meant to be used with [opt](https://llvm.org/docs/CommandGuide/opt.html), the LLVM optimizer. Opt will use the pass included in this plugin to hook calls to Windows functions based on a config file and point those calls to a single function, which, given an ID identifying the function to be called, will apply dynamic stack obfuscation, and if the function is a syscall stub, will invoke it using "indirect syscalling".
9 |
10 |
11 | **Usage brief**: Set up a config file indicating the functions to be hooked, write your C code without caring about Windows function calls, compile with clang to generate .ir files, give them to opt along with this plugin, opt hooks functions, llc compiles the .ir to a.obj, and ld links it to an executable that automatically obfuscates function calls.
12 |
13 | ## Table of Contents
14 | > * [Setup](#setup)
15 | > * [Usage and example](#usage-and-example)
16 | > * [Developer guide](#developer-guide)
17 | > * [File distribution](#file-distribution)
18 | > * [How the pass works](#how-the-pass-works)
19 | > * [How the dispatching system works](#how-the-dispatching-system-works)
20 | > * [Thanks](#thanks)
21 | > * [TODO](#todo)
22 |
23 | ## Setup
24 | This instructions are written for Windows, but it should be possible to setup this environment in Linux easily (still output executables can only be built for Windows x64). Tested with LLVM 16.x and 17.x.
25 |
26 | All the commands described in the following steps are supossed to be used in an MSYS2 terminal ie they have Linux format.
27 |
28 | * **Dependencies**:
29 |
30 | To be able to compile this project, we mainly need 2 things: LLVM (libraries and headers, along with some tools) and CMAKE.
31 |
32 | LLVM libraries and tools can be either compiled from source, downloaded from the LLVM releases, or installed through MSYS2 (using pacman). To make it easier, here we will use MSYS2. We also need to set up CMAKE and our build tools. Clang is required to compile the helpers library. For the linker, we don't care too much. Lastly, as a generator, I prefer Ninja, but make, nmake or msbuild will work.
33 |
34 | Everything listed above can be installed with pacman by using MSYS2.
35 |
36 | * Download and install [MSYS2](https://www.msys2.org/).
37 | * Launch an MSYS2 Mingw64 terminal and install the following packages:
38 | * Install LLVM: ```pacman -S mingw-w64-x86_64-llvm```
39 | * Install Clang: ```pacman -S mingw-w64-x86_64-clang```
40 | * Install Nasm: ```pacman -S mingw-w64-x86_64-nasm```
41 | * Install Cmake: ```pacman -S mingw-w64-x86_64-cmake```
42 | * Install Ninja: ```pacman -S mingw-w64-x86_64-ninja```
43 | * Install Git: ```pacman -S git```
44 |
45 | Restart the MSYS2 terminal, It may help with env variables, and gives luck for the following building ritual.
46 |
47 | * **Building**:
48 |
49 | First, clone this project, pretty obvious:
50 |
51 | git clone https://github.com/janoglezcampos/llvm-yx-callobfuscator
52 |
53 |
54 | To build this project, we will be using CMAKE. Because I find it convenient, I use VScode with the CMake extension. You can find my VSCODE config at ```.vscode_conf/setting.json```
55 |
56 | In any other case, launch an MSYS2 Mingw64 terminal and do exactly what I say (without checking what any of the commands Im giving you will do to your beloved machine):
57 |
58 | Go to the root directory of this repo:
59 |
60 | cd /llvm-yx-obfuscator
61 |
62 | Create a build directory and change the directory to it:
63 |
64 | mkdir build; cd build
65 |
66 | Choose an install location. I recommend doing this so it will be easier to either get the files in the right place or just be able to pick them up easily. Also, the [usage](#usage-and-example) section will use this folder as the relative folder for accessing these files when needed, so if you add it to the PATH, the commands will work right away. I use ```C:/Users//llvm-plugins```, but creating a folder in the project directory called ```install```, side by side with ```build``` will do. This folder will not be edited if you don't run the install command, but you will have to go get the files in the build folder.
67 |
68 | Configure the project; here you specify the installation folder. ```DCMAKE_BUILD_TYPE``` will set the default mode: Debug, Release or MinSizeRel. Depending on which generator you are using, you are going to be able to change this later or not (means you will have to reconfigure or not). I use Nija as generator, but you can use any other.
69 |
70 | cmake -G Ninja -DCMAKE_INSTALL_PREFIX="" -DCMAKE_BUILD_TYPE=Release ./..
71 |
72 | Build the project; optionally choose mode with ```—-config Release``` (if your generator lets you):
73 |
74 | cmake --build .
75 |
76 | Move the generated files to the install directory you chose before. If you don't want to use the install "feature" of CMake, just get the files (```libCallObfuscatorHelpers.a``` and ```CallObfuscatorPlugin.dll```) from the build directory and put them in a place you remember; you will need them.
77 |
78 | cmake --build . --target install
79 |
80 | At this point, you should have two files:
81 | * ```libCallObfuscatorHelpers.a```: A C library that includes all the logic that needs to be executed at runtime.
82 | * ```CallObfuscatorPlugin.dll```: The actual plugin containing the pass, will be given as an offer to opt.
83 |
84 | Once all this is done I like to add the path I used to install the plugin to the user path, so it is easier to import it after.
85 | To do this, add the following line to ```~/.bash_profile``` if exists, if not, add it to ```~/.profile```. Also, modify the library path, so you wont need to specify the path to helpers everytime you link.
86 |
87 | export PATH=$PATH:""
88 | export LIBRARY_PATH=$ LIBRARY_PATH:"/llvm-yx-callobfuscator/plugin-helpers"
89 |
90 | Remember the path format changes from Windows, where C: becomes /c/ (because PATH separator is ```:```), for example, in my case would:
91 |
92 | export PATH=$PATH:"/c/Users//llvm-plugins"
93 | export LIBRARY_PATH=$LIBRARY_PATH:"/c/Users//llvm-plugins/llvm-yx-callobfuscator/plugin-helpers"
94 |
95 |
96 | ## Usage and example
97 | First of all, we need to set up our configuration file. In this section, we will be using the config file found in the ```./example``` folder. You can add any number of functions to the file, and the functions do not need to appear in the program.
98 |
99 | The plugin will get the path to the config file from an environment variable called ```LLVM_OBF_FUNCTIONS```. You can either add it along with all the other environment variables for the user, the system, or just set it up for the current terminal. You can also set it from a makefile, if using one. To set the example config for the current terminal:
100 |
101 | export LLVM_OBF_FUNCTIONS=
102 |
103 | Now it is time to run the pass. A more detailed explanation about every step can be found [here](https://github.com/janoglezcampos/llvm-pass-plugin-skeleton?tab=readme-ov-file#running-you-pass).
104 |
105 | * Go inside the example folder and create a build folder; inside, create 2 folders: irs and objs.
106 |
107 | cd example; mkdir build; mkdir build/irs; mkdir build/objs
108 |
109 | ### NOTE: Any path in MYSYS2 must be written using / and not \
110 |
111 | * Compile the C files to LLVM-IR:
112 |
113 | clang -O0 -Xclang -disable-O0-optnone -S -emit-llvm ./source/utils.c -Iheaders -o ./build/irs/utils.ll
114 |
115 | clang -O0 -Xclang -disable-O0-optnone -S -emit-llvm ./source/main.c -Iheaders -o ./build/irs/main.ll
116 |
117 | * Merge all files:
118 |
119 | llvm-link ./build/irs/main.ll ./build/irs/utils.ll -S -o ./build/irs/example.ll
120 |
121 | * Run obfusction pass:
122 |
123 | opt -S -load-pass-plugin="" -passes="callobfuscator-pass" ./build/irs/example.ll -o ./build/irs/example.obf.ll
124 |
125 | If added install path to user or system path, then:
126 |
127 | opt -S -load-pass-plugin="llvm-yx-callobfuscator/CallObfuscatorPlugin.dll" -passes="callobfuscator-pass" ./build/irs/example.ll -o ./build/irs/example.obf.ll
128 |
129 | * Run optimization passes:
130 |
131 | opt -S -O3 ./build/irs/example.obf.ll -o ./build/irs/example.op.ll
132 |
133 | * Compile to windows x86_64 assembly:
134 |
135 | llc --mtriple=x86_64-pc-windows-msvc -filetype=obj ./build/irs/example.op.ll -o ./build/objs/example.obj
136 |
137 | * Link:
138 |
139 | clang ./build/objs/example.obj -o ./build/example.exe -L" -lCallObfuscatorHelpers
140 |
141 | If added helpers path to LIBRARY_PATH then you can ommit the ```-L``` option.
142 |
143 | clang ./build/objs/example.obj -o ./build/example.exe -lCallObfuscatorHelpers
144 |
145 | Now you should have ```./build/example.exe```, the final executable.
146 |
147 | In case you are thinking that those are a lot of commands, well, they are always "the same", so writing makefiles helps, Im leaving a makefile example inside the example folder to compile the same code as before.
148 |
149 | ## Developer guide
150 | * ### File distribution
151 | ---
152 | The code is always divided into two folders, one called headers, for definitions and macros mainly, and the other called source, containing the actual source code. For every source code file, there is a header file matching the relative path to the source folder. Documentation for functions is always found at headers files.
153 |
154 | You will find two source codebases in this project:
155 |
156 | * **CallObfuscatorPlugin**: The actual plugin, written in C++, that will be compiled and linked to a dll.
157 | * **CallObfuscator**: Includes the logic to transparently apply call obfucation at compile time.
158 | * **CallObfuscatorPass**: Initalization and management of the obfuscator pass.
159 | * **CallObfuscatorPluginRegister**: Plugin registration.
160 |
161 |
162 | * **CallObfuscatorHelpers**: A C library that includes all the logic that needs to be executed at runtime.
163 | * **common**: Common functionality that is used across the project.
164 | * **pe**: Utilities to manipulate and work with in-memory PEs.
165 | * **callDispatcher**: Functionality to invoke Windows native functions applying obfuscation.
166 | * **stackSpoof**: Functionality to apply dynamic stack spoofing in Windows x64 environments.
167 | * **syscalls**: Utilities to work with Windows x64 syscalls.
168 |
169 |
170 | * ### How the pass works
171 | ---
172 | Knoledge about common terms like hooks, register, stack... is assumed.
173 | This is not an in-depth guide, just enough to get you throw the execution flow.
174 |
175 | First, we go through every defined function in the code; if any of them is found in the config file, we store it. Once we find all the functions that will be obfuscated, we create two tables:
176 | * ```__callobf_dllTable```: This contains all required dlls for obfuscated functions; each dll has an ID, which is its index in the table.
177 | * ```__callobf_functionTable```: This contains all obfuscated functions and information about which dll contains them, the number of arguments of the function, if it is a syscall, etc.
178 |
179 | At compile time, this tables will be partially initialized, but the only value we need at this moment is the function ID (its index in the function table).
180 |
181 | After building the tables, we find every call to the obfuscated functions; for each of them, replace the call by a call to ```__callobf_callDispatcher```, and pass the ID as the first argument, then pass all the other function arguments.
182 |
183 | ```__callobf_callDispatcher``` is defined as ```PVOID __callobf_callDispatcher(DWORD32 index, ...)```. It will get all the info it needs from the function table by using the ID (index) in the first argument.
184 |
185 | A function has its entry partially initialized until it is called; at that moment, ```__callobf_callDispatcher``` will store all the required information to call and obfuscate the function and pass the other arguments to the function being called.
186 |
187 | * ### How the dispatching system works
188 | ---
189 | The dispatching system starts by initializing the entry in the function table.
190 | * Get the dll from the dll table and load it if needed.
191 | * Find the function in the IAT and store the address in the function table.
192 | * Find if the call is a syscall; if it is, get the ssn and store it in the function table.
193 |
194 | If not already done, initialize the frame table (```__callobf_globalFrameTable```), used to cache posible frames and gadgets that will be used to build the obfuscated stack. The obfuscation method is the same as explained [here](https://klezvirus.github.io/RedTeaming/AV_Evasion/StackSpoofing/), still an outstanding job.
195 |
196 | After the the function is loaded and the frame table is initialized:
197 |
198 | * Build a fake stack using the values stored in the frame table and store it over the current stack pointer.
199 | * Update the ciclic value used to pick which values are used for building the stack.
200 | * Move arguments to their right place.
201 | * Change rsp to match the start of the fake stack.
202 | * If syscall, set ssn in rax.
203 | * If syscall, set r10 to hold the first argument.
204 | * Jump to the function or syscall instruction.
205 |
206 | ## Thanks
207 | To Arash Parsa, aka [waldoirc](https://twitter.com/waldoirc), Athanasios Tserpelis, aka [trickster0](https://twitter.com/trickster012) and Alessandro Magnosi, aka [klezVirus](https://twitter.com/klezVirus) because of [SilentMoonwalk](https://klezvirus.github.io/RedTeaming/AV_Evasion/StackSpoofing/)
208 |
209 | ---
210 | > ## TODO:
211 | > This includes things that I really dont want to forget, but more stuff could be added here. Not by now
212 | >### Docs/formatting:
213 | >* Somehow improve stackSpoofHelper.x64.asm readability.
214 | >
215 | >### Opsec:
216 | >* EAF bypass.
217 | >* Encript strings that cant be hashed.
218 | >
219 | >### General quality:
220 | >* Group all globals, or somehow make it clear in the code where all globals are declared.
221 | >* Put MIN_ADD_RSP_FRAME_SIZE to work.
222 | >* Optionally validate config file entries against local dlls.
223 | >* Optionally return load errors through messagebox pop ups, similarly to what ms does with CRTs.
224 | >
225 | >### Functionality:
226 | >* Handle invoke instructions and exception stuff (should not happen in C but...)
227 | >* Handle indirect calls
228 | >* Handle function address reads (In fact indirect calls always start with a value read, so they are the same case)
229 |
--------------------------------------------------------------------------------
/example/callobfuscator.conf:
--------------------------------------------------------------------------------
1 | {
2 | "dll_hooks": [
3 | {
4 | "dll_name": "ntdll.dll",
5 | "hooked_functions": [
6 | "NtAllocateVirtualMemory",
7 | "NtFreeVirtualMemory"
8 | ]
9 | },
10 | {
11 | "dll_name": "kernel32.dll",
12 | "hooked_functions": [
13 | "CreateActCtxA",
14 | "Sleep"
15 | ]
16 | },
17 | {
18 | "dll_name": "user32.dll",
19 | "hooked_functions": [
20 | "MessageBoxA"
21 | ]
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/example/headers/utils.h:
--------------------------------------------------------------------------------
1 |
2 | #include
3 |
4 | NTSTATUS NtAllocateVirtualMemory(
5 | HANDLE ProcessHandle,
6 | PVOID *BaseAddress,
7 | ULONG_PTR ZeroBits,
8 | PSIZE_T RegionSize,
9 | ULONG AllocationType,
10 | ULONG Protect);
11 |
12 | NTSTATUS NtFreeVirtualMemory(
13 | HANDLE ProcessHandle,
14 | PVOID *BaseAddress,
15 | PSIZE_T RegionSize,
16 | ULONG FreeType);
17 |
18 | PVOID memoryTest();
--------------------------------------------------------------------------------
/example/makefile_example:
--------------------------------------------------------------------------------
1 |
2 |
3 | VPATH=source
4 |
5 | PROJECT_NAME = example
6 |
7 | OUTPUT_DIR = "./build"
8 | C_OBJS_DIR = "./build/objs"
9 | C_IRS_DIR = "./build/irs"
10 |
11 | OUT_NAME = ./build/$(PROJECT_NAME).exe
12 | OBJ_OUT_NAME = ./build/objs/$(PROJECT_NAME).obj
13 | IR_OUT_NAME = ./build/irs/$(PROJECT_NAME).ll
14 |
15 | CCX64 = clang
16 | CCX86 = clang
17 |
18 | CFLAGS = -O0 -Xclang -disable-O0-optnone -S -emit-llvm
19 | #CFLAGS = -fsanitize=address
20 |
21 | LDX64 = clang
22 | LFLAGS =
23 |
24 | AR = ar
25 |
26 | LLVM_LINK = llvm-link
27 | LLVM_LINK_FLAGS = -S
28 |
29 | OBF_PLUGIN_PATH = "llvm-yx-callobfuscator/CallObfuscatorPlugin.dll"
30 | OBF_PASS_NAME = callobfuscator-pass
31 |
32 | LLVM_OBF_HELPERS =
33 | export LLVM_OBF_FUNCTIONS="callobfuscator.conf"
34 |
35 | OPT = opt
36 | OPT_FLAGS = -load-pass-plugin=$(OBF_PLUGIN_PATH) -passes=$(OBF_PASS_NAME) -S
37 |
38 | ARFLAGS = rcs
39 |
40 | C_SOURCES := $(shell find . -name '*.c')
41 | C_IRS := $(patsubst %.c, ./build/irs/%.ll, $(notdir $(C_SOURCES)))
42 |
43 | ifeq ($(ARCH),x86)
44 | CC = $(CCX86)
45 | AS = $(ASX86)
46 | NASM_SOURCE = $(NASM_SOURCE_X86)
47 | else
48 | CC = $(CCX64)
49 | AS = $(ASX64)
50 | NASM_SOURCE = $(NASM_SOURCE_X64)
51 | endif
52 |
53 | all: create_directories $(OUT_NAME)
54 |
55 | .FORCE:
56 | run_opt: $(IR_OUT_NAME) .FORCE
57 | $(OPT) $(OPT_FLAGS) $(IR_OUT_NAME) -o opt_check.ll
58 |
59 | $(OUT_NAME): $(OBJ_OUT_NAME)
60 | $(LDX64) $^ -o $@ $(LFLAGS) -lCallObfuscatorHelpers
61 |
62 | $(OBJ_OUT_NAME): $(IR_OUT_NAME)
63 | llc -filetype=obj --mtriple=x86_64-pc-windows-msvc $(IR_OUT_NAME) -o $(OBJ_OUT_NAME)
64 |
65 | $(IR_OUT_NAME): $(C_IRS)
66 | $(LLVM_LINK) $(LLVM_LINK_FLAGS) $^ -o $@
67 | $(OPT) $(OPT_FLAGS) $@ -o $@
68 |
69 | ./build/irs/%.ll:%.c
70 | $(CC) $< $(CFLAGS) -o $@ -Iheaders
71 |
72 |
73 | create_directories:
74 | @if [ ! -d $(OUTPUT_DIR) ]; then \
75 | mkdir $(OUTPUT_DIR); \
76 | fi
77 | @if [ ! -d $(C_OBJS_DIR) ]; then \
78 | mkdir $(C_OBJS_DIR); \
79 | fi
80 | @if [ ! -d $(C_IRS_DIR) ]; then \
81 | mkdir $(C_IRS_DIR); \
82 | fi
83 |
84 | clean:
85 | -rm $(C_OBJS) $(OUT_NAME) $(IR_OUT_NAME) $(OBJ_OUT_NAME) $(C_IRS)
--------------------------------------------------------------------------------
/example/source/main.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "utils.h"
3 | #include
4 |
5 | // This is absolutly optional, but if you suspect something
6 | // is going wrong, use it to check if there was an error during
7 | // invokation.
8 | extern DWORD32 __callobf_getLastError();
9 |
10 | int main()
11 | {
12 | printf("[INFO] Running call obfuscator test\n");
13 |
14 | PVOID p_kernel32 = LoadLibraryA("kernel32");
15 | printf("[INFO] kernel32 addr: %p\n", p_kernel32);
16 | PVOID addr = memoryTest();
17 | Sleep(1000);
18 | MessageBoxA(NULL, "Use this time to check the function stack.", "Break", 0);
19 |
20 | printf("[INFO] Ended\n");
21 | return 0;
22 | }
--------------------------------------------------------------------------------
/example/source/utils.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "utils.h"
4 |
5 | PVOID memoryTest()
6 | {
7 | PVOID p_baseAddress = NULL;
8 | SIZE_T requiredSize = 0x1100;
9 |
10 | NTSTATUS status = NtAllocateVirtualMemory((HANDLE)-1, &p_baseAddress, 0, &requiredSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
11 |
12 | if (status)
13 | {
14 | printf("[ERROR] NtAllocateVirtualMemory status: 0x%lX\n", status);
15 | }
16 |
17 | printf("[INFO] Allocated 0x%llX bytes at %p\n", requiredSize, p_baseAddress);
18 | return p_baseAddress;
19 | }
--------------------------------------------------------------------------------