diff --git a/README.md b/README.md
index 007c8c87..08b942d0 100644
--- a/README.md
+++ b/README.md
@@ -2,35 +2,40 @@

-Hades 是一款运行在 Linux 下的 HIDS,目前还在开发中。支持内核态(ebpf)以及用户态(cn_proc)的事件进程采集。其中借鉴了非常多的代码和思想(from meituan, Elkeid, tracee)
+Hades 是一个基于 eBPF 的主机入侵检测系统,同时兼容低版本下通过 Netlink 进行事件审计。
-## 架构设计以及引擎
+项目借鉴了 [Tracee](https://github.com/aquasecurity/tracee) 以及 [Elkeid](https://github.com/bytedance/Elkeid) 中的代码以及思想
-> 注: Agent 部分基本参照 Elkeid 1.7 部分重构, eBPF 部分借鉴 Elkeid 思路,tracee 等
+## Hades 架构图
-### 架构图
+> 注: Agent 部分基本参照 Elkeid 1.7 部分重构。后续考虑插件全部能兼容至 `Elkeid` 项目下
-
+### Agent 部分
-### 数据处理
+
-> Agent 字段连接公司对应的 cmdb,做初步扩展。之后走入 Flink CEP 做初步的节点数据清洗。打入 HIVE 时根据情况,也可再做一次清洗减小性能消耗。清洗过后的数据走入第二个 Flink CEP 以及规则引擎,HIDS 的规则部分其实较为头疼,是一个 HIDS 能否用好的关键所在,后续会把自己的想法逐步开源
+### 数据处理流程

-## 目前阶段
+## 插件列表
-用户态基本完成,eBPF 进行中, 目前 execve 字段全部采集完毕, 包括进程树, envp, cwd...
+- [Driver-eBPF](https://github.com/chriskaliX/Hades/tree/main/plugin/driver/eBPF)
+- [Collector](https://github.com/chriskaliX/Hades/tree/main/plugin/collector)
+- HoneyPot
+- Monitor
+- Scanner
+- Logger
-目前在重要的字段下先对齐 Elkeid, 还有一些纰漏, 慢慢的修复
+## 目前进展
-
+支持 `13` 种 Hook,涵盖大部分安全审计检测需求
## 开发计划
> 记录一些方案选择, 目前进度等,另外 golang 1.18 上线啦~ [官方 Tutorial](https://golang.google.cn/doc/tutorial/generics),后续会开始多试试 Generics
-### 插件交互
+### Agent-插件 交互
> Linux 进程间通信的[方式](https://www.linuxprobe.com/linux-process-method.html)
@@ -76,15 +81,14 @@ Hades 是一款运行在 Linux 下的 HIDS,目前还在开发中。支持内
- [x] channel 消费无上限, 过多会导致 ringbuffer full, 自带 drop
- [x] 过 Prctl 部分, 字节只 hook PR_SET_NAME,考虑添加 PR_SET_MM
- [x] (100%)第一轮 review 修改进行中. 使用 ebpfmanager 重构了一下. memfd_create 添加, LSM bind 函数 ipv6 添加, 有个小的问题: json 效率和 inline
- - [ ] eBPF uprobe(openjdk/readline)...
+ - [x] eBPF uprobe(openjdk/readline)... (对于OpenJDK)的观测稍微延后, 需要作为一个可配置模块
- [x] 面向对象, ebpfmanager review 使用
- [x] eBPF 进程监控
- [x] socket 下完全支持 ipv6, 字段丰富 EXE 完成(跟之前一样, 无 lock 操作, 可能有读错的问题)
- [ ] 整理 ebpf 初版, 预备 release version
- [ ] (20%)code review tracee 函数 get_path_str, 本周完成与 fsprobe 的方式对比以及原理, 更新在 private repo, 到时候写个小文章
- [x] 目前非 CO-RE, 后续支持
- - [ ] ehids 下有个 JVM Hook 的文章, 2022 年 3 月份内 go through , 最好能实现 rmi 等 hook
- - [ ] 在 4 月份左右会完成 CO-RE 的兼容, 同时会开始编写配套的 BPF Rootkit(读 cfc4n 师傅有感, 另外盘古实验室的[文章](https://www.pangulab.cn/post/the_bvp47_a_top-tier_backdoor_of_us_nsa_equation_group/)好像提到了 BPF 作用于通信隐藏, 改正:内核版本太低了, 不会是 XDP... 新的任务是稍微看一下 Linux 网络协议这一块(源码级别)后续会有笔记放开)
+ - [x] ehids 下有个 JVM Hook 的文章, 2022 年 3 月份内 go through , 最好能实现 rmi 等 hook
- [x] [cd00r.c](https://github.com/ehids/rootkit-sample)这个 2000 的 backdoor 稍微看了一下,以及对应的 pdf。本质上新颖的地方在于不会暴露端口,libpcap 的模式来监听 knock, 看起来和 tcpdump 一样, 上述盘古文章里的后门里,这个应该是很小的一环。cd00r.c 是用户态的一个 demo,如果整合进来做成 rootkit 也挺好。
- [ ] 完成轮询交互
- [x] Agent 端 HTTPS 心跳 & 配置检测
@@ -99,10 +103,9 @@ Hades 是一款运行在 Linux 下的 HIDS,目前还在开发中。支持内
## Other
+- [Linux RootKit初窥(一)IDT](https://chriskalix.github.io/2022/03/19/linux-rootkit%E5%88%9D%E7%AA%A5-%E4%B8%80-idt)
- [阿里云 Rootkit 检测产品 Simple Doc](https://help.aliyun.com/document_detail/194087.html?spm=5176.24320532.content1.3.7389ece6Exy34X)
## 交流群
-
-
diff --git a/plugin/driver/eBPF/kernel/include/define.h b/plugin/driver/eBPF/kernel/include/define.h
index 7d57dcc7..cbaf38f4 100644
--- a/plugin/driver/eBPF/kernel/include/define.h
+++ b/plugin/driver/eBPF/kernel/include/define.h
@@ -1,5 +1,6 @@
#ifndef __DEFINE_H
#define __DEFINE_H
+#ifndef CORE
#include
#include
#include
@@ -17,6 +18,10 @@
#include
#include
#include
+#else
+#include
+#endif
+
#include "bpf_helpers.h"
#include "bpf_core_read.h"
@@ -25,7 +30,6 @@
#define MAX_PERCPU_BUFSIZE (1 << 14)
#define MAX_STRING_SIZE 256 // Same with Elkeid, but it's larger in tracee or other project
#define MAX_STR_ARR_ELEM 32
-#define MAX_PID_LEN 7 // (up to 2^22 = 4194304)
#define MAX_PATH_COMPONENTS 20
#define MAX_BUFFERS 3
@@ -33,7 +37,7 @@
#define SUBMIT_BUF_IDX 0
#define STRING_BUF_IDX 1
-#define EXECVE_GET_SOCK_FD_LIMIT 12
+#define EXECVE_GET_SOCK_FD_LIMIT 8
#define EXECVE_GET_SOCK_PID_LIMIT 4
/* ========== MAP MICRO DEFINATION ========== */
@@ -69,12 +73,11 @@ typedef struct data_context
{
u64 ts; // timestamp
u64 cgroup_id; // cgroup_id
- u32 uts_inum; // in Elkeid, they use pid_inum and root_pid_inum. TODO: go through this
+ u32 pns; // in Elkeid, they use pid_inum and root_pid_inum. TODO: go through this
u32 type; // type of struct
u32 pid; // processid
u32 tid; // thread id
u32 uid; // user id
- u32 euid; // effective user id
u32 gid; // group id
u32 ppid; // parent pid => which is tpid, pid is for the kernel space. In user space, it's tgid actually
u32 sessionid;
@@ -155,7 +158,8 @@ BPF_PERF_OUTPUT(net_events, 1024);
BPF_PERCPU_ARRAY(bufs, buf_t, 3);
BPF_PERCPU_ARRAY(bufs_off, u32, MAX_BUFFERS);
-/* read micro */
+// CORE, just like in tracee
+#ifndef CORE
#define READ_KERN(ptr) \
({ \
typeof(ptr) _val; \
@@ -163,6 +167,17 @@ BPF_PERCPU_ARRAY(bufs_off, u32, MAX_BUFFERS);
bpf_probe_read((void *)&_val, sizeof(_val), &ptr); \
_val; \
})
+#define GET_FIELD_ADDR(field) &field
+#else
+#define READ_KERN(ptr) \
+ ({ \
+ typeof(ptr) _val; \
+ __builtin_memset((void *)&_val, 0, sizeof(_val)); \
+ bpf_core_read((void *)&_val, sizeof(_val), &ptr); \
+ _val; \
+ })
+#define GET_FIELD_ADDR(field) __builtin_preserve_access_index(&field)
+#endif
/* array related function */
static __always_inline buf_t *get_buf(int idx)
@@ -189,8 +204,6 @@ static inline struct mount *real_mount(struct vfsmount *mnt)
return container_of(mnt, struct mount, mnt);
}
-#define GET_FIELD_ADDR(field) &field
-
/* hook point id */
// TODO: gather all and update
#define SYS_ENTER_PTRACE 164
diff --git a/plugin/driver/eBPF/kernel/include/hades_net.h b/plugin/driver/eBPF/kernel/include/hades_net.h
index 8e5020d9..6db4e491 100644
--- a/plugin/driver/eBPF/kernel/include/hades_net.h
+++ b/plugin/driver/eBPF/kernel/include/hades_net.h
@@ -17,7 +17,6 @@ int kprobe_security_socket_connect(struct pt_regs *ctx)
struct sockaddr *address = (struct sockaddr *)PT_REGS_PARM2(ctx);
if (!address)
return 0;
-
sa_family_t sa_fam = READ_KERN(address->sa_family);
if ((sa_fam != AF_INET) && (sa_fam != AF_INET6))
{
diff --git a/plugin/driver/eBPF/kernel/include/utils.h b/plugin/driver/eBPF/kernel/include/utils.h
index cdd1c64a..274c67d4 100644
--- a/plugin/driver/eBPF/kernel/include/utils.h
+++ b/plugin/driver/eBPF/kernel/include/utils.h
@@ -1,14 +1,20 @@
#ifndef __UTILS_H
#define __UTILS_H
-#include "bpf_helpers.h"
-#include "bpf_core_read.h"
-#include "bpf_endian.h"
+#ifndef CORE
#include
#include
#include
#include
#include
#include
+#include
+#else
+#include
+#endif
+
+#include "bpf_helpers.h"
+#include "bpf_core_read.h"
+#include "bpf_endian.h"
// TODO: 后期改成动态的
/* R3 max value is outside of the array range */
@@ -21,10 +27,8 @@
/* init_context */
static __always_inline int init_context(context_t *context, struct task_struct *task)
{
- // 获取 timestamp
- struct task_struct *realparent;
- bpf_probe_read(&realparent, sizeof(realparent), &task->real_parent);
- bpf_probe_read(&context->ppid, sizeof(context->ppid), &realparent->tgid);
+ struct task_struct *realparent = READ_KERN(task->real_parent);
+ context->ppid = READ_KERN(realparent->tgid);
context->ts = bpf_ktime_get_ns();
// 填充 id 相关信息
u64 id = bpf_get_current_uid_gid();
@@ -34,24 +38,25 @@ static __always_inline int init_context(context_t *context, struct task_struct *
context->pid = id;
context->tid = id >> 32;
context->cgroup_id = bpf_get_current_cgroup_id();
- // 读取 cred 下, 填充 id
- struct cred *cred;
- // 有三个 ptracer_cred, real_cred, cred, 看kernel代码即可
- bpf_probe_read(&cred, sizeof(cred), &task->real_cred);
- bpf_probe_read(&context->euid, sizeof(context->euid), &cred->euid);
// 容器相关信息
// Elkeid - ROOT_PID_NS_INUM = task->nsproxy->pid_ns_for_children->ns.inum;
// namespace: https://zhuanlan.zhihu.com/p/307864233
- struct nsproxy *nsp;
- struct uts_namespace *uts_ns;
+ struct nsproxy *nsp = READ_KERN(task->nsproxy);
+ struct uts_namespace *uts_ns = READ_KERN(nsp->uts_ns);
+ struct pid_namespace *pid_ns = READ_KERN(nsp->pid_ns_for_children);
// nodename
- bpf_probe_read(&nsp, sizeof(nsp), &task->nsproxy);
- bpf_probe_read(&uts_ns, sizeof(uts_ns), &nsp->uts_ns);
bpf_probe_read_str(&context->nodename, sizeof(context->nodename), &uts_ns->name.nodename);
// pid_namespace
- bpf_probe_read(&context->uts_inum, sizeof(context->uts_inum), &uts_ns->ns.inum);
+ context->pns = READ_KERN(pid_ns->ns.inum);
+ // For root pid_namespace, it's not that easy in eBPF. The way that we can get pid=1 task
+ // is to lookup (task->parent) recursively. And We should not do this for every time. (cache)
+ // Also, we should considered the situation that we can not get in the very first time
+ // since we used a bounded loop to get the root pid (The NUM 1 pid)
+ // Still, I think it's fine if we just get the root pid_namespace from usersapce. I do not
+ // catch the reason that Elkeid get this in root.
// sessionid
bpf_probe_read(&context->sessionid, sizeof(context->sessionid), &task->sessionid);
+ // This may changed since
struct pid_cache_t *parent = bpf_map_lookup_elem(&pid_cache_lru, &context->pid);
if (parent)
bpf_probe_read_str(&context->pcomm, sizeof(context->pcomm), &parent->pcomm);
diff --git a/plugin/driver/eBPF/userspace/decoder/decoder.go b/plugin/driver/eBPF/userspace/decoder/decoder.go
index 5405f6ae..957f25dc 100644
--- a/plugin/driver/eBPF/userspace/decoder/decoder.go
+++ b/plugin/driver/eBPF/userspace/decoder/decoder.go
@@ -54,27 +54,26 @@ func (decoder *EbpfDecoder) ReadAmountBytes() int {
func (decoder *EbpfDecoder) DecodeContext() (c *Context, err error) {
offset := decoder.cursor
- if len(decoder.buffer[offset:]) < 168 {
+ if len(decoder.buffer[offset:]) < 160 {
err = fmt.Errorf("can't read context from buffer: buffer too short")
return
}
ctx := NewContext()
ctx.Ts = binary.LittleEndian.Uint64(decoder.buffer[offset : offset+8])
ctx.CgroupID = binary.LittleEndian.Uint64(decoder.buffer[offset+8 : offset+16])
- ctx.UtsInum = binary.LittleEndian.Uint32(decoder.buffer[offset+16 : offset+20])
+ ctx.Pns = binary.LittleEndian.Uint32(decoder.buffer[offset+16 : offset+20])
ctx.Type = binary.LittleEndian.Uint32(decoder.buffer[offset+20 : offset+24])
ctx.Pid = binary.LittleEndian.Uint32(decoder.buffer[offset+24 : offset+28])
ctx.Tid = binary.LittleEndian.Uint32(decoder.buffer[offset+28 : offset+32])
ctx.Uid = binary.LittleEndian.Uint32(decoder.buffer[offset+32 : offset+36])
- ctx.EUid = binary.LittleEndian.Uint32(decoder.buffer[offset+36 : offset+40])
- ctx.Gid = binary.LittleEndian.Uint32(decoder.buffer[offset+40 : offset+44])
- ctx.Ppid = binary.LittleEndian.Uint32(decoder.buffer[offset+44 : offset+48])
- ctx.Sessionid = binary.LittleEndian.Uint32(decoder.buffer[offset+48 : offset+52])
- ctx.Comm = helper.ZeroCopyString(bytes.Trim(decoder.buffer[offset+52:offset+68], "\x00"))
- ctx.PComm = helper.ZeroCopyString(bytes.Trim(decoder.buffer[offset+68:offset+84], "\x00"))
- ctx.Nodename = helper.ZeroCopyString(bytes.Trim(decoder.buffer[offset+84:offset+148], "\x00"))
- ctx.RetVal = uint64(binary.LittleEndian.Uint64(decoder.buffer[offset+148 : offset+156]))
- ctx.Argnum = uint8(binary.LittleEndian.Uint16(decoder.buffer[offset+156 : offset+168]))
+ ctx.Gid = binary.LittleEndian.Uint32(decoder.buffer[offset+36 : offset+40])
+ ctx.Ppid = binary.LittleEndian.Uint32(decoder.buffer[offset+40 : offset+44])
+ ctx.Sessionid = binary.LittleEndian.Uint32(decoder.buffer[offset+44 : offset+48])
+ ctx.Comm = helper.ZeroCopyString(bytes.Trim(decoder.buffer[offset+48:offset+64], "\x00"))
+ ctx.PComm = helper.ZeroCopyString(bytes.Trim(decoder.buffer[offset+64:offset+80], "\x00"))
+ ctx.Nodename = helper.ZeroCopyString(bytes.Trim(decoder.buffer[offset+80:offset+144], "\x00"))
+ ctx.RetVal = uint64(binary.LittleEndian.Uint64(decoder.buffer[offset+144 : offset+152]))
+ ctx.Argnum = uint8(binary.LittleEndian.Uint16(decoder.buffer[offset+152 : offset+160]))
decoder.cursor += int(ctx.GetSizeBytes())
c = ctx
return
diff --git a/plugin/driver/eBPF/userspace/decoder/event.go b/plugin/driver/eBPF/userspace/decoder/event.go
index 9f212a0a..e24f9296 100644
--- a/plugin/driver/eBPF/userspace/decoder/event.go
+++ b/plugin/driver/eBPF/userspace/decoder/event.go
@@ -16,10 +16,6 @@ type Event interface {
var eventMap map[uint32]Event = make(map[uint32]Event)
func Regist(event Event) {
- // if event.ID() != 2001 {
- // return
- // }
- // fmt.Println(event.String(), " loaded!")
eventMap[event.ID()] = event
}
diff --git a/plugin/driver/eBPF/userspace/decoder/protocol.go b/plugin/driver/eBPF/userspace/decoder/protocol.go
index ec74de8c..613d70c9 100644
--- a/plugin/driver/eBPF/userspace/decoder/protocol.go
+++ b/plugin/driver/eBPF/userspace/decoder/protocol.go
@@ -15,23 +15,22 @@ func init() {
}
type Context struct {
- Ts uint64 `json:"timestamp"`
- CgroupID uint64 `json:"cgroupid"`
- UtsInum uint32 `json:"utsinum"`
- Type uint32 `json:"type"`
- Pid uint32 `json:"pid"`
- Tid uint32 `json:"tid"`
- Uid uint32 `json:"uid"`
- EUid uint32 `json:"euid"`
- Gid uint32 `json:"gid"`
- Ppid uint32 `json:"ppid"`
- Sessionid uint32 `json:"sessionid"`
- Comm string `json:"comm"`
- PComm string `json:"pomm"`
- Nodename string `json:"nodename"`
- RetVal uint64 `json:"retval"`
- Argnum uint8 `json:"argnum"`
- _ [11]byte `json:"-"`
+ Ts uint64 `json:"timestamp"`
+ CgroupID uint64 `json:"cgroupid"`
+ Pns uint32 `json:"pns"`
+ Type uint32 `json:"type"`
+ Pid uint32 `json:"pid"`
+ Tid uint32 `json:"tid"`
+ Uid uint32 `json:"uid"`
+ Gid uint32 `json:"gid"`
+ Ppid uint32 `json:"ppid"`
+ Sessionid uint32 `json:"sessionid"`
+ Comm string `json:"comm"`
+ PComm string `json:"pomm"`
+ Nodename string `json:"nodename"`
+ RetVal uint64 `json:"retval"`
+ Argnum uint8 `json:"-"`
+ _ [7]byte `json:"-"`
// added
Sha256 string `json:"sha256"`
Username string `json:"username"`
@@ -42,7 +41,7 @@ type Context struct {
}
func (Context) GetSizeBytes() uint32 {
- return 168
+ return 160
}
func (c *Context) SetEvent(event Event) {