├── .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 |
--------------------------------------------------------------------------------