diff --git a/README.md b/README.md index 442f202..5d2f303 100644 --- a/README.md +++ b/README.md @@ -203,11 +203,29 @@ _For more examples, please refer to examples below_ - [Reading a memory address - Windows](src/examples/windows/read_addr.c) - [Writing to a memory address - Windows](src/examples/windows/write_addr.c) - [Getting PID from a process - Windows](src/examples/windows/pid.c) -- [Injecting DLL into another process - Windows](src/examples/windows/dll_inject.c) - - [Dll Source Code - Windows](src/examples/windows/hello.c) -- [Writing to a memory address - Linux](src/examples/linux/write_addr.c) - - +- [Injecting DLL into another process - Windows](src/examples/windows/dll_inject.c) + - [Dll Source Code - Windows](src/examples/windows/hello.c) +- [Writing to a memory address - Linux](src/examples/linux/write_addr.c) + +### Pointer chains on Linux + +Use `libhack_read_int_from_pointer_chain64` or +`libhack_write_int_to_pointer_chain64` for a pointer path copied from Cheat +Engine. For `offsets = {0x10, 0x20, 0x8}`, libhack resolves +`*(*(base_address + 0x10) + 0x20) + 0x8` and performs the operation at the +resulting address. + +```c +const DWORD64 offsets[] = {0x10, 0x20, 0x8}; +int value; + +long status = libhack_read_int_from_pointer_chain64( + handle, base_address, offsets, arraySize(offsets), &value); +``` + +The target process must have the same pointer width as the libhack process. + + ## Roadmap See the [open issues](https://github.com/lucas-engen/libhack/issues) for a list of proposed features (and known issues). diff --git a/src/process.c b/src/process.c index ae7e842..2242f80 100644 --- a/src/process.c +++ b/src/process.c @@ -1078,8 +1078,8 @@ long libhack_get_base_addr64(struct libhack_handle *handle) return libhack_get_base_addr(handle); } -long libhack_read_int_from_addr64(const struct libhack_handle *handle, - DWORD64 addr, int *value) +long libhack_read_int_from_addr64(const struct libhack_handle *handle, + DWORD64 addr, int *value) { struct iovec local; struct iovec remote; @@ -1100,11 +1100,116 @@ long libhack_read_int_from_addr64(const struct libhack_handle *handle, return errno; } - return LIBHACK_OK; -} - -long libhack_write_int_to_addr(const struct libhack_handle *handle, DWORD addr, - int value) + return LIBHACK_OK; +} + +static long libhack_read_pointer_from_addr64(const struct libhack_handle *handle, + DWORD64 addr, + DWORD64 *pointer_value) +{ + struct iovec local; + struct iovec remote; + uintptr_t value; + ssize_t readed; + + if (handle == NULL || pointer_value == NULL) + return -1; + + local.iov_base = &value; + local.iov_len = sizeof(value); + remote.iov_base = (void *)(uintptr_t)addr; + remote.iov_len = sizeof(value); + + readed = process_vm_readv(handle->pid, &local, 1, &remote, 1, 0); + if (readed == -1 || readed != (ssize_t)sizeof(value)) + { + libhack_err("Failed to read pointer at address %llx from %d: %d", addr, + handle->pid, errno); + return readed == -1 ? errno : EIO; + } + + *pointer_value = (DWORD64)value; + return LIBHACK_OK; +} + +long libhack_resolve_pointer_chain64(struct libhack_handle *handle, + DWORD64 base_addr, + const DWORD64 *offsets, + size_t offset_count, + DWORD64 *target_addr) +{ + DWORD64 current_addr; + size_t index; + long status; + + if (handle == NULL || offsets == NULL || offset_count == 0 || + target_addr == NULL) + return -1; + + if (handle->pid == -1) + { + errno = 0; + if (libhack_get_process_id(handle) == -1) + return errno != 0 ? errno : ESRCH; + } + + current_addr = base_addr; + for (index = 0; index + 1 < offset_count; ++index) + { + if (UINT64_MAX - current_addr < offsets[index]) + return EOVERFLOW; + + status = libhack_read_pointer_from_addr64(handle, + current_addr + offsets[index], + ¤t_addr); + if (status != LIBHACK_OK) + return status; + } + + if (UINT64_MAX - current_addr < offsets[offset_count - 1]) + return EOVERFLOW; + + *target_addr = current_addr + offsets[offset_count - 1]; + return LIBHACK_OK; +} + +long libhack_read_int_from_pointer_chain64(struct libhack_handle *handle, + DWORD64 base_addr, + const DWORD64 *offsets, + size_t offset_count, int *value) +{ + DWORD64 target_addr; + long status; + + if (value == NULL) + return -1; + + status = libhack_resolve_pointer_chain64(handle, base_addr, offsets, + offset_count, &target_addr); + if (status != LIBHACK_OK) + return status; + + return libhack_read_int_from_addr64(handle, target_addr, value); +} + +long libhack_write_int_to_pointer_chain64(struct libhack_handle *handle, + DWORD64 base_addr, + const DWORD64 *offsets, + size_t offset_count, int value) +{ + DWORD64 target_addr; + long status; + + status = libhack_resolve_pointer_chain64(handle, base_addr, offsets, + offset_count, &target_addr); + if (status != LIBHACK_OK) + return status; + + return libhack_write_int_to_addr64(handle, target_addr, value); +} + +long libhack_write_int_to_addr(const struct libhack_handle *handle, DWORD addr, + int value) { return libhack_write_int_to_addr64(handle, (DWORD64)addr, value); } diff --git a/src/process.h b/src/process.h index 8a83705..41c21f0 100644 --- a/src/process.h +++ b/src/process.h @@ -199,9 +199,46 @@ bool libhack_process_is_running(struct libhack_handle *handle); int libhack_write_string_to_addr(const struct libhack_handle *handle, DWORD addr, const char *string, size_t string_len); int libhack_write_string_to_addr64(const struct libhack_handle *handle, DWORD64 addr, const char *string, size_t string_len); -__int64_t libhack_read_int64_from_addr64(const struct libhack_handle *handle, DWORD64 addr); - -#endif +__int64_t libhack_read_int64_from_addr64(const struct libhack_handle *handle, DWORD64 addr); + +/** + * @brief Resolve a Cheat Engine-style pointer chain in a Linux process. + * + * Given a base address and offsets {a, b, c}, this resolves the target as + * `*(*(base + a) + b) + c`. All offsets except the last one are followed as + * native pointers of the process running libhack. The target process must use + * the same pointer width as libhack. + * + * @param handle Handle returned by libhack_init. + * @param base_addr Static or module-relative base address. + * @param offsets Array containing at least one offset. + * @param offset_count Number of entries in offsets. + * @param target_addr Receives the resolved final address. + * @return LIBHACK_OK on success; otherwise errno or -1 for invalid arguments. + */ +long libhack_resolve_pointer_chain64(struct libhack_handle *handle, + DWORD64 base_addr, + const DWORD64 *offsets, + size_t offset_count, + DWORD64 *target_addr); + +/** + * @brief Read an int through a Cheat Engine-style pointer chain. + */ +long libhack_read_int_from_pointer_chain64(struct libhack_handle *handle, + DWORD64 base_addr, + const DWORD64 *offsets, + size_t offset_count, int *value); + +/** + * @brief Write an int through a Cheat Engine-style pointer chain. + */ +long libhack_write_int_to_pointer_chain64(struct libhack_handle *handle, + DWORD64 base_addr, + const DWORD64 *offsets, + size_t offset_count, int value); + +#endif #ifdef __cplusplus } diff --git a/src/unit_test.c b/src/unit_test.c index a8c2707..19e0fc6 100644 --- a/src/unit_test.c +++ b/src/unit_test.c @@ -142,6 +142,11 @@ static void test_process_memory(void) const char replacement[] = "after"; int target_int = 1234; int read_int = 0; + uintptr_t first_pointer; + uintptr_t second_pointer; + const DWORD64 pointer_offsets[] = {0, 0, 0}; + const DWORD64 overflow_offset[] = {1}; + DWORD64 resolved_address = 0; int64_t target_int64 = INT64_C(0x1020304050607080); struct libhack_handle *handle; long base_address; @@ -162,6 +167,16 @@ static void test_process_memory(void) EXPECT(libhack_write_string_to_addr64(NULL, 0, replacement, sizeof(replacement)) == -1); EXPECT(libhack_read_int64_from_addr64(NULL, 0) == -1); + EXPECT(libhack_resolve_pointer_chain64(NULL, 0, pointer_offsets, + arraySize(pointer_offsets), + &resolved_address) == -1); + EXPECT(libhack_resolve_pointer_chain64(handle, 0, NULL, 1, + &resolved_address) == -1); + EXPECT(libhack_resolve_pointer_chain64(handle, 0, pointer_offsets, 0, + &resolved_address) == -1); + EXPECT(libhack_resolve_pointer_chain64(handle, UINT64_MAX, + overflow_offset, 1, + &resolved_address) == EOVERFLOW); EXPECT(libhack_get_process_id(handle) == getpid()); read_result = libhack_read_int_from_addr64( @@ -177,6 +192,21 @@ static void test_process_memory(void) EXPECT(read_result == LIBHACK_OK); EXPECT(read_int == target_int); + first_pointer = (uintptr_t)&second_pointer; + second_pointer = (uintptr_t)&target_int; + EXPECT(libhack_resolve_pointer_chain64( + handle, (DWORD64)(uintptr_t)&first_pointer, pointer_offsets, + arraySize(pointer_offsets), &resolved_address) == LIBHACK_OK); + EXPECT(resolved_address == (DWORD64)(uintptr_t)&target_int); + EXPECT(libhack_read_int_from_pointer_chain64( + handle, (DWORD64)(uintptr_t)&first_pointer, pointer_offsets, + arraySize(pointer_offsets), &read_int) == LIBHACK_OK); + EXPECT(read_int == target_int); + EXPECT(libhack_write_int_to_pointer_chain64( + handle, (DWORD64)(uintptr_t)&first_pointer, pointer_offsets, + arraySize(pointer_offsets), 2468) == LIBHACK_OK); + EXPECT(target_int == 2468); + base_address = libhack_get_base_addr(handle); EXPECT(base_address > 0); EXPECT(handle->base_addr == base_address);