Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<!-- ROADMAP -->
- [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 -->
## Roadmap

See the [open issues](https://github.com/lucas-engen/libhack/issues) for a list of proposed features (and known issues).
Expand Down
119 changes: 112 additions & 7 deletions src/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
* @return true On success
* @return false On error
*/
static bool libhack_perform_check(struct libhack_handle *handle,

Check warning on line 83 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make the type of this parameter a pointer-to-const. The current type of "handle" is "struct libhack_handle *".

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPO&open=AaCwc9_mGvjr3YIaPDPO&pullRequest=32
enum CHECK_TYPES type)
{
bool bCheck = true;
Expand All @@ -88,7 +88,7 @@
if (type & WRITE_CHECK || type & READ_CHECK)
{
// Checks if the process can be opened for read or write
if ((!handle) || !(handle->bProcessIsOpen))

Check warning on line 91 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this "if" statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPP&open=AaCwc9_mGvjr3YIaPDPP&pullRequest=32
return false;
}

Expand Down Expand Up @@ -192,7 +192,7 @@
if (!hSnapshot)
{
libhack_debug("Failed to create snapshot");
return 0;

Check failure on line 195 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Potential leak of memory pointed to by 'entry'

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPW&open=AaCwc9_mGvjr3YIaPDPW&pullRequest=32
}

if (!Process32First(hSnapshot, entry))
Expand Down Expand Up @@ -403,7 +403,7 @@
}

/* Read memory at the specified address */
if (!ReadProcessMemory(handle->hProcess, (const void *)addr, (void *)&value,

Check warning on line 406 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

cast to 'const void *' from smaller integer type 'DWORD' (aka 'unsigned long')

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPJ&open=AaCwc9_mGvjr3YIaPDPJ&pullRequest=32
sizeof(int), &readed))
{
libhack_debug("Failed to read memory: %lu", GetLastError());
Expand All @@ -427,7 +427,7 @@
}

/* Write memory at the specified address */
if (!WriteProcessMemory(handle->hProcess, (void *)addr, (const void *)&value,

Check warning on line 430 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

cast to 'void *' from smaller integer type 'DWORD' (aka 'unsigned long')

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPK&open=AaCwc9_mGvjr3YIaPDPK&pullRequest=32
sizeof(int), &written))
{
libhack_debug("Failed to write memory: %lu\n", GetLastError());
Expand Down Expand Up @@ -462,7 +462,7 @@

/* Checks if we have a base address already */
if (handle->base_addr)
return handle->base_addr;

Check warning on line 465 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

implicit conversion loses integer precision: 'DWORD64' (aka 'unsigned long long') to 'DWORD' (aka 'unsigned long')

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPL&open=AaCwc9_mGvjr3YIaPDPL&pullRequest=32

long count = libhack_get_modules_count(handle, filter);
libhack_assert_or_return(count > 0, 0);
Expand Down Expand Up @@ -491,7 +491,7 @@
if (strnicmp(procName, handle->process_name,
strlen(handle->process_name)) == 0)
{
DWORD modAddr = (DWORD)modules[i];

Check failure on line 494 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

An integral type is too small to hold a pointer value.

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPQ&open=AaCwc9_mGvjr3YIaPDPQ&pullRequest=32
handle->hModule = modules[i];

// Free memory
Expand Down Expand Up @@ -557,7 +557,7 @@
}

/* Write memory at the specified address */
if (!WriteProcessMemory(handle->hProcess, (void *)addr, string, string_len,

Check warning on line 560 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

cast to 'void *' from smaller integer type 'DWORD' (aka 'unsigned long')

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPM&open=AaCwc9_mGvjr3YIaPDPM&pullRequest=32
&written))
{
libhack_debug("Failed to write memory: %lu\n", GetLastError());
Expand Down Expand Up @@ -605,7 +605,7 @@
void *pDllPath = NULL;
DWORD threadId;
HANDLE hRemoteThread = NULL;
DWORD waitStatus;

Check warning on line 608 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

unused variable 'waitStatus'

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPN&open=AaCwc9_mGvjr3YIaPDPN&pullRequest=32
HANDLE hKernel32 = NULL;
size_t dll_path_len = 0;

Expand Down Expand Up @@ -721,9 +721,9 @@
// Convert basename to lowercase
strlwr(basename);

if (strnicmp(basename, module_name, strlen(module_name)) == 0)

Check failure on line 724 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPR&open=AaCwc9_mGvjr3YIaPDPR&pullRequest=32
{
addr = (DWORD)modules[i];

Check failure on line 726 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

An integral type is too small to hold a pointer value.

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPS&open=AaCwc9_mGvjr3YIaPDPS&pullRequest=32
break;
}
}
Expand Down Expand Up @@ -776,7 +776,7 @@
{
strlwr(basename);

if (strnicmp(basename, module_name, strlen(module_name)) == 0)

Check failure on line 779 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPT&open=AaCwc9_mGvjr3YIaPDPT&pullRequest=32
{
addr = (DWORD64)modules[i];
break;
Expand All @@ -790,7 +790,7 @@
return addr;
}

LIBHACK_API DWORD64 libhack_getsubmodule_addr64v2(struct libhack_handle *handle,

Check warning on line 793 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make the type of this parameter a pointer-to-const. The current type of "handle" is "struct libhack_handle *".

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPU&open=AaCwc9_mGvjr3YIaPDPU&pullRequest=32
const char *module_name)
{
HANDLE hSnapshot;
Expand Down Expand Up @@ -854,7 +854,7 @@

if (NULL != fnIsWow64Process)
{
if (!fnIsWow64Process(hProcess, &bIsWow64))

Check warning on line 857 in src/process.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this "if" statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=lucasvmx_libhack&issues=AaCwc9_mGvjr3YIaPDPV&open=AaCwc9_mGvjr3YIaPDPV&pullRequest=32
{
// Set error
*error = GetLastError();
Expand Down Expand Up @@ -1078,8 +1078,8 @@
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;
Expand All @@ -1100,11 +1100,116 @@
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],
&current_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);
}
Expand Down
43 changes: 40 additions & 3 deletions src/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
30 changes: 30 additions & 0 deletions src/unit_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand All @@ -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);
Expand Down