Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add signature scanning #58

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 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
38 changes: 38 additions & 0 deletions docs/auto-splitters.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,44 @@ end

* Cheat Engine is a tool that allows you to easily find Addresses and Pointer Paths for those Addresses, so you don't need to debug the game to figure out the structure of the memory.

## sig_scan
sig_scan performs a signature/pattern scan on the provided IDA-style byte array and optional integer offset and returns a string representation of the found address

Example:
`signature = sig_scan("89 5C 24 ?? 89 44 24 ?? 74 ?? 48 8D 15", 4)`

Returns:
`"14123ce19"`

### Notes
* Lua automatically handles the conversion of hexadecimal strings to numbers so parsing/casting it manually is not required.
* Until the address is found, sig_scan returns a 0.
* Signature scanning is an expensive action. So, once an address has been found, it's recommended to reassign the sig_scan variable with the result of the sig_scan function to stop the scanning.


Mini example script with the game SPRAWL:
```lua
process('Sprawl-Win64-Shipping.exe')

local featuretest = 0

function state()
-- Perform the signature scan to find the initial address
featuretest = sig_scan("89 5C 24 ?? 89 44 24 ?? 74 ?? 48 8D 15", 4)

if featuretest == 0 then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem here, this will NEVER be true (if sig_scan returns "0")
image

print("Signature scan did not find the address.")
else
-- Read an integer value from the found address
local readValue = readAddress('int', 'Sprawl-Win64-Shipping.exe', featuretest)
print("Feature test address: ", featuretest)
print("Read value: ", readValue)
end
end
```



## getPID
* Returns the current PID

Expand Down
3 changes: 3 additions & 0 deletions src/auto-splitter.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "memory.h"
#include "process.h"
#include "settings.h"
#include "signature.h"

char auto_splitter_file[PATH_MAX];
int refresh_rate = 60;
Expand Down Expand Up @@ -323,6 +324,8 @@ void run_auto_splitter()
lua_setglobal(L, "process");
lua_pushcfunction(L, read_address);
lua_setglobal(L, "readAddress");
lua_pushcfunction(L, perform_sig_scan);
lua_setglobal(L, "sig_scan");
lua_pushcfunction(L, getPid);
lua_setglobal(L, "getPID");

Expand Down
234 changes: 234 additions & 0 deletions src/signature.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#include "signature.h"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

#include "memory.h"
#include "process.h"

#include <luajit.h>

extern game_process process;

// Error handling macro
#define HANDLE_ERROR(msg) \
do { \
perror(msg); \
return NULL; \
} while (0)

// Error logging function
void log_error(const char* format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "Error in sig_scan: ");
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}

MemoryRegion* get_memory_regions(pid_t pid, int* count)
{
char maps_path[256];
if (snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid) < 0) {
HANDLE_ERROR("Failed to create maps path");
}

FILE* maps_file = fopen(maps_path, "r");
if (!maps_file) {
HANDLE_ERROR("Failed to open maps file");
}

MemoryRegion* regions = NULL;
int capacity = 0;
*count = 0;

char line[256];
while (fgets(line, sizeof(line), maps_file)) {
if (*count >= capacity) {
capacity = capacity == 0 ? 10 : capacity * 2;
MemoryRegion* temp = realloc(regions, capacity * sizeof(MemoryRegion));
if (!temp) {
free(regions);
fclose(maps_file);
HANDLE_ERROR("Failed to allocate memory for regions");
}
regions = temp;
}

uintptr_t start, end;
if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR, &start, &end) != 2) {
continue; // Skip lines that don't match the expected format
}
regions[*count].start = start;
regions[*count].end = end;
(*count)++;
}

fclose(maps_file);
return regions;
}

bool match_pattern(const uint8_t* data, const uint16_t* pattern, size_t pattern_size)
{
for (size_t i = 0; i < pattern_size; ++i) {
uint8_t byte = pattern[i] & 0xFF;
bool ignore = (pattern[i] >> 8) & 0x1;
if (!ignore && data[i] != byte) {
return false;
}
}
return true;
}

