diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h
index f07483915f4a..17f36dae37bd 100644
--- a/pkg/ebpf/c/maps.h
+++ b/pkg/ebpf/c/maps.h
@@ -265,14 +265,6 @@ struct sys_exit_init_tail {
 
 typedef struct sys_exit_init_tail sys_exit_init_tail_t;
 
-// store program for performing syscall checking logic
-struct check_syscall_source_tail {
-    __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
-    __uint(max_entries, MAX_EVENT_ID);
-    __type(key, u32);
-    __type(value, u32);
-} check_syscall_source_tail SEC(".maps");
-
 // store syscalls with abnormal source per VMA per process
 struct {
     __uint(type, BPF_MAP_TYPE_LRU_HASH);
diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c
index 68d2b04c0d4f..85d8ab49d02e 100644
--- a/pkg/ebpf/c/tracee.bpf.c
+++ b/pkg/ebpf/c/tracee.bpf.c
@@ -57,10 +57,6 @@ int tracepoint__raw_syscalls__sys_enter(struct bpf_raw_tracepoint_args *ctx)
         id = *id_64;
     }
 
-    // Call syscall checker if registered for this syscall.
-    // If so, it will make sure the following tail is called.
-    bpf_tail_call(ctx, &check_syscall_source_tail, id);
-
     bpf_tail_call(ctx, &sys_enter_init_tail, id);
     return 0;
 }
@@ -5209,50 +5205,41 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma)
     return VMA_OTHER;
 }
 
-SEC("raw_tracepoint/check_syscall_source")
-int check_syscall_source(struct bpf_raw_tracepoint_args *ctx)
+SEC("kprobe/check_syscall_source")
+int BPF_KPROBE(check_syscall_source)
 {
-    // Get syscall ID.
-    // NOTE: this must happen first before any logic that may fail,
-    // because we must know the syscall ID for the tail call we preceded.
-    struct task_struct *task = (struct task_struct *) bpf_get_current_task();
-    u32 id = ctx->args[1];
-    if (is_compat(task)) {
-        // Translate 32bit syscalls to 64bit syscalls
-        u32 *id_64 = bpf_map_lookup_elem(&sys_32_to_64_map, &id);
-        if (id_64 == 0)
-            return 0;
-        id = *id_64;
-    }
-
     program_data_t p = {};
     if (!init_program_data(&p, ctx, CHECK_SYSCALL_SOURCE))
-        goto out;
+        return 0;
 
     if (!evaluate_scope_filters(&p))
-        goto out;
+        return 0;
 
     // Get instruction pointer
-    struct pt_regs *regs = (struct pt_regs *) ctx->args[0];
-#if defined(bpf_target_x86)
-    u64 ip = BPF_CORE_READ(regs, ip);
-#elif defined(bpf_target_arm64)
-    u64 ip = BPF_CORE_READ(regs, pc);
-#endif
+    struct pt_regs *regs = ctx;
+    if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER))
+        regs = (struct pt_regs *) PT_REGS_PARM1(ctx);
+    u64 ip = PT_REGS_IP_CORE(regs);
 
     // Find VMA which contains the instruction pointer
+    struct task_struct *task = (struct task_struct *) bpf_get_current_task();
+    if (unlikely(task == NULL))
+        return 0;
     struct vm_area_struct *vma = find_vma(task, ip);
-    if (vma == NULL)
-        goto out;
+    if (unlikely(vma == NULL))
+        return 0;
 
     // Get VMA type and make sure it's abnormal (stack/heap/anonymous VMA)
     enum vma_type vma_type = get_vma_type(vma);
     if (vma_type == VMA_OTHER)
-        goto out;
+        return 0;
+
+    // Get syscall ID
+    u32 syscall = get_syscall_id_from_regs(regs);
 
     // Build a key that identifies the combination of syscall,
     // source VMA and process so we don't submit it multiple times
-    syscall_source_key_t key = {.syscall = id,
+    syscall_source_key_t key = {.syscall = syscall,
                                 .tgid = get_task_ns_tgid(task),
                                 .tgid_start_time = get_task_start_time(get_leader_task(task)),
                                 .vma_addr = get_vma_start(vma)};
@@ -5261,13 +5248,13 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx)
     // Try updating the map with the requirement that this key does not exist yet
     if ((int) bpf_map_update_elem(&syscall_source_map, &key, &val, BPF_NOEXIST) == -17 /* EEXIST */)
         // This key already exists, no need to submit the same syscall-vma-process combination again
