PTRACE, таким образом, инъекция не удается (~ 90% случаев), длопен возвращает значение мусораLinux

Ответить
Anonymous
 PTRACE, таким образом, инъекция не удается (~ 90% случаев), длопен возвращает значение мусора

Сообщение Anonymous »

Я узнаю о впрыске процесса в Linux (x86-64), и я пытаюсь написать программу C для внедрения общего объекта (.so) в целевой процесс с использованием ptrace.
Мой подход заключается в следующем:
Запуск целевого процесса. /> он находит адрес dlopen в пространстве памяти цели, вычисляя смещение из Libc. < /p>
Это сохраняет текущие регистры цели (user_regs_struct). < /p>
Записывает путь к моей платежной нагрузке. Текущий указатель инструкции (RIP). < /p>
Это изменяет регистры, чтобы вызвать Dlopen: RIP установлен на адрес Dlopen, а RDI, RSI установлены с аргументами. Поддельный адрес возврата (местоположение точки останова) подталкивается в стек. /root/code/c/injected.txt, чтобы доказать, что он был загружен и выполнен. Это всего лишь около 10% случаев. The logs show that dlopen returns a small, garbage-like value in the rax register (e.g., 0xdb).
In the rare 10% of cases where it succeeds, the file is created, and the logs show that dlopen returns a valid, high-memory address pointer, which is the expected handle for the loaded library.
My Question:
Какова конкретная причина этого частых сбоев? Почему dlopen возвращает значение мусора вместо 0 (для ошибки) или допустимого направления? />/root/code/c/so_injector.cобразное#define _GNU_SOURCE // Must be defined before all includes to enable RTLD_DEFAULT
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

// Function: Get the base address of a specified library in the target process
void* get_lib_base_addr(pid_t pid, const char* lib_name) {
char maps_path[256];
snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);

FILE* maps_file = fopen(maps_path, "r");
if (!maps_file) {
perror("fopen maps");
return NULL;
}

char line[512];
void* base_addr = NULL;
while (fgets(line, sizeof(line), maps_file)) {
// We are looking for the r-xp (read-executable) memory segment, which is the code segment
if (strstr(line, lib_name) && strstr(line, "r-xp")) {
base_addr = (void*)strtoul(line, NULL, 16);
break;
}
}

fclose(maps_file);
return base_addr;
}

// Function: Write data to the target process via ptrace
int ptrace_write(pid_t pid, void* addr, const void* data, size_t len) {
const long* src = (const long*)data;
// POKEDATA writes one word at a time, so we loop to write
for (size_t i = 0; i < len; i += sizeof(long)) {
if (ptrace(PTRACE_POKEDATA, pid, (char*)addr + i, *src++) == -1) {
perror("ptrace_write POKEDATA");
return -1;
}
}
return 0;
}

// --- The following are helper functions for automatically managing the target process ---
pid_t get_target_pid() {
FILE *fp = fopen("/tmp/target.pid", "r");
if (!fp) return -1;
pid_t pid = 0;
fscanf(fp, "%d", &pid);
fclose(fp);
return pid;
}

void kill_old_target() {
pid_t old_pid = get_target_pid();
if (old_pid > 0) {
kill(old_pid, SIGKILL);
}
remove("/tmp/target.pid");
}

