Skip to content

Commit

Permalink
Revert "revert: revert recently changed hooked_syscall event (aquasec…
Browse files Browse the repository at this point in the history
…urity#3597)"

This reverts commit 1ecedcc.
  • Loading branch information
OriGlassman authored and rafaeldtinoco committed Oct 26, 2023
1 parent cc534be commit 43a3eac
Show file tree
Hide file tree
Showing 21 changed files with 1,196 additions and 379 deletions.
26 changes: 26 additions & 0 deletions docs/docs/events/builtin/extra/hooked_syscall.md
@@ -0,0 +1,26 @@
# hooked_syscall

## Intro
`hooked_syscall` is an event that checks syscall table for any syscall hooking.

## Description
The purpose of the `hooked_syscall` event is to monitor for system call hooking in the Linux kernel. It verifies each sys call points to its corresponding sys call function symbol. This helps identify instances of kernel code modifications, often used for malicious activities such as hiding processes, files, or network connections.

## Hooks
### Various system calls
#### Type
Uprobe
#### Purpose
Detection of syscall hooking.

## Example Use Case
The `hooked_syscall` event could be used as part of a broader system integrity monitoring solution. For example, a security engineer could use it to raise alerts or run further investigations if unexpected syscall hooking activities are detected. This could aid in the early detection and mitigation of malware or rootkit infections.

```console
tracee -e hooked_syscall
```

## Issues

## Related Events

71 changes: 0 additions & 71 deletions docs/docs/events/builtin/extra/hooked_syscalls.md

This file was deleted.

