├── .clang-format ├── .gitignore ├── Makefile ├── README.md ├── examples ├── flatout_2_trainer │ ├── Makefile │ ├── README.md │ └── src │ │ └── main.cpp └── gta_sa_trainer │ ├── Makefile │ ├── README.md │ └── src │ ├── game │ ├── gta_sa.cpp │ ├── gta_sa.hpp │ ├── quick_functions.cpp │ ├── quick_functions.hpp │ └── types.hpp │ ├── main.cpp │ ├── utils.cpp │ └── utils.hpp ├── src ├── core │ ├── ep_core.c │ └── ep_core.h ├── external_process.cpp └── external_process.hpp └── test ├── external_process_simulator ├── Makefile └── src │ └── external_process_simulator.cpp └── unit_tests ├── Makefile └── src ├── test.h ├── test_utils.cpp ├── test_utils.hpp └── unit_tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: Microsoft 3 | ColumnLimit: 80 4 | AccessModifierOffset: -4 5 | UseTab: Never 6 | SortIncludes: false 7 | IndentExternBlock: NoIndent 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | /*/ 3 | !/.gitignore 4 | !/README.md 5 | !/Makefile 6 | !/.clang-format 7 | 8 | !/src 9 | !/test/ 10 | /test/* 11 | #unit_tests src and Makefile 12 | !/test/unit_tests/ 13 | /test/unit_tests/* 14 | !/test/unit_tests/src 15 | !/test/unit_tests/Makefile 16 | #external_process_simulator src and Makefile 17 | !/test/external_process_simulator/ 18 | /test/external_process_simulator/* 19 | !/test/external_process_simulator/src 20 | !/test/external_process_simulator/Makefile 21 | #examples 22 | #FlatOut 2 23 | !/examples 24 | /examples/* 25 | !/examples/flatout_2_trainer 26 | /examples/flatout_2_trainer/* 27 | !/examples/flatout_2_trainer/src 28 | !/examples/flatout_2_trainer/Makefile 29 | #GTA San Andreas 1.0 US 30 | !/examples 31 | /examples/* 32 | !/examples/gta_sa_trainer 33 | /examples/gta_sa_trainer/* 34 | !/examples/gta_sa_trainer/src 35 | !/examples/gta_sa_trainer/Makefile 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SETTINGS ##################################################################### 2 | SIM_NAME:=external_process_simulator.exe 3 | SIM_DIR:=test/external_process_simulator 4 | 5 | # IMPLEMENTATION ############################################################### 6 | build: build_external_process_simulator build_unit_tests 7 | 8 | ifeq ($(OS),Windows_NT) 9 | WINE:= 10 | else 11 | WINE:=wine 12 | endif 13 | 14 | run_tests: build 15 | # CMD /C start /MIN $(SIM_DIR)/bin/$(SIM_NAME) wait_for_input 16 | # test\unit_tests\bin\test.exe 17 | # taskkill /IM $(SIM_NAME) 18 | ln -f $(SIM_DIR)/bin/$(SIM_NAME) test/unit_tests/bin/$(SIM_NAME) 19 | cd test/unit_tests/bin && $(WINE) ./test.exe 20 | 21 | build_unit_tests: 22 | $(MAKE) -C test/unit_tests/ EXTERNAL_PROCESS_SIMULATOR_NAME=$(SIM_NAME) 23 | 24 | clean_unit_tests: 25 | $(MAKE) -C test/unit_tests/ clean 26 | 27 | build_external_process_simulator: 28 | $(MAKE) -C $(SIM_DIR) EXTERNAL_PROCESS_SIMULATOR_NAME=$(SIM_NAME) 29 | 30 | clean_external_process_simulator: 31 | $(MAKE) -C $(SIM_DIR) clean 32 | 33 | clean: clean_unit_tests clean_external_process_simulator 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # external-process 2 | _A framework for interacting with external Win32 processes._ 3 | 4 | ## Table of Contents 5 | - [Repository structure](#Repository-structure) 6 | - [Features](#Features) 7 | - [Usage](#Usage) 8 | - [Testing](#Testing) 9 | 10 | ## Repository Structure 11 | - [/src](/src) - Source code of the framework 12 | - [/test](/test) - Resources for writing and running tests 13 | - [/examples](/examples) - Examples of using the framework 14 | 15 | ## Features 16 | - Read and write to an external process memory 17 | - Allocate and deallocate external process memory 18 | - Call function of an external process (currently supports cdecl, stdcall, and MSVC thiscall calling conventions) 19 | - Code injection using various methods 20 | - Search for byte sequences in external process memory using signatures 21 | 22 | ## Usage 23 | Add [/src/external_process.hpp](/src/external_process.hpp), [/src/external_process.cpp](/src/external_process.cpp) to your project. Inherit from the '__ExternalProcess__' class and implement the functionality you need. 24 | You can find examples of how to use the framework in the [/examples](/examples) directory. 25 | 26 | ## Testing 27 | The /test directory contains everything you need to write and run tests. As the primary functionality of the framework is based on the interaction between two Win32 processes, most tests require a "victim" application with which the framework will interact. For this purpose, the [/test/external_process_simulator](/test/external_process_simulator) simulator is provided, which compiles into a 32-bit PE file. The test engine, tests, and auxiliary functionality are located in the [/test/unit_tests](test/unit_tests) directory. Tests are also compiled into a 32-bit PE file. Running this file will launch the simulator and start executing tests through interaction with it. 28 | 29 | To run the tests, execute the ```make run_tests``` command. This will build the external process simulator (output as **external_process_simulator.exe** in *test/external_process_simulator/bin*). Next, a link to **external_process_simulator.exe** will be moved to *test/unit_tests/bin*. Afterward, **test.exe** will start and the tests will run. The entire process is implemented in the (Makefile)[/Makefile] located in the repository root. 30 | 31 | Test results, including test names and passed/failed test statistics, will be displayed in the console. Additionally, upon completion, **test.exe** will return a value equal to the number of failed tests. 32 | 33 | You can also run tests manually without using the Makefile. To do this, place **external_process_simulator.exe** and **test.exe** in the same directory and execute **test.exe** 34 | 35 | ## Examples 36 |
37 | FlatOut 2 trainer 38 | https://user-images.githubusercontent.com/46194184/233863589-82d6642b-f4a2-4b80-a6d3-7517e2dcdec8.mp4 39 |
40 | 41 |
42 | GTA San Andreas trainer 43 | POV hack: 44 | https://user-images.githubusercontent.com/46194184/233863892-846cd69c-14e4-4293-ac94-a97a6f2ba99c.mp4 45 |
46 | -------------------------------------------------------------------------------- /examples/flatout_2_trainer/Makefile: -------------------------------------------------------------------------------- 1 | # SETTINGS ##################################################################### 2 | APP_NAME:=FlatOut2_trainer 3 | BUILD_DIR:=build 4 | SRC_DIRS:=src ../../src 5 | CC:=i686-w64-mingw32-gcc 6 | CXX:=i686-w64-mingw32-g++ 7 | LINKER := $(CXX) 8 | STATIC_LIBS := 9 | # RELEASE 10 | CC_CMP_FLAGS_RELEASE := -O3 -Wall -Wextra -Werror 11 | CXX_CMP_FLAGS_RELEASE := $(CC_CMP_FLAGS_RELEASE) 12 | LINK_FLAGS_RELEASE := -static 13 | # DEBUG 14 | CC_CMP_FLAGS_DEBUG := -O0 -g -D DEBUG_ 15 | CXX_CMP_FLAGS_DEBUG := $(CC_CMP_FLAGS_DEBUG) 16 | LINK_FLAGS_DEBUG := $(LINK_FLAGS_RELEASE) 17 | 18 | # IMPLEMENTATION ############################################################### 19 | .DEFAULT_GOAL := help 20 | MKDIR_P := mkdir -p 21 | RM_R := rm -fr 22 | 23 | CFG ?= release 24 | ifeq ($(CFG),debug) 25 | CC_CMP_FLAGS = $(CC_CMP_FLAGS_DEBUG) 26 | CXX_CMP_FLAGS = $(CXX_CMP_FLAGS_DEBUG) 27 | LINK_FLAGS := $(LINK_FLAGS_DEBUG) 28 | APP_NAME := $(APP_NAME)_dbg.exe 29 | _BUILD_DIR := debug 30 | else 31 | CC_CMP_FLAGS = $(CC_CMP_FLAGS_RELEASE) 32 | CXX_CMP_FLAGS = $(CXX_CMP_FLAGS_RELEASE) 33 | LINK_FLAGS := $(LINK_FLAGS_RELEASE) 34 | APP_NAME := $(APP_NAME).exe 35 | _BUILD_DIR := release 36 | endif 37 | 38 | 39 | OBJ_DIR := $(BUILD_DIR)/$(_BUILD_DIR)/obj 40 | BIN_DIR := $(BUILD_DIR) 41 | rwc=$(sort $(wildcard $1$2)) $(foreach d,$(wildcard $1*),$(call rwc,$d/,$2)) 42 | 43 | C_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.c)) 44 | CPP_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.cpp)) 45 | OBJ_FILES = $(C_FILES:%=$(OBJ_DIR)/%.o) $(CPP_FILES:%=$(OBJ_DIR)/%.o) 46 | H_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.h)) 47 | HPP_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.hpp)) 48 | H_DIRS:=$(sort $(dir $(H_FILES))) 49 | HPP_DIRS:=$(sort $(dir $(HPP_FILES))) 50 | H_INCLUDES:=$(addprefix -I, $(H_DIRS)) 51 | HPP_INCLUDES:=$(addprefix -I, $(HPP_DIRS)) 52 | INCLUDES:=$(addprefix -I, $(SRC_DIRS)) $(H_INCLUDES) $(HPP_INCLUDES) 53 | 54 | # compile .c files 55 | $(OBJ_DIR)/%.c.o: %.c 56 | $(MKDIR_P) $(dir $@) 57 | $(CC) -c $(CC_CMP_FLAGS) -o $@ $(INCLUDES) $< 58 | 59 | # compile .cpp files 60 | $(OBJ_DIR)/%.cpp.o: %.cpp 61 | $(MKDIR_P) $(dir $@) 62 | $(CXX) -c $(CXX_CMP_FLAGS) -o $@ $(INCLUDES) $< 63 | 64 | # # link 65 | $(BIN_DIR)/$(APP_NAME): $(OBJ_FILES) 66 | $(MKDIR_P) $(dir $@) 67 | $(LINKER) $^ $(LINK_FLAGS) -o $@ 68 | 69 | build: $(BIN_DIR)/$(APP_NAME) 70 | clean: 71 | $(RM_R) $(BUILD_DIR) 72 | .PHONY: help 73 | help: 74 | @echo "Usage: make [TARGET] [VARIABLES]" 75 | @echo "" 76 | @echo "Targets:" 77 | @echo " build - build the project" 78 | @echo " clean - remove $(BUILD_DIR)" 79 | @echo " help - display this help message (default)" 80 | @echo "" 81 | @echo "Variables:" 82 | @echo " CFG - build configuration(release(default)/debug)" 83 | @echo "" 84 | @echo "Examples:" 85 | @echo " make - display this help message" 86 | @echo " make build - build the project" 87 | @echo " make build CFG=debug - build the project without optimizations" 88 | @echo " and include debugging information" 89 | -------------------------------------------------------------------------------- /examples/flatout_2_trainer/README.md: -------------------------------------------------------------------------------- 1 | # FlatOut 2 Trainer 2 | ## Usage 3 | * F1 - Repair the player's vehicle. 4 | * F2 - Enhance acceleration performance. 5 | * F3 - Boost nitro efficiency. 6 | * F4 - Eliminate opponents. 7 | * F5 - Switch positions with the closest opponent. 8 | * LEFT/RIGHT/UP/DOWN arrows - Move opponents. 9 | 10 | ## Demonstration 11 | https://github.com/ep1h/external-process/assets/46194184/187e9f2a-d6da-4a42-9a44-3fba77e4fd90 12 | 13 | -------------------------------------------------------------------------------- /examples/flatout_2_trainer/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file main.cpp 3 | * 4 | * @brief This file contains the source code for a basic FlatOut 2 game trainer, 5 | * demonstrating the capabilities of the ExternalProcess class using a 6 | * practical example with room for improvement. 7 | * 8 | * Usage: 9 | * F1 - Repair the player's vehicle. 10 | * F2 - Enhance acceleration performance. 11 | * F3 - Boost nitro efficiency. 12 | * F4 - Eliminate opponents. 13 | * F5 - Switch positions with the closest opponent. 14 | * LEFT/RIGHT/UP/DOWN arrows - Reposition opponents. 15 | * 16 | */ 17 | #include "../../../src/external_process.hpp" 18 | #include 19 | #include 20 | 21 | using namespace P1ExternalProcess; 22 | 23 | enum enActors 24 | { 25 | PLAYER, 26 | ENEMY_1, 27 | ENEMY_2, 28 | ENEMY_3, 29 | ENEMY_4, 30 | ENEMY_5, 31 | ENEMY_6, 32 | ENEMY_7 33 | }; 34 | 35 | enum enVehInfoOffsets 36 | { 37 | VEH_NAME = 0x40, 38 | POS_Y = 0x1E0, 39 | POS_Z = 0x1E4, 40 | POS_X = 0x1E8, 41 | VEL_Y = 0x280, 42 | VEL_Z = 0x284, 43 | VEL_X = 0x288, 44 | VEL_ROLL = 0x290, 45 | VEL_YAW = 0x294, 46 | VEL_PITCH = 0x298, 47 | ACCELERATION_FORCE = 0x5C4, 48 | NITRO_ACCELERATION_FORCE = 0x5C8, 49 | NITRO = 0x5CC, 50 | MAX_NITRO = 0x5D0, /* default 5.0f */ 51 | HEALTH = 0x6AA0 /* 0.0f - max, 1.0f - min*/ 52 | }; 53 | 54 | struct vec3f 55 | { 56 | float x; 57 | float y; 58 | float z; 59 | }; 60 | 61 | ExternalProcess fo2("FlatOut2.exe"); 62 | 63 | uint32_t get_veh_info(enActors actor) 64 | { 65 | uint32_t base_address = fo2.get_module_address("FlatOut2.exe"); 66 | uint32_t global_struct = fo2.read(base_address + 0x296DC8); 67 | uint32_t actors_ptr_array = fo2.read(global_struct + 0x14); 68 | uint32_t player_actor_info = 69 | fo2.read(actors_ptr_array + actor * 4); 70 | uint32_t vehicle_info = fo2.read(player_actor_info + 0x33C); 71 | return vehicle_info; 72 | } 73 | 74 | void repair(void) 75 | { 76 | uint32_t player_veh = get_veh_info(PLAYER); 77 | fo2.write(player_veh + HEALTH, 0.0f); 78 | } 79 | 80 | void super_acceleration(void) 81 | { 82 | uint32_t player_veh = get_veh_info(PLAYER); 83 | float cur = fo2.read(player_veh + ACCELERATION_FORCE); 84 | if (cur < 5.0f) 85 | { 86 | fo2.write(player_veh + ACCELERATION_FORCE, 5.0f); 87 | } 88 | } 89 | 90 | void super_nitro(void) 91 | { 92 | uint32_t player_veh = get_veh_info(PLAYER); 93 | float cur = fo2.read(player_veh + NITRO_ACCELERATION_FORCE); 94 | if (cur < 5.0f) 95 | { 96 | fo2.write(player_veh + NITRO_ACCELERATION_FORCE, 5.0f); 97 | } 98 | } 99 | 100 | void destroy_opponents(void) 101 | { 102 | for (int i = 1; i < 8; i++) 103 | { 104 | uint32_t veh = get_veh_info((enActors)i); 105 | fo2.write(veh + VEL_Z, -30.0f); 106 | } 107 | } 108 | 109 | float calculate_distance_3d(const vec3f &a, const vec3f &b) 110 | { 111 | return sqrtf(powf(a.x - b.x, 2.0) + powf(a.y - b.y, 2.0f) + 112 | powf(a.z - b.z, 2.0f)); 113 | } 114 | 115 | void swap_with_nearest_vehicle(void) 116 | { 117 | uint32_t player_veh = get_veh_info(PLAYER); 118 | vec3f player_pos = fo2.read(player_veh + POS_Y); 119 | uint32_t nearest_veh = 0; 120 | float nearest_dist = 999999.0f; 121 | for (int i = 1; i < 8; i++) 122 | { 123 | uint32_t veh = get_veh_info((enActors)i); 124 | vec3f pos = fo2.read(veh + POS_Y); 125 | float dist = calculate_distance_3d(player_pos, pos); 126 | if (dist < nearest_dist) 127 | { 128 | nearest_dist = dist; 129 | nearest_veh = veh; 130 | } 131 | } 132 | vec3f pos = fo2.read(nearest_veh + POS_Y); 133 | vec3f vel = fo2.read(nearest_veh + VEL_Y); 134 | vec3f vel_rot = fo2.read(nearest_veh + VEL_ROLL); 135 | 136 | vec3f player_vel = fo2.read(player_veh + VEL_Y); 137 | vec3f player_vel_rot = fo2.read(player_veh + VEL_ROLL); 138 | 139 | fo2.write(nearest_veh + POS_Y, player_pos); 140 | fo2.write(nearest_veh + VEL_Y, player_vel); 141 | fo2.write(nearest_veh + VEL_ROLL, player_vel_rot); 142 | 143 | fo2.write(player_veh + POS_Y, pos); 144 | fo2.write(player_veh + VEL_Y, vel); 145 | fo2.write(player_veh + VEL_ROLL, vel_rot); 146 | } 147 | 148 | void cc(void) 149 | { 150 | if (GetAsyncKeyState(VK_UP)) 151 | { 152 | for (int i = 1; i < 8; i++) 153 | { 154 | uint32_t veh = get_veh_info((enActors)i); 155 | float v = fo2.read(veh + VEL_Z); 156 | fo2.write(veh + VEL_Z, v + 10.0f); 157 | fo2.write(veh + VEL_X, 0.0f); 158 | fo2.write(veh + VEL_Y, 0.0f); 159 | } 160 | } 161 | if (GetAsyncKeyState(VK_DOWN)) 162 | { 163 | for (int i = 1; i < 8; i++) 164 | { 165 | uint32_t veh = get_veh_info((enActors)i); 166 | float v = fo2.read(veh + VEL_Z); 167 | fo2.write(veh + VEL_Z, v - 10.0f); 168 | // fo2.write(veh + VEL_X, 0.0f); 169 | // fo2.write(veh + VEL_Y, 0.0f); 170 | } 171 | } 172 | if (GetAsyncKeyState(VK_LEFT)) 173 | { 174 | for (int i = 1; i < 8; i++) 175 | { 176 | uint32_t veh = get_veh_info((enActors)i); 177 | float v = fo2.read(veh + VEL_Z); 178 | fo2.write(veh + VEL_X, v - 2.0f); 179 | fo2.write(veh + VEL_ROLL, 30.0f); 180 | } 181 | } 182 | if (GetAsyncKeyState(VK_RIGHT)) 183 | { 184 | for (int i = 1; i < 8; i++) 185 | { 186 | uint32_t veh = get_veh_info((enActors)i); 187 | float v = fo2.read(veh + VEL_Z); 188 | fo2.write(veh + VEL_X, v + 2.0f); 189 | fo2.write(veh + VEL_ROLL, -30.0f); 190 | } 191 | } 192 | } 193 | 194 | int main(int argc, char *argv[]) 195 | { 196 | (void)argc; 197 | (void)argv; 198 | 199 | while (true) 200 | { 201 | if (GetAsyncKeyState(VK_F1)) 202 | { 203 | repair(); 204 | } 205 | if (GetAsyncKeyState(VK_F2)) 206 | { 207 | super_acceleration(); 208 | } 209 | if (GetAsyncKeyState(VK_F3)) 210 | { 211 | super_nitro(); 212 | } 213 | if (GetAsyncKeyState(VK_F4)) 214 | { 215 | destroy_opponents(); 216 | } 217 | if (GetAsyncKeyState(VK_F5)) 218 | { 219 | swap_with_nearest_vehicle(); 220 | } 221 | cc(); 222 | Sleep(100); 223 | } 224 | return 0; 225 | } 226 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/Makefile: -------------------------------------------------------------------------------- 1 | # SETTINGS ##################################################################### 2 | APP_NAME:=GtaSA_trainer 3 | BUILD_DIR:=build 4 | SRC_DIRS:=src ../../src 5 | CC:=i686-w64-mingw32-gcc 6 | CXX:=i686-w64-mingw32-g++ 7 | LINKER := $(CXX) 8 | STATIC_LIBS := 9 | # RELEASE 10 | CC_CMP_FLAGS_RELEASE := -O3 -Wall -Wextra -Werror 11 | CXX_CMP_FLAGS_RELEASE := $(CC_CMP_FLAGS_RELEASE) 12 | LINK_FLAGS_RELEASE := -static 13 | # DEBUG 14 | CC_CMP_FLAGS_DEBUG := -O0 -g -D DEBUG_ 15 | CXX_CMP_FLAGS_DEBUG := $(CC_CMP_FLAGS_DEBUG) 16 | LINK_FLAGS_DEBUG := $(LINK_FLAGS_RELEASE) 17 | 18 | # IMPLEMENTATION ############################################################### 19 | .DEFAULT_GOAL := help 20 | MKDIR_P := mkdir -p 21 | RM_R := rm -fr 22 | 23 | CFG ?= release 24 | ifeq ($(CFG),debug) 25 | CC_CMP_FLAGS = $(CC_CMP_FLAGS_DEBUG) 26 | CXX_CMP_FLAGS = $(CXX_CMP_FLAGS_DEBUG) 27 | LINK_FLAGS := $(LINK_FLAGS_DEBUG) 28 | APP_NAME := $(APP_NAME)_dbg.exe 29 | _BUILD_DIR := debug 30 | else 31 | CC_CMP_FLAGS = $(CC_CMP_FLAGS_RELEASE) 32 | CXX_CMP_FLAGS = $(CXX_CMP_FLAGS_RELEASE) 33 | LINK_FLAGS := $(LINK_FLAGS_RELEASE) 34 | APP_NAME := $(APP_NAME).exe 35 | _BUILD_DIR := release 36 | endif 37 | 38 | 39 | OBJ_DIR := $(BUILD_DIR)/$(_BUILD_DIR)/obj 40 | BIN_DIR := $(BUILD_DIR) 41 | rwc=$(sort $(wildcard $1$2)) $(foreach d,$(wildcard $1*),$(call rwc,$d/,$2)) 42 | 43 | C_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.c)) 44 | CPP_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.cpp)) 45 | OBJ_FILES = $(C_FILES:%=$(OBJ_DIR)/%.o) $(CPP_FILES:%=$(OBJ_DIR)/%.o) 46 | H_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.h)) 47 | HPP_FILES:=$(foreach d,$(SRC_DIRS),$(call rwc,$(d),*.hpp)) 48 | H_DIRS:=$(sort $(dir $(H_FILES))) 49 | HPP_DIRS:=$(sort $(dir $(HPP_FILES))) 50 | H_INCLUDES:=$(addprefix -I, $(H_DIRS)) 51 | HPP_INCLUDES:=$(addprefix -I, $(HPP_DIRS)) 52 | INCLUDES:=$(addprefix -I, $(SRC_DIRS)) $(H_INCLUDES) $(HPP_INCLUDES) 53 | 54 | # compile .c files 55 | $(OBJ_DIR)/%.c.o: %.c 56 | $(MKDIR_P) $(dir $@) 57 | $(CC) -c $(CC_CMP_FLAGS) -o $@ $(INCLUDES) $< 58 | 59 | # compile .cpp files 60 | $(OBJ_DIR)/%.cpp.o: %.cpp 61 | $(MKDIR_P) $(dir $@) 62 | $(CXX) -c $(CXX_CMP_FLAGS) -o $@ $(INCLUDES) $< 63 | 64 | # # link 65 | $(BIN_DIR)/$(APP_NAME): $(OBJ_FILES) 66 | $(MKDIR_P) $(dir $@) 67 | $(LINKER) $^ $(LINK_FLAGS) -o $@ 68 | 69 | build: $(BIN_DIR)/$(APP_NAME) 70 | clean: 71 | $(RM_R) $(BUILD_DIR) 72 | .PHONY: help 73 | help: 74 | @echo "Usage: make [TARGET] [VARIABLES]" 75 | @echo "" 76 | @echo "Targets:" 77 | @echo " build - build the project" 78 | @echo " clean - remove $(BUILD_DIR)" 79 | @echo " help - display this help message (default)" 80 | @echo "" 81 | @echo "Variables:" 82 | @echo " CFG - build configuration(release(default)/debug)" 83 | @echo "" 84 | @echo "Examples:" 85 | @echo " make - display this help message" 86 | @echo " make build - build the project" 87 | @echo " make build CFG=debug - build the project without optimizations" 88 | @echo " and include debugging information" 89 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/README.md: -------------------------------------------------------------------------------- 1 | # GTA San Andreas 1.0 US Trainer 2 | ## Usage 3 | * F6 - Decrease FOV. 4 | * F7 - Increase FOV. 5 | 6 | ## Demonstration 7 | https://github.com/ep1h/external-process/assets/46194184/670d3daf-346d-44b1-b3be-b77d59d81e10 8 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/game/gta_sa.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file gta_sa.cpp 3 | * 4 | * @brief Implementation of the GtaSA class. 5 | * 6 | */ 7 | #include "gta_sa.hpp" 8 | #include 9 | 10 | #include "quick_functions.hpp" 11 | 12 | GtaSA::GtaSA(void) : ExternalProcess("gta_sa.exe") 13 | { 14 | _ped_pool = read(0xB74490); 15 | _ped_pool_objects = read(_ped_pool); 16 | _ped_pool_bytemap = read(_ped_pool + 4); 17 | } 18 | 19 | uint32_t GtaSA::get_player_ped(void) 20 | { 21 | return read(0xB6F5F0); 22 | } 23 | 24 | vec3f GtaSA::get_ped_pos(uint32_t ped) 25 | { 26 | return read(read(ped + 0x14) + 0x30); 27 | } 28 | 29 | float GtaSA::get_ped_health(uint32_t ped) 30 | { 31 | return read(ped + 0x540); 32 | } 33 | 34 | uint8_t GtaSA::get_ped_armed_weapon(uint32_t ped) 35 | { 36 | return read(ped + 0x740); 37 | } 38 | 39 | uint32_t GtaSA::get_ped_by_handle(uint32_t handle) 40 | { 41 | // return call_cdecl_function(0x54FF90, 1, handle); 42 | uint8_t h_handle = read((handle >> 8) + _ped_pool_bytemap); 43 | if (h_handle == (handle & 0x000000FF)) 44 | { 45 | return _ped_pool_objects + 0x7C4 * (handle >> 8); 46 | } 47 | else 48 | { 49 | return 0; 50 | } 51 | } 52 | 53 | vec2ui GtaSA::get_screen_resolution(void) 54 | { 55 | return read(0xC17044); 56 | } 57 | 58 | vec2f GtaSA::get_crosshair_position(void) 59 | { 60 | vec2f result = read(0xB6EC10); 61 | std::swap(result.x, result.y); 62 | return result; 63 | } 64 | 65 | float GtaSA::get_aspect_ratio(void) 66 | { 67 | return read(0xC3EFA4); 68 | } 69 | 70 | float GtaSA::get_cam_x_angle(void) 71 | { 72 | return read(0xB6F258); 73 | } 74 | 75 | float GtaSA::get_cam_y_angle(void) 76 | { 77 | return read(0xB6F248); 78 | } 79 | 80 | uint32_t GtaSA::get_streamed_peds(uint32_t **out_ped_ptr_array) 81 | { 82 | static uint32_t peds[140]; // TODO: Read max peds number from 0xB74498 83 | *out_ped_ptr_array = peds; 84 | int i = 0; 85 | uint32_t v1 = _ped_pool_bytemap; 86 | for (uint32_t v2 = 0; v2 < 0x8b00; v2 += 0x100) 87 | { 88 | uint32_t handle = read(v1); 89 | handle &= 0x000000FF; 90 | v1 += 1; 91 | if (handle < 0x80) 92 | { 93 | handle += v2; 94 | peds[i++] = get_ped_by_handle(handle); 95 | } 96 | } 97 | return i; 98 | } 99 | 100 | bool GtaSA::is_player_aiming(void) 101 | { 102 | return get_onfoot_key_state(enOnfootKeys::AIM_WEAPON) & 0xFF; 103 | } 104 | 105 | mat44f GtaSA::get_view_matrix(void) 106 | { 107 | return read(0xB6FA2C); 108 | } 109 | 110 | vec3f GtaSA::get_bone_position(uint32_t ped, uint32_t bone_id, 111 | uint8_t is_dynamic) 112 | { 113 | static uint32_t result_address = alloc(sizeof(vec3f)); 114 | call_thiscall_function(0x5E4280, ped, 3, result_address, bone_id, 115 | is_dynamic); 116 | return read(result_address); 117 | } 118 | 119 | vec3f GtaSA::get_bone_position_quick(uint32_t ped, uint32_t bone_id, 120 | uint8_t is_dynamic) 121 | { 122 | return QuickFunctions::getBonePosition_sub_5E4280(ped, bone_id, is_dynamic); 123 | } 124 | 125 | vec3f GtaSA::get_screen_position(const vec3f &world_pos) 126 | { 127 | /* 0x71DA00 */ 128 | vec3f screen_pos; 129 | mat44f view = get_view_matrix(); 130 | 131 | vec2ui screen_resolution = get_screen_resolution(); 132 | 133 | screen_pos.x = (world_pos.z * view.m[2][0]) + (world_pos.y * view.m[1][0]) + 134 | (world_pos.x * view.m[0][0]) + view.m[3][0]; 135 | screen_pos.y = (world_pos.z * view.m[2][1]) + (world_pos.y * view.m[1][1]) + 136 | (world_pos.x * view.m[0][1]) + view.m[3][1]; 137 | screen_pos.z = (world_pos.z * view.m[2][2]) + (world_pos.y * view.m[1][2]) + 138 | (world_pos.x * view.m[0][2]) + view.m[3][2]; 139 | 140 | float recip = 1.0f / screen_pos.z; 141 | screen_pos.x *= (float)(recip * (int)screen_resolution.x); 142 | screen_pos.y *= (float)(recip * (int)screen_resolution.y); 143 | return screen_pos; 144 | } 145 | 146 | void GtaSA::give_weapon(uint32_t ped, enWeaponId weapon_id, uint32_t ammo, 147 | uint32_t like_unused) 148 | { 149 | uint32_t model_id = get_weapon_model_id(weapon_id); 150 | /* Load model */ 151 | call_cdecl_function(0x4087E0, 2, model_id, 2); 152 | /* Give weapon */ 153 | call_thiscall_function(0x5E6080, ped, 3, weapon_id, ammo, like_unused); 154 | /* Rrelease model */ 155 | // call_cdecl_function(0x409C10, 1, model_id); 156 | } 157 | 158 | void GtaSA::show_dialog_message(const char *text, uint32_t time) 159 | { 160 | /* Perhaps these manipulations are not necessary, since the page size is 161 | * enough for any text that can be displayed using the 0x69F1E0 function. */ 162 | static uint32_t size = strlen(text); 163 | static uint32_t buf = alloc(size); 164 | if (size < strlen(text)) 165 | { 166 | size = strlen(text); 167 | free(buf); 168 | buf = alloc(size); 169 | } 170 | write_buf(buf, size, text); 171 | call_cdecl_function(0x69F1E0, 4, (uint32_t)buf, time, 1, 1); 172 | } 173 | 174 | void GtaSA::unlock_fps(bool state) 175 | { 176 | set_virtual_protect(0x53E94C, 1, enVirtualProtect::READ_WRITE_EXECUTE); 177 | write(0x53E94C, state ? 0x00 : 0x0E); 178 | restore_virtual_protect(0x53E94C); 179 | } 180 | 181 | void GtaSA::auto_sprint_bug(bool state) 182 | { 183 | set_virtual_protect(0x60A68E, 1, enVirtualProtect::READ_WRITE_EXECUTE); 184 | write(0x60A68E, state ? 0x0E : 0x5E); 185 | restore_virtual_protect(0x60A68E); 186 | } 187 | 188 | uint16_t GtaSA::get_onfoot_key_state(enOnfootKeys id) 189 | { 190 | return read(0xB73458 + id * 2); 191 | } 192 | 193 | void GtaSA::set_onfoot_key_state(enOnfootKeys id, uint16_t state) 194 | { 195 | write(0xB73458 + id * 2, state); 196 | } 197 | 198 | void GtaSA::request_model(int32_t id, uint32_t flags) 199 | { 200 | call_cdecl_function(0x4087E0, 2, id, flags); 201 | } 202 | 203 | void GtaSA::set_model_deletable(int32_t id) 204 | { 205 | call_cdecl_function(0x409C10, 1, id); 206 | } 207 | 208 | uint32_t GtaSA::get_weapon_model_id(enWeaponId id) 209 | { 210 | uint32_t result = 0; 211 | if (id >= 0 && id <= 9) 212 | { 213 | result += 330; 214 | } 215 | else if (id > 9 && id <= 15) 216 | { 217 | result += 311; 218 | } 219 | else if (id > 15 && id <= 29) 220 | { 221 | result += 326; 222 | } 223 | else if (id > 29 && id <= 31) 224 | { 225 | result += 325; 226 | } 227 | else if (id == 32) 228 | { 229 | result += 350; 230 | } 231 | else if (id > 32 && id <= 45) 232 | { 233 | result += 324; 234 | } 235 | else if (id > 45 && id == 46) 236 | { 237 | result += 325; 238 | } 239 | return result + id; 240 | } 241 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/game/gta_sa.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file gta_sa.hpp 3 | * 4 | * @brief Declaration of the GtaSA class. This class is inherited from the 5 | * ExternalProcess class and provides functionality to interact with the 6 | * game process. 7 | * 8 | */ 9 | #ifndef GTA_SA_HPP 10 | #define GTA_SA_HPP 11 | 12 | #include "../../../../src/external_process.hpp" 13 | #include "../utils.hpp" 14 | #include "types.hpp" 15 | 16 | using namespace P1ExternalProcess; 17 | 18 | class GtaSA : public ExternalProcess 19 | { 20 | public: 21 | GtaSA(void); 22 | uint32_t get_player_ped(void); 23 | vec3f get_ped_pos(uint32_t ped); 24 | float get_ped_health(uint32_t ped); 25 | uint8_t get_ped_armed_weapon(uint32_t ped); 26 | uint32_t get_ped_by_handle(uint32_t handle); 27 | vec2ui get_screen_resolution(void); 28 | vec2f get_crosshair_position(void); 29 | float get_aspect_ratio(void); 30 | float get_cam_x_angle(void); 31 | float get_cam_y_angle(void); 32 | uint32_t get_streamed_peds(uint32_t **out_ped_ptr_array); 33 | bool is_player_aiming(void); 34 | mat44f get_view_matrix(void); 35 | vec3f get_bone_position(uint32_t ped, uint32_t bone_id, uint8_t is_dynamic); 36 | vec3f get_bone_position_quick(uint32_t ped, uint32_t bone_id, 37 | uint8_t is_dynamic); 38 | vec3f get_screen_position(const vec3f &world_pos); 39 | void give_weapon(uint32_t ped, enWeaponId weapon_id, uint32_t ammo, 40 | uint32_t like_unused); 41 | void show_dialog_message(const char *text, uint32_t time); 42 | void unlock_fps(bool state); 43 | void auto_sprint_bug(bool state); 44 | 45 | private: 46 | uint16_t get_onfoot_key_state(enOnfootKeys id); 47 | void set_onfoot_key_state(enOnfootKeys id, uint16_t state); 48 | void request_model(int32_t id, uint32_t flags); 49 | void set_model_deletable(int32_t id); 50 | uint32_t get_weapon_model_id(enWeaponId id); 51 | 52 | uint32_t _ped_pool; 53 | uint32_t _ped_pool_objects; 54 | uint32_t _ped_pool_bytemap; 55 | }; 56 | 57 | #endif /* GTA_SA_HPP */ 58 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/game/quick_functions.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file quick_functions.cpp 3 | * 4 | */ 5 | #include "quick_functions.hpp" 6 | #include "gta_sa.hpp" 7 | 8 | extern GtaSA gta; 9 | 10 | static vec3f *operator_mul_sub_59C890(vec3f *a1, float *a2, float *a3); 11 | static uint32_t RpSkinAtomicGetHAnimHierarchy_sub_7C7540(uint32_t a1); 12 | static uint32_t skinAtomicGetHAnimHierarchCB_sub_734A20(uint32_t a1, 13 | uint32_t a2); 14 | static uint32_t RpClumpForAllAtomics_sub_749B70(uint32_t a1, uint32_t callback, 15 | uint32_t a3); 16 | static uint32_t GetAnimHierarchyFromSkinClump_sub_734A40(uint32_t a1); 17 | static int32_t RpHAnimIDGetIndex_sub_7C51A0(uint32_t a1, uint32_t bone_id); 18 | static uint32_t RpHAnimHierarchyGetMatrixArray_sub_7C5120(uint32_t a1); 19 | 20 | vec3f QuickFunctions::getBonePosition_sub_5E4280(uint32_t ped_addr, 21 | uint32_t bone_id, 22 | uint8_t is_dynamic) 23 | { 24 | 25 | is_dynamic = false; 26 | // TODO: This function has unreversed block (0x532B20 function call). So, 27 | // the whole idea to speed up the time of obtaining the coordinates of 28 | // the player's bones by getting rid of callers will lose its meaning 29 | // if the is_dynamic argument is true. 30 | 31 | uint32_t v5_ped_flags; // eax 32 | int AnimHierarchyFromSkinClump_sub_734A40; // eax 33 | int v8; // eax 34 | int v14; // esi 35 | vec3f *v15; // eax 36 | vec3f result; 37 | v5_ped_flags = gta.read(ped_addr + 0x474); 38 | 39 | if (is_dynamic) 40 | { 41 | if ((v5_ped_flags & 0x400) == 0) 42 | { 43 | gta.call_thiscall_function(0x532B20, ped_addr, 0, 44 | 0); // sub_532B20(); 45 | gta.write((uint32_t)ped_addr + 285, 46 | gta.read(ped_addr + 285) | 47 | 0x400u); // ped_addr[285] |= 0x400u; 48 | } 49 | } 50 | else if ((v5_ped_flags & 0x400) == 0) 51 | { 52 | uint32_t tmp = gta.read(ped_addr + 0x14); 53 | uint8_t matrix[0x40]; 54 | vec3f point = gta.read(12 * bone_id + 0x8D13A8); 55 | gta.read_buf(tmp, sizeof(matrix), matrix); 56 | operator_mul_sub_59C890(&result, (float *)matrix, (float *)&point); 57 | return result; 58 | } 59 | uint32_t tmp = gta.read(ped_addr + 0x18); 60 | AnimHierarchyFromSkinClump_sub_734A40 = 61 | GetAnimHierarchyFromSkinClump_sub_734A40(tmp); 62 | // AnimHierarchyFromSkinClump_sub_734A40 = 63 | // gta.call_cdecl_function(0x734A40, 1, tmp); 64 | // //GetAnimHierarchyFromSkinClump_sub_734A40(ped_addr[6]); 65 | 66 | if (AnimHierarchyFromSkinClump_sub_734A40) 67 | { 68 | v14 = RpHAnimIDGetIndex_sub_7C51A0( 69 | AnimHierarchyFromSkinClump_sub_734A40, bone_id); 70 | // v14 = gta.call_cdecl_function(0x7C51A0, 2, 71 | // AnimHierarchyFromSkinClump_sub_734A40, bone_id); 72 | v15 = (vec3f *)((v14 << 6) + 73 | RpHAnimHierarchyGetMatrixArray_sub_7C5120( 74 | AnimHierarchyFromSkinClump_sub_734A40) + 75 | 48); 76 | result = gta.read((uint32_t)v15); 77 | } 78 | else 79 | { 80 | v8 = gta.read(ped_addr + 0x14); 81 | if (v8) 82 | { 83 | result = gta.read(v8 + 48); 84 | } 85 | else 86 | { 87 | result = gta.read(ped_addr + 4); 88 | } 89 | } 90 | return result; 91 | } 92 | 93 | static vec3f *operator_mul_sub_59C890(vec3f *a1, float *a2, float *a3) 94 | { 95 | a1->x = a2[8] * a3[2] + a2[4] * a3[1] + *a2 * *a3 + a2[12]; 96 | a1->y = a2[9] * a3[2] + a2[1] * *a3 + a2[5] * a3[1] + a2[13]; 97 | a1->z = a2[10] * a3[2] + a2[2] * *a3 + a2[6] * a3[1] + a2[14]; 98 | return a1; 99 | } 100 | 101 | static uint32_t RpSkinAtomicGetHAnimHierarchy_sub_7C7540(uint32_t a1) 102 | { 103 | uint32_t result = gta.read(0xC978A4); 104 | result = gta.read(result + a1); 105 | return result; 106 | } 107 | 108 | static uint32_t skinAtomicGetHAnimHierarchCB_sub_734A20(uint32_t a1, 109 | uint32_t a2) 110 | { 111 | *(uint32_t *)a2 = 112 | RpSkinAtomicGetHAnimHierarchy_sub_7C7540(a1); // TODO: Check! 113 | return 0; 114 | } 115 | 116 | static uint32_t RpClumpForAllAtomics_sub_749B70(uint32_t a1, uint32_t callback, 117 | uint32_t a3) 118 | { 119 | uint32_t v3; // eax 120 | uint32_t v4; // esi 121 | 122 | v3 = gta.read(a1 + 8); 123 | // v3 = *(uint32_t**)(a1 + 8); 124 | if (v3 != (a1 + 8)) 125 | { 126 | do 127 | { 128 | // v4 = (uint32_t*)*v3; 129 | v4 = gta.read(v3); 130 | uint32_t (*cb)(uint32_t, uint32_t) = 131 | (uint32_t(*)(uint32_t, uint32_t))callback; 132 | if (!cb(v3 - 16 * 4, a3)) 133 | break; 134 | v3 = v4; 135 | } while (v4 != (a1 + 8)); 136 | } 137 | return a1; 138 | return 0; 139 | } 140 | 141 | static uint32_t GetAnimHierarchyFromSkinClump_sub_734A40(uint32_t a1) 142 | { 143 | // return gta.call_cdecl_function(0x734A40, 1, a1); 144 | int v2 = 0; // [esp+0h] [ebp-4h] BYREF 145 | RpClumpForAllAtomics_sub_749B70( 146 | a1, (uint32_t)skinAtomicGetHAnimHierarchCB_sub_734A20, (uint32_t)&v2); 147 | return v2; 148 | } 149 | 150 | static int32_t RpHAnimIDGetIndex_sub_7C51A0(uint32_t a1, uint32_t bone_id) 151 | { 152 | int result; // eax 153 | uint32_t v3; // esi 154 | uint32_t v4; // edx 155 | uint32_t v5; // ecx 156 | 157 | result = -1; 158 | // v3 = *(uint32_t**)(a1 + 16); 159 | v3 = gta.read(a1 + 16); 160 | // v4 = *(uint32_t*)(a1 + 4); 161 | v4 = gta.read(a1 + 4); 162 | v5 = 0; 163 | if (v4 > 0) 164 | { 165 | while (bone_id != gta.read(v3)) 166 | { 167 | ++v5; 168 | v3 += 16; 169 | if (v5 >= v4) 170 | return result; 171 | } 172 | return v5; 173 | } 174 | return result; 175 | } 176 | 177 | static uint32_t RpHAnimHierarchyGetMatrixArray_sub_7C5120(uint32_t a1) 178 | { 179 | return gta.read(a1 + 8); 180 | } 181 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/game/quick_functions.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file quick_functions.hpp 3 | * 4 | */ 5 | #ifndef QUICK_FUNCTIONS_HPP 6 | #define QUICK_FUNCTIONS_HPP 7 | 8 | #include 9 | #include "utils.hpp" 10 | 11 | namespace QuickFunctions 12 | { 13 | 14 | vec3f getBonePosition_sub_5E4280(uint32_t ped_addr, uint32_t bone_id, 15 | uint8_t is_dynamic); 16 | 17 | } 18 | 19 | #endif /* QUICK_FUNCTIONS_HPP */ 20 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/game/types.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file types.hpp 3 | * 4 | */ 5 | #ifndef TYPES_HPP 6 | #define TYPES_HPP 7 | 8 | #include 9 | 10 | enum enOnfootKeys : uint8_t 11 | { 12 | LEFT_RIGHT = 0, 13 | FORWARD_BACKWARD, 14 | /* ... */ 15 | AIM_WEAPON = 6, 16 | /* ... */ 17 | JUMP = 14, 18 | /* ... */ 19 | SPRINT = 16, 20 | FIRE, 21 | WALK = 21 22 | }; 23 | 24 | enum enWeaponId 25 | { 26 | WEAPON_UNARMED = 0, 27 | WEAPON_BRASSKNUCKLE = 1, 28 | WEAPON_GOLFCLUB = 2, 29 | WEAPON_NIGHTSTICK = 3, 30 | WEAPON_KNIFE = 4, 31 | WEAPON_BASEBALLBAT = 5, 32 | WEAPON_SHOVEL = 6, 33 | WEAPON_POOLCUE = 7, 34 | WEAPON_KATANA = 8, 35 | WEAPON_CHAINSAW = 9, 36 | WEAPON_DILDO1 = 10, 37 | WEAPON_DILDO2 = 11, 38 | WEAPON_VIBE1 = 12, 39 | WEAPON_VIBE2 = 13, 40 | WEAPON_FLOWERS = 14, 41 | WEAPON_CANE = 15, 42 | WEAPON_GRENADE = 16, 43 | WEAPON_TEARGAS = 17, 44 | WEAPON_MOLOTOV = 18, 45 | WEAPON_ROCKET = 19, 46 | WEAPON_ROCKET_HS = 20, 47 | WEAPON_FREEFALL_BOMB = 21, 48 | WEAPON_PISTOL = 22, 49 | WEAPON_PISTOL_SILENCED = 23, 50 | WEAPON_DESERT_EAGLE = 24, 51 | WEAPON_SHOTGUN = 25, 52 | WEAPON_SAWNOFF = 26, 53 | WEAPON_SPAS12 = 27, 54 | WEAPON_MICRO_UZI = 28, 55 | WEAPON_MP5 = 29, 56 | WEAPON_AK47 = 30, 57 | WEAPON_M4 = 31, 58 | WEAPON_TEC9 = 32, 59 | WEAPON_COUNTRYRIFLE = 33, 60 | WEAPON_SNIPERRIFLE = 34, 61 | WEAPON_RLAUNCHER = 35, 62 | WEAPON_RLAUNCHER_HS = 36, 63 | WEAPON_FTHROWER = 37, 64 | WEAPON_MINIGUN = 38, 65 | WEAPON_SATCHEL_CHARGE = 39, 66 | WEAPON_DETONATOR = 40, 67 | WEAPON_SPRAYCAN = 41, 68 | WEAPON_EXTINGUISHER = 42, 69 | WEAPON_CAMERA = 43, 70 | WEAPON_NIGHTVISION = 44, 71 | WEAPON_INFRARED = 45, 72 | WEAPON_PARACHUTE = 46, 73 | WEAPON_UNUSED = 47, 74 | WEAPON_ARMOUR = 48, 75 | WEAPON_FLARE = 58 76 | }; 77 | 78 | #endif /* TYPES_HPP */ 79 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file main.cpp 3 | * 4 | * @brief This file contains the source code for a basic GTA SA game trainer, 5 | * demonstrating the capabilities of the ExternalProcess class using a 6 | * practical example with room for improvement. 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | 13 | #include "game/gta_sa.hpp" 14 | #include "../utils.hpp" 15 | 16 | GtaSA gta; 17 | 18 | void get_bone_position_benchmark(void) 19 | { 20 | uint32_t *streamed_peds = nullptr; 21 | uint32_t peds_number = gta.get_streamed_peds(&streamed_peds); 22 | if (peds_number == 0) 23 | { 24 | return; 25 | } 26 | 27 | auto start = std::chrono::steady_clock::now(); 28 | for (volatile uint32_t i = 0; i < 1000; i++) 29 | { 30 | for (uint32_t j = 0; j < peds_number; j++) 31 | { 32 | volatile vec3f bone_pos = 33 | gta.get_bone_position(streamed_peds[j], 6, 0); 34 | (void)bone_pos; 35 | } 36 | } 37 | auto end = std::chrono::steady_clock::now(); 38 | std::cout << 1000 * peds_number 39 | << " calls of get_bone_position (1000 time for " << peds_number 40 | << " peds): " 41 | << std::chrono::duration_cast(end - 42 | start) 43 | .count() 44 | << " microseconds." << std::endl; 45 | 46 | start = std::chrono::steady_clock::now(); 47 | for (volatile uint32_t i = 0; i < 1000; i++) 48 | { 49 | for (uint32_t j = 0; j < peds_number; j++) 50 | { 51 | volatile vec3f bone_pos = 52 | gta.get_bone_position_quick(streamed_peds[j], 6, 0); 53 | (void)bone_pos; 54 | } 55 | } 56 | end = std::chrono::steady_clock::now(); 57 | std::cout << 1000 * peds_number 58 | << " calls of get_bone_position_quick (1000 time for " 59 | << peds_number << " peds): " 60 | << std::chrono::duration_cast(end - 61 | start) 62 | .count() 63 | << " microseconds." << std::endl; 64 | } 65 | 66 | int main(int argc, char *argv[]) 67 | { 68 | (void)argc; 69 | (void)argv; 70 | while (true) 71 | { 72 | Sleep(10); 73 | if (GetAsyncKeyState(0x31)) 74 | { 75 | gta.auto_sprint_bug(true); 76 | gta.show_dialog_message("Auto sprint bug ~G~enabled", 1000); 77 | Sleep(500); 78 | } 79 | if (GetAsyncKeyState(0x32)) 80 | { 81 | gta.auto_sprint_bug(false); 82 | gta.show_dialog_message("Auto sprint bug ~R~disabled", 1000); 83 | Sleep(500); 84 | } 85 | if (GetAsyncKeyState(0x33)) 86 | { 87 | uint32_t player_ped = gta.get_player_ped(); 88 | vec3f pos = gta.get_ped_pos(player_ped); 89 | vec3f bone_pos_1 = gta.get_bone_position(player_ped, 6, 0); 90 | vec3f bone_pos_2 = gta.get_bone_position_quick(player_ped, 6, 0); 91 | 92 | std::cout << "pos: " << pos.x << " " << pos.y << " " << pos.z 93 | << std::endl; 94 | std::cout << "bone_pos_1: " << bone_pos_1.x << " " << bone_pos_1.y 95 | << " " << bone_pos_1.z << std::endl; 96 | std::cout << "bone_pos_2: " << bone_pos_2.x << " " << bone_pos_2.y 97 | << " " << bone_pos_2.z << std::endl; 98 | 99 | std::cout << std::endl; 100 | Sleep(500); 101 | } 102 | if (GetAsyncKeyState(0x34)) 103 | { 104 | get_bone_position_benchmark(); 105 | Sleep(500); 106 | } 107 | if (GetAsyncKeyState(0x35)) 108 | { 109 | uint32_t player = gta.get_player_ped(); 110 | gta.give_weapon(player, enWeaponId::WEAPON_MINIGUN, 1837, 0); 111 | Sleep(500); 112 | } 113 | if (GetAsyncKeyState(VK_F6)) 114 | { 115 | uint8_t b = 0xC3; 116 | gta.patch(0x6FF410, &b, 1); 117 | float FOV = gta.read(0x8D5038); 118 | FOV -= 0.5f; 119 | if ((int)FOV % 180 == 0) 120 | { 121 | FOV -= 0.5f; 122 | } 123 | gta.write(0x8D5038, FOV); 124 | char msg[24]; 125 | std::snprintf(msg, sizeof(msg), "FOV: ~R~%.1f", FOV); 126 | gta.show_dialog_message(msg, 1000); 127 | } 128 | if (GetAsyncKeyState(VK_F7)) 129 | { 130 | uint8_t b = 0xC3; 131 | gta.patch(0x6FF410, &b, 1); 132 | float FOV = gta.read(0x8D5038); 133 | FOV += 0.5f; 134 | if ((int)FOV % 180 == 0) 135 | { 136 | FOV += 0.5f; 137 | } 138 | gta.write(0x8D5038, FOV); 139 | char msg[24]; 140 | std::snprintf(msg, sizeof(msg), "FOV: ~G~%.1f", FOV); 141 | gta.show_dialog_message(msg, 1000); 142 | } 143 | } 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/utils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_utils.cpp 3 | * 4 | */ 5 | #include "utils.hpp" 6 | #include 7 | 8 | float calculate_distance_2d(const vec2f &a, const vec2f &b) 9 | { 10 | return sqrtf(powf(a.x - b.x, 2.0) + powf(a.y - b.y, 2.0f)); 11 | } 12 | 13 | float calculate_distance_3d(const vec3f &a, const vec3f &b) 14 | { 15 | return sqrtf(powf(a.x - b.x, 2.0) + powf(a.y - b.y, 2.0f) + 16 | powf(a.z - b.z, 2.0f)); 17 | } 18 | -------------------------------------------------------------------------------- /examples/gta_sa_trainer/src/utils.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_utils.cpp 3 | * 4 | */ 5 | #ifndef UTILS_HPP 6 | #define UTILS_HPP 7 | 8 | #include 9 | 10 | struct vec2ui 11 | { 12 | uint32_t x; 13 | uint32_t y; 14 | }; 15 | 16 | struct vec2f 17 | { 18 | float x; 19 | float y; 20 | }; 21 | 22 | struct vec3f 23 | { 24 | float x; 25 | float y; 26 | float z; 27 | }; 28 | 29 | struct mat44f 30 | { 31 | float m[4][4]; 32 | }; 33 | 34 | float calculate_distance_2d(const vec2f& a, const vec2f& b); 35 | float calculate_distance_3d(const vec3f& a, const vec3f& b); 36 | 37 | #endif /* UTILS_HPP */ 38 | -------------------------------------------------------------------------------- /src/core/ep_core.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ep_core.c 3 | * @brief Implementation of the ep_core component. 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include "ep_core.h" 9 | 10 | #define LOCAL_CALLER_BUF_SIZE 128 11 | 12 | typedef struct EPCProcess 13 | { 14 | HANDLE handle; 15 | uint32_t process_id; 16 | } EPCProcess; 17 | 18 | typedef struct EpCoreFunctionCaller 19 | { 20 | const EPCProcess *process; 21 | uintptr_t function_address; 22 | EPCCallConvention call_convention; 23 | size_t argc; 24 | uintptr_t caller_address; 25 | uintptr_t remote_process_args_ptr; 26 | } EpCoreFunctionCaller; 27 | 28 | typedef NTSTATUS(NTAPI *NtReadVirtualMemory_t)( 29 | IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID buffer, 30 | IN ULONG NumberOfBytesRead, OUT PULONG NumberOfBytesReaded OPTIONAL); 31 | 32 | typedef NTSTATUS(NTAPI *NtWriteVirtualMemory_t)( 33 | IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID buffer, 34 | IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL); 35 | 36 | typedef NTSTATUS(NTAPI *NtAllocateVirtualMemory_t)( 37 | IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG_PTR ZeroBits, 38 | IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect); 39 | 40 | typedef NTSTATUS(NTAPI *NtFreeVirtualMemory_t)(IN HANDLE ProcessHandle, 41 | IN OUT PVOID *BaseAddress, 42 | IN OUT PSIZE_T RegionSize, 43 | IN ULONG FreeType); 44 | 45 | typedef NTSTATUS(NTAPI *NtProtectVirtualMemory_t)( 46 | IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, 47 | IN OUT PULONG NumberOfBytesToProtect, IN ULONG NewAccessProtection, 48 | OUT PULONG OldAccessProtection); 49 | 50 | /** 51 | * @brief Converts a Windows memory protection type values to the component's 52 | * memory protection type values. 53 | * @param[in] protect The Windows memory protection type value. 54 | * @return The component's memory protection type value. 55 | */ 56 | static EPCVirtualProtect convert_win_protect_to_ep_protect(DWORD protect); 57 | 58 | /** 59 | * @brief Converts the component's memory protection type values to Windows 60 | * memory protection type values. 61 | * @param[in] protect The component's memory protection type value. 62 | * @return The Windows memory protection type value. 63 | */ 64 | static DWORD convert_ep_protect_to_win_protect(EPCVirtualProtect protect); 65 | 66 | /** 67 | * @brief Allocates and initializes a buffer in a remote process for calling 68 | * functions using 'cdecl' call convention. 69 | * 70 | * Creates a buffer with execution rights in the external process. Writes to 71 | * this buffer a set of i686 instructions that: 72 | * - push arguments in the amount of @argc onto the stack 73 | * - call function at @address using 'cdecl' call convention 74 | * - restore stack 75 | * - return 76 | * 77 | * cdecl-function caller structure: 78 | * BYTES INSTRUCTION SIZE COMMENT 79 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the last arg. value 80 | * ... 81 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 2-nd arg. value 82 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 1-st arg. value 83 | * E8 XXXXXXXX call XXXXXXXX 5 XXXXXXXX = function address 84 | * 83 C4 XX add esp, XX 3 Restore stack. XX = 4 * @argc 85 | * C3 ret 1 Return (terminate thread) 86 | * 87 | * @param[in] process The process for which the caller should be created. 88 | * @param[in] c The function caller object. It's fields must be initialized 89 | * before calling this function. 90 | * @return The address of the caller buffer in the remote process memory. 91 | * @return 0 if the caller buffer could not be built. 92 | */ 93 | static uintptr_t build_cdecl_caller(const EPCProcess *process, 94 | EpCoreFunctionCaller *c); 95 | 96 | /** 97 | * @brief Allocates and initializes a buffer in a remote process for calling 98 | * functions using 'stdcall' call convention. 99 | * 100 | * Creates a buffer with execution rights in the external process. Writes to 101 | * this buffer a set of i686 instructions that: 102 | * - push arguments in the amount of @argc onto the stack 103 | * - call function at @address using 'stdcall' call convention 104 | * - return 105 | * 106 | * stdcall-function caller structure: 107 | * BYTES INSTRUCTION SIZE COMMENT 108 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the last arg. value 109 | * ... 110 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 2-nd arg. value 111 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 1-st arg. value 112 | * E8 XXXXXXXX call XXXXXXXX 5 XXXXXXXX = function address 113 | * C3 ret 1 Return (terminate the thread) 114 | * 115 | * @param[in] process The process for which the caller should be created. 116 | * @param c The function caller object. It's fields must be initialized before 117 | * calling this function. 118 | * @return The address of the caller buffer in the remote process memory. 119 | * @return 0 if the caller buffer could not be built. 120 | */ 121 | static uintptr_t build_stdcall_caller(const EPCProcess *process, 122 | EpCoreFunctionCaller *c); 123 | 124 | /** 125 | * @brief Allocates and initializes a buffer in a remote process for calling 126 | * functions using MSVC 'thiscall' call convention. 127 | * 128 | * Creates a buffer with execution rights in the external process. Writes 129 | * to this buffer a set of i686 instructions that: 130 | * - push arguments in the amount of @argc onto the stack 131 | * - put 'this' for which a method at @address is called into EAX register 132 | * - call class method at @address using 'stdcall' call convention 133 | * - return 134 | * 135 | * thiscall-function caller structure: 136 | * BYTES INSTRUCTION SIZE COMMENT 137 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the last arg. value 138 | * ... 139 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 2-nd arg. value 140 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 1-st arg. value 141 | * B9 XXXXXXXX mov ecx, XXXXXXXX 5 XXXXXXXX = 'this' 142 | * E8 XXXXXXXX call XXXXXXXX 5 XXXXXXXX = function address 143 | * C3 ret 1 Return (terminate the thread) 144 | * 145 | * @param[in] process The process for which the caller should be created. 146 | * @param c The function caller object. It's fields must be initialized before 147 | * calling this function. 148 | * @return The address of the caller buffer in the remote process memory. 149 | * @return 0 if the caller buffer could not be built. 150 | */ 151 | static uintptr_t build_thiscall_msvc_caller(const EPCProcess *process, 152 | EpCoreFunctionCaller *c); 153 | 154 | static EPCVirtualProtect convert_win_protect_to_ep_protect(DWORD protect) 155 | { 156 | EPCVirtualProtect result = EPCVP_EMPTY; 157 | if (protect == PAGE_READONLY) 158 | { 159 | result = EPCVP_READ; 160 | } 161 | else if (protect == PAGE_READWRITE) 162 | { 163 | result = EPCVP_READ | EPCVP_WRITE; 164 | } 165 | else if (protect == PAGE_EXECUTE_READ) 166 | { 167 | result = EPCVP_READ | EPCVP_EXECUTE; 168 | } 169 | else if (protect == PAGE_EXECUTE_READWRITE) 170 | { 171 | result = EPCVP_READ | EPCVP_WRITE | EPCVP_EXECUTE; 172 | } 173 | else 174 | { 175 | // TODO: Throw an error. 176 | } 177 | return result; 178 | } 179 | 180 | static DWORD convert_ep_protect_to_win_protect(EPCVirtualProtect protect) 181 | { 182 | DWORD result = 0; 183 | if (protect == EPCVP_READ) 184 | { 185 | result = PAGE_READONLY; 186 | } 187 | else if (protect == EPCVP_WRITE) 188 | { 189 | result = PAGE_READWRITE; 190 | } 191 | else if (protect == (EPCVP_READ | EPCVP_WRITE)) 192 | { 193 | result = PAGE_READWRITE; 194 | } 195 | else if (protect == EPCVP_EXECUTE || 196 | protect == (EPCVP_READ | EPCVP_EXECUTE)) 197 | { 198 | result = PAGE_EXECUTE_READ; 199 | } 200 | else if (protect == (EPCVP_READ | EPCVP_WRITE | EPCVP_EXECUTE) || 201 | protect == (EPCVP_WRITE | EPCVP_EXECUTE)) 202 | { 203 | result = PAGE_EXECUTE_READWRITE; 204 | } 205 | else 206 | { 207 | // TODO: Throw an error. 208 | } 209 | return result; 210 | } 211 | 212 | static uintptr_t build_cdecl_caller(const EPCProcess *process, 213 | EpCoreFunctionCaller *c) 214 | { 215 | /* local buffer to be write in remote process' memory */ 216 | uint8_t local_caller_buffer[LOCAL_CALLER_BUF_SIZE] = {0}; 217 | /* Calculate caller size */ 218 | size_t caller_size = 219 | (1 + 4) * c->argc + /* (push-instruction size + arg size) * argc */ 220 | 1 + 4 + /* call-instruction size + address size */ 221 | 3 + /* restore-stack-instructions size */ 222 | 1; /* ret instruction size */ 223 | if (caller_size > LOCAL_CALLER_BUF_SIZE) 224 | { 225 | return 0; 226 | } 227 | /* Allocate space for the caller in the remote process's address space */ 228 | uintptr_t caller_address = 0; 229 | if (!epc_alloc(process, caller_size, &caller_address)) 230 | { 231 | return 0; 232 | } 233 | c->process = process; 234 | /* Write push-args instructions in the caller. */ 235 | for (size_t i = 0; i < c->argc; i++) 236 | { 237 | local_caller_buffer[i * 5] = 0x68; /* push */ 238 | *(uintptr_t *)(local_caller_buffer + i * 5 + 1) = 0; /* argument */ 239 | } 240 | /* Write call-instruction to the caller bytes */ 241 | local_caller_buffer[c->argc * 5] = 0xE8; /* call */ 242 | /* Calculate and write an address for the near call instruction */ 243 | *(uintptr_t *)(local_caller_buffer + c->argc * 5 + 1) = 244 | c->function_address - (caller_address + c->argc * 5) - 5; 245 | /* Write restore-stack-instructions to the caller bytes */ 246 | // TOOD: Use 0xC4 ret instead. 247 | local_caller_buffer[c->argc * 5 + 5] = 0x83; /* add */ 248 | local_caller_buffer[c->argc * 5 + 6] = 0xC4; /* esp */ 249 | local_caller_buffer[c->argc * 5 + 7] = 4 * c->argc; /* args size */ 250 | /* Write ret-instruction to the caller bytes */ 251 | local_caller_buffer[c->argc * 5 + 8] = 0xC3; /* ret */ 252 | /* Write caller bytes to the remote process's memory */ 253 | if (!epc_write_buf(c->process, caller_address, caller_size, 254 | local_caller_buffer)) 255 | { 256 | return 0; 257 | } 258 | return caller_address; 259 | } 260 | 261 | static uintptr_t build_stdcall_caller(const EPCProcess *process, 262 | EpCoreFunctionCaller *c) 263 | { 264 | /* local buffer to be write in remote process' memory */ 265 | uint8_t local_caller_buffer[LOCAL_CALLER_BUF_SIZE] = {0}; 266 | /* Calculate caller size */ 267 | size_t caller_size = 268 | (1 + 4) * c->argc + /* (push-instruction size + argument size) * argc */ 269 | 1 + 4 + /* call-instruction size + address size */ 270 | 1; /* ret-instruction size */ 271 | if (caller_size > LOCAL_CALLER_BUF_SIZE) 272 | { 273 | return 0; 274 | } 275 | /* Allocate space for the caller in the remote process's address space */ 276 | uintptr_t caller_address = 0; 277 | if (!epc_alloc(process, caller_size, &caller_address)) 278 | { 279 | return 0; 280 | } 281 | c->process = process; 282 | /* Write push-args instructions in the caller. */ 283 | for (size_t i = 0; i < c->argc; i++) 284 | { 285 | local_caller_buffer[i * 5] = 0x68; /* push */ 286 | *(uintptr_t *)(local_caller_buffer + i * 5 + 1) = 0; /* argument */ 287 | } 288 | /* Write call-instruction to the caller bytes */ 289 | local_caller_buffer[c->argc * 5] = 0xE8; /* call */ 290 | /* Calculate and write an address for the near call instruction */ 291 | *(uintptr_t *)(local_caller_buffer + c->argc * 5 + 1) = 292 | c->function_address - (caller_address + c->argc * 5) - 5; 293 | /* Write ret-instruction to the caller bytes */ 294 | local_caller_buffer[c->argc * 5 + 5] = 0xC3; /* ret */ 295 | /* Write caller bytes to the remote process's memory */ 296 | if (!epc_write_buf(c->process, caller_address, caller_size, 297 | local_caller_buffer)) 298 | { 299 | return 0; 300 | } 301 | return caller_address; 302 | } 303 | 304 | static uintptr_t build_thiscall_msvc_caller(const EPCProcess *process, 305 | EpCoreFunctionCaller *c) 306 | { 307 | /* local buffer to be write in remote process' memory */ 308 | uint8_t local_caller_buffer[LOCAL_CALLER_BUF_SIZE] = {0}; 309 | /* Calculate caller size */ 310 | size_t caller_size = 311 | (1 + 4) * c->argc + /* (push-instruction size + argument size) * argc */ 312 | 5 + /* mov _this to ecx */ 313 | 1 + 4 + /* call-instruction size + address size */ 314 | 1; /* ret-instruction size */ 315 | if (caller_size > LOCAL_CALLER_BUF_SIZE) 316 | { 317 | return 0; 318 | } 319 | /* Allocate space for the caller in the remote process's address space */ 320 | uintptr_t caller_address = 0; 321 | if (!epc_alloc(process, caller_size, &caller_address)) 322 | { 323 | return 0; 324 | } 325 | c->process = process; 326 | /* Write push-args instructions in the caller. */ 327 | for (size_t i = 0; i < c->argc; i++) 328 | { 329 | local_caller_buffer[i * 5] = 0x68; /* push */ 330 | *(uintptr_t *)(local_caller_buffer + i * 5 + 1) = 0; /* argument */ 331 | } 332 | /* Write [mov ecx, _this] to the caller bytes */ 333 | local_caller_buffer[c->argc * 5] = 0xB9; /* mov ecx, */ 334 | *(uintptr_t *)(local_caller_buffer + c->argc * 5 + 1) = 0; 335 | 336 | /* Write call-instruction to the caller bytes */ 337 | local_caller_buffer[c->argc * 5 + 5] = 0xE8; /* call */ 338 | /* Calculate and write an address for the near call instruction */ 339 | *(uintptr_t *)(local_caller_buffer + c->argc * 5 + 5 + 1) = 340 | c->function_address - (caller_address + c->argc * 5 + 5) - 5; 341 | /* Write ret-instruction to the caller bytes */ 342 | local_caller_buffer[c->argc * 5 + 5 + 5] = 0xC3; /* ret */ 343 | /* Write caller bytes to the remote process's memory */ 344 | if (!epc_write_buf(c->process, caller_address, caller_size, 345 | local_caller_buffer)) 346 | { 347 | return 0; 348 | } 349 | return caller_address; 350 | } 351 | 352 | bool epc_create(EPCProcess **out_process) 353 | { 354 | if (!out_process) 355 | { 356 | return false; 357 | } 358 | *out_process = (EPCProcess *)malloc(sizeof(**out_process)); 359 | if (!*out_process) 360 | { 361 | return false; 362 | } 363 | (*out_process)->handle = NULL; 364 | (*out_process)->process_id = 0; 365 | return true; 366 | } 367 | 368 | bool epc_destroy(EPCProcess *process) 369 | { 370 | if (!process) 371 | { 372 | return false; 373 | } 374 | if (process->handle) 375 | { 376 | epc_close_process(process); 377 | } 378 | free(process); 379 | return true; 380 | } 381 | 382 | bool epc_open_process(EPCProcess *process, unsigned int process_id) 383 | { 384 | if (!process) 385 | { 386 | return false; 387 | } 388 | process->handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id); 389 | if (!process->handle) 390 | { 391 | return false; 392 | } 393 | process->process_id = process_id; 394 | return true; 395 | } 396 | 397 | bool epc_close_process(EPCProcess *process) 398 | { 399 | if (!process) 400 | { 401 | return false; 402 | } 403 | if (!CloseHandle(process->handle)) 404 | { 405 | return false; 406 | } 407 | process->handle = NULL; 408 | return true; 409 | } 410 | 411 | bool epc_get_process_id_by_name(const char *process_name, 412 | unsigned int *out_process_id) 413 | { 414 | if (!process_name || !out_process_id) 415 | { 416 | return false; 417 | } 418 | HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 419 | if (snapshot_handle == INVALID_HANDLE_VALUE) 420 | { 421 | return false; 422 | } 423 | PROCESSENTRY32 process_entry; 424 | process_entry.dwSize = sizeof(process_entry); 425 | if (Process32First(snapshot_handle, &process_entry)) 426 | { 427 | do 428 | { 429 | if (!_strcmpi(process_entry.szExeFile, process_name)) 430 | { 431 | *out_process_id = process_entry.th32ProcessID; 432 | if (!CloseHandle(snapshot_handle)) 433 | { 434 | return false; 435 | } 436 | return true; 437 | } 438 | } while (Process32Next(snapshot_handle, &process_entry)); 439 | } 440 | CloseHandle(snapshot_handle); 441 | return false; 442 | } 443 | 444 | bool epc_read_buf(const EPCProcess *process, uintptr_t address, size_t size, 445 | void *out_result) 446 | { 447 | if (!process || !out_result) 448 | { 449 | return false; 450 | } 451 | return ReadProcessMemory(process->handle, (LPCVOID)address, out_result, 452 | size, 0) != 0; 453 | } 454 | 455 | bool epc_read_buf_nt(const EPCProcess *process, uintptr_t address, size_t size, 456 | void *out_result) 457 | { 458 | NtReadVirtualMemory_t NtReadVirtualMemory = 459 | (NtReadVirtualMemory_t)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 460 | "NtReadVirtualMemory"); 461 | return NtReadVirtualMemory(process->handle, (PVOID)address, out_result, 462 | size, 0) == 0; 463 | } 464 | 465 | bool epc_write_buf(const EPCProcess *process, uintptr_t address, size_t size, 466 | const void *data) 467 | { 468 | if (!process || !data) 469 | { 470 | return false; 471 | } 472 | return WriteProcessMemory(process->handle, (LPVOID)address, data, size, 473 | 0) != 0; 474 | } 475 | bool epc_write_buf_nt(const EPCProcess *process, uintptr_t address, size_t size, 476 | const void *data) 477 | { 478 | NtWriteVirtualMemory_t NtWriteVirtualMemory = 479 | (NtWriteVirtualMemory_t)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 480 | "NtWriteVirtualMemory"); 481 | return NtWriteVirtualMemory(process->handle, (PVOID)address, (PVOID)data, 482 | size, 0) == 0; 483 | } 484 | 485 | bool epc_alloc(const EPCProcess *process, size_t size, uintptr_t *out_address) 486 | { 487 | if (!process || !out_address) 488 | { 489 | return false; 490 | } 491 | *out_address = (uintptr_t)VirtualAllocEx(process->handle, NULL, size, 492 | MEM_COMMIT | MEM_RESERVE, 493 | PAGE_EXECUTE_READWRITE); 494 | return *out_address != 0; 495 | } 496 | bool epc_alloc_nt(const EPCProcess *process, size_t size, 497 | uintptr_t *out_address) 498 | { 499 | NtAllocateVirtualMemory_t NtAllocateVirtualMemory = 500 | (NtAllocateVirtualMemory_t)(void *)GetProcAddress( 501 | LoadLibrary("ntdll.dll"), "NtAllocateVirtualMemory"); 502 | return NtAllocateVirtualMemory(process->handle, (PVOID *)out_address, 0, 503 | (PSIZE_T)&size, MEM_COMMIT | MEM_RESERVE, 504 | PAGE_EXECUTE_READWRITE) == 0; 505 | } 506 | bool epc_free(const EPCProcess *process, uintptr_t address) 507 | { 508 | if (!process) 509 | { 510 | return false; 511 | } 512 | return VirtualFreeEx(process->handle, (LPVOID)address, 0, MEM_RELEASE) != 0; 513 | } 514 | bool epc_free_nt(const EPCProcess *process, uintptr_t address) 515 | { 516 | NtFreeVirtualMemory_t NtFreeVirtualMemory = 517 | (NtFreeVirtualMemory_t)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 518 | "NtFreeVirtualMemory"); 519 | return NtFreeVirtualMemory(process->handle, (PVOID *)&address, 0, 520 | MEM_RELEASE) == 0; 521 | } 522 | 523 | bool epc_virtual_protect(const EPCProcess *process, uintptr_t address, 524 | size_t size, EPCVirtualProtect protect, 525 | EPCVirtualProtect *out_old_protect) 526 | { 527 | if (!process) 528 | { 529 | return false; 530 | } 531 | if (protect == EPCVP_EMPTY) 532 | { 533 | return false; 534 | } 535 | if (!out_old_protect) 536 | { 537 | return false; 538 | } 539 | DWORD new_protect = 0; 540 | DWORD old_protect = 0; 541 | new_protect = convert_ep_protect_to_win_protect(protect); 542 | if (VirtualProtectEx(process->handle, (LPVOID)address, size, new_protect, 543 | &old_protect) == 0) 544 | { 545 | return false; 546 | } 547 | *out_old_protect = convert_win_protect_to_ep_protect(old_protect); 548 | return true; 549 | } 550 | 551 | bool epc_virtual_protect_nt(const EPCProcess *process, uintptr_t address, 552 | size_t size, EPCVirtualProtect protect, 553 | EPCVirtualProtect *out_old_protect) 554 | { 555 | NtProtectVirtualMemory_t NtProtectVirtualMemory = 556 | (NtProtectVirtualMemory_t)(void *)GetProcAddress( 557 | LoadLibrary("ntdll.dll"), "NtProtectVirtualMemory"); 558 | if (!process) 559 | { 560 | return false; 561 | } 562 | if (protect == EPCVP_EMPTY) 563 | { 564 | return false; 565 | } 566 | if (!out_old_protect) 567 | { 568 | return false; 569 | } 570 | DWORD new_protect = 0; 571 | DWORD old_protect = 0; 572 | new_protect = convert_ep_protect_to_win_protect(protect); 573 | if (NtProtectVirtualMemory(process->handle, (PVOID *)&address, 574 | (PULONG)&size, new_protect, &old_protect) != 0) 575 | { 576 | return false; 577 | } 578 | *out_old_protect = convert_win_protect_to_ep_protect(old_protect); 579 | return true; 580 | } 581 | 582 | bool epc_get_module_address(const EPCProcess *process, const char *module_name, 583 | uintptr_t *out_address) 584 | { 585 | if (!process || !module_name || !out_address) 586 | { 587 | return false; 588 | } 589 | HANDLE snapshot_handle = CreateToolhelp32Snapshot( 590 | TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process->process_id); 591 | if (snapshot_handle == INVALID_HANDLE_VALUE) 592 | { 593 | return false; 594 | } 595 | MODULEENTRY32 module_entry; 596 | module_entry.dwSize = sizeof(module_entry); 597 | if (Module32First(snapshot_handle, &module_entry)) 598 | { 599 | do 600 | { 601 | if (!_strcmpi(module_entry.szModule, module_name)) 602 | { 603 | *out_address = (uintptr_t)(module_entry.modBaseAddr); 604 | break; 605 | } 606 | } while (Module32Next(snapshot_handle, &module_entry)); 607 | } 608 | if (!CloseHandle(snapshot_handle)) 609 | { 610 | return false; 611 | } 612 | return true; 613 | } 614 | 615 | bool epc_function_caller_build(const EPCProcess *process, 616 | uintptr_t function_address, 617 | EPCCallConvention call_convention, size_t argc, 618 | EpCoreFunctionCaller **out_caller) 619 | { 620 | if (call_convention <= EPCCC_INVALID || call_convention >= EPCCC_COUNT_) 621 | { 622 | return false; 623 | } 624 | if (!out_caller) 625 | { 626 | return false; 627 | } 628 | *out_caller = malloc(sizeof(**out_caller)); 629 | (*out_caller)->process = process; 630 | (*out_caller)->function_address = function_address; 631 | (*out_caller)->call_convention = call_convention; 632 | (*out_caller)->argc = argc; 633 | switch (call_convention) 634 | { 635 | case EPCCC_CDECL: { 636 | (*out_caller)->caller_address = 637 | build_cdecl_caller(process, *out_caller); 638 | break; 639 | } 640 | case EPCCC_STDCALL: { 641 | (*out_caller)->caller_address = 642 | build_stdcall_caller(process, *out_caller); 643 | break; 644 | } 645 | case EPCCC_THISCALL_MSVC: { 646 | (*out_caller)->caller_address = 647 | build_thiscall_msvc_caller(process, *out_caller); 648 | break; 649 | } 650 | default: { 651 | return false; 652 | } 653 | } 654 | return (*out_caller)->caller_address != 0; 655 | } 656 | 657 | bool epc_function_caller_destroy(EpCoreFunctionCaller *caller) 658 | { 659 | if (!caller) 660 | { 661 | return false; 662 | } 663 | if (caller->caller_address) 664 | { 665 | if (!epc_free(caller->process, caller->caller_address)) 666 | { 667 | free(caller); 668 | return false; 669 | } 670 | } 671 | free(caller); 672 | return true; 673 | } 674 | 675 | bool epc_function_caller_send_args(const EpCoreFunctionCaller *caller, 676 | size_t argc, ...) 677 | { 678 | static uint8_t local_caller_args_buffer[LOCAL_CALLER_BUF_SIZE]; 679 | if (!caller) 680 | { 681 | return false; 682 | } 683 | 684 | if (argc != caller->argc) 685 | { 686 | return false; 687 | } 688 | if (!caller->caller_address) 689 | { 690 | return false; 691 | } 692 | uintptr_t argv = (uintptr_t)(&argc) + 4; 693 | switch (caller->call_convention) 694 | { 695 | case EPCCC_CDECL: 696 | case EPCCC_STDCALL: { 697 | for (size_t i = 0; i < caller->argc; i++) 698 | { 699 | local_caller_args_buffer[i * 5] = 0x68; /* push */ 700 | /* argument */ 701 | *(uintptr_t *)(local_caller_args_buffer + i * 5 + 1) = 702 | *(((uintptr_t *)argv) + caller->argc - i - 1); 703 | } 704 | return epc_write_buf(caller->process, caller->caller_address, 705 | caller->argc * 5, local_caller_args_buffer); 706 | } 707 | case EPCCC_THISCALL_MSVC: { 708 | if (caller->argc < 1) 709 | { 710 | /* Error. Expected 'this' argument. */ 711 | return false; 712 | } 713 | for (size_t i = 1; i < caller->argc; i++) 714 | { 715 | local_caller_args_buffer[(i - 1) * 5] = 0x68; /* push */ 716 | /* argument */ 717 | *(uintptr_t *)(local_caller_args_buffer + (i - 1) * 5 + 1) = 718 | *(((uintptr_t *)argv) + caller->argc - (i - 1) - 1); 719 | } 720 | /* MOV ECX, THIS_PTR */ 721 | local_caller_args_buffer[(caller->argc - 1) * 5] = 0xB9; 722 | *(uintptr_t *)(local_caller_args_buffer + (caller->argc - 1) * 5 + 1) = 723 | *(uintptr_t *)argv; 724 | return epc_write_buf(caller->process, caller->caller_address, 725 | caller->argc * 5, local_caller_args_buffer); 726 | } 727 | default: 728 | return false; 729 | } 730 | } 731 | 732 | bool epc_function_caller_call(const EpCoreFunctionCaller *caller, 733 | uintptr_t *out_result, bool wait_for_return) 734 | { 735 | // TODO: This function should be able to be called with out_result == NULL. 736 | if (!caller || !out_result) 737 | { 738 | return false; 739 | } 740 | /* Create a thread in the remote process */ 741 | HANDLE thread_handle = CreateRemoteThread( 742 | caller->process->handle, NULL, 0, 743 | (LPTHREAD_START_ROUTINE)caller->caller_address, NULL, 0, NULL); 744 | /* Wait for a return from the function */ 745 | while (WaitForSingleObject(caller->process->handle, 746 | wait_for_return ? INFINITE : 0) != 0) 747 | { 748 | } 749 | /* Get the returned value */ 750 | if (!GetExitCodeThread(thread_handle, (LPDWORD)(out_result))) 751 | { 752 | return false; 753 | } 754 | if (!CloseHandle(thread_handle)) 755 | { 756 | return false; 757 | } 758 | return true; 759 | } 760 | -------------------------------------------------------------------------------- /src/core/ep_core.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ep_core.h 3 | * @brief Core external process interaction functionality. 4 | */ 5 | #ifndef EP_CORE_H_ 6 | #define EP_CORE_H_ 7 | 8 | #ifdef __cplusplus 9 | extern "C" 10 | { 11 | #endif /* __cplusplus */ 12 | #include 13 | #include 14 | 15 | typedef struct EPCProcess EPCProcess; 16 | 17 | typedef enum EPCVirtualProtect 18 | { 19 | EPCVP_EMPTY = 0, 20 | EPCVP_READ = 0b1, 21 | EPCVP_WRITE = 0b10, 22 | EPCVP_EXECUTE = 0b100, 23 | } EPCVirtualProtect; 24 | 25 | typedef enum EPCCallConvention 26 | { 27 | EPCCC_INVALID = 0, 28 | EPCCC_CDECL = 1, 29 | EPCCC_STDCALL = 2, 30 | EPCCC_THISCALL_MSVC = 3, 31 | EPCCC_FASTCALL = 4, 32 | EPCCC_COUNT_, 33 | } EPCCallConvention; 34 | 35 | typedef struct EpCoreFunctionCaller EpCoreFunctionCaller; 36 | 37 | /** 38 | * @brief Creates a new Windows process interaction object. 39 | * @param[out] out_process The process object to write to. 40 | * @return true if the process was created successfully. 41 | * @return false if the process could not be created. 42 | */ 43 | bool epc_create(EPCProcess **out_process); 44 | 45 | /** 46 | * @brief Destroys a Windows process interaction object. 47 | * @param[in] process The process object to destroy. 48 | * @return true if the process was destroyed successfully. 49 | * @return false if the process could not be destroyed. 50 | */ 51 | bool epc_destroy(EPCProcess *process); 52 | 53 | /** 54 | * @brief Opens a Windows process by its ID. 55 | * 56 | * @param[in] process_id The ID of the process to open. 57 | * @param[out] out_process The process object to write to. 58 | * @return true if the process was opened successfully. 59 | * @return false if the process could not be opened. 60 | */ 61 | bool epc_open_process(EPCProcess *process, unsigned int process_id); 62 | 63 | /** 64 | * @brief Closes a Windows process by its name. 65 | * @param[in] process The process object to close. 66 | */ 67 | bool epc_close_process(EPCProcess *process); 68 | 69 | /** 70 | * @brief Returns process ID by its name. 71 | * @param[in] process_name The name of the process to find. 72 | * @param[out] out_process_id The ID of the process to write to. 73 | * @return true if the process was found. 74 | * @return false if the process was not found. 75 | */ 76 | bool epc_get_process_id_by_name(const char *process_name, 77 | unsigned int *out_process_id); 78 | 79 | /** 80 | * @brief Reads \p size bytes from \p address in the \p process process. 81 | * @param[in] process The process to read from. 82 | * @param[in] address The address to read from. 83 | * @param[in] size The number of bytes to read. 84 | * @param[out] out_result The buffer to write the result to. 85 | * @return true if the read was successful. 86 | */ 87 | bool epc_read_buf(const EPCProcess *process, uintptr_t address, size_t size, 88 | void *out_result); 89 | 90 | bool epc_read_buf_nt(const EPCProcess *process, uintptr_t address, size_t size, 91 | void *out_result); 92 | 93 | /** 94 | * @brief Writes \p size bytes to \p address in the \p process process. 95 | * @param[in] process The process to write to. 96 | * @param[in] address The address to write to. 97 | * @param[in] size The number of bytes to write. 98 | * @param[in] data The data to write. 99 | * @return true if the write was successful. 100 | * @return false if the write was not successful. 101 | */ 102 | bool epc_write_buf(const EPCProcess *process, uintptr_t address, size_t size, 103 | const void *data); 104 | 105 | bool epc_write_buf_nt(const EPCProcess *process, uintptr_t address, size_t size, 106 | const void *data); 107 | 108 | /** 109 | * @brief Allocates \p size bytes in the \p process process. 110 | * @param[in] process The process to allocate in. 111 | * @param[in] size The number of bytes to allocate. 112 | * @param[out] out_address The address of the allocated memory. 113 | * @return true if the allocation was successful. 114 | * @return false if the allocation was not successful. 115 | */ 116 | bool epc_alloc(const EPCProcess *process, size_t size, uintptr_t *out_address); 117 | 118 | bool epc_alloc_nt(const EPCProcess *process, size_t size, 119 | uintptr_t *out_address); 120 | 121 | /** 122 | * @brief Frees \p size bytes at \p address in the \p process process 123 | * @param[in] process The process to free in. 124 | * @param[in] address The address to free. 125 | * @return true if the free was successful. 126 | * @return false if the free was not successful. 127 | */ 128 | bool epc_free(const EPCProcess *process, uintptr_t address); 129 | 130 | bool epc_free_nt(const EPCProcess *process, uintptr_t address); 131 | 132 | /** 133 | * @brief Changes the memory protection of \p size bytes at \p address in the 134 | * \p process . 135 | * @param[in] process The process to change the protection in. 136 | * @param[in] address The address to change the protection of. 137 | * @param[in] size The number of bytes to change the protection of. 138 | * @param[in] protect The new protection. 139 | * @param[out] out_old_protect The old protection. 140 | * @return true if the protection was changed successfully. 141 | * @return false if the protection was not changed successfully. 142 | */ 143 | bool epc_virtual_protect(const EPCProcess *process, uintptr_t address, 144 | size_t size, EPCVirtualProtect protect, 145 | EPCVirtualProtect *out_old_protect); 146 | 147 | bool epc_virtual_protect_nt(const EPCProcess *process, uintptr_t address, 148 | size_t size, EPCVirtualProtect protect, 149 | EPCVirtualProtect *out_old_protect); 150 | 151 | /** 152 | * @brief Returns the address of a module named \p module_name in the \p process 153 | * @param[in] process The process to search in. 154 | * @param[in] module_name The name of the module to search for. 155 | * @param[out] out_address The address of the module. 156 | * @return true if the module was found. 157 | * @return false if the module was not found. 158 | */ 159 | bool epc_get_module_address(const EPCProcess *process, const char *module_name, 160 | uintptr_t *out_address); 161 | 162 | /** 163 | * @brief Builds instructions to call a remote process' \p process function 164 | * located at \p function_address address and acceping \p argc arguments. Writes 165 | * the built instructions to remote process' memory and returns a caller object 166 | * which can be used to pass arguments and call the function. 167 | * @param[in] process The process to build the caller in. 168 | * @param[in] function_address The address of the function to call. 169 | * @param[in] call_convention The calling convention of the function. 170 | * @param[in] argc The number of arguments the function accepts. 171 | * @param[out] out_caller The caller object to write to. 172 | * @return true if the caller was built successfully. 173 | * @return false if the caller was not built successfully 174 | */ 175 | bool epc_function_caller_build(const EPCProcess *process, 176 | uintptr_t function_address, 177 | EPCCallConvention call_convention, size_t argc, 178 | EpCoreFunctionCaller **out_caller); 179 | 180 | /** 181 | * @brief Destroys a function caller object. 182 | * @param[in] caller The caller to destroy. 183 | * @return true if the caller was destroyed successfully. 184 | * @return false if the caller was not destroyed successfully. 185 | */ 186 | bool epc_function_caller_destroy(EpCoreFunctionCaller *caller); 187 | 188 | /** 189 | * @brief Sends \p argc arguments for the \p caller function caller. 190 | * For class methods, the first argument should be the class instance. 191 | * @param[in] caller The caller to send the arguments for. 192 | * @param[in] argc The number of arguments to send. 193 | * @param[in] ... The arguments to send. 194 | * @return true if the arguments were sent successfully. 195 | * @return false if the arguments were not sent successfully. 196 | */ 197 | bool epc_function_caller_send_args(const EpCoreFunctionCaller *caller, 198 | size_t argc, ...); 199 | 200 | /** 201 | * @brief Calls the function of the \p caller function caller. Waits (if 202 | * required) for the function to return and returns the result using 203 | * \p out_result. 204 | * @param[in] caller The caller to call. 205 | * @param[out] out_result The result of the function call. 206 | * @param[in] wait_for_return Whether to wait for the function to return. 207 | * @return true if the function was called successfully. 208 | * @return false if the function was not called successfully. 209 | */ 210 | bool epc_function_caller_call(const EpCoreFunctionCaller *caller, 211 | uintptr_t *out_result, bool wait_for_return); 212 | 213 | #ifdef __cplusplus 214 | } 215 | #endif /* __cplusplus */ 216 | 217 | #endif /* EP_CORE_H_ */ 218 | -------------------------------------------------------------------------------- /src/external_process.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file external_process.cpp 3 | * 4 | * @brief ExternalProcess class implementation. 5 | * 6 | */ 7 | #include "external_process.hpp" 8 | #include 9 | #include 10 | 11 | // #define SHOW_ERRORS 12 | 13 | #ifdef SHOW_ERRORS 14 | #include 15 | #include 16 | static std::string get_winapi_error_text(BOOL error_id); 17 | #define SHOW_WINAPI_ERROR(error_code) \ 18 | std::cerr << std::endl << "Error: " << error_code << std::endl; \ 19 | std::cerr << " File: " << __FILE__ << std::endl; \ 20 | std::cerr << " Line: " << std::dec << __LINE__ << std::endl; \ 21 | std::cerr << " Function: " << __func__ << std::endl; \ 22 | std::cerr << " Message: " << get_winapi_error_text(error_code) \ 23 | << std::endl \ 24 | << std::endl; 25 | 26 | #define WINAPI_CALL(call) \ 27 | call; \ 28 | do \ 29 | { \ 30 | DWORD ________last_error = GetLastError(); \ 31 | SetLastError(0); \ 32 | if (________last_error) \ 33 | { \ 34 | SHOW_WINAPI_ERROR(________last_error); \ 35 | } \ 36 | } while (0) 37 | #else 38 | #define WINAPI_CALL(call) call 39 | #endif /* SHOW_ERRORS */ 40 | 41 | using namespace P1ExternalProcess; 42 | 43 | typedef NTSTATUS(NTAPI *tNtReadVirtualMemory)( 44 | IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID buffer, 45 | IN ULONG NumberOfBytesRead, OUT PULONG NumberOfBytesReaded OPTIONAL); 46 | 47 | typedef NTSTATUS(NTAPI *tNtWriteVirtualMemory)( 48 | IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID buffer, 49 | IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL); 50 | 51 | typedef NTSTATUS(NTAPI *tNtAllocateVirtualMemory)( 52 | IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG_PTR ZeroBits, 53 | IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect); 54 | 55 | typedef NTSTATUS(NTAPI *tNtFreeVirtualMemory)(IN HANDLE ProcessHandle, 56 | IN OUT PVOID *BaseAddress, 57 | IN OUT PSIZE_T RegionSize, 58 | IN ULONG FreeType); 59 | 60 | typedef NTSTATUS(NTAPI *tNtProtectVirtualMemory)( 61 | IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, 62 | IN OUT PULONG NumberOfBytesToProtect, IN ULONG NewAccessProtection, 63 | OUT PULONG OldAccessProtection); 64 | 65 | static tNtReadVirtualMemory NtReadVirtualMemory = 66 | (tNtReadVirtualMemory)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 67 | "NtReadVirtualMemory"); 68 | 69 | static tNtWriteVirtualMemory NtWriteVirtualMemory = 70 | (tNtWriteVirtualMemory)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 71 | "NtWriteVirtualMemory"); 72 | 73 | static tNtAllocateVirtualMemory NtAllocateVirtualMemory = 74 | (tNtAllocateVirtualMemory)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 75 | "NtAllocateVirtualMemory"); 76 | 77 | static tNtFreeVirtualMemory NtFreeVirtualMemory = 78 | (tNtFreeVirtualMemory)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 79 | "NtFreeVirtualMemory"); 80 | 81 | static tNtProtectVirtualMemory NtProtectVirtualMemory = 82 | (tNtProtectVirtualMemory)(void *)GetProcAddress(LoadLibrary("ntdll.dll"), 83 | "NtProtectVirtualMemory"); 84 | 85 | #ifdef SHOW_ERRORS 86 | static std::string get_winapi_error_text(BOOL error_id) 87 | { 88 | if (!error_id) 89 | return ""; 90 | 91 | LPSTR error_msg_buffer = nullptr; 92 | size_t size = FormatMessageA( 93 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | 94 | FORMAT_MESSAGE_IGNORE_INSERTS, 95 | NULL, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 96 | (LPSTR)&error_msg_buffer, 0, NULL); 97 | std::string error_msg(error_msg_buffer, size); 98 | LocalFree(error_msg_buffer); 99 | return error_msg; 100 | } 101 | #endif /* SHOW_ERRORS */ 102 | 103 | ExternalProcess::ExternalProcess(uint32_t process_id) : _process_id(process_id) 104 | { 105 | _handle = WINAPI_CALL(OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id)); 106 | } 107 | 108 | ExternalProcess::ExternalProcess(const char *process_name) 109 | : ExternalProcess(get_process_id_by_process_name(process_name)) 110 | { 111 | } 112 | 113 | ExternalProcess::~ExternalProcess() 114 | { 115 | for (auto &i : _callers) 116 | { 117 | free(i.second.caller_address); // TODO: Implement ~caller_destroyer. 118 | } 119 | for (auto &i : _injected_code) 120 | { 121 | uninject_code(i.first); 122 | } 123 | for (auto i = _allocated_memory.cbegin(), n = i; 124 | i != _allocated_memory.cend(); i = n) 125 | { 126 | ++n; 127 | free(i->first); 128 | } 129 | for (auto &i : _virtual_protect) 130 | { 131 | restore_virtual_protect(i.first); 132 | } 133 | WINAPI_CALL(CloseHandle((HANDLE)_handle)); 134 | } 135 | 136 | void ExternalProcess::read_buf(uintptr_t address, size_t size, 137 | void *out_result) const 138 | { 139 | // ReadProcessMemory(static_cast(_handle), 140 | // reinterpret_cast(address), out_result, size, 141 | // 0); 142 | NtReadVirtualMemory(static_cast(_handle), 143 | reinterpret_cast(address), out_result, size, 0); 144 | } 145 | 146 | void ExternalProcess::write_buf(uintptr_t address, size_t size, 147 | const void *data) const 148 | { 149 | // WriteProcessMemory(static_cast(_handle), 150 | // reinterpret_cast(address), data, size, 0); 151 | 152 | NtWriteVirtualMemory(static_cast(_handle), 153 | reinterpret_cast(address), 154 | const_cast(data), size, 0); 155 | } 156 | 157 | uintptr_t ExternalProcess::alloc(const size_t size) 158 | { 159 | // TODO: Add rights as function argument. 160 | // void *address = 161 | // VirtualAllocEx(static_cast(_handle), NULL, size, 162 | // MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 163 | void *address = NULL; 164 | SIZE_T sz = size; 165 | NtAllocateVirtualMemory(static_cast(_handle), &address, 0, &sz, 166 | MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 167 | if (address != NULL) 168 | { 169 | _allocated_memory[reinterpret_cast(address)] = size; 170 | } 171 | return reinterpret_cast(address); 172 | } 173 | 174 | void ExternalProcess::free(uintptr_t address) 175 | { 176 | auto result = _allocated_memory.find(address); 177 | if (result != _allocated_memory.end()) 178 | { 179 | // VirtualFreeEx((HANDLE)_handle, reinterpret_cast(address), 180 | // result->second, MEM_RELEASE); 181 | PVOID addr = reinterpret_cast(address); 182 | SIZE_T sz = 0; 183 | NtFreeVirtualMemory(static_cast(_handle), &addr, &sz, 184 | MEM_RELEASE); 185 | _allocated_memory.erase(address); 186 | _virtual_protect.erase(address); 187 | } 188 | } 189 | 190 | void ExternalProcess::set_virtual_protect(uintptr_t address, size_t size, 191 | enVirtualProtect type) 192 | { 193 | ULONG old_protect = 0; 194 | PVOID addr = reinterpret_cast(address); 195 | ULONG sz = size; 196 | ULONG protect = 0; 197 | if (type == (enVirtualProtect::READ | enVirtualProtect::WRITE | 198 | enVirtualProtect::EXECUTE)) 199 | { 200 | protect = PAGE_READWRITE; 201 | } 202 | else if (type == (enVirtualProtect::READ & enVirtualProtect::WRITE)) 203 | { 204 | protect = PAGE_READWRITE; // TODO: Not PAGE_READWRITE 205 | } 206 | else if (type == (enVirtualProtect::READ & enVirtualProtect::EXECUTE)) 207 | { 208 | protect = PAGE_EXECUTE_READ; 209 | } 210 | else if (type == enVirtualProtect::READ) 211 | { 212 | protect = PAGE_READONLY; 213 | } 214 | else if (type == enVirtualProtect::NOACCESS) 215 | { 216 | protect = PAGE_NOACCESS; 217 | } 218 | 219 | NtProtectVirtualMemory(static_cast(_handle), &addr, &sz, protect, 220 | &old_protect); 221 | 222 | auto original_vp = _virtual_protect.find(address); 223 | if (original_vp == _virtual_protect.end()) 224 | { 225 | _virtual_protect[address] = {size, old_protect}; 226 | } 227 | else if (original_vp->second.size < size) 228 | { 229 | original_vp->second.size = size; 230 | } 231 | } 232 | 233 | void ExternalProcess::restore_virtual_protect(uintptr_t address) 234 | { 235 | auto vp = _virtual_protect.find(address); 236 | if (vp != _virtual_protect.end()) 237 | { 238 | ULONG old_protect; 239 | PVOID addr = reinterpret_cast(address); 240 | ULONG sz; 241 | NtProtectVirtualMemory(static_cast(_handle), &addr, &sz, 242 | vp->second.original_protect, &old_protect); 243 | _virtual_protect.erase(address); 244 | } 245 | } 246 | 247 | uintptr_t ExternalProcess::get_module_address(const char *module_name) 248 | { 249 | // TODO: Store modules addresses in map. 250 | uintptr_t result = 0; 251 | HANDLE snapshot_handle = WINAPI_CALL(CreateToolhelp32Snapshot( 252 | TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, _process_id)); 253 | if (snapshot_handle != INVALID_HANDLE_VALUE) 254 | { 255 | MODULEENTRY32 module_entry; 256 | module_entry.dwSize = sizeof(module_entry); 257 | if (Module32First(snapshot_handle, &module_entry)) 258 | { 259 | do 260 | { 261 | if (!_strcmpi(module_entry.szModule, module_name)) 262 | { 263 | result = 264 | reinterpret_cast(module_entry.modBaseAddr); 265 | break; 266 | } 267 | } while (Module32Next(snapshot_handle, &module_entry)); 268 | } 269 | } 270 | WINAPI_CALL(CloseHandle(snapshot_handle)); 271 | return result; 272 | } 273 | 274 | uintptr_t ExternalProcess::call_cdecl_function(uintptr_t address, size_t argc, 275 | ...) 276 | { 277 | if (_callers.find(address) == _callers.end()) 278 | { 279 | auto &caller = _callers[address]; 280 | /* Fill in the caller info structure */ 281 | caller.function_address = address; 282 | caller.caller_address = build_cdecl_caller(address, argc); 283 | caller.argc = argc; 284 | caller.cc = enCallConvention::ECC_CDECL; 285 | } 286 | // TODO: Optimize: store last args and send new only if there are diffs. 287 | send_external_caller_arguments(_callers[address], 288 | reinterpret_cast(&argc) + 4); 289 | 290 | return call_external_function(_callers[address]); 291 | } 292 | 293 | uintptr_t ExternalProcess::call_stdcall_function(uintptr_t address, size_t argc, 294 | ...) 295 | { 296 | if (_callers.find(address) == _callers.end()) 297 | { 298 | auto &caller = _callers[address]; 299 | /* Fill in the caller info structure */ 300 | caller.function_address = address; 301 | caller.caller_address = build_stdcall_caller(address, argc); 302 | caller.argc = argc; 303 | caller.cc = enCallConvention::ECC_STDCALL; 304 | } 305 | // TODO: Optimize: store last args and send new only if there are diffs. 306 | send_external_caller_arguments(_callers[address], 307 | reinterpret_cast(&argc) + 4); 308 | 309 | return call_external_function(_callers[address]); 310 | } 311 | 312 | uintptr_t ExternalProcess::call_thiscall_function(uintptr_t address, 313 | uintptr_t this_ptr, 314 | size_t argc, ...) 315 | { 316 | if (_callers.find(address) == _callers.end()) 317 | { 318 | auto &caller = _callers[address]; 319 | /* Fill in the caller info structure */ 320 | caller.function_address = address; 321 | caller.caller_address = build_thiscall_caller(address, argc); 322 | caller.argc = argc; 323 | caller.cc = enCallConvention::ECC_THISCALL; 324 | } 325 | // TODO: Optimize: store last args and send new only if there are diffs. 326 | send_external_caller_arguments(_callers[address], 327 | reinterpret_cast(&argc) + 4); 328 | send_thiscall_this_ptr(_callers[address], this_ptr); 329 | return call_external_function(_callers[address]); 330 | } 331 | 332 | void ExternalProcess::inject_code(uintptr_t address, const uint8_t *bytes, 333 | size_t bytes_size, 334 | size_t overwrite_bytes_size, 335 | enInjectionType it) 336 | { 337 | // TODO: Implement call-based injector. 338 | // TODO: Support injecting and uninjecting multiple code-injections in the 339 | // same address. 340 | 341 | /* If colde has not already injected on @address address */ 342 | if (_injected_code.find(address) == _injected_code.end()) 343 | { 344 | uintptr_t result = 0; 345 | switch (it) 346 | { 347 | case enInjectionType::EIT_JMP: 348 | result = inject_code_using_jmp(address, bytes, bytes_size, 349 | overwrite_bytes_size); 350 | break; 351 | case enInjectionType::EIT_PUSHRET: 352 | result = inject_code_using_push_ret(address, bytes, bytes_size, 353 | overwrite_bytes_size); 354 | break; 355 | } 356 | if (result) 357 | { 358 | InjectedCodeInfo ici; 359 | ici.injected_bytes_number = bytes_size; 360 | ici.overwritten_bytes_number = overwrite_bytes_size; 361 | ici.allocated_buffer = result; 362 | _injected_code[address] = ici; 363 | } 364 | } 365 | } 366 | 367 | void ExternalProcess::uninject_code(uintptr_t address) 368 | { 369 | auto ici = _injected_code.find(address); 370 | if (ici != _injected_code.end()) 371 | { 372 | /* Set vp */ 373 | set_virtual_protect(address, ici->second.overwritten_bytes_number, 374 | enVirtualProtect::READ_WRITE_EXECUTE); 375 | 376 | /* Create buffer to store original bytes */ 377 | uint8_t *original_bytes = 378 | new uint8_t[ici->second.overwritten_bytes_number]; 379 | 380 | /* Read original bytes to this buffer */ 381 | read_buf(ici->second.allocated_buffer + 382 | ici->second.injected_bytes_number, 383 | ici->second.overwritten_bytes_number, original_bytes); 384 | 385 | /* Restore original bytes */ 386 | write_buf(ici->first, ici->second.overwritten_bytes_number, 387 | original_bytes); 388 | 389 | /* Restore vp */ 390 | restore_virtual_protect(address); 391 | 392 | delete[] original_bytes; 393 | free(ici->second.allocated_buffer); 394 | _injected_code.erase(address); 395 | } 396 | } 397 | 398 | void ExternalProcess::patch(uintptr_t address, const uint8_t *bytes, 399 | size_t size) 400 | { 401 | // TODO: Store patched bytes to be able to unpatch later. 402 | /* Set vp */ 403 | set_virtual_protect(address, size, enVirtualProtect::READ_WRITE_EXECUTE); 404 | 405 | /* Write bytes */ 406 | write_buf(address, size, bytes); 407 | 408 | /* Restore vp */ 409 | restore_virtual_protect(address); 410 | } 411 | 412 | uintptr_t ExternalProcess::find_signature(uintptr_t address, size_t size, 413 | const uint8_t *signature, 414 | const char *mask) const 415 | { 416 | if (!signature) 417 | { 418 | return 0; 419 | } 420 | if (!mask) 421 | { 422 | return 0; 423 | } 424 | if (strlen(mask) > size) 425 | { 426 | return 0; 427 | } 428 | uint8_t *buffer = new uint8_t[size]; 429 | read_buf(address, size, buffer); 430 | uintptr_t result = 0; 431 | for (size_t i = 0; i <= size - strlen(mask); i++) 432 | { 433 | uintptr_t mask_offset = 0; 434 | while (mask[mask_offset]) 435 | { 436 | if (mask[mask_offset] == 'x' && 437 | buffer[i + mask_offset] != signature[mask_offset]) 438 | { 439 | mask_offset = 0; 440 | break; 441 | } 442 | ++mask_offset; 443 | } 444 | if (mask_offset != 0) 445 | { 446 | result = address + i; 447 | break; 448 | } 449 | } 450 | delete[] buffer; 451 | return result; 452 | } 453 | 454 | uint32_t ExternalProcess::get_process_id_by_process_name( 455 | const char *process_name) const 456 | { 457 | uint32_t result = 0; 458 | HANDLE snapshot_handle = 459 | WINAPI_CALL(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); 460 | if (snapshot_handle != INVALID_HANDLE_VALUE) 461 | { 462 | PROCESSENTRY32 process_entry; 463 | process_entry.dwSize = sizeof(process_entry); 464 | if (Process32First(snapshot_handle, &process_entry)) 465 | { 466 | do 467 | { 468 | if (!_strcmpi(process_entry.szExeFile, process_name)) 469 | { 470 | result = process_entry.th32ProcessID; 471 | break; 472 | } 473 | } while (Process32Next(snapshot_handle, &process_entry)); 474 | } 475 | } 476 | WINAPI_CALL(CloseHandle(snapshot_handle)); 477 | return result; 478 | } 479 | 480 | void ExternalProcess::provide_debug_access(void) 481 | { 482 | WINAPI_CALL(OpenProcessToken(static_cast(_handle), 483 | TOKEN_ADJUST_PRIVILEGES, &_dbg_handle)); 484 | 485 | TOKEN_PRIVILEGES tp; 486 | LUID luid; 487 | 488 | WINAPI_CALL(LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)); 489 | 490 | tp.PrivilegeCount = 1; 491 | tp.Privileges[0].Luid = luid; 492 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 493 | 494 | WINAPI_CALL(AdjustTokenPrivileges(_dbg_handle, FALSE, &tp, 495 | sizeof(TOKEN_PRIVILEGES), 496 | (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)); 497 | } 498 | 499 | uintptr_t ExternalProcess::build_cdecl_caller(uintptr_t address, size_t argc) 500 | { 501 | /* Calculate caller size */ 502 | size_t caller_size = 503 | (1 + 4) * argc + /* (push-instruction size + argument size) * argc */ 504 | 1 + 4 + /* call-instruction size + address size */ 505 | 3 + /* restore-stack-instructions size */ 506 | 1; /* ret-instruction size */ 507 | 508 | /* Allocate space for the caller in the remote process's address space */ 509 | uintptr_t caller_address = alloc(caller_size); 510 | 511 | /* Create local buffer to be sent in remote process */ 512 | uint8_t *local_caller_buffer = new uint8_t[caller_size]; 513 | 514 | /* Write push-args instructions in the caller. */ 515 | for (size_t i = 0; i < argc; i++) 516 | { 517 | local_caller_buffer[i * 5] = 0x68; /* push */ 518 | /* argument */ 519 | *reinterpret_cast(local_caller_buffer + i * 5 + 1) = 0; 520 | } 521 | 522 | /* Write call-instruction to the caller bytes */ 523 | local_caller_buffer[argc * 5] = 0xe8; /* call */ 524 | 525 | /* Calculate and write an address for the near call instruction */ 526 | *reinterpret_cast(local_caller_buffer + argc * 5 + 1) = 527 | address - (caller_address + argc * 5) - 5; 528 | 529 | /* Write restore-stack-instructions to the caller bytes */ 530 | local_caller_buffer[argc * 5 + 5] = 0x83; /* add */ 531 | local_caller_buffer[argc * 5 + 6] = 0xc4; /* esp */ 532 | local_caller_buffer[argc * 5 + 7] = 4 * argc; /* args size */ 533 | 534 | /* Write ret-instruction to the caller bytes */ 535 | local_caller_buffer[argc * 5 + 8] = 0xc3; /* ret */ 536 | 537 | /* Write caller bytes to the remote process's memory */ 538 | write_buf(caller_address, caller_size, local_caller_buffer); 539 | 540 | delete[] local_caller_buffer; 541 | 542 | return caller_address; 543 | } 544 | 545 | uintptr_t ExternalProcess::build_stdcall_caller(uintptr_t address, size_t argc) 546 | { 547 | /* Calculate caller size */ 548 | size_t caller_size = 549 | (1 + 4) * argc + /* (push-instruction size + argument size) * argc */ 550 | 1 + 4 + /* call-instruction size + address size */ 551 | 1; /* ret-instruction size */ 552 | 553 | /* Allocate space for the caller in the remote process's address space */ 554 | uintptr_t caller_address = alloc(caller_size); 555 | 556 | /* Create local buffer to be sent in remote process */ 557 | uint8_t *local_caller_buffer = new uint8_t[caller_size]; 558 | 559 | /* Write push-args instructions in the caller. */ 560 | for (size_t i = 0; i < argc; i++) 561 | { 562 | local_caller_buffer[i * 5] = 0x68; /* push */ 563 | /* argument */ 564 | *reinterpret_cast(local_caller_buffer + i * 5 + 1) = 0; 565 | } 566 | 567 | /* Write call-instruction to the caller bytes */ 568 | local_caller_buffer[argc * 5] = 0xe8; /* call */ 569 | 570 | /* Calculate and write an address for the near call instruction */ 571 | *reinterpret_cast(local_caller_buffer + argc * 5 + 1) = 572 | address - (caller_address + argc * 5) - 5; 573 | 574 | /* Write ret-instruction to the caller bytes */ 575 | local_caller_buffer[argc * 5 + 5] = 0xc3; /* ret */ 576 | 577 | /* Write caller bytes to the remote process's memory */ 578 | write_buf(caller_address, caller_size, local_caller_buffer); 579 | 580 | delete[] local_caller_buffer; 581 | 582 | return caller_address; 583 | } 584 | 585 | uintptr_t ExternalProcess::build_thiscall_caller(uintptr_t address, size_t argc) 586 | { 587 | /* Calculate caller size */ 588 | size_t caller_size = 589 | (1 + 4) * argc + /* (push-instruction size + argument size) * argc */ 590 | 5 + /* mov _this to ecx */ 591 | 1 + 4 + /* call-instruction size + address size */ 592 | 1; /* ret-instruction size */ 593 | 594 | /* Allocate space for the caller in the remote process's address space */ 595 | uintptr_t caller_address = alloc(caller_size); 596 | 597 | /* Create local buffer to be sent in remote process */ 598 | uint8_t *local_caller_buffer = new uint8_t[caller_size]; 599 | 600 | /* Write push-args instructions in the caller. */ 601 | for (size_t i = 0; i < argc; i++) 602 | { 603 | local_caller_buffer[i * 5] = 0x68; /* push */ 604 | /* argument */ 605 | *reinterpret_cast(local_caller_buffer + i * 5 + 1) = 0; 606 | } 607 | 608 | /* Write [mov ecx, _this] to the caller bytes */ 609 | local_caller_buffer[argc * 5] = 0xB9; /* mov ecx, */ 610 | *reinterpret_cast(local_caller_buffer + argc * 5 + 1) = 0; 611 | 612 | /* Write call-instruction to the caller bytes */ 613 | local_caller_buffer[argc * 5 + 5] = 0xe8; /* call */ 614 | 615 | /* Calculate and write an address for the near call instruction */ 616 | *reinterpret_cast(local_caller_buffer + argc * 5 + 5 + 1) = 617 | address - (caller_address + argc * 5 + 5) - 5; 618 | 619 | /* Write ret-instruction to the caller bytes */ 620 | local_caller_buffer[argc * 5 + 5 + 5] = 0xc3; /* ret */ 621 | 622 | /* Write caller bytes to the remote process's memory */ 623 | write_buf(caller_address, caller_size, local_caller_buffer); 624 | 625 | delete[] local_caller_buffer; 626 | 627 | return caller_address; 628 | } 629 | 630 | void ExternalProcess::send_external_caller_arguments(ExternalCaller const &ec, 631 | uintptr_t args, ...) 632 | { 633 | // for (uint32_t i = 0; i < ec.argc; i++) 634 | //{ 635 | // write(ec.caller_address + i * 5 + 1, 636 | // *(((uint32_t *)args) + ec.argc - i - 1)); 637 | //} 638 | 639 | /* Local buffer to build code for pushing arguments onto stack */ 640 | uint8_t *push_args_block = new uint8_t[ec.argc * 5]; 641 | 642 | /* Write push-args instructions in the local buffer */ 643 | for (size_t i = 0; i < ec.argc; i++) 644 | { 645 | push_args_block[i * 5] = 0x68; /* push */ 646 | /* argument */ 647 | *reinterpret_cast(push_args_block + i * 5 + 1) = 648 | *(((uintptr_t *)args) + ec.argc - i - 1); 649 | } 650 | 651 | /* Write the local buffer in caller @ec in the external process */ 652 | write_buf(ec.caller_address, ec.argc * 5, push_args_block); 653 | 654 | delete[] push_args_block; 655 | } 656 | 657 | void ExternalProcess::send_thiscall_this_ptr(const ExternalCaller &ec, 658 | uintptr_t this_ptr) 659 | { 660 | if (ec.cc == enCallConvention::ECC_THISCALL) 661 | { 662 | write(ec.caller_address + ec.argc * 5 + 1, this_ptr); 663 | } 664 | return; 665 | } 666 | 667 | uintptr_t ExternalProcess::call_external_function( 668 | const ExternalProcess::ExternalCaller &ec) const 669 | { 670 | /* Create a thread in the remote process */ 671 | HANDLE thread_handle = WINAPI_CALL(CreateRemoteThread( 672 | static_cast(_handle), NULL, 0, 673 | reinterpret_cast(ec.caller_address), NULL, 0, 674 | NULL)); 675 | 676 | /* Wait for a return from the function */ 677 | while (WaitForSingleObject(thread_handle, 0) != 0) 678 | { 679 | } 680 | 681 | uintptr_t result = 0; 682 | /* Get the returned value */ 683 | WINAPI_CALL( 684 | GetExitCodeThread(thread_handle, reinterpret_cast(&result))); 685 | 686 | WINAPI_CALL(CloseHandle(thread_handle)); 687 | return reinterpret_cast(result); 688 | } 689 | 690 | uintptr_t ExternalProcess::inject_code_using_jmp(uintptr_t address, 691 | const uint8_t *bytes, 692 | size_t size, 693 | size_t overwrite_bytes_size) 694 | { 695 | if (overwrite_bytes_size < 5) /* should be at least 5 bytes for jmp */ 696 | { 697 | return 0; 698 | } 699 | /* Set vp */ 700 | set_virtual_protect(address, overwrite_bytes_size, 701 | enVirtualProtect::READ_WRITE_EXECUTE); 702 | 703 | /* Store original bytes to be overwritten */ 704 | uint8_t *original_bytes = new uint8_t[overwrite_bytes_size]; 705 | read_buf(address, overwrite_bytes_size, original_bytes); 706 | 707 | /* Calculate whole remote process buffer size */ 708 | size_t allocated_buf_size = size + overwrite_bytes_size + 5; 709 | 710 | /* Allocate memory for remote buffer */ 711 | uintptr_t allocated_buf = alloc(allocated_buf_size); 712 | 713 | /* Create local buffer to be sent in remote process' buf */ 714 | uint8_t *local_buf = new uint8_t[allocated_buf_size]; 715 | 716 | /* Fill local buffer with data */ 717 | /* Put injected bytes in local buffer */ 718 | memcpy(local_buf, bytes, size); 719 | 720 | /* Put original bytes in local buffer */ 721 | memcpy(local_buf + size, original_bytes, overwrite_bytes_size); 722 | 723 | /* Put jmp instruction in local buffer */ 724 | local_buf[size + overwrite_bytes_size] = 0xE9; 725 | 726 | /* Put jmp address in local buffer */ 727 | *reinterpret_cast( 728 | &(local_buf[size + overwrite_bytes_size + 1])) = 729 | (address + overwrite_bytes_size) - 730 | (allocated_buf + size + overwrite_bytes_size) - 5; 731 | 732 | /* Write local buffer in remote process' allocated buffer */ 733 | write_buf(allocated_buf, allocated_buf_size, local_buf); 734 | 735 | /* Overwrite original bytes */ 736 | /* Don't need local_buf anymore, so it can be used here */ 737 | size_t nops_number = overwrite_bytes_size - 5; 738 | if (nops_number > 0) 739 | { 740 | memset(local_buf, 0x90, nops_number); /* Put nops in local buffer */ 741 | } 742 | local_buf[nops_number] = 0xE9; /* Put jmp instruction in local buffer */ 743 | 744 | /* Put jmp address in local buffer */ 745 | *reinterpret_cast(&(local_buf[nops_number + 1])) = 746 | allocated_buf - (address + nops_number) - 5; 747 | 748 | /* Write local buffer in remote process */ 749 | write_buf(address, overwrite_bytes_size, local_buf); 750 | 751 | /* Restore vp */ 752 | restore_virtual_protect(address); 753 | 754 | delete[] original_bytes; 755 | delete[] local_buf; 756 | return allocated_buf; 757 | } 758 | 759 | uintptr_t ExternalProcess::inject_code_using_push_ret( 760 | uintptr_t address, const uint8_t *bytes, size_t bytes_size, 761 | size_t overwrite_bytes_size) 762 | { 763 | if (overwrite_bytes_size < 6) /* should be at least 6 bytes for push-ret */ 764 | { 765 | return 0; 766 | } 767 | /* Set vp */ 768 | set_virtual_protect(address, overwrite_bytes_size, 769 | enVirtualProtect::READ_WRITE_EXECUTE); 770 | 771 | /* Store original bytes to be overwritten */ 772 | uint8_t *original_bytes = new uint8_t[overwrite_bytes_size]; 773 | read_buf(address, overwrite_bytes_size, original_bytes); 774 | 775 | /* Calculate whole remote process buffer size */ 776 | size_t allocated_buf_size = bytes_size + overwrite_bytes_size + 5; 777 | 778 | /* Allocate memory for remote buffer */ 779 | uintptr_t allocated_buf = alloc(allocated_buf_size); 780 | 781 | /* Create local buffer to be sent in remote process' buf */ 782 | uint8_t *local_buf = new uint8_t[allocated_buf_size]; 783 | 784 | /* Fill local buffer with data */ 785 | /* Put injected bytes in local buffer */ 786 | memcpy(local_buf, bytes, bytes_size); 787 | 788 | /* Put original bytes in local buffer */ 789 | memcpy(local_buf + bytes_size, original_bytes, overwrite_bytes_size); 790 | 791 | /* Put jmp instruction in local buffer */ 792 | local_buf[bytes_size + overwrite_bytes_size] = 0xE9; 793 | 794 | /* Put jmp address in local buffer */ 795 | *reinterpret_cast( 796 | &(local_buf[bytes_size + overwrite_bytes_size + 1])) = 797 | (address + overwrite_bytes_size) - 798 | (allocated_buf + bytes_size + overwrite_bytes_size) - 5; 799 | 800 | /* Write local buffer in remote process' allocated buffer */ 801 | write_buf(allocated_buf, allocated_buf_size, local_buf); 802 | 803 | /* Overwrite original bytes */ 804 | /* Don't need local_buf anymore, so it can be used here */ 805 | size_t nops_number = overwrite_bytes_size - 6; 806 | if (nops_number > 0) 807 | { 808 | memset(local_buf, 0x90, nops_number); /* Put nops in local buffer */ 809 | } 810 | local_buf[nops_number] = 0x68; /* Put push instruction in local buffer */ 811 | /* Put return address in local buffer */ 812 | *reinterpret_cast(&(local_buf[nops_number + 1])) = 813 | allocated_buf; 814 | /* Put ret instruction in local buffer */ 815 | local_buf[nops_number + 5] = 0xC3; 816 | 817 | /* Write local buffer in remote process */ 818 | write_buf(address, overwrite_bytes_size, local_buf); 819 | 820 | /* Restore vp */ 821 | restore_virtual_protect(address); 822 | 823 | delete[] original_bytes; 824 | delete[] local_buf; 825 | return allocated_buf; 826 | } 827 | -------------------------------------------------------------------------------- /src/external_process.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file external_process.hpp 3 | * 4 | * @brief Declaration of ExternalProcess class. This class provides 5 | * functionality for interacting with external Win32 processess. 6 | * 7 | */ 8 | #ifndef EXTERNAL_PROCESS_HPP 9 | #define EXTERNAL_PROCESS_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace P1ExternalProcess 15 | { 16 | 17 | enum class enInjectionType 18 | { 19 | EIT_JMP = 1, 20 | EIT_PUSHRET, 21 | }; 22 | 23 | enum enVirtualProtect 24 | { 25 | NOACCESS = 0b0001, 26 | READ = 0b0010, 27 | WRITE = 0b0100, 28 | EXECUTE = 0b1000, 29 | READ_EXECUTE = READ | EXECUTE, 30 | READ_WRITE = READ | WRITE, 31 | READ_WRITE_EXECUTE = READ | WRITE | EXECUTE 32 | }; 33 | 34 | class ExternalProcess 35 | { 36 | public: 37 | /** 38 | * @brief This constructor takes a process id @process_id, gets the process 39 | * handle using OpenProcess method and assigns it to @_handle field. 40 | * 41 | * @param process_id Process id. 42 | */ 43 | ExternalProcess(uint32_t process_id); 44 | 45 | /** 46 | * @brief This constructor takes a process name @process_name, gets the 47 | * process id using @get_process_id_by_process_name method and 48 | * delegates object creation to the constructor that takes a process 49 | * id. 50 | * 51 | * @param process_name Process name. 52 | */ 53 | ExternalProcess(const char *process_name); 54 | 55 | /** 56 | * @brief Removes all created external callers and injected code from remote 57 | * process memory. Frees all memory allocated in remote process. 58 | * Closes process handle. 59 | */ 60 | ~ExternalProcess(); 61 | 62 | /** 63 | * @brief Reads @size bytes at @address address of the external process. 64 | * Writes the result to @out_result. 65 | * 66 | * @param address Address to read from. 67 | * @param size Number of bytes to read. 68 | * @param out_result Buffer to write the result to. 69 | */ 70 | void read_buf(uintptr_t address, size_t size, void *out_result) const; 71 | 72 | /** 73 | * @brief Writes @size bytes from @data buffer to the external process at 74 | * @address address. 75 | * 76 | * @param address Address to write to. 77 | * @param size Number of bytes to write. 78 | * @param data Buffer to read data from. 79 | */ 80 | void write_buf(uintptr_t address, size_t size, const void *data) const; 81 | 82 | /** 83 | * @brief Allocates @size bytes in the external process. 84 | * 85 | * If allocation is successful, adds an entry to the @_allocated_memory map, 86 | * where the key is the address where the memory was allocated, the value is 87 | * the number of allocated bytes. 88 | * 89 | * @param size Number of bytes to allocate. 90 | * 91 | * @return 92 | * Address of the allocated memory if success. 93 | * NULL if fail. 94 | */ 95 | uintptr_t alloc(const size_t size); 96 | 97 | /** 98 | * @brief Frees allocated region located at @address address in the external 99 | * process address space. 100 | * 101 | * Also, removes an entry from the @_allocated_memory map, where the key is 102 | * @address. 103 | * 104 | * @param address Address of the region to free. 105 | */ 106 | void free(uintptr_t address); 107 | 108 | /** 109 | * @brief Changes the protection on a region of committed pages in the 110 | * virtual address space of the calling process. 111 | * 112 | * @param address Region address. 113 | * @param size Region size. 114 | * @param type Protection type. 115 | */ 116 | void set_virtual_protect(uintptr_t address, size_t size, 117 | enVirtualProtect type); 118 | 119 | /** 120 | * @brief Restores the original protection on a region of committed pages in 121 | * the virtual address space of the calling process. 122 | * 123 | * @param address Region address. 124 | */ 125 | void restore_virtual_protect(uintptr_t address); 126 | 127 | /** 128 | * @brief Return an address of the module named @module_name (if exists). 129 | * 130 | * @param module_name Module name. 131 | * 132 | * @return 133 | * Non-zero module address if success. 134 | * 0 if failire. 135 | */ 136 | uintptr_t get_module_address(const char *module_name); 137 | 138 | /** 139 | * @brief Calls a function at @address address with @args arguments using 140 | * 'cdecl' call convention. 141 | * 142 | * Allocates a buffer with execution rights in the remote process. 143 | * Writes to this buffer a set of x86 instructions that call a function at 144 | * @address with @args arguments using 'cdecl' call convention. 145 | * Creates a thread that executes the code in this buffer. 146 | * Waits for this thread to finish executing. 147 | * Returns a value returned by the function at the @address address (or 148 | * value of EAX register for void functions). 149 | * 150 | * @param address An address of the function to be called. 151 | * @param argc A number of arguments that the function takes. 152 | * @param args Function arguments (if any). 153 | * 154 | * @return 155 | * A value returned by the called function (or a value of EAX register for 156 | * functions of 'void' type). 157 | */ 158 | uintptr_t call_cdecl_function(uintptr_t address, size_t argc, ...); 159 | 160 | /** 161 | * @brief Calls a function at @address address with @args arguments using 162 | * 'stdcall' call convention. 163 | * 164 | * Allocates a buffer with execution rights in the remote process. 165 | * Writes to this buffer a set of x86 instructions that call a function at 166 | * @address with @args arguments using 'stdcall' call convention. 167 | * Creates a thread that executes the code in this buffer. 168 | * Waits for this thread to finish executing. 169 | * Returns a value returned by the function at the @address address (or 170 | * value of EAX register for void functions). 171 | * 172 | * @param address An address of the function to be called. 173 | * @param argc A number of arguments that the function takes. 174 | * @param args Function arguments (if any). 175 | * 176 | * @return 177 | * A value returned by the called function (or a value of EAX register 178 | * for functions of 'void' type). 179 | */ 180 | uintptr_t call_stdcall_function(uintptr_t address, size_t argc, ...); 181 | 182 | /** 183 | * @brief Calls a function at @address address with @args arguments using 184 | * 'thiscall' call convention. 185 | * 186 | * Allocates a buffer with execution rights in the remote process. 187 | * Writes to this buffer a set of x86 instructions that call a function at 188 | * @address with @args arguments using 'thiscall' call convention. 189 | * Creates a thread that executes the code in this buffer. 190 | * Waits for this thread to finish executing. 191 | * Returns a value returned by the function at the @address address (or 192 | * value of EAX register for void functions). 193 | * 194 | * @param address An address of the function to be called. 195 | * @param this_ptr A pointer to an object for which the @ec caller will 196 | * call method at @ec.function_address address. 197 | * @param argc A number of arguments that the function takes. 198 | * @param args Function arguments (if any). 199 | * 200 | * @return A value returned by the called function (or a value of EAX 201 | * register for functions of 'void' type). 202 | */ 203 | uintptr_t call_thiscall_function(uintptr_t address, uintptr_t this_ptr, 204 | size_t argc, ...); 205 | 206 | /** 207 | * @brief Injects @bytes_size bytes of code into the external process at 208 | * @address address. 209 | * 210 | * @param address An address where code should be injected. 211 | * @param bytes Code bytes to be injected at @address 212 | * address. 213 | * @param bytes_size Number of the bytes to be injected. 214 | * @param overwrite_bytes_size Number of original bytes to be overwritten 215 | * and executed after injected code. 216 | * @param it Type of transition to the injected code. 217 | */ 218 | void inject_code(uintptr_t address, const uint8_t *bytes, size_t bytes_size, 219 | size_t overwrite_bytes_size, enInjectionType it); 220 | 221 | /** 222 | * @brief Removes code injected at @address address (if any), restores the 223 | * original bytes overwritten by the injector. 224 | * 225 | * @param address An address where the code was injected. 226 | */ 227 | void uninject_code(uintptr_t address); 228 | 229 | /** 230 | * @brief Patches the external process memory with @size bytes of @bytes 231 | * code at @address address. 232 | * 233 | * @param address An address where the code should be patched. 234 | * @param bytes Code bytes to be patched at @address address. 235 | * @param size Number of the bytes to be patched. 236 | */ 237 | void patch(uintptr_t address, const uint8_t *bytes, size_t size); 238 | 239 | /** 240 | * @brief Searches for a sequence of bytes in the external process.memory. 241 | * 242 | * @param address The address from which to start the search. 243 | * @param size The size of the memory area in which to search for the 244 | * signature. 245 | * @param signature The byte sequence to be found. 246 | * @param mask The mask to search by. The character 'x' is a complete 247 | * match. Characters other than 'x' ignore the 248 | * corresponding byte. 249 | * 250 | * @return The address of the first occurrence of @signature by mask @mask 251 | * in memory area [@address ... @address + @size), or zero on unsuccessful 252 | * search. 253 | */ 254 | uintptr_t find_signature(uintptr_t address, size_t size, 255 | const uint8_t *signature, const char *mask) const; 256 | 257 | /** @brief Reads a value of type T from the specified address in the 258 | * external process' memory. 259 | * 260 | * @tparam T A type of the value to be read. 261 | * @param address An address to read the value from. 262 | * 263 | * @return A value read from the specified address. 264 | */ 265 | template T read(uintptr_t address) const; 266 | 267 | /** @brief Writes a value of type T to the specified address in the 268 | * external process' memory. 269 | * 270 | * @tparam T A type of the value to be written. 271 | * @param address An address to write the value to. 272 | * @param data A value to be written. 273 | */ 274 | template void write(uintptr_t address, const T &data) const; 275 | 276 | private: 277 | enum class enCallConvention 278 | { 279 | ECC_CDECL = 1, 280 | ECC_STDCALL, 281 | ECC_THISCALL 282 | }; 283 | struct VirtualProtect 284 | { 285 | size_t size; 286 | uint32_t original_protect; 287 | }; 288 | struct ExternalCaller 289 | { 290 | uintptr_t function_address; 291 | uintptr_t caller_address; 292 | size_t argc; 293 | enCallConvention cc; 294 | }; 295 | struct InjectedCodeInfo 296 | { 297 | size_t injected_bytes_number; 298 | size_t overwritten_bytes_number; 299 | uintptr_t allocated_buffer; 300 | }; 301 | 302 | /** 303 | * @brief Return process id of process named @process_name (if one is 304 | * running). 305 | * 306 | * @param process_name Process name. 307 | * 308 | * @return 309 | * Process id if success. 310 | * 0 if failire. 311 | */ 312 | uint32_t get_process_id_by_process_name(const char *process_name) const; 313 | 314 | /** 315 | * @brief Adds debug access rights to the external process. 316 | * 317 | * // TODO: Test. 318 | */ 319 | void provide_debug_access(void); 320 | 321 | /** 322 | * @brief Allocates and initializes a buffer in a remote process for calling 323 | * functions using'cdecl' call convention. 324 | * 325 | * Creates a buffer with execution rights in the external process. Writes to 326 | * this buffer a set of i686 instructions that: 327 | * - push arguments in the amount of @argc onto the stack 328 | * - call function at @address using 'cdecl' call convention 329 | * - restore stack 330 | * - return 331 | * 332 | * cdecl-function caller structure: 333 | * BYTES INSTRUCTION SIZE COMMENT 334 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the last arg. value 335 | * ... 336 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 2-nd arg. value 337 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 1-st arg. value 338 | * E8 XXXXXXXX call XXXXXXXX 5 XXXXXXXX = function address 339 | * 83 C4 XX add esp, XX 3 Restore stack. XX = 4 * @argc 340 | * C3 ret 1 Return (terminate thread) 341 | * 342 | * @param address An address of a function to be called. 343 | * @param argc A number of arguments that the function takes. 344 | * 345 | * @return Address where the caller is placed in the external process. 346 | */ 347 | uintptr_t build_cdecl_caller(uintptr_t address, size_t argc); 348 | 349 | /** 350 | * @brief Allocates and initializes a buffer in a remote process for calling 351 | * functions using 'stdcall' call convention. 352 | * 353 | * Creates a buffer with execution rights in the external process. Writes to 354 | * this buffer a set of i686 instructions that: 355 | * - push arguments in the amount of @argc onto the stack 356 | * - call function at @address using 'stdcall' call convention 357 | * - return 358 | * 359 | * stdcall-function caller structure: 360 | * BYTES INSTRUCTION SIZE COMMENT 361 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the last arg. value 362 | * ... 363 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 2-nd arg. value 364 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 1-st arg. value 365 | * E8 XXXXXXXX call XXXXXXXX 5 XXXXXXXX = function address 366 | * C3 ret 1 Return (terminate the thread) 367 | * 368 | * @param address An address of the function to be called. 369 | * @param argc A number of arguments that the function takes. 370 | * 371 | * @return Address where the caller is placed in the external process. 372 | */ 373 | uintptr_t build_stdcall_caller(uintptr_t address, size_t argc); 374 | 375 | /** 376 | * @brief Allocates and initializes a buffer in a remote process for calling 377 | * functions using MSVC 'thiscall' call convention. 378 | * 379 | * Creates a buffer with execution rights in the external process. Writes 380 | * to this buffer a set of i686 instructions that: 381 | * - push arguments in the amount of @argc onto the stack 382 | * - put 'this' for which a method at @address is called into EAX register 383 | * - call class method at @address using 'stdcall' call convention 384 | * - return 385 | * 386 | * thiscall-function caller structure: 387 | * BYTES INSTRUCTION SIZE COMMENT 388 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the last arg. value 389 | * ... 390 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 2-nd arg. value 391 | * 68 XXXXXXXX push XXXXXXXX 5 XXXXXXXX = the 1-st arg. value 392 | * B9 XXXXXXXX mov ecx, XXXXXXXX 5 XXXXXXXX = 'this' 393 | * E8 XXXXXXXX call XXXXXXXX 5 XXXXXXXX = function address 394 | * C3 ret 1 Return (terminate the thread) 395 | * 396 | * @param address An address of the function to be called. 397 | * @param argc A number of arguments that the function takes. 398 | * 399 | * @return Address where the caller is placed in the external process. 400 | */ 401 | uintptr_t build_thiscall_caller(uintptr_t address, size_t argc); 402 | 403 | /** 404 | * @brief 405 | * Writes arguments @args with which the function should be called. Since 406 | * instructions for pushing arguments on stack for all implemented callers 407 | * (cdecl, thiscall, stdcall) are at the very beginning, this function is 408 | * universal and applicable to callers of all call supported call 409 | * conventions. 410 | * 411 | * @param ec A caller to specify arguments for. 412 | * @param args Arguments. 413 | */ 414 | void send_external_caller_arguments(const ExternalCaller &ec, 415 | uintptr_t args, ...); 416 | 417 | /** 418 | * @brief 419 | * Puts @this_ptr in caller @ec. The ffset is calculated as follows: 420 | * @ec.argc * 5 is a push args instructions size. +1 is a MOV ECX 421 | * instruction size. It must be followed by @this_ptr. 422 | * 423 | * @param ec A caller to specify class object ptr for. 424 | * @param this_ptr A pointer to an object for which the @ec caller will 425 | * call method at @ec.function_address address. 426 | */ 427 | void send_thiscall_this_ptr(const ExternalCaller &ec, uintptr_t this_ptr); 428 | 429 | /** 430 | * @brief Calls a function using information from a caller. 431 | * 432 | * Creates a thread in remote process. This thread executes the code located 433 | * in @ec.caller_address buffer. Waits for this thread to finish executing. 434 | * Returns a value returned by the function at the @ec.function+address 435 | * address (or a value of EAX register for void functions). 436 | * 437 | * @param eс Caller whose function is to be called. 438 | * 439 | * @return A value returned by the called function (or a value of EAX 440 | * register for void functions. 441 | */ 442 | uintptr_t call_external_function(const ExternalCaller &ec) const; 443 | 444 | /** 445 | * @brief Injects @bytes_size bytes of code from the @bytes argument into 446 | * remote process at @address address. 447 | * 448 | * Logic: 449 | * Allocates a @allocated_buf buffer in a remote process with execute 450 | * permissions. 451 | * Writes @bytes injected code to it. Writes an unconditional jump 452 | * instruction 'JMP'to @allocated_buf at @address. This JMP instruction 453 | * overwrites 5 bytes at @address. These 5 bytes are recovered in the 454 | * @allocated_buf buffer after the injected code. 455 | * There are cases where the fifth byte (@address+4) is not the last byte of 456 | * the instruction. In this case, the number of overwritten bytes must be 457 | * specified explicitly (argument @overwrite_bytes_size) and all of them 458 | * will be executed in the @allocated_buf buffer after the injected code. 459 | * If @overwrite_bytes_size is greater than 5, then to save addressing, the 460 | * first (@overwrite_bytes_size - 5) bytes at @address will be replaced with 461 | * nops (0x90). 462 | * In the example below, there is a situation when it is impossible to write 463 | * a JMP instruction at @address without explicitly specifying 464 | * @overwrite_bytes_size. 465 | * 466 | * Original bytes: 467 | * ADDRESS INSTRUCTION(S) INSTRUCTION(S) LEN 468 | * @address + 00 instruction #1 4 469 | * @address + 04 instruction #2 2 470 | * @address + 06 instruction #3 4 471 | * 472 | * After injection with @overwrite_bytes_size = 6: 473 | * ADDRESS INSTRUCTION(S) INSTRUCTION(S) LEN 474 | * @address + 00 nop 1 475 | * @address + 01 jmp @allocated_buf 5 476 | * @address + 06 instruction #3 4 477 | * 478 | * Allocated buffer: 479 | * ADDRESS INSTRUCTION(S) INSTRUCTION(S) LEN 480 | * @allocated_buf + 00 injected code @size 481 | * @allocated_buf + @size instruction #1 4 482 | * @allocated_buf + @size + 4 instruction #2 2 483 | * @allocated_buf + @size + 4 + 2 jmp @address + 06 5 484 | * 485 | * @param address An address where code should be injected. 486 | * @param bytes Code bytes to be injected at @address 487 | * address. 488 | * @param bytes_size Number of the bytes to be injected. 489 | * @param overwrite_bytes_size The number of original bytes to be 490 | * overwritten and executed after injected code 491 | * 492 | * @return allocated buffer address @allocated_buf 493 | */ 494 | uintptr_t inject_code_using_jmp(uintptr_t address, const uint8_t *bytes, 495 | size_t bytes_size, 496 | size_t overwrite_bytes_size); 497 | 498 | /** 499 | * @brief Injects @bytes_size bytes of code from the @bytes argument into 500 | * remote process at @address address. 501 | * 502 | * @param address An address where code should be injected. 503 | * @param bytes Code bytes to be injected at @address 504 | * address. 505 | * @param bytes_size Number of the bytes to be injected. 506 | * @param overwrite_bytes_size The number of original bytes to be 507 | * overwritten and executed after injected code 508 | * 509 | * @return allocated buffer address @allocated_buf 510 | */ 511 | uintptr_t inject_code_using_push_ret(uintptr_t address, 512 | const uint8_t *bytes, 513 | size_t bytes_size, 514 | size_t overwrite_bytes_size); 515 | 516 | void *_handle; 517 | void *_dbg_handle; 518 | uint32_t _process_id; 519 | std::unordered_map _allocated_memory; 520 | std::unordered_map _virtual_protect; 521 | std::unordered_map _callers; 522 | std::unordered_map _injected_code; 523 | }; 524 | 525 | template inline T ExternalProcess::read(uintptr_t address) const 526 | { 527 | T result; 528 | read_buf(address, sizeof(T), &result); 529 | return result; 530 | } 531 | 532 | template 533 | inline void ExternalProcess::write(uintptr_t address, const T &data) const 534 | { 535 | write_buf(address, sizeof(data), &data); 536 | } 537 | 538 | } /* namespace P1ExternalProcess */ 539 | 540 | #endif /* EXTERNAL_PROCESS_HPP */ 541 | -------------------------------------------------------------------------------- /test/external_process_simulator/Makefile: -------------------------------------------------------------------------------- 1 | # SETTINGS ##################################################################### 2 | EXTERNAL_PROCESS_SIMULATOR_NAME ?= external_process_simulator.exe 3 | APP_NAME:=$(EXTERNAL_PROCESS_SIMULATOR_NAME) 4 | OBJ_DIR:=obj 5 | BIN_DIR:=bin 6 | SRC_ROOT_DIR_S:=src 7 | 8 | C_COMPILER:=i686-w64-mingw32-gcc 9 | CPP_COMPILER:=i686-w64-mingw32-g++ 10 | CMP_FLAGS:=-Wall -Wextra -Werror -O2 -DTEST_RUN 11 | LINKER:=$(CPP_COMPILER) 12 | LINK_FLAGS:=-static #--disable-dynamicbase -Wl,--image-base,0x400000 13 | 14 | # IMPLEMENTATION ############################################################### 15 | build: $(OBJ_DIR) $(BIN_DIR) $(BIN_DIR)/$(APP_NAME) 16 | 17 | rwc=$(sort $(wildcard $1$2)) $(foreach d,$(wildcard $1*),$(call rwc,$d/,$2)) 18 | 19 | SRC_ROOT_DIRS:=$(SRC_ROOT_DIR_S) 20 | SRC_DIRS:=$(sort $(dir $(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*/)))) 21 | 22 | VPATH := $(SRC_DIRS) 23 | 24 | $(OBJ_DIR): 25 | # if not exist "$(OBJ_DIR)" mkdir "$(OBJ_DIR)" 26 | mkdir -p $(OBJ_DIR) 27 | 28 | $(BIN_DIR): 29 | # if not exist "$(BIN_DIR)" mkdir "$(BIN_DIR)" 30 | mkdir -p $(BIN_DIR) 31 | 32 | CPP_FILES:=$(notdir $(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.cpp))) 33 | C_FILES:=$(notdir $(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.c))) 34 | HPP_FILES:=$(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.hpp)) 35 | H_FILES:=$(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.h)) 36 | 37 | H_HPP_DIRS:=$(dir $(HPP_FILES)) $(dir $(H_FILES)) 38 | INCLUDES:=$(addprefix -I, $(H_HPP_DIRS)) 39 | 40 | OBJ_FILES := $(CPP_FILES:%=$(OBJ_DIR)/%.o) $(C_FILES:%=$(OBJ_DIR)/%.o) 41 | 42 | # compile .cpp files 43 | $(OBJ_DIR)/%.cpp.o: %.cpp 44 | $(CPP_COMPILER) -c -o $@ $(CMP_FLAGS) $(INCLUDES) $< 45 | 46 | # compile .c files 47 | $(OBJ_DIR)/%.c.o: %.c 48 | $(C_COMPILER) -c -o $@ $(CMP_FLAGS) $(INCLUDES) $< 49 | 50 | # link 51 | $(BIN_DIR)/$(APP_NAME): $(OBJ_FILES) 52 | $(LINKER) $(LINK_FLAGS) -o $@ $^ 53 | 54 | clean: 55 | # if exist $(BIN_DIR) rmdir /Q /S $(BIN_DIR) 56 | # if exist $(OBJ_DIR) rmdir /Q /S $(OBJ_DIR) 57 | # del /Q /F /S $(BIN_DIR) $(OBJ_DIR) 58 | rm -fr $(BIN_DIR) 59 | rm -fr $(OBJ_DIR) 60 | -------------------------------------------------------------------------------- /test/external_process_simulator/src/external_process_simulator.cpp: -------------------------------------------------------------------------------- 1 | /**----------------------------------------------------------------------------- 2 | ; @file external_process_simulator.cpp 3 | ; 4 | ; @brief 5 | ; The file contains implementation of application used as an external process 6 | ; to test functionality of ExternalProcess class. 7 | ; 8 | ; @usage 9 | ; external_process_simulator.exe [{sum_cdecl,sum_stdcall,sum_thiscall, 10 | ; buffer,buffer_size,obj,loop,wait_for_input}] 11 | ; 12 | ; @parameters 13 | ; sum_cdecl The program will terminate immediately and return the 14 | ; address of @sum_cdecl function. 15 | ; sum_stdcall The program will terminate immediately and return the 16 | ; address of @sum_stdcall function. 17 | ; sum_thiscall The program will terminate immediately and return the 18 | ; address of @sum_thiscall function. 19 | ; buffer The program will terminate immediately and return the 20 | ; address of @buffer array. 21 | ; buffer_size The program will terminate immediately and return the size 22 | ; of @buffer array. 23 | ; obj The program will immediately exit and return the address of 24 | ; @obj object. 25 | ; loop The program will start an infinite loop. 26 | ; wait_for_input The program will start and wait for any line to be entered 27 | ; (pressing Enter) to finish. 28 | ; @author ep1h 29 | -----------------------------------------------------------------------------**/ 30 | #include 31 | #include 32 | 33 | #if !defined __GNUC__ && !defined _MSC_VER 34 | #error Unsupported compiler 35 | #endif 36 | 37 | #if defined __GNUC__ 38 | #define P1_NOINLINE __attribute__((noinline)) 39 | #define P1_CDECL __attribute((cdecl)) 40 | #define P1_STDCALL __attribute((stdcall)) 41 | #else 42 | #if defined _MSC_VER 43 | #define P1_NOINLINE __declspec(noinline) 44 | #define P1_CDECL __cdecl 45 | #define P1_STDCALL __stdcall 46 | #endif 47 | #endif 48 | 49 | char buffer[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 50 | 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 51 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 52 | 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}; 53 | 54 | int P1_NOINLINE P1_CDECL sum_cdecl(int a, int b) 55 | { 56 | int result = a + b; 57 | std::cout << "sum_cdecl(" << a << ", " << b << ") = " << result 58 | << std::endl; 59 | return result; 60 | } 61 | 62 | int P1_NOINLINE P1_STDCALL sum_stdcall(int a, int b) 63 | { 64 | int result = a + b; 65 | std::cout << "sum_stdcall(" << a << ", " << b << ") = " << result 66 | << std::endl; 67 | return result; 68 | } 69 | 70 | class Class 71 | { 72 | public: 73 | int P1_NOINLINE sum_thiscall(int a, int b) 74 | { 75 | int result = a + b; 76 | std::cout << "sum_thiscall(" << a << ", " << b << ") = " << result 77 | << std::endl; 78 | return result; 79 | } 80 | 81 | private: 82 | int _var; 83 | }; 84 | 85 | void print_help(void) 86 | { 87 | std::cout << "Usage:" << std::endl; 88 | std::cout << "\texternal_process_simulator.exe " 89 | "[{sum_cdecl,sum_stdcall,sum_thiscall,buffer,buffer_size,obj," 90 | "loop,wait_for_input}]" 91 | << std::endl; 92 | std::cout << "Parameters:" << std::endl; 93 | std::cout << "\tsum_cdecl - The program will terminate immediately and " 94 | "return the address of 'sum_cdecl' function.\n\tsum_stdcall - " 95 | "The program will terminate immediately and return the " 96 | "address of 'sum_stdcall' function." 97 | << std::endl; 98 | std::cout << "\tsum_thiscall -The program will terminate immediately and " 99 | "return the address of 'sum_thiscall' function." 100 | << std::endl; 101 | std::cout << "\tbuffer - The program will terminate immediately and return " 102 | "the address of 'buffer' array." 103 | << std::endl; 104 | std::cout << "\tbuffer_size - The program will terminate immediately and " 105 | "return the size of 'buffer' array." 106 | << std::endl; 107 | std::cout << "\tobj - The program will immediately exit and return the " 108 | "address of 'obj' object." 109 | << std::endl; 110 | std::cout << "\tloop - The program will start an infinite loop." 111 | << std::endl; 112 | std::cout << "\twait_for_input - The program will start and wait for any " 113 | "line to be entered (pressing Enter) to finish." 114 | << std::endl 115 | << std::endl; 116 | std::cout << "One of the above startup arguments must be passed to the " 117 | "application." 118 | << std::endl; 119 | } 120 | 121 | int main(int argc, char *argv[]) 122 | { 123 | if (argc != 2) 124 | { 125 | print_help(); 126 | return -1; 127 | } 128 | std::cout << std::hex; 129 | Class obj; 130 | auto sum_thiscall_ptr = &Class::sum_thiscall; 131 | 132 | if (argc == 2) 133 | { 134 | if (std::string("sum_cdecl") == argv[1]) 135 | { 136 | std::cout << "sum_cdecl address: " << (void *)sum_cdecl 137 | << std::endl; 138 | return (int)(void *)sum_cdecl; 139 | } 140 | else if (std::string("sum_stdcall") == argv[1]) 141 | { 142 | std::cout << "sum_stdcall address: " << (void *)sum_stdcall 143 | << std::endl; 144 | return (int)(void *)sum_stdcall; 145 | } 146 | else if (std::string("sum_thiscall") == argv[1]) 147 | { 148 | std::cout << "sum_thiscall address: " << (void *&)(sum_thiscall_ptr) 149 | << std::endl; 150 | return (int)(void *&)sum_thiscall_ptr; 151 | } 152 | else if (std::string("buffer") == argv[1]) 153 | { 154 | std::cout << "buffer[" << sizeof(buffer) 155 | << "] address: " << (void *)buffer << std::endl; 156 | return (int)buffer; 157 | } 158 | else if (std::string("buffer_size") == argv[1]) 159 | { 160 | std::cout << "buffer size: " << sizeof(buffer) << std::endl; 161 | return (int)sizeof(buffer); 162 | } 163 | else if (std::string("obj") == argv[1]) 164 | { 165 | std::cout << "Class object address: " << (void *)&obj << std::endl; 166 | return (int)(void *)&obj; 167 | } 168 | else if (std::string("wait_for_input") == argv[1]) 169 | { 170 | std::cout << "Press Enter for exit." << std::endl; 171 | std::cin.get(); 172 | } 173 | else if (std::string("loop") == argv[1]) 174 | { 175 | std::cout << "Infinite loop begins..." << std::endl; 176 | while (true) 177 | { 178 | } 179 | } 180 | } 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /test/unit_tests/Makefile: -------------------------------------------------------------------------------- 1 | # SETTINGS ##################################################################### 2 | EXTERNAL_PROCESS_SIMULATOR_NAME?=external_process_simulator.exe 3 | APP_NAME:=test.exe 4 | OBJ_DIR:=obj 5 | BIN_DIR:=bin 6 | SRC_ROOT_DIR_S:=src ../../src 7 | 8 | C_COMPILER:=i686-w64-mingw32-gcc 9 | CPP_COMPILER:=i686-w64-mingw32-g++ 10 | CMP_FLAGS:=-Wall -Werror -O2 -DTEST_RUN \ 11 | -DEXTERNAL_PROCESS_SIMULATOR_NAME=$(EXTERNAL_PROCESS_SIMULATOR_NAME) 12 | LINKER:=$(CPP_COMPILER) 13 | LINK_FLAGS:=-static 14 | 15 | # IMPLEMENTATION ############################################################### 16 | build: $(OBJ_DIR) $(BIN_DIR) $(BIN_DIR)/$(APP_NAME) 17 | 18 | rwc=$(sort $(wildcard $1$2)) $(foreach d,$(wildcard $1*),$(call rwc,$d/,$2)) 19 | 20 | SRC_ROOT_DIRS:=$(SRC_ROOT_DIR_S) 21 | SRC_DIRS:=$(sort $(dir $(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*/)))) 22 | 23 | VPATH := $(SRC_DIRS) 24 | 25 | $(OBJ_DIR): 26 | # if not exist "$(OBJ_DIR)" mkdir "$(OBJ_DIR)" 27 | mkdir -p $(OBJ_DIR) 28 | 29 | $(BIN_DIR): 30 | # if not exist "$(BIN_DIR)" mkdir "$(BIN_DIR)" 31 | mkdir -p $(BIN_DIR) 32 | 33 | CPP_FILES:=$(notdir $(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.cpp))) 34 | C_FILES:=$(notdir $(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.c))) 35 | HPP_FILES:=$(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.hpp)) 36 | H_FILES:=$(foreach d,$(SRC_ROOT_DIRS),$(call rwc,$(d),*.h)) 37 | 38 | H_HPP_DIRS:=$(dir $(HPP_FILES)) $(dir $(H_FILES)) 39 | INCLUDES:=$(addprefix -I, $(H_HPP_DIRS)) 40 | 41 | OBJ_FILES := $(CPP_FILES:%=$(OBJ_DIR)/%.o) $(C_FILES:%=$(OBJ_DIR)/%.o) 42 | 43 | # compile .cpp files 44 | $(OBJ_DIR)/%.cpp.o: %.cpp 45 | $(CPP_COMPILER) -c -o $@ $(CMP_FLAGS) $(INCLUDES) $< 46 | 47 | # compile .c files 48 | $(OBJ_DIR)/%.c.o: %.c 49 | $(C_COMPILER) -c -o $@ $(CMP_FLAGS) $(INCLUDES) $< 50 | 51 | # link 52 | $(BIN_DIR)/$(APP_NAME): $(OBJ_FILES) 53 | @echo Linking... 54 | $(LINKER) $(LINK_FLAGS) -o $@ $^ 55 | 56 | clean: 57 | # if exist $(BIN_DIR) rmdir /Q /S $(BIN_DIR) 58 | # if exist $(OBJ_DIR) rmdir /Q /S $(OBJ_DIR) 59 | # del /Q /F /S $(BIN_DIR) $(OBJ_DIR) 60 | rm -fr $(BIN_DIR) 61 | rm -fr $(OBJ_DIR) 62 | -------------------------------------------------------------------------------- /test/unit_tests/src/test.h: -------------------------------------------------------------------------------- 1 | /**----------------------------------------------------------------------------- 2 | ; @file test.h 3 | ; 4 | ; @language C99 5 | ; 6 | ; @brief 7 | ; The file contains functionality for writing unit tests. 8 | ; 9 | ; Usage: 10 | ; Define TEST_RUN preprocessor macro to be any non-zero value. 11 | ; Create c/cpp file for writing tests. 12 | ; Include this header file in the created c/cpp file. 13 | ; Include file(s) with the functionality to be tested in the c/cpp file. 14 | ; Describe test cases. 15 | ; Testcase description syntax: 16 | ; - Test cases begin with the TEST_BEGIN statement, followed by the name 17 | ; of the test case in parentheses. 18 | ; - Test cases end with TEST_END statement. 19 | ; Write RUN_TESTS followed by the names of the test cases to be executed 20 | ; in parentheses. 21 | ; 22 | ; Example: 23 | ; 24 | ; #include "test.h" 25 | ; #include "unit_under_test.hpp" 26 | ; 27 | ; TEST_BEGIN(sum_test) 28 | ; int result = unit_under_test_sum(2, 2); 29 | ; EXPECT(result, 4); 30 | ; TEST_END 31 | ; 32 | ; TEST_BEGIN(mul_test) 33 | ; int result = unit_under_test_mul(10, 0); 34 | ; EXPECT_ZERO(result); 35 | ; TEST_END 36 | ; 37 | ; RUN_TESTS(sum_test, mul_test); 38 | ; 39 | ; Each test case described between TEST_BEGIN and TEST_END is a separate 40 | ; function with all the consequences. The namespaces of each test case do not 41 | ; overlap. All local objects are automatically deleted after the test case 42 | ; ends. For dynamically allocated objects, the release of memory falls on the 43 | ; shoulders of the one who writes the test. 44 | ; 45 | ; Tests executed in the order they are specified in arguments for RUN_TESTS. 46 | ; Anyway, results of good test cases should not depend on the order in which 47 | ; they are executed. 48 | ; 49 | ; The RUN_TESTS macro creates an entry point to the program. To build tests, 50 | ; original entry point must be out of scope (for example, placed inside the 51 | ; #ifndef TEST_RUN construct). 52 | ; 53 | ; @author ep1h 54 | ;-----------------------------------------------------------------------------*/ 55 | // TODO: Support unicode. 56 | // TODO: Implement output in file. 57 | // TODO: Implement global initialization function. 58 | // TODO: Support entry points for Windows applications. 59 | // TODO: Expectation macros for strings(data buffers). 60 | // TODO: Implement support for multiple RUN_TESTS(...) calls. 61 | 62 | #ifndef TEST_HPP 63 | #define TEST_HPP 64 | 65 | #ifndef TEST_RUN 66 | #error This file should not be included in non-test builds. Define the build as\ 67 | a test build (define the "TEST_RUN" preprocessor macro to a non-zero value),\ 68 | or exclude this file file from the build. 69 | #endif 70 | 71 | /* Provide access to private and protected members of classes */ 72 | #ifdef __cplusplus 73 | #define private public 74 | #define protected public 75 | #endif 76 | 77 | #include 78 | 79 | #define PRINTF PRINTF_A 80 | #define OUTPUT WRITE_IN_CONSOLE 81 | 82 | #define PRINTF_A printf 83 | 84 | #define WRITE_IN_CONSOLE(format, ...) \ 85 | do \ 86 | { \ 87 | PRINTF(format, ##__VA_ARGS__); \ 88 | } while (0) 89 | 90 | #define TEST_PASS 0 91 | #define TEST_FAIL 1 92 | 93 | #define TOTAL_EXPECTEDS total_expecteds 94 | #define FAILED_EXPECTEDS failed_expecteds 95 | #define TEST_RESULT result 96 | 97 | typedef struct stTestCaseInfo 98 | { 99 | int TOTAL_EXPECTEDS; 100 | int FAILED_EXPECTEDS; 101 | int TEST_RESULT; 102 | } stTestCaseInfo; 103 | 104 | #define TEST_BEGIN(TEST_NAME) \ 105 | static void TEST_NAME(stTestCaseInfo *ti) \ 106 | { \ 107 | int TOTAL_EXPECTEDS = 0; \ 108 | int FAILED_EXPECTEDS = 0; \ 109 | int TEST_RESULT = 0; \ 110 | OUTPUT("Executing test \'%s\'...\n", #TEST_NAME); 111 | 112 | #define TEST_FOOTER \ 113 | do \ 114 | { \ 115 | ti->TOTAL_EXPECTEDS = TOTAL_EXPECTEDS; \ 116 | ti->FAILED_EXPECTEDS = FAILED_EXPECTEDS; \ 117 | ti->TEST_RESULT = TEST_RESULT; \ 118 | } while (0); 119 | 120 | #define TEST_END \ 121 | TEST_FOOTER; \ 122 | return; \ 123 | } 124 | 125 | #define EXPECT(value, expected) \ 126 | do \ 127 | { \ 128 | ++TOTAL_EXPECTEDS; \ 129 | if (value != expected) \ 130 | { \ 131 | ++FAILED_EXPECTEDS; \ 132 | TEST_RESULT = TEST_FAIL; \ 133 | } \ 134 | } while (false) 135 | 136 | #define EXPECT_ZERO(value) EXPECT((value == 0), 1) 137 | 138 | #define EXPECT_NOT_ZERO(value) EXPECT((value == 0), 0) 139 | 140 | #define FORCE_FAIL_TEST \ 141 | TEST_RESULT = TEST_FAIL; \ 142 | TEST_FOOTER; \ 143 | return; 144 | 145 | #define TESTS_SEPARATOR \ 146 | "------------------------------------------------------------------------" \ 147 | "--------\n" 148 | 149 | #define RUN_TESTS(...) \ 150 | int main(int argc, char *argv[]) \ 151 | { \ 152 | unsigned int failed_testcases = 0; \ 153 | void (*tests_array[])(stTestCaseInfo *) = {__VA_ARGS__}; \ 154 | stTestCaseInfo ti = {0}; \ 155 | for (unsigned int i = 0; i < sizeof(tests_array) / sizeof(void *); \ 156 | i++) \ 157 | { \ 158 | tests_array[i](&ti); \ 159 | if (ti.TEST_RESULT == TEST_PASS) \ 160 | { \ 161 | OUTPUT("PASSED "); \ 162 | } \ 163 | else \ 164 | { \ 165 | OUTPUT("FAILED "); \ 166 | ++failed_testcases; \ 167 | } \ 168 | OUTPUT("(%d/%d)\n", ti.TOTAL_EXPECTEDS - ti.FAILED_EXPECTEDS, \ 169 | ti.TOTAL_EXPECTEDS); \ 170 | OUTPUT(TESTS_SEPARATOR); \ 171 | } \ 172 | OUTPUT("Executed tests: %d (%d passed, %d failed).\n", \ 173 | sizeof(tests_array) / sizeof(void *), \ 174 | sizeof(tests_array) / sizeof(void *) - failed_testcases, \ 175 | failed_testcases); \ 176 | return failed_testcases; \ 177 | } 178 | 179 | #endif /* TEST_HPP */ 180 | -------------------------------------------------------------------------------- /test/unit_tests/src/test_utils.cpp: -------------------------------------------------------------------------------- 1 | /**----------------------------------------------------------------------------- 2 | ; @file test_utils.cpp 3 | ; 4 | ; @author ep1h 5 | ;-----------------------------------------------------------------------------*/ 6 | #include "test_utils.hpp" 7 | #include 8 | 9 | const char test_application[] = Q(EXTERNAL_PROCESS_SIMULATOR_NAME); 10 | static HANDLE bkg_sim_process_handle = nullptr; 11 | 12 | static uint32_t run_executable(const char *app, const char *args, 13 | bool background) 14 | { 15 | // std::string command; 16 | // if (background) 17 | // { 18 | // command += "start /MIN "; 19 | // } 20 | // command += app; 21 | // command += " "; 22 | // command += args; 23 | // return system(command.c_str()); 24 | 25 | DWORD result = 0; 26 | 27 | if (background) 28 | { 29 | if (bkg_sim_process_handle) 30 | { 31 | return result; 32 | } 33 | } 34 | 35 | SHELLEXECUTEINFO sei = {0}; 36 | sei.cbSize = sizeof(SHELLEXECUTEINFO); 37 | sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE; 38 | sei.hwnd = NULL; 39 | sei.lpVerb = "open"; 40 | sei.lpFile = app; 41 | sei.lpParameters = args; 42 | sei.lpDirectory = NULL; 43 | sei.nShow = SW_HIDE; 44 | sei.hInstApp = NULL; 45 | ShellExecuteEx(&sei); 46 | if (!background) 47 | { 48 | WaitForSingleObject(sei.hProcess, INFINITE); 49 | GetExitCodeProcess(sei.hProcess, &result); 50 | } 51 | else 52 | { 53 | bkg_sim_process_handle = sei.hProcess; 54 | Sleep(50); 55 | } 56 | return result; 57 | } 58 | 59 | uint32_t run_external_process_simulator(void) 60 | { 61 | return run_executable(test_application, "loop", true); 62 | } 63 | 64 | uint32_t run_external_process_simulator(const char *arg) 65 | { 66 | return run_executable(test_application, arg, false); 67 | } 68 | 69 | void terminate_external_process_simulator(void) 70 | { 71 | // std::string command = "taskkill /IM "; 72 | // command += test_application; 73 | // system(command.c_str()); 74 | 75 | if (bkg_sim_process_handle) 76 | { 77 | TerminateProcess(bkg_sim_process_handle, 9); 78 | bkg_sim_process_handle = nullptr; 79 | } 80 | } 81 | 82 | const stSimulatorInfo *get_sim_info(void) 83 | { 84 | static stSimulatorInfo *si_ptr = nullptr; 85 | if (si_ptr == nullptr) 86 | { 87 | static stSimulatorInfo si; 88 | si.sum_cdecl_function_address = 89 | run_external_process_simulator("sum_cdecl"); 90 | si.sum_stdcall_function_address = 91 | run_external_process_simulator("sum_stdcall"); 92 | si.sum_thiscall_function_address = 93 | run_external_process_simulator("sum_thiscall"); 94 | si.buffer_address = 95 | run_external_process_simulator("buffer"); 96 | si.buffer_size = run_external_process_simulator("buffer_size"); 97 | si_ptr = &si; 98 | } 99 | return si_ptr; 100 | } 101 | -------------------------------------------------------------------------------- /test/unit_tests/src/test_utils.hpp: -------------------------------------------------------------------------------- 1 | /**----------------------------------------------------------------------------- 2 | ; @file test_utils.cpp 3 | ; 4 | ; @brief 5 | ; The file contains auxiliary functionality (e.g., interaction with simulator) 6 | ; for creating and executing tests of ExternalProcess class. 7 | ; 8 | ; @author ep1h 9 | ;-----------------------------------------------------------------------------*/ 10 | #ifndef TEST_UTILS_HPP 11 | #define TEST_UTILS_HPP 12 | 13 | #include 14 | 15 | #define QQ(x) #x 16 | #define Q(x) QQ(x) 17 | 18 | #ifndef EXTERNAL_PROCESS_SIMULATOR_NAME 19 | #define EXTERNAL_PROCESS_SIMULATOR_NAME external_process_simulator.exe 20 | #endif /* EXTERNAL_PROCESS_SIMULATOR_NAME */ 21 | 22 | /* Remote process name. Testing functionality of ExternalProcess class will be 23 | performed based on interaction with this process. */ 24 | extern const char test_application[]; 25 | 26 | struct stSimulatorInfo 27 | { 28 | uint32_t sum_cdecl_function_address; 29 | uint32_t sum_stdcall_function_address; 30 | uint32_t sum_thiscall_function_address; 31 | uint32_t buffer_address; 32 | uint32_t buffer_size; 33 | uint32_t class_object_address; 34 | }; 35 | 36 | uint32_t run_external_process_simulator(void); 37 | uint32_t run_external_process_simulator(const char *arg); 38 | void terminate_external_process_simulator(void); 39 | const stSimulatorInfo *get_sim_info(void); 40 | 41 | #endif /* TEST_UTILS_HPP */ 42 | -------------------------------------------------------------------------------- /test/unit_tests/src/unit_tests.cpp: -------------------------------------------------------------------------------- 1 | /**----------------------------------------------------------------------------- 2 | ; @file unit_tests.cpp 3 | ; 4 | ; @brief 5 | ; The file contains tests for ExternalProcess class. 6 | ; 7 | ; @author ep1h 8 | ;-----------------------------------------------------------------------------*/ 9 | #include 10 | #include "test.h" 11 | #include "test_utils.hpp" 12 | #include "../../../src/external_process.hpp" 13 | 14 | using P1ExternalProcess::ExternalProcess; 15 | 16 | TEST_BEGIN(get_process_id_by_non_existent_process_name) 17 | { 18 | ExternalProcess ep((uint32_t)0); 19 | EXPECT(ep.get_process_id_by_process_name("!@#$^&*()_+"), 0); 20 | } 21 | TEST_END 22 | 23 | TEST_BEGIN(get_process_id_by_existent_process_name) 24 | { 25 | run_external_process_simulator(); 26 | ExternalProcess ep((uint32_t)0); 27 | EXPECT_NOT_ZERO(ep.get_process_id_by_process_name(test_application)); 28 | terminate_external_process_simulator(); 29 | } 30 | TEST_END 31 | 32 | TEST_BEGIN(read_buf) 33 | { 34 | uint32_t buf_addr = get_sim_info()->buffer_address; 35 | uint32_t buf_size = get_sim_info()->buffer_size; 36 | uint8_t *buf = new uint8_t[buf_size]; 37 | run_external_process_simulator(); 38 | ExternalProcess ep(test_application); 39 | ep.read_buf(buf_addr, buf_size, buf); 40 | for (unsigned int i = 0; i < buf_size; i++) 41 | { 42 | EXPECT(buf[i], i); 43 | } 44 | terminate_external_process_simulator(); 45 | delete[] buf; 46 | } 47 | TEST_END 48 | 49 | TEST_BEGIN(write_buf) 50 | { 51 | uint32_t buf_addr = get_sim_info()->buffer_address; 52 | uint32_t buf_size = get_sim_info()->buffer_size; 53 | uint8_t *buf = new uint8_t[buf_size]; 54 | memset(buf, 0xBB, buf_size); 55 | run_external_process_simulator(); 56 | ExternalProcess ep(test_application); 57 | ep.write_buf(buf_addr, buf_size, buf); 58 | memset(buf, 0, buf_size); 59 | ep.read_buf(buf_addr, buf_size, buf); 60 | for (unsigned int i = 0; i < buf_size; i++) 61 | { 62 | EXPECT(buf[i], 0xBB); 63 | } 64 | terminate_external_process_simulator(); 65 | delete[] buf; 66 | } 67 | TEST_END 68 | 69 | TEST_BEGIN(alloc_free) 70 | { 71 | run_external_process_simulator(); 72 | ExternalProcess ep(test_application); 73 | EXPECT(ep._allocated_memory.size(), 0); 74 | uint32_t result_1 = ep.alloc(64); 75 | EXPECT(ep._allocated_memory.size(), 1); 76 | uint32_t result_2 = ep.alloc(32); 77 | EXPECT(ep._allocated_memory.size(), 2); 78 | uint32_t result_3 = ep.alloc(16); 79 | EXPECT(ep._allocated_memory.size(), 3); 80 | 81 | /* Alloc 0 bytes */ 82 | uint32_t result_4 = ep.alloc(0); 83 | EXPECT(result_4, 0); 84 | EXPECT(ep._allocated_memory.size(), 3); 85 | 86 | /* Alloc too many bytes */ 87 | uint32_t result_5 = ep.alloc(-1); 88 | EXPECT(result_5, 0); 89 | EXPECT(ep._allocated_memory.size(), 3); 90 | 91 | /* Free unallocated memory */ 92 | ep.free(0); 93 | EXPECT(ep._allocated_memory.size(), 3); 94 | 95 | EXPECT(ep._allocated_memory.at(result_1), 64); 96 | EXPECT(ep._allocated_memory.at(result_2), 32); 97 | EXPECT(ep._allocated_memory.at(result_3), 16); 98 | ep.free(result_2); 99 | EXPECT(ep._allocated_memory.size(), 2); 100 | ep.free(result_1); 101 | ep.free(result_3); 102 | EXPECT(ep._allocated_memory.size(), 0); 103 | terminate_external_process_simulator(); 104 | } 105 | TEST_END 106 | 107 | TEST_BEGIN(virtual_protect) 108 | { 109 | run_external_process_simulator(); 110 | uint32_t buf_addr = get_sim_info()->buffer_address; 111 | uint32_t buf_size = get_sim_info()->buffer_size; 112 | ExternalProcess ep(test_application); 113 | 114 | /* Create a local buffer */ 115 | uint8_t *buf = new uint8_t[buf_size]; 116 | memset(buf, 0xF9, buf_size); 117 | 118 | /* Set read-only virtual protect */ 119 | ep.set_virtual_protect(buf_addr, buf_size, 120 | P1ExternalProcess::enVirtualProtect::READ); 121 | 122 | /* Try to write created bufer in remote process */ 123 | ep.write_buf(buf_addr, buf_size, buf); 124 | 125 | /* Test that remote process' buffer does not change */ 126 | for (uint32_t i = 0; i < buf_size; i++) 127 | { 128 | EXPECT_ZERO((ep.read(buf_addr + i) == buf[i])); 129 | } 130 | 131 | /* Restore virtual protect */ 132 | ep.restore_virtual_protect(buf_addr); 133 | 134 | /* Try to write again */ 135 | ep.write_buf(buf_addr, buf_size, buf); 136 | 137 | /* Test that remote process' buffer changes */ 138 | for (uint32_t i = 0; i < buf_size; i++) 139 | { 140 | EXPECT(ep.read(buf_addr + i), buf[i]); 141 | } 142 | delete[] buf; 143 | } 144 | TEST_END 145 | 146 | TEST_BEGIN(cdecl_caller) 147 | { 148 | uint32_t cdecl_sum_func_addr = get_sim_info()->sum_cdecl_function_address; 149 | run_external_process_simulator(); 150 | ExternalProcess ep(test_application); 151 | uint32_t sum = ep.call_cdecl_function(cdecl_sum_func_addr, 2, 2, 4); 152 | EXPECT(sum, 6); 153 | terminate_external_process_simulator(); 154 | } 155 | TEST_END 156 | 157 | TEST_BEGIN(stdcall_caller) 158 | { 159 | uint32_t stdcall_sum_func_addr = 160 | get_sim_info()->sum_stdcall_function_address; 161 | run_external_process_simulator(); 162 | ExternalProcess ep(test_application); 163 | uint32_t sum = ep.call_stdcall_function(stdcall_sum_func_addr, 2, 4, 3); 164 | EXPECT(sum, 7); 165 | terminate_external_process_simulator(); 166 | } 167 | TEST_END 168 | 169 | TEST_BEGIN(thiscall_caller) 170 | { 171 | uint32_t thiscall_sum_func_addr = 172 | get_sim_info()->sum_thiscall_function_address; 173 | uint32_t class_obj_address = get_sim_info()->class_object_address; 174 | run_external_process_simulator(); 175 | ExternalProcess ep(test_application); 176 | uint32_t sum = ep.call_thiscall_function(thiscall_sum_func_addr, 177 | class_obj_address, 2, 5, 5); 178 | EXPECT(sum, 10); 179 | terminate_external_process_simulator(); 180 | } 181 | TEST_END 182 | 183 | TEST_BEGIN(jmp_injector) 184 | { 185 | uint32_t cdecl_sum_func_addr = get_sim_info()->sum_cdecl_function_address; 186 | run_external_process_simulator(); 187 | ExternalProcess ep(test_application); 188 | 189 | /* Add 1 to each argument */ 190 | uint8_t injected_bytes[] = { 191 | 0x83, 0x44, 0x24, 0x04, 0x01, /* add DWORD PTR [esp0x4],0x1 */ 192 | 0x83, 0x44, 0x24, 0x08, 0x01, /* add DWORD PTR [esp0x8],0x1 */ 193 | }; 194 | ep.inject_code_using_jmp(cdecl_sum_func_addr, injected_bytes, 195 | sizeof(injected_bytes), 6); 196 | uint32_t sum = ep.call_cdecl_function(cdecl_sum_func_addr, 2, 0, 0); 197 | EXPECT(sum, 2); 198 | sum = ep.call_cdecl_function(cdecl_sum_func_addr, 2, 6, 4); 199 | EXPECT(sum, 12); 200 | terminate_external_process_simulator(); 201 | } 202 | TEST_END 203 | 204 | TEST_BEGIN(push_ret_injector) 205 | { 206 | uint32_t stdcall_sum_func_addr = 207 | get_sim_info()->sum_stdcall_function_address; 208 | run_external_process_simulator(); 209 | ExternalProcess ep(test_application); 210 | 211 | /* Add 2 to each argument */ 212 | uint8_t injected_bytes[] = { 213 | 0x83, 0x44, 0x24, 0x04, 0x02, /* add DWORD PTR [esp0x4],0x2 */ 214 | 0x83, 0x44, 0x24, 0x08, 0x02, /* add DWORD PTR [esp0x8],0x2 */ 215 | }; 216 | ep.inject_code_using_push_ret(stdcall_sum_func_addr, injected_bytes, 217 | sizeof(injected_bytes), 6); 218 | uint32_t sum = ep.call_stdcall_function(stdcall_sum_func_addr, 2, 0, 0); 219 | EXPECT(sum, 4); 220 | sum = ep.call_stdcall_function(stdcall_sum_func_addr, 2, 1, 2); 221 | EXPECT(sum, 7); 222 | terminate_external_process_simulator(); 223 | } 224 | TEST_END 225 | 226 | TEST_BEGIN(uninject) 227 | { 228 | uint32_t cdecl_sum_func_addr = get_sim_info()->sum_cdecl_function_address; 229 | run_external_process_simulator(); 230 | ExternalProcess ep(test_application); 231 | 232 | /* Add 5 to each argument */ 233 | uint8_t injected_bytes[] = { 234 | 0x83, 0x44, 0x24, 0x04, 0x05, /* add DWORD PTR [esp0x4],0x5 */ 235 | 0x83, 0x44, 0x24, 0x08, 0x05, /* add DWORD PTR [esp0x8],0x5 */ 236 | }; 237 | ep.inject_code(cdecl_sum_func_addr, injected_bytes, sizeof(injected_bytes), 238 | 6, P1ExternalProcess::enInjectionType::EIT_JMP); 239 | uint32_t sum = ep.call_cdecl_function(cdecl_sum_func_addr, 2, 0, 0); 240 | EXPECT(sum, 10); 241 | sum = ep.call_cdecl_function(cdecl_sum_func_addr, 2, 6, 4); 242 | EXPECT(sum, 20); 243 | ep.uninject_code(cdecl_sum_func_addr); 244 | sum = ep.call_cdecl_function(cdecl_sum_func_addr, 2, 6, 4); 245 | EXPECT(sum, 10); 246 | terminate_external_process_simulator(); 247 | } 248 | TEST_END 249 | 250 | TEST_BEGIN(signature_scanner) 251 | { 252 | run_external_process_simulator(); 253 | ExternalProcess ep(test_application); 254 | 255 | uint32_t buf_address = get_sim_info()->buffer_address; 256 | uint32_t buf_size = get_sim_info()->buffer_size; 257 | 258 | /* Search for signature in the begin */ 259 | const char *mask_1 = "xxxx"; 260 | const uint8_t sig_1[] = {0x00, 0x01, 0x02, 0x03}; 261 | uint32_t res = ep.find_signature(buf_address, buf_size, sig_1, mask_1); 262 | EXPECT(res, buf_address); 263 | 264 | /* Search for signature in the end */ 265 | const uint8_t sig_2[] = {0x1C, 0x1D, 0x1E, 0x1F}; 266 | res = ep.find_signature(buf_address, buf_size, sig_2, mask_1); 267 | EXPECT(res, buf_address + buf_size - 4); 268 | 269 | /* Search for signature in the ~middle */ 270 | const uint8_t sig_3[] = {0x10, 0x11, 0x12, 0x13}; 271 | res = ep.find_signature(buf_address, buf_size, sig_3, mask_1); 272 | EXPECT(res, buf_address + 0x10); 273 | 274 | /* Search for false signature */ 275 | const uint8_t sig_4[] = {0xFF, 0xBA, 0xDD, 0x11}; 276 | res = ep.find_signature(buf_address, buf_size, sig_4, mask_1); 277 | EXPECT(res, 0); 278 | 279 | /* Search for signature ignoring bytes by the mask */ 280 | const uint8_t sig_5[] = {0x05, 0xBA, 0xAD, 0x08}; 281 | const char *mask_2 = "x??x"; 282 | res = ep.find_signature(buf_address, buf_size, sig_5, mask_2); 283 | EXPECT(res, buf_address + 0x05); 284 | 285 | /* Use the previous signature, but mask is shorter than signature */ 286 | const char *mask_3 = "x??"; 287 | res = ep.find_signature(buf_address, buf_size, sig_5, mask_3); 288 | EXPECT(res, buf_address + 0x05); 289 | 290 | /* Use the previous signature, but mask is longer than signature */ 291 | const char *mask_4 = "x??xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 292 | res = ep.find_signature(buf_address, buf_size, sig_5, mask_4); 293 | EXPECT(res, 0); 294 | 295 | /* Nulls */ 296 | res = ep.find_signature(buf_address, buf_size, 0, mask_1); 297 | EXPECT(res, 0); 298 | res = ep.find_signature(buf_address, buf_size, sig_3, 0); 299 | EXPECT(res, 0); 300 | res = ep.find_signature(buf_address, 0, sig_3, mask_1); 301 | EXPECT(res, 0); 302 | 303 | terminate_external_process_simulator(); 304 | } 305 | TEST_END 306 | 307 | TEST_BEGIN(get_module_name) 308 | { 309 | run_external_process_simulator(); 310 | ExternalProcess ep(test_application); 311 | 312 | EXPECT_NOT_ZERO(ep.get_module_address(test_application)); 313 | EXPECT_NOT_ZERO(ep.get_module_address("ntdll.dll")); 314 | EXPECT_ZERO(ep.get_module_address("!@#$%^&*()_")); 315 | EXPECT_ZERO(ep.get_module_address("")); 316 | EXPECT_ZERO(ep.get_module_address(NULL)); 317 | 318 | terminate_external_process_simulator(); 319 | } 320 | TEST_END 321 | 322 | RUN_TESTS(get_process_id_by_non_existent_process_name, 323 | get_process_id_by_existent_process_name, read_buf, write_buf, 324 | alloc_free, virtual_protect, cdecl_caller, stdcall_caller, thiscall_caller, 325 | jmp_injector, push_ret_injector, uninject, signature_scanner, 326 | get_module_name); 327 | --------------------------------------------------------------------------------