int main(int argc, char* argv[]) {
// --- Automatically manage the target process ---
printf("[+] Managing target process...\n");
kill_old_target(); // Kill any existing target process

pid_t target_pid = fork();
if (target_pid == 0) {
// Child process: execute the target program, do not redirect output, so that the output is visible
// If you need to log the output to a file, you can open a file for redirection
int fd = open("/tmp/target_output.txt", O_CREAT | O_WRONLY | O_APPEND, 0644);
if (fd != -1) {
dup2(fd, 2); // Only redirect stderr, keep stdout visible
close(fd);
}
execl("./target", "./target", (char*)NULL);
perror("execl"); // If execl returns, it means an error occurred
exit(1);
} else if (target_pid < 0) {
perror("fork");
return 1;
}

// Parent process: save PID and wait
FILE* fp = fopen("/tmp/target.pid", "w");
if(fp) {
fprintf(fp, "%d", target_pid);
fclose(fp);
}

printf("[+] Target process started with PID: %d\n", target_pid);

// =========================================================================
// === Key modification: Point the path to a directory with loose permissions ===
// =========================================================================
const char* payload_path = "/root/code/c/payload.so";
// =========================================================================

const char* libc_map_name = "/libc-";

// Poll to wait for the target process to load libc.so
printf("[+] Waiting for target to load libc.so...\n");
void* target_libc_base = NULL;
int attempts = 0;
const int max_attempts = 50; // 5 second timeout

while (attempts < max_attempts) {
target_libc_base = get_lib_base_addr(target_pid, libc_map_name);
if (target_libc_base) {
printf("[+] libc.so found in target process after %dms.\n", attempts * 100);
break;
}
usleep(100000); // Wait for 100ms
attempts++;
}

if (!target_libc_base) {
fprintf(stderr, "Timeout: Failed to find libc.so in target process.\n");
kill(target_pid, SIGKILL);
return 1;
}
printf(" -> Target's libc base: %p\n", target_libc_base);

// 1. Calculate the address of dlopen in the target process
printf("[+] Calculating address of dlopen in target process...\n");
void* self_libc_base = get_lib_base_addr(getpid(), libc_map_name);
if (!self_libc_base) { fprintf(stderr, "Failed to get self libc base address.\n"); kill(target_pid, SIGKILL); return 1; }
printf(" -> Injector's libc base: %p\n", self_libc_base);

void* self_dlopen_addr = dlsym(RTLD_DEFAULT, "dlopen"); // Use RTLD_DEFAULT for better portability
if (!self_dlopen_addr) { fprintf(stderr, "dlsym failed for dlopen: %s\n", dlerror()); kill(target_pid, SIGKILL); return 1; }
printf(" -> Injector's dlopen addr: %p\n", self_dlopen_addr);

long dlopen_offset = (long)self_dlopen_addr - (long)self_libc_base;
printf(" -> dlopen offset: 0x%lx\n", dlopen_offset);

void* target_dlopen_addr = (void*)((long)target_libc_base + dlopen_offset);
printf("[+] Calculated target dlopen address: %p\n", target_dlopen_addr);

// 2. Attach to target and save registers
printf("[+] Attaching to process %d...\n", target_pid);
if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) { perror("ptrace attach"); return 1; }
waitpid(target_pid, NULL, 0);
printf("[+] Attached.\n");

struct user_regs_struct old_regs;
if (ptrace(PTRACE_GETREGS, target_pid, NULL, &old_regs) == -1) { perror("ptrace GETREGS"); ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return 1; }
printf("[+] Saved original registers. RIP: 0x%llx\n", old_regs.rip);

// 3. Write payload path to target's memory (stack)
void* remote_path_addr = (void*)(old_regs.rsp - strlen(payload_path) - 256); // Allocate space on the stack
printf("[+] Writing payload path to remote stack at %p\n", remote_path_addr);
if (ptrace_write(target_pid, remote_path_addr, payload_path, strlen(payload_path) + 1) == -1) {
ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return 1;
}

// =========================================================================
// === New part: 1. Set a software breakpoint (INT 3) ===
// =========================================================================
printf("[+] Setting software breakpoint at original RIP: %p\n", (void*)old_regs.rip);

// Read and save the original instruction
long original_instruction = ptrace(PTRACE_PEEKTEXT, target_pid, (void*)old_regs.rip, NULL);
if (original_instruction == -1) { perror("ptrace PEEKTEXT"); ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return 1; }
printf(" -> Original instruction: 0x%lx\n", original_instruction);

// Construct the breakpoint instruction (0xCC)
long breakpoint_instruction = (original_instruction & ~0xFF) | 0xCC;

// Write the breakpoint instruction
if (ptrace(PTRACE_POKETEXT, target_pid, (void*)old_regs.rip, (void*)breakpoint_instruction) == -1) {
perror("ptrace POKETEXT for breakpoint"); ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return 1;
}
printf(" -> Breakpoint instruction 0x%lx set.\n", breakpoint_instruction);
// =========================================================================

