Skip to content

Commit

Permalink
Merge pull request #26 from chriskaliX/v1.0.0
Browse files Browse the repository at this point in the history
pre-CORE work & README update
  • Loading branch information
chriskaliX authored Mar 25, 2022
2 parents 8b9d673 + 780b69c commit 1f7f588
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 75 deletions.
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,40 @@

![language](https://shields.io/github/languages/top/chriskalix/HIDS-Linux)

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` 项目下
![data](https://github.com/chriskaliX/HIDS-Linux/blob/main/imgs/agent.png)
### Agent 部分

### 数据处理
![data](https://github.com/chriskaliX/HIDS-Linux/blob/main/imgs/agent.png)

> Agent 字段连接公司对应的 cmdb,做初步扩展。之后走入 Flink CEP 做初步的节点数据清洗。打入 HIVE 时根据情况,也可再做一次清洗减小性能消耗。清洗过后的数据走入第二个 Flink CEP 以及规则引擎,HIDS 的规则部分其实较为头疼,是一个 HIDS 能否用好的关键所在,后续会把自己的想法逐步开源
### 数据处理流程

![data](https://github.com/chriskaliX/HIDS-Linux/blob/main/imgs/data_analyze.png)

## 目前阶段
## 插件列表

用户态基本完成,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, 还有一些纰漏, 慢慢的修复
## 目前进展

![data](https://github.com/chriskaliX/HIDS-Linux/blob/main/imgs/examples.png)
支持 `13` 种 Hook,涵盖大部分安全审计检测需求

## 开发计划

> 记录一些方案选择, 目前进度等,另外 golang 1.18 上线啦~ [官方 Tutorial](https://golang.google.cn/doc/tutorial/generics),后续会开始多试试 Generics
### 插件交互
### Agent-插件 交互

> Linux 进程间通信的[方式](https://www.linuxprobe.com/linux-process-method.html)
Expand Down Expand Up @@ -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 心跳 & 配置检测
Expand All @@ -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)

## 交流群

<img src="https://github.com/chriskaliX/Hades/blob/main/imgs/feishu.png" width="50%" style="float:left;"/>

<img src="https://github.com/chriskaliX/Hades/blob/main/imgs/WechatIMG120.jpeg" width="50%" style="float:left;"/>
27 changes: 20 additions & 7 deletions plugin/driver/eBPF/kernel/include/define.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#ifndef __DEFINE_H
#define __DEFINE_H
#ifndef CORE
#include <linux/kconfig.h>
#include <linux/sched.h>
#include <linux/nsproxy.h>
Expand All @@ -17,6 +18,10 @@
#include <linux/fs.h>
#include <net/inet_sock.h>
#include <uapi/linux/un.h>
#else
#include <vmlinux.h>
#endif

#include "bpf_helpers.h"
#include "bpf_core_read.h"

Expand All @@ -25,15 +30,14 @@
#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
#define TMP_BUF_IDX 1
#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 ========== */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -155,14 +158,26 @@ 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; \
__builtin_memset((void *)&_val, 0, sizeof(_val)); \
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)
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion plugin/driver/eBPF/kernel/include/hades_net.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
39 changes: 22 additions & 17 deletions plugin/driver/eBPF/kernel/include/utils.h
Original file line number Diff line number Diff line change
@@ -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 <linux/sched.h>
#include <linux/fdtable.h>
#include <utils_buf.h>
#include <linux/mm_types.h>
#include <net/ipv6.h>
#include <linux/ipv6.h>
#include <linux/pid_namespace.h>
#else
#include <vmlinux.h>
#endif

#include "bpf_helpers.h"
#include "bpf_core_read.h"
#include "bpf_endian.h"

// TODO: 后期改成动态的
/* R3 max value is outside of the array range */
Expand All @@ -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();
Expand All @@ -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);
Expand Down
21 changes: 10 additions & 11 deletions plugin/driver/eBPF/userspace/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions plugin/driver/eBPF/userspace/decoder/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
35 changes: 17 additions & 18 deletions plugin/driver/eBPF/userspace/decoder/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -42,7 +41,7 @@ type Context struct {
}

func (Context) GetSizeBytes() uint32 {
return 168
return 160
}

func (c *Context) SetEvent(event Event) {
Expand Down

0 comments on commit 1f7f588

Please sign in to comment.