-        goto out;
+        return 0;
 
     bool is_stack = vma_type == VMA_STACK;
     bool is_heap = vma_type == VMA_HEAP;
     bool is_anon = vma_type == VMA_ANON;
 
-    save_to_submit_buf(&p.event->args_buf, &id, sizeof(id), 0);
+    save_to_submit_buf(&p.event->args_buf, &syscall, sizeof(syscall), 0);
     save_to_submit_buf(&p.event->args_buf, &ip, sizeof(ip), 1);
     save_to_submit_buf(&p.event->args_buf, &is_stack, sizeof(is_stack), 2);
     save_to_submit_buf(&p.event->args_buf, &is_heap, sizeof(is_heap), 3);
@@ -5275,10 +5262,6 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx)
 
     events_perf_submit(&p, 0);
 
-out:
-    // Call sys_enter_init_tail which we preceded
-    bpf_tail_call(ctx, &sys_enter_init_tail, id);
-
     return 0;
 }
 
diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go
index 38b174824514..a6ba4d7c9b85 100644
--- a/pkg/ebpf/event_filters.go
+++ b/pkg/ebpf/event_filters.go
@@ -1,26 +1,26 @@
 package ebpf
 
 import (
+	"fmt"
 	"maps"
 	"strconv"
-	"unsafe"
 
-	bpf "github.com/aquasecurity/libbpfgo"
-
-	"github.com/aquasecurity/tracee/pkg/errfmt"
+	"github.com/aquasecurity/tracee/pkg/ebpf/probes"
 	"github.com/aquasecurity/tracee/pkg/events"
 	"github.com/aquasecurity/tracee/pkg/filters"
 	"github.com/aquasecurity/tracee/pkg/logger"
 )
 
-type eventFilterHandler func(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error
+type eventFilterHandler func(t *Tracee, eventFilters map[string]filters.Filter[*filters.StringFilter]) error
 
 var eventFilterHandlers = map[events.ID]eventFilterHandler{
-	events.CheckSyscallSource: populateMapsCheckSyscallSource,
+	events.CheckSyscallSource: attachCheckSyscallSourceProbes,
 }
 
-// populateEventFilterMaps populates maps with data from special event filters
-func (t *Tracee) populateEventFilterMaps() error {
+// handleEventFilters performs eBPF related actions according to special event filters.
+// For example, an event can use one of its filters to populate eBPF maps, or perhaps
+// attach eBPF programs according to the filters.
+func (t *Tracee) handleEventFilters() error {
 	// Iterate through registerd event filter handlers
 	for eventID, handler := range eventFilterHandlers {
 		// Make sure this event is selected
@@ -43,7 +43,7 @@ func (t *Tracee) populateEventFilterMaps() error {
 		}
 
 		// Call handler
-		err := handler(eventFilters, t.bpfModule)
+		err := handler(t, eventFilters)
 		if err != nil {
 			logger.Errorw("Failed to handle event filters", "event", events.Core.GetDefinitionByID(eventID).GetName(), "error", err)
 			err = t.eventsDependencies.RemoveEvent(eventID)
@@ -55,38 +55,36 @@ func (t *Tracee) populateEventFilterMaps() error {
 	return nil
 }
 
-func populateMapsCheckSyscallSource(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error {
+func attachCheckSyscallSourceProbes(t *Tracee, eventFilters map[string]filters.Filter[*filters.StringFilter]) error {
 	// Get syscalls to trace
 	syscallsFilter, ok := eventFilters["syscall"].(*filters.StringFilter)
 	if !ok {
 		return nil
 	}
-	syscalls := syscallsFilter.Equal()
-
-	// Get map and program for check_syscall_source tailcall
-	checkSyscallSourceTail, err := bpfModule.GetMap("check_syscall_source_tail")
-	if err != nil {
-		return errfmt.Errorf("could not get BPF map \"check_syscall_source_tail\": %v", err)
-	}
-	checkSyscallSourceProg, err := bpfModule.GetProgram("check_syscall_source")
-	if err != nil {
-		return errfmt.Errorf("could not get BPF program \"check_syscall_source\": %v", err)
-	}
-	checkSyscallSourceProgFD := checkSyscallSourceProg.FileDescriptor()
-	if checkSyscallSourceProgFD < 0 {
-		return errfmt.Errorf("could not get BPF program FD for \"check_syscall_source\": %v", err)
-	}
-
-	// Add each syscall to the tail call map
-	for _, syscall := range syscalls {
-		syscallID, err := strconv.Atoi(syscall)
+	syscalls := make([]string, 0)
+	for _, entry := range syscallsFilter.Equal() {
+		syscallID, err := strconv.Atoi(entry)
 		if err != nil {
-			return errfmt.WrapError(err)
+			return err
 		}
+		if !events.Core.IsDefined(events.ID(syscallID)) {
+			return fmt.Errorf("syscall id %d is not defined", syscallID)
+		}
+		syscalls = append(syscalls, events.Core.GetDefinitionByID(events.ID(syscallID)).GetName())
+	}
 
-		err = checkSyscallSourceTail.Update(unsafe.Pointer(&syscallID), unsafe.Pointer(&checkSyscallSourceProgFD))
-		if err != nil {
-			return errfmt.WrapError(err)
+	// Create probe group
+	probeMap := make(map[probes.Handle]probes.Probe)
+	for i, syscall := range syscalls {
+		probeMap[probes.Handle(i)] = probes.NewTraceProbe(probes.SyscallEnter, syscall, "check_syscall_source")
+	}
+	t.checkSyscallSourceProbes = probes.NewProbeGroup(t.bpfModule, probeMap)
+
+	// Attach probes
+	for i, syscall := range syscalls {
+		if err := t.checkSyscallSourceProbes.Attach(probes.Handle(i), t.kernelSymbols); err != nil {
+			// Report attachment errors but don't fail, because it may be a syscall that doesn't exist on this system
+			logger.Warnw("Failed to attach check_syscall_source kprobe", "syscall", syscall, "error", err)
 		}
 	}
 
diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go
index 87004b42a581..8c7e54cd0d68 100644
--- a/pkg/ebpf/tracee.go
+++ b/pkg/ebpf/tracee.go
@@ -125,6 +125,8 @@ type Tracee struct {
 	// This does not mean they are required for tracee to function.
 	// TODO: remove this in favor of dependency manager nodes
 	requiredKsyms []string
+	// Probes created for check_syscall_source event
+	checkSyscallSourceProbes *probes.ProbeGroup
 }
 
 func (t *Tracee) Stats() *metrics.Stats {
@@ -518,6 +520,16 @@ func (t *Tracee) Init(ctx gocontext.Context) error {
 		},
 	}
 
+	// Perform extra initializtion steps required by specific events according to their arguments
+	err = capabilities.GetInstance().EBPF(
+		func() error {
+			return t.handleEventFilters()
+		},
+	)
+	if err != nil {
+		return errfmt.WrapError(err)
+	}
+
 	return nil
 }
 
@@ -1107,12 +1119,6 @@ func (t *Tracee) populateBPFMaps() error {
 		}
 	}
 
-	// Populate maps according to BPF-level event argument filters
-	err = t.populateEventFilterMaps()
-	if err != nil {
-		return errfmt.WrapError(err)
-	}
-
 	return nil
 }
 
@@ -1492,6 +1498,12 @@ func (t *Tracee) Close() {
 			logger.Errorw("failed to detach probes when closing tracee", "err", err)
 		}
 	}
+	if t.checkSyscallSourceProbes != nil {
+		err := t.checkSyscallSourceProbes.DetachAll()
+		if err != nil {
+			logger.Errorw("failed to detach check_syscall_source probes when closing tracee", "err", err)
+		}
+	}
 	if t.bpfModule != nil {
 		t.bpfModule.Close()
 	}
diff --git a/pkg/events/core.go b/pkg/events/core.go
index 3a9bb302e2cb..346dfd8a624a 100644
--- a/pkg/events/core.go
+++ b/pkg/events/core.go
@@ -13065,15 +13065,7 @@ var CoreEvents = map[ID]Definition{
 		id:      CheckSyscallSource,
 		id32Bit: Sys32Undefined,
 		name:    "check_syscall_source",
-		dependencies: Dependencies{
-			probes: []Probe{
-				{handle: probes.SyscallEnter__Internal, required: true},
-			},
-			tailCalls: []TailCall{
-				{"check_syscall_source_tail", "check_syscall_source", []uint32{ /* Map will be populated at runtime according to event filter */ }},
-			},
-		},
-		sets: []string{},
+		sets:    []string{},
 		params: []trace.ArgMeta{
 			{Type: "int", Name: "syscall"},
 			{Type: "void*", Name: "ip"},