// 4. Set registers to call dlopen
struct user_regs_struct new_regs = old_regs;
new_regs.rip = (unsigned long long)target_dlopen_addr; // Instruction pointer points to dlopen
new_regs.rdi = (unsigned long long)remote_path_addr; // First argument (rdi) is the so path
new_regs.rsi = RTLD_LAZY; // Second argument (rsi) is the flag

// Simulate a CALL instruction: push the forged return address (our breakpoint address) onto the stack
unsigned long long aligned_rsp = new_regs.rsp & -16LL; // 16-byte align the stack
new_regs.rsp = aligned_rsp - sizeof(long); // Allocate space for the return address
ptrace_write(target_pid, (void*)new_regs.rsp, &old_regs.rip, sizeof(long));

printf("[+] Setting registers for remote call. RIP: 0x%llx, RDI: 0x%llx\n", new_regs.rip, new_regs.rdi);
if (ptrace(PTRACE_SETREGS, target_pid, NULL, &new_regs) == -1) { perror("ptrace SETREGS"); ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return 1; }

// 5. Continue execution to trigger dlopen
printf("[+] Continuing process to execute dlopen...\n");
if (ptrace(PTRACE_CONT, target_pid, NULL, NULL) == -1) { perror("ptrace CONT"); return 1; }

// Wait until our breakpoint is hit (triggers SIGTRAP)
waitpid(target_pid, NULL, 0);
printf("[+] Breakpoint hit! dlopen call has finished.\n");

// =========================================================================
// === New part: 2. Check the result and restore the context ===
// =========================================================================
// Get the register state after the dlopen call
struct user_regs_struct post_call_regs;
if(ptrace(PTRACE_GETREGS, target_pid, NULL, &post_call_regs) == -1) { perror("ptrace GETREGS post-call"); }

// The return value of a function is usually in the rax register
printf("[+] dlopen returned 0x%llx (0 means error, non-zero means success).\n", post_call_regs.rax);
if (post_call_regs.rax == 0) {
fprintf(stderr, "[!] Injection failed! dlopen returned an error.\n");
} else {
printf("[+] Injection successful! Handle: 0x%llx\n", post_call_regs.rax);
}

// Restore the original instruction that we modified to a breakpoint
printf("[+] Restoring original instruction at %p...\n", (void*)old_regs.rip);
if (ptrace(PTRACE_POKETEXT, target_pid, (void*)old_regs.rip, (void*)original_instruction) == -1) {
perror("ptrace POKETEXT to restore instruction");
}
// =========================================================================

// 6. Restore original registers and detach
printf("[+] Restoring original registers and detaching...\n");
if (ptrace(PTRACE_SETREGS, target_pid, NULL, &old_regs) == -1) { perror("ptrace restore SETREGS"); }
if (ptrace(PTRACE_DETACH, target_pid, NULL, NULL) == -1) { perror("ptrace DETACH"); }

// =========================================================================
// === Key modification: Add a delay to give the constructor time to execute ===
// =========================================================================
printf("[+] Detached. Giving payload 1 second to run its constructor...\n");
sleep(1);
// =========================================================================

printf("[+] Done! Killing target process for cleanup.\n");
kill(target_pid, SIGKILL);

return 0;
}


/root/code/c/payload.c
#include
#include
#include
#include
#include
#include

// Use __attribute__((constructor)) to define a constructor function
// This function will be executed automatically when the .so file is loaded
__attribute__((constructor))
void payload_init() {
// 1. Try to create a file in a directory with loose permissions
int fd = open("/root/code/c/injected.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
const char* msg = "Payload injected and constructor executed successfully!\n";
write(fd, msg, strlen(msg));

// 2. Check and log dlerror(), this is a very useful debugging technique
const char* dlsym_error = dlerror();
if (dlsym_error) {
write(fd, "\nDLERROR at constructor time: ", strlen("\nDLERROR at constructor time: "));
write(fd, dlsym_error, strlen(dlsym_error));
}

close(fd);
}
}