uint16_t* convert_signature(const char* signature, size_t* pattern_size)
{
char* signature_copy = strdup(signature);
if (!signature_copy) {
return NULL;
}

char* token = strtok(signature_copy, " ");
size_t size = 0;
size_t capacity = 10;
uint16_t* pattern = (uint16_t*)malloc(capacity * sizeof(uint16_t));
if (!pattern) {
free(signature_copy);
return NULL;
}

while (token != NULL) {
if (size >= capacity) {
capacity *= 2;
uint16_t* temp = (uint16_t*)realloc(pattern, capacity * sizeof(uint16_t));
if (!temp) {
free(pattern);
free(signature_copy);
return NULL;
}
pattern = temp;
}

if (strcmp(token, "??") == 0) {
pattern[size] = 0xFF00; // Set the upper byte to 1 to indicate ignoring this byte
} else {
pattern[size] = strtol(token, NULL, 16);
}
size++;
token = strtok(NULL, " ");
}

free(signature_copy);
*pattern_size = size;
return pattern;
}

bool validate_process_memory(pid_t pid, uintptr_t address, void* buffer, size_t size)
{
struct iovec local_iov = { buffer, size };
struct iovec remote_iov = { (void*)address, size };
ssize_t nread = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0);

return nread == (ssize_t)size;
}

int perform_sig_scan(lua_State* L)
{
if (lua_gettop(L) != 2) {
log_error("Invalid number of arguments: expected 2 (signature, offset)");
lua_pushnil(L);
return 1;
}

if (!lua_isstring(L, 1) || !lua_isnumber(L, 2)) {
log_error("Invalid argument types: expected (string, number)");
lua_pushnil(L);
return 1;
}

pid_t p_pid = process.pid;
const char* signature = lua_tostring(L, 1);
int offset = lua_tointeger(L, 2);

// Validate signature string
if (strlen(signature) == 0) {
log_error("Signature string cannot be empty");
lua_pushnil(L);
return 1;
}

size_t pattern_length;
uint16_t* pattern = convert_signature(signature, &pattern_length);
if (!pattern) {
log_error("Failed to convert signature");
lua_pushnil(L);
return 1;
}

int regions_count = 0;
MemoryRegion* regions = get_memory_regions(p_pid, &regions_count);
if (!regions) {
free(pattern);
log_error("Failed to get memory regions");
lua_pushnil(L);
return 1;
}

for (int i = 0; i < regions_count; i++) {
MemoryRegion region = regions[i];
ssize_t region_size = region.end - region.start;
uint8_t* buffer = malloc(region_size);
if (!buffer) {
free(pattern);
free(regions);
log_error("Failed to allocate memory for region buffer");
lua_pushnil(L);
return 1;
}

if (!validate_process_memory(p_pid, region.start, buffer, region_size)) {
free(buffer);
continue; // Continue to next region
}

for (size_t j = 0; j <= region_size - pattern_length; ++j) {
if (match_pattern(buffer + j, pattern, pattern_length)) {
uintptr_t result = region.start + j + offset;

char full_hex_str[32]; // Increased buffer size for safety
if (snprintf(full_hex_str, sizeof(full_hex_str), "0x%" PRIxPTR, result) < 0) {
free(buffer);
free(pattern);
free(regions);
log_error("Failed to convert result to string");
lua_pushnil(L);
return 1;
}

free(buffer);
free(pattern);
free(regions);

lua_pushstring(L, full_hex_str);
return 1;
}
}

free(buffer);
}

free(pattern);
free(regions);

// No match found
log_error("No match found for the given signature");
lua_pushnil(L);
return 1;
}
21 changes: 21 additions & 0 deletions src/signature.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef __SIGNATURE_H__
#define __SIGNATURE_H__

#include <luajit.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>

typedef struct {
uintptr_t start;
uintptr_t end;
} MemoryRegion;

MemoryRegion* get_memory_regions(pid_t pid, int* count);
bool match_pattern(const uint8_t* data, const uint16_t* pattern, size_t pattern_size);
uint16_t* convert_signature(const char* signature, size_t* pattern_size);
bool validate_process_memory(pid_t pid, uintptr_t address, void* buffer, size_t size);
int perform_sig_scan(lua_State* L);

#endif /* __SIGNATURE_H__ */