4 changes: 2 additions & 2 deletions pkg/cmd/initialize/sigs_test.go
Expand Up @@ -27,7 +27,7 @@ func Test_CreateEventsFromSigs(t *testing.T) {
newFakeSignature(
"fake_event_0",
[]string{
"hooked_syscalls",
"hooked_syscall",
},
),
},
Expand All @@ -43,7 +43,7 @@ func Test_CreateEventsFromSigs(t *testing.T) {
false, // syscall
[]string{"signatures", "default"}, // sets
events.NewDependencies(
[]events.ID{events.HookedSyscalls},
[]events.ID{events.HookedSyscall},
[]events.KSymbol{},
[]events.Probe{},
[]events.TailCall{},
Expand Down
1 change: 1 addition & 0 deletions pkg/ebpf/c/common/consts.h
Expand Up @@ -17,6 +17,7 @@
#define ARGS_BUF_SIZE 32000
#define SEND_META_SIZE 28

#define MAX_SYS_CALL_TABLE_SIZE 500
#define MAX_MEM_DUMP_SIZE 127


Expand Down
1 change: 1 addition & 0 deletions pkg/ebpf/c/maps.h
Expand Up @@ -97,6 +97,7 @@ BPF_ARRAY(file_write_path_filter, path_filter_t, 3); // filter fil
BPF_ARRAY(file_read_path_filter, path_filter_t, 3); // filter file read captures
BPF_ARRAY(file_type_filter, file_type_filter_t, 2); // filter file types
BPF_ARRAY(netconfig_map, netconfig_entry_t, 1); // network related configurations
BPF_ARRAY(expected_sys_call_table, syscall_table_entry_t, MAX_SYS_CALL_TABLE_SIZE); // expected addresses of sys call table
BPF_PERCPU_ARRAY(bufs, buf_t, MAX_BUFFERS); // percpu global buffer variables
BPF_PROG_ARRAY(prog_array, MAX_TAIL_CALL); // store programs for tail calls
BPF_PROG_ARRAY(prog_array_tp, MAX_TAIL_CALL); // store programs for tail calls
Expand Down
70 changes: 32 additions & 38 deletions pkg/ebpf/c/tracee.bpf.c
Expand Up @@ -1468,30 +1468,40 @@ int BPF_KPROBE(trace_do_exit)
return events_perf_submit(&p, DO_EXIT, code);
}

// uprobe_syscall_trigger submit to the buff the syscalls function handlers
// address from the syscall table. the syscalls are checked in user mode for hooks.

SEC("uprobe/trigger_syscall_event")
int uprobe_syscall_trigger(struct pt_regs *ctx)
statfunc void syscall_table_check(program_data_t *p)
{
u64 table_count = 0;
u64 caller_ctx_id = 0;
char sys_call_table_symbol[15] = "sys_call_table";
u64 *sys_call_table = (u64 *) get_symbol_addr(sys_call_table_symbol);

// clang-format off
//
// Golang calling convention per architecture
int index = 0; // For the verifier

#if defined(bpf_target_x86)
caller_ctx_id = ctx->bx; // 1st arg
table_count = ctx->cx; // 2nd arg
#elif defined(bpf_target_arm64)
caller_ctx_id = ctx->user_regs.regs[1]; // 1st arg
table_count = ctx->user_regs.regs[2]; // 2nd arg
#else
return 0;
#endif
// clang-format on
#pragma unroll
for (int i = 0; i < MAX_SYS_CALL_TABLE_SIZE; i++) {
index = i;
syscall_table_entry_t *expected_entry =
bpf_map_lookup_elem(&expected_sys_call_table, &index);

if (!expected_entry || expected_entry->address == 0) {
continue;
}

u64 effective_address;
bpf_probe_read(&effective_address, sizeof(u64), sys_call_table + index);

if (expected_entry->address != effective_address) {
reset_event_args(p);
save_to_submit_buf(&(p->event->args_buf), &index, sizeof(int), 0);
save_to_submit_buf(&(p->event->args_buf), &effective_address, sizeof(u64), 1);

events_perf_submit(p, SYSCALL_TABLE_CHECK, 0);
}
}
}

// syscall_table_check
SEC("uprobe/syscall_table_check")
int uprobe_syscall_table_check(struct pt_regs *ctx)
{
program_data_t p = {};
if (!init_program_data(&p, ctx))
return 0;
Expand All @@ -1505,25 +1515,9 @@ int uprobe_syscall_trigger(struct pt_regs *ctx)
p.event->context.syscall = NO_SYSCALL;
p.event->context.matched_policies = ULLONG_MAX;

char syscall_table_sym[15] = "sys_call_table";
u64 *syscall_table_addr = (u64 *) get_symbol_addr(syscall_table_sym);

if (unlikely(syscall_table_addr == 0))
return 0;

// Get per-cpu string buffer
buf_t *string_p = get_buf(STRING_BUF_IDX);
if (string_p == NULL)
return 0;

u64 table_size = table_count * sizeof(u64);
if ((table_size > MAX_PERCPU_BUFSIZE) || (table_size <= 0)) // verify no wrap occurred
return 0;

save_u64_arr_to_buf(&p.event->args_buf, (const u64 *) syscall_table_addr, table_count, 0);
save_to_submit_buf(&p.event->args_buf, (void *) &caller_ctx_id, sizeof(uint64_t), 1);
syscall_table_check(&p);

return events_perf_submit(&p, PRINT_SYSCALL_TABLE, 0);
return 0;
}

SEC("uprobe/trigger_seq_ops_event")
Expand Down
6 changes: 5 additions & 1 deletion pkg/ebpf/c/types.h
Expand Up @@ -96,7 +96,7 @@ enum event_id_e
CALL_USERMODE_HELPER,
DIRTY_PIPE_SPLICE,
DEBUGFS_CREATE_FILE,
PRINT_SYSCALL_TABLE,
SYSCALL_TABLE_CHECK,
DEBUGFS_CREATE_DIR,
DEVICE_ADD,
REGISTER_CHRDEV,
Expand Down Expand Up @@ -351,6 +351,10 @@ typedef struct netconfig_entry {
u32 capture_length; // amount of network packet payload to capture (pcap)
} netconfig_entry_t;

typedef struct syscall_table_entry {
u64 address;
} syscall_table_entry_t;

typedef struct args_buffer {
u8 argnum;
char args[ARGS_BUF_SIZE];
Expand Down
104 changes: 104 additions & 0 deletions pkg/ebpf/hooked_syscall_table.go
@@ -0,0 +1,104 @@
package ebpf

import (
gocontext "context"
"runtime"
"strings"
"time"
"unsafe"

bpf "github.com/aquasecurity/libbpfgo"

"github.com/aquasecurity/tracee/pkg/events"
"github.com/aquasecurity/tracee/pkg/events/derive"
"github.com/aquasecurity/tracee/pkg/logger"
"github.com/aquasecurity/tracee/pkg/utils"
)

var expectedSyscallTableInit = false

// hookedSyscallTableRoutine the main routine that checks if there's a hooked syscall in the syscall table.
// It runs on tracee's startup and from time to time.
func (t *Tracee) hookedSyscallTableRoutine(ctx gocontext.Context) {
logger.Debugw("Starting hookedSyscallTable goroutine")
defer logger.Debugw("Stopped hookedSyscallTable goroutine")

if t.eventsState[events.HookedSyscall].Emit == 0 {
return
}

if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
logger.Debugw("hooked syscall table: unsupported architecture")
return
}

err := derive.InitHookedSyscall()
if err != nil {
logger.Errorw("Error occurred InitHookedSyscall: " + err.Error())
return
}

expectedSyscallTableMap, err := t.bpfModule.GetMap("expected_sys_call_table")
if err != nil {
logger.Errorw("Error occurred GetMap: " + err.Error())
return
}

if err := t.populateExpectedSyscallTableArray(expectedSyscallTableMap); err != nil {
logger.Errorw("Error populating expected syscall table array: " + err.Error())
return
}

expectedSyscallTableInit = true

t.triggerSyscallTableIntegrityCheckCall() // First time run immediately

// Run from time to time
for {
select {
case <-ctx.Done():
return
case <-time.After(utils.GenerateRandomDuration(10, 300)):
t.triggerSyscallTableIntegrityCheckCall()
}
}
}

// populateExpectedSyscallTableArray fills the expected values of the syscall table
func (t *Tracee) populateExpectedSyscallTableArray(tableMap *bpf.BPFMap) error {
// Get address to the function that defines the not implemented sys call
niSyscallSymbol, err := t.kernelSymbols.GetSymbolByName("system", events.SyscallPrefix+"ni_syscall")
if err != nil {
return err
}
niSyscallAddress := niSyscallSymbol.Address

for i, syscallName := range events.SyscallSymbolNames {
var index = uint32(i)

if strings.HasPrefix(syscallName, events.SyscallNotImplemented) {
err = tableMap.Update(unsafe.Pointer(&index), unsafe.Pointer(&niSyscallAddress))
if err != nil {
return err
}
continue
}

kernelSymbol, err := t.kernelSymbols.GetSymbolByName("system", events.SyscallPrefix+syscallName)
if err != nil {
return err
}

var expectedAddress = kernelSymbol.Address
err = tableMap.Update(unsafe.Pointer(&index), unsafe.Pointer(&expectedAddress))
if err != nil {
return err
}
}

return nil
}

//go:noinline
func (t *Tracee) triggerSyscallTableIntegrityCheckCall() {
}
2 changes: 1 addition & 1 deletion pkg/ebpf/probes/probe_group.go
Expand Up @@ -165,7 +165,7 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool) (*ProbeGroup, err
LoadElfPhdrs: NewTraceProbe(KProbe, "load_elf_phdrs", "trace_load_elf_phdrs"),
Filldir64: NewTraceProbe(KProbe, "filldir64", "trace_filldir64"),
TaskRename: NewTraceProbe(RawTracepoint, "task:task_rename", "tracepoint__task__task_rename"),
PrintSyscallTable: NewUprobe("print_syscall_table", "uprobe_syscall_trigger", binaryPath, "github.com/aquasecurity/tracee/pkg/ebpf.(*Tracee).triggerSyscallsIntegrityCheckCall"),
SyscallTableCheck: NewUprobe("syscall_table_check", "uprobe_syscall_table_check", binaryPath, "github.com/aquasecurity/tracee/pkg/ebpf.(*Tracee).triggerSyscallTableIntegrityCheckCall"),
HiddenKernelModuleSeeker: NewUprobe("hidden_kernel_module", "uprobe_lkm_seeker", binaryPath, "github.com/aquasecurity/tracee/pkg/ebpf.(*Tracee).triggerKernelModuleSeeker"),
HiddenKernelModuleVerifier: NewUprobe("hidden_kernel_module", "uprobe_lkm_seeker_submitter", binaryPath, "github.com/aquasecurity/tracee/pkg/ebpf.(*Tracee).triggerKernelModuleSubmitter"),
PrintNetSeqOps: NewUprobe("print_net_seq_ops", "uprobe_seq_ops_trigger", binaryPath, "github.com/aquasecurity/tracee/pkg/ebpf.(*Tracee).triggerSeqOpsIntegrityCheckCall"),
Expand Down
2 changes: 1 addition & 1 deletion pkg/ebpf/probes/probes.go
Expand Up @@ -82,7 +82,7 @@ const (
Filldir64
SecurityFilePermission
TaskRename
PrintSyscallTable
SyscallTableCheck
PrintNetSeqOps
SecurityInodeRename
DoSigaction
Expand Down
1 change: 0 additions & 1 deletion pkg/ebpf/processor.go
Expand Up @@ -108,7 +108,6 @@ func (t *Tracee) registerEventProcessors() {
t.RegisterEventProcessor(events.DoInitModule, t.processDoInitModule)
t.RegisterEventProcessor(events.HookedProcFops, t.processHookedProcFops)
t.RegisterEventProcessor(events.PrintNetSeqOps, t.processTriggeredEvent)
t.RegisterEventProcessor(events.PrintSyscallTable, t.processTriggeredEvent)
t.RegisterEventProcessor(events.PrintMemDump, t.processTriggeredEvent)
t.RegisterEventProcessor(events.PrintMemDump, t.processPrintMemDump)

Expand Down
10 changes: 3 additions & 7 deletions pkg/ebpf/processor_funcs.go
Expand Up @@ -216,7 +216,7 @@ func (t *Tracee) processSchedProcessExec(event *trace.Event) error {

// processDoFinitModule handles a do_finit_module event and triggers other hooking detection logic.
func (t *Tracee) processDoInitModule(event *trace.Event) error {
_, okSyscalls := t.eventsState[events.HookedSyscalls]
_, okSyscalls := t.eventsState[events.HookedSyscall]
_, okSeqOps := t.eventsState[events.HookedSeqOps]
_, okProcFops := t.eventsState[events.HookedProcFops]
_, okMemDump := t.eventsState[events.PrintMemDump]
Expand All @@ -233,12 +233,8 @@ func (t *Tracee) processDoInitModule(event *trace.Event) error {
if err != nil {
return errfmt.WrapError(err)
}
if okSyscalls {
// Trigger syscalls hooking detection
err = t.triggerSyscallsIntegrityCheck(*event)
if err != nil {
logger.Warnw("hooked_syscalls returned an error", "error", err)
}
if okSyscalls && expectedSyscallTableInit {
t.triggerSyscallTableIntegrityCheckCall()
}
if okSeqOps {
// Trigger seq_ops hooking detection
Expand Down

0 comments on commit 43a3eac

Please sign in to comment.