/root/code/c/target.c
#include
#include

void victim_func() {
printf("Please don't hook me!\n");
}

int main() {
printf("My PID is: %d\n", getpid());
while(1) {
victim_func();
sleep(2);
}
return 0;
}

< /code>
Запуск журналов, вероятность сбоя около 90%, 10% успех < /p>
(py39) > gcc -o target target.c && gcc -shared -fPIC -o payload.so payload.c && gcc -o so_injector so_injector.c -ldl && ./so_injector ./target /root/code/c/payload.so && sleep 1 && cat /root/code/c/injected.txt
[+] Managing target process...
[+] Target process started with PID: 148276
[+] Waiting for target to load libc.so...
My PID is: 148276
I am the victim! Please don't hook me!
[+] libc.so found in target process after 100ms.
-> Target's libc base: 0x792c2cc47000
[+] Calculating address of dlopen in target process...
-> Injector's libc base: 0x7a15a1634000
-> Injector's dlopen addr: 0x7a15a1805390
-> dlopen offset: 0x1d1390
[+] Calculated target dlopen address: 0x792c2ce18390
[+] Attaching to process 148276...
[+] Attached.
[+] Saved original registers. RIP: 0x792c2cd021b4
[+] Writing payload path to remote stack at 0x7ffc18220969
[+] Setting software breakpoint at original RIP: 0x792c2cd021b4
-> Original instruction: 0x840475eaf883c289
-> Breakpoint instruction 0x840475eaf883c2cc set.
[+] Setting registers for remote call. RIP: 0x792c2ce18390, RDI: 0x7ffc18220969
[+] Continuing process to execute dlopen...
[+] Breakpoint hit! dlopen call has finished.
[+] dlopen returned 0xdb (0 means error, non-zero means success).
[+] Injection successful! Handle: 0xdb
[+] Restoring original instruction at 0x792c2cd021b4...
[+] Restoring original registers and detaching...
[+] Detached. Giving payload 1 second to run its constructor...
[+] Done! Killing target process for cleanup.
cat: /root/code/c/injected.txt: 没有那个文件或目录 # Failed
(py39) > gcc -o target target.c && gcc -shared -fPIC -o payload.so payload.c && gcc -o so_injector so_injector.c -ldl && ./so_injector ./target /root/code/c/payload.so && sleep 1 && cat /root/code/c/injected.txt
[+] Managing target process...
[+] Target process started with PID: 148326
[+] Waiting for target to load libc.so...
[+] libc.so found in target process after 0ms.
-> Target's libc base: 0x7531a5cba000
[+] Calculating address of dlopen in target process...
-> Injector's libc base: 0x7531a5cba000
-> Injector's dlopen addr: 0x7531a5e8b390
-> dlopen offset: 0x1d1390
[+] Calculated target dlopen address: 0x7531a5e8b390
[+] Attaching to process 148326...
[+] Attached.
[+] Saved original registers. RIP: 0x7531a5d7af3f
[+] Writing payload path to remote stack at 0x7ffcaa9e5079
[+] Setting software breakpoint at original RIP: 0x7531a5d7af3f
-> Original instruction: 0x870ffffff0003d48
-> Breakpoint instruction 0x870ffffff0003dcc set.
[+] Setting registers for remote call. RIP: 0x7531a5e8b390, RDI: 0x7ffcaa9e5079
[+] Continuing process to execute dlopen...
[+] Breakpoint hit! dlopen call has finished.
[+] dlopen returned 0x617f469c68b0 (0 means error, non-zero means success).
[+] Injection successful! Handle: 0x617f469c68b0
[+] Restoring original instruction at 0x7531a5d7af3f...
[+] Restoring original registers and detaching...
[+] Detached. Giving payload 1 second to run its constructor...
My PID is: 148326
I am the victim! Please don't hook me!
[+] Done! Killing target process for cleanup.
Payload injected and constructor executed successfully! # Success, cat reads what payload.so wrote


Подробнее здесь: https://stackoverflow.com/questions/796 ... turns-garb
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Linux»