diff --git a/src/blackjack/Makefile b/src/blackjack/Makefile index 75540f5..a70e9ca 100644 --- a/src/blackjack/Makefile +++ b/src/blackjack/Makefile @@ -4,9 +4,9 @@ CFLAGS = -Wall -I$(INC_DIR) ASFLAGS = LDFLAGS = -z noexecstack -lcap -SRC = main.c process.c +SRC = main.c procstat.c process.c OBJ := $(addprefix $(OBJ_DIR)/,$(patsubst %.s,%.o,$(patsubst %.c,%.o,$(SRC)))) -DEPS = process.h process_debug.h +DEPS = procstat.h process.h process_debug.h $(OBJ_DIR)/%.o: %.c @mkdir -p $(OBJ_DIR) diff --git a/src/blackjack/main.c b/src/blackjack/main.c index 16bb1c6..781279b 100644 --- a/src/blackjack/main.c +++ b/src/blackjack/main.c @@ -5,9 +5,10 @@ #include #include #include -#include "process.h" +#include "blackjack/procstat.h" +#include "blackjack/process.h" -void print_process(process_status_t* proc) +void print_status(procstat_status_t* proc) { puts("Process:"); printf("name: %s\n", proc->name); @@ -24,39 +25,39 @@ void print_process(process_status_t* proc) int main(int argc, char** argv) { - process_status_t* list = NULL; + procstat_status_t* list = NULL; size_t count = 0; // find process - process_by_name("dummy_target", &list, &count); + procstat_by_name("dummy_target", &list, &count); // get real parent - process_status_t* parent; - if (process_determine_parent(list, count, &parent)) + procstat_status_t* parent; + if (procstat_determine_parent(list, count, &parent)) { fputs("unable to determine parent process. exiting\n", stderr); free(list); return 1; } - print_process(parent); + print_status(parent); // find active thread puts("Looking for active thread.."); - process_status_t* threads = NULL; + procstat_status_t* threads = NULL; size_t thread_count = 0; - process_status_t* active; + procstat_status_t* active; while (1) { - if (process_get_threads(parent->pid, &threads, &thread_count)) + if (procstat_get_threads(parent->pid, &threads, &thread_count)) { fputs("failed to obtain process threads\n", stderr); free(list); return 1; } - if (process_find_active(threads, thread_count, &active)) + if (procstat_find_active(threads, thread_count, &active)) { // no active threads - free list and continue free(threads); @@ -70,7 +71,7 @@ int main(int argc, char** argv) } puts("Active thread:"); - print_process(active); + print_status(active); if (!process_ptrace_permissions()) { diff --git a/src/blackjack/process.c b/src/blackjack/process.c index a494477..8e74e18 100644 --- a/src/blackjack/process.c +++ b/src/blackjack/process.c @@ -1,223 +1,14 @@ #define _DEFAULT_SOURCE -#include "process.h" -#include "debug.h" -#include "process_debug.h" +#include "blackjack/process.h" +#include "blackjack/debug.h" +#include "blackjack/process_debug.h" #include #include #include #include #include #include -#include -#include #include -#include -#include -#include - -int process_parse_status(pid_t pid, process_status_t* status) -{ - char statusPath[256] = {0}; - snprintf(statusPath, sizeof(statusPath), "/proc/%d/status", pid); - - int statusFd = open(statusPath, O_RDONLY); - if (statusFd < 0) return 1; - - char buffer[4096] = {0}; - int rd = read(statusFd, buffer, sizeof(buffer)); - close(statusFd); - - if (rd < 1) return 1; - - char* lineptr = NULL, *line = strtok_r(buffer, "\n", &lineptr); - while (line != NULL) - { - char* fieldptr = NULL; - const char* key = (const char*)strtok_r(line, ":\t", &fieldptr); - const char* value = (const char*)strtok_r(NULL, ":\t", &fieldptr); - - if (!strcmp(key, "Name")) - strncpy(status->name, value, MAX_PROCESS_NAME); - else if (!strcmp(key, "Umask")) - status->umask = (unsigned int)strtoul(value, NULL, 8); - else if (!strcmp(key, "State")) - status->state = (process_state_t)value[0]; - else if (!strcmp(key, "Tgid")) - status->tgid = atoi(value); - else if (!strcmp(key, "Ngid")) - status->ngid = atoi(value); - else if (!strcmp(key, "Pid")) - status->pid = atoi(value); - else if (!strcmp(key, "PPid")) - status->ppid = atoi(value); - else if (!strcmp(key, "TracerPid")) - status->tracer_pid = atoi(value); - else if (!strcmp(key, "Uid")) - status->uid = atoi(value); - else if (!strcmp(key, "Gid")) - status->gid = atoi(value); - - line = strtok_r(NULL, "\n", &lineptr); - } - - return 0; -} - -static int is_numeric(const char* str) -{ - for (const char* p = str; *p; p++) - if (!isdigit(*p)) return 0; - - return 1; -} - -int process_by_name(const char* name, process_status_t** list, size_t* count) -{ - *list = NULL; - *count = 0; - - DIR* proc = opendir("/proc"); - if (!proc) return 1; - - struct dirent* entry; - while ((entry = readdir(proc))) - { - if (entry->d_type != DT_DIR) continue; - if (!is_numeric(entry->d_name)) continue; - - pid_t pid = strtod(entry->d_name, NULL); - process_status_t status = {}; - if (process_parse_status(pid, &status)) - { - TRACE("process parse status failed for %d\n", pid); - continue; - } - - if (!strcasecmp(status.name, name)) - { - // we have match! lets add it to list - size_t _count = *count; - process_status_t* _list = *list; - - if (_count) _list = (process_status_t*)realloc(_list, ++_count * sizeof(process_status_t)); - else _list = (process_status_t*)malloc(++_count * sizeof(process_status_t)); - - if (!_list) - { - TRACE("out of memory for process status!\n"); - closedir(proc); - return 1; - } - - // copy process status to list - memcpy(&_list[_count - 1], &status, sizeof(process_status_t)); - - // update pointers - *list = _list; - *count = _count; - } - } - - closedir(proc); - return 0; -} - -static process_status_t* process_by_pid_in_list(pid_t pid, process_status_t* list, size_t count) -{ - for (size_t i = 0; i < count; i++) - if (list[i].pid == pid) return &list[i]; - - return NULL; -} - -int process_determine_parent(process_status_t* list, size_t count, process_status_t** parent) -{ - // we're gonna find any process that doesnt have parent in this list, - // that means we hit real parent, not descendant - for (size_t i = 0; i < count; i++) - { - if (!process_by_pid_in_list(list[i].ppid, list, count)) - { - // that's real parent - *parent = &list[i]; - return 0; - } - } - - return 1; -} - -int process_get_threads(pid_t pid, process_status_t** list, size_t* count) -{ - *list = NULL; - *count = 0; - - char taskPath[256] = {0}; - snprintf(taskPath, sizeof(taskPath), "/proc/%d/task", pid); - - DIR* taskDir = opendir(taskPath); - if (!taskDir) return 1; - - struct dirent* entry; - while ((entry = readdir(taskDir))) - { - if (entry->d_type != DT_DIR) continue; - if (!is_numeric(entry->d_name)) continue; - - pid_t pid = strtod(entry->d_name, NULL); - process_status_t status = {}; - if (process_parse_status(pid, &status)) - { - // if we can't some threads, we should fail completely - // failure is better than incomplete info - TRACE("failed to parse %d task status\n", pid); - closedir(taskDir); - return 1; - } - - // add thread to list - process_status_t* _list = *list; - size_t _count = *count; - - if (_count) _list = (process_status_t*)realloc(_list, ++_count * sizeof(process_status_t)); - else _list = (process_status_t*)malloc(++_count * sizeof(process_status_t)); - - if (!_list) - { - TRACE("out of memory for process status!\n"); - closedir(taskDir); - return 1; - } - - // copy thread to list - memcpy(&_list[_count - 1], &status, sizeof(process_status_t)); - - *list = _list; - *count = _count; - } - - closedir(taskDir); - return 0; -} - -int process_is_considered_active(process_state_t state) -{ - return state == INTERRUPTIBLE_SLEEP || state == RUNNING; -} - -int process_find_active(process_status_t* list, size_t count, process_status_t** thread) -{ - for (size_t i = 0; i < count; i++) - { - TRACE("task %d state %d\n", list[i].pid, list[i].state); - if (process_is_considered_active(list[i].state)) - { - *thread = &list[i]; - return 0; - } - } - return 1; -} int process_ptrace_permissions() { @@ -242,7 +33,7 @@ int process_ptrace_permissions() return 0; } -int process_attach_all(process_status_t* threads, size_t thread_count) +int process_attach_all(procstat_status_t* threads, size_t thread_count) { for (size_t i = 0; i < thread_count; i++) { @@ -278,7 +69,7 @@ int process_attach_all(process_status_t* threads, size_t thread_count) return 0; } -void process_detach_all(process_status_t* threads, size_t thread_count) +void process_detach_all(procstat_status_t* threads, size_t thread_count) { while (thread_count--) ptrace(PTRACE_DETACH, threads[thread_count].pid, NULL, NULL); } @@ -286,12 +77,12 @@ void process_detach_all(process_status_t* threads, size_t thread_count) // hardcoded syscall instruction size #define BJ_PTRACE_CONT_OFFSET 2 -uintptr_t process_calculate_ip(process_status_t* thread, uintptr_t addr) +uintptr_t process_calculate_ip(procstat_status_t* thread, uintptr_t addr) { return addr + BJ_PTRACE_CONT_OFFSET; } -int process_read_registers(process_status_t* thread, struct user_regs_struct* regs) +int process_read_registers(procstat_status_t* thread, struct user_regs_struct* regs) { struct iovec data = { .iov_base = regs, @@ -304,7 +95,7 @@ int process_read_registers(process_status_t* thread, struct user_regs_struct* re return ret < 0; } -int process_write_registers(process_status_t* thread, const struct user_regs_struct* regs) +int process_write_registers(procstat_status_t* thread, const struct user_regs_struct* regs) { struct iovec data = { .iov_base = (void*)regs, diff --git a/src/blackjack/process.h b/src/blackjack/process.h index d24fa0b..81722f8 100644 --- a/src/blackjack/process.h +++ b/src/blackjack/process.h @@ -1,75 +1,27 @@ #ifndef __PROCESS_H #define __PROCESS_H +#include "blackjack/procstat.h" #include #include #include -typedef enum { - UNINTERRUPTABLE_SLEEP = 'D', - IDLE_KERNEL_THREAD = 'I', - RUNNING = 'R', - INTERRUPTIBLE_SLEEP = 'S', - STOPPED = 'T', - STOPPED_BY_DEBUGGER = 't', - DEAD = 'X', - ZOMBIE = 'Z' -} process_state_t; - -#define MAX_PROCESS_NAME 256 -typedef struct { - char name[MAX_PROCESS_NAME]; - mode_t umask; - process_state_t state; - pid_t tgid; - pid_t ngid; - pid_t pid; - pid_t ppid; - pid_t tracer_pid; - uid_t uid; - gid_t gid; -} process_status_t; - -// parse process status from procfs. returns 0 no errors and 1 on any kind of error -// error information obtain from errno -int process_parse_status(pid_t pid, process_status_t* status); - -// find any process that matches name, case insensitive. -// list pointer must point to NULL-initialized pointer, and count pointer must pount to initialized 0 -// will skip any process which status couldn't be parsed -// deallocate list with free later -int process_by_name(const char* name, process_status_t** list, size_t* count); - -// determine parent process amongst children and set parent pointer to element in list -// process list must consist of parent and children processes, -// obtained from processes_by_name call. of course parent pointer shouldn't be NULL -int process_determine_parent(process_status_t* list, size_t count, process_status_t** parent); - -// get all process threads. for list and count same rules applies as for processes_by_name -int process_get_threads(pid_t pid, process_status_t** list, size_t* count); - -// returns 1 if state considered active for a process/thread -int process_is_considered_active(process_state_t state); - -// find any active (running) thread and returns 0 and success, otherwise non zero -int process_find_active(process_status_t* list, size_t count, process_status_t** thread); - // check if this process has any capability or is ran as root to be able to ptrace attach int process_ptrace_permissions(); // attach to all threads of the process. on error returns 1 and detaches from already attached -int process_attach_all(process_status_t* threads, size_t thread_count); +int process_attach_all(procstat_status_t* threads, size_t thread_count); // detaches from all threads -void process_detach_all(process_status_t* threads, size_t thread_count); +void process_detach_all(procstat_status_t* threads, size_t thread_count); // calculate correct instruction pointer address for hijacking -uintptr_t process_calculate_ip(process_status_t* thread, uintptr_t addr); +uintptr_t process_calculate_ip(procstat_status_t* thread, uintptr_t addr); // read registers of thread. returns 0 on success, 1 on error -int process_read_registers(process_status_t* thread, struct user_regs_struct* regs); +int process_read_registers(procstat_status_t* thread, struct user_regs_struct* regs); // write registers for thread. for return value same rules apply as read registers function -int process_write_registers(process_status_t* thread, const struct user_regs_struct* regs); +int process_write_registers(procstat_status_t* thread, const struct user_regs_struct* regs); #endif \ No newline at end of file diff --git a/src/blackjack/procstat.c b/src/blackjack/procstat.c new file mode 100644 index 0000000..3dd2095 --- /dev/null +++ b/src/blackjack/procstat.c @@ -0,0 +1,214 @@ +#define _DEFAULT_SOURCE +#include "blackjack/procstat.h" +#include "blackjack/debug.h" +#include +#include +#include +#include +#include +#include +#include + +int procstat_parse_status(pid_t pid, procstat_status_t* status) +{ + char statusPath[256] = {0}; + snprintf(statusPath, sizeof(statusPath), "/proc/%d/status", pid); + + int statusFd = open(statusPath, O_RDONLY); + if (statusFd < 0) return 1; + + char buffer[4096] = {0}; + int rd = read(statusFd, buffer, sizeof(buffer)); + close(statusFd); + + if (rd < 1) return 1; + + char* lineptr = NULL, *line = strtok_r(buffer, "\n", &lineptr); + while (line != NULL) + { + char* fieldptr = NULL; + const char* key = (const char*)strtok_r(line, ":\t", &fieldptr); + const char* value = (const char*)strtok_r(NULL, ":\t", &fieldptr); + + if (!strcmp(key, "Name")) + strncpy(status->name, value, MAX_PROCESS_NAME); + else if (!strcmp(key, "Umask")) + status->umask = (unsigned int)strtoul(value, NULL, 8); + else if (!strcmp(key, "State")) + status->state = (procstat_state_t)value[0]; + else if (!strcmp(key, "Tgid")) + status->tgid = atoi(value); + else if (!strcmp(key, "Ngid")) + status->ngid = atoi(value); + else if (!strcmp(key, "Pid")) + status->pid = atoi(value); + else if (!strcmp(key, "PPid")) + status->ppid = atoi(value); + else if (!strcmp(key, "TracerPid")) + status->tracer_pid = atoi(value); + else if (!strcmp(key, "Uid")) + status->uid = atoi(value); + else if (!strcmp(key, "Gid")) + status->gid = atoi(value); + + line = strtok_r(NULL, "\n", &lineptr); + } + + return 0; +} + +static int is_numeric(const char* str) +{ + for (const char* p = str; *p; p++) + if (!isdigit(*p)) return 0; + + return 1; +} + +int procstat_by_name(const char* name, procstat_status_t** list, size_t* count) +{ + *list = NULL; + *count = 0; + + DIR* proc = opendir("/proc"); + if (!proc) return 1; + + struct dirent* entry; + while ((entry = readdir(proc))) + { + if (entry->d_type != DT_DIR) continue; + if (!is_numeric(entry->d_name)) continue; + + pid_t pid = strtod(entry->d_name, NULL); + procstat_status_t status = {}; + if (procstat_parse_status(pid, &status)) + { + TRACE("process parse status failed for %d\n", pid); + continue; + } + + if (!strcasecmp(status.name, name)) + { + // we have match! lets add it to list + size_t _count = *count; + procstat_status_t* _list = *list; + + if (_count) _list = (procstat_status_t*)realloc(_list, ++_count * sizeof(procstat_status_t)); + else _list = (procstat_status_t*)malloc(++_count * sizeof(procstat_status_t)); + + if (!_list) + { + TRACE("out of memory for process status!\n"); + closedir(proc); + return 1; + } + + // copy process status to list + memcpy(&_list[_count - 1], &status, sizeof(procstat_status_t)); + + // update pointers + *list = _list; + *count = _count; + } + } + + closedir(proc); + return 0; +} + +static procstat_status_t* procstat_by_pid_in_list(pid_t pid, procstat_status_t* list, size_t count) +{ + for (size_t i = 0; i < count; i++) + if (list[i].pid == pid) return &list[i]; + + return NULL; +} + +int procstat_determine_parent(procstat_status_t* list, size_t count, procstat_status_t** parent) +{ + // we're gonna find any process that doesnt have parent in this list, + // that means we hit real parent, not descendant + for (size_t i = 0; i < count; i++) + { + if (!procstat_by_pid_in_list(list[i].ppid, list, count)) + { + // that's real parent + *parent = &list[i]; + return 0; + } + } + + return 1; +} + +int procstat_get_threads(pid_t pid, procstat_status_t** list, size_t* count) +{ + *list = NULL; + *count = 0; + + char taskPath[256] = {0}; + snprintf(taskPath, sizeof(taskPath), "/proc/%d/task", pid); + + DIR* taskDir = opendir(taskPath); + if (!taskDir) return 1; + + struct dirent* entry; + while ((entry = readdir(taskDir))) + { + if (entry->d_type != DT_DIR) continue; + if (!is_numeric(entry->d_name)) continue; + + pid_t pid = strtod(entry->d_name, NULL); + procstat_status_t status = {}; + if (procstat_parse_status(pid, &status)) + { + // if we can't some threads, we should fail completely + // failure is better than incomplete info + TRACE("failed to parse %d task status\n", pid); + closedir(taskDir); + return 1; + } + + // add thread to list + procstat_status_t* _list = *list; + size_t _count = *count; + + if (_count) _list = (procstat_status_t*)realloc(_list, ++_count * sizeof(procstat_status_t)); + else _list = (procstat_status_t*)malloc(++_count * sizeof(procstat_status_t)); + + if (!_list) + { + TRACE("out of memory for process status!\n"); + closedir(taskDir); + return 1; + } + + // copy thread to list + memcpy(&_list[_count - 1], &status, sizeof(procstat_status_t)); + + *list = _list; + *count = _count; + } + + closedir(taskDir); + return 0; +} + +int procstat_is_considered_active(procstat_state_t state) +{ + return state == INTERRUPTIBLE_SLEEP || state == RUNNING; +} + +int procstat_find_active(procstat_status_t* list, size_t count, procstat_status_t** thread) +{ + for (size_t i = 0; i < count; i++) + { + TRACE("task %d state %d\n", list[i].pid, list[i].state); + if (procstat_is_considered_active(list[i].state)) + { + *thread = &list[i]; + return 0; + } + } + return 1; +} diff --git a/src/blackjack/procstat.h b/src/blackjack/procstat.h new file mode 100644 index 0000000..115d55e --- /dev/null +++ b/src/blackjack/procstat.h @@ -0,0 +1,55 @@ +#ifndef __PROCSTAT_H +#define __PROCSTAT_H + +#include + +typedef enum { + UNINTERRUPTABLE_SLEEP = 'D', + IDLE_KERNEL_THREAD = 'I', + RUNNING = 'R', + INTERRUPTIBLE_SLEEP = 'S', + STOPPED = 'T', + STOPPED_BY_DEBUGGER = 't', + DEAD = 'X', + ZOMBIE = 'Z' +} procstat_state_t; + +#define MAX_PROCESS_NAME 256 +typedef struct { + char name[MAX_PROCESS_NAME]; + mode_t umask; + procstat_state_t state; + pid_t tgid; + pid_t ngid; + pid_t pid; + pid_t ppid; + pid_t tracer_pid; + uid_t uid; + gid_t gid; +} procstat_status_t; + +// parse process status from procfs. returns 0 no errors and 1 on any kind of error +// error information obtain from errno +int procstat_parse_status(pid_t pid, procstat_status_t* status); + +// find any process that matches name, case insensitive. +// list pointer must point to NULL-initialized pointer, and count pointer must pount to initialized 0 +// will skip any process which status couldn't be parsed +// deallocate list with free later +int procstat_by_name(const char* name, procstat_status_t** list, size_t* count); + +// determine parent process amongst children and set parent pointer to element in list +// process list must consist of parent and children processes, +// obtained from processes_by_name call. of course parent pointer shouldn't be NULL +int procstat_determine_parent(procstat_status_t* list, size_t count, procstat_status_t** parent); + +// get all process threads. for list and count same rules applies as for processes_by_name +int procstat_get_threads(pid_t pid, procstat_status_t** list, size_t* count); + +// returns 1 if state considered active for a process/thread +int procstat_is_considered_active(procstat_state_t state); + +// find any active (running) thread and returns 0 and success, otherwise non zero +int procstat_find_active(procstat_status_t* list, size_t count, procstat_status_t** thread); + +#endif \ No newline at end of file