Skip to content

Commit

Permalink
[feat] support capture dash and zsh command
Browse files Browse the repository at this point in the history
  • Loading branch information
SenberHu committed Nov 21, 2024
1 parent fcddaeb commit e15bb70
Show file tree
Hide file tree
Showing 15 changed files with 1,217 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
* SSL/TLS plaintext capture, support openssl\libressl\boringssl\gnutls\nspr(nss) libraries.
* GoTLS plaintext support go tls library, which refers to encrypted communication in https/tls programs written in the golang language.
* bash audit, capture bash command for Host Security Audit.
* dash audit, capture dash command for Host Security Audit.
* zsh audit, capture zsh command for Host Security Audit.
* mysql query SQL audit, support mysqld 5.6\5.7\8.0, and mariadDB.

![](./images/ecapture-help-v0.8.9.svg)
Expand Down Expand Up @@ -149,6 +151,8 @@ The document has moved
## Modules
The eCapture tool comprises 8 modules that respectively support plaintext capture for TLS/SSL encryption libraries like OpenSSL, GnuTLS, NSPR, BoringSSL, and GoTLS. Additionally, it facilitates software audits for Bash, MySQL, and PostgreSQL applications.
* bash capture bash command
* dash capture dash command
* zsh capture zsh command
* gnutls capture gnutls text content without CA cert for gnutls libraries.
* gotls Capturing plaintext communication from Golang programs encrypted with TLS/HTTPS.
* mysqld capture sql queries from mysqld 5.6/5.7/8.0 .
Expand Down
3 changes: 2 additions & 1 deletion builder/rpmBuild.spec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ GoTLS plaintext support go tls library, which refers to encrypted
Communication in https/tls programs written in the golang language.

Bash audit, capture bash command for Host Security Audit.

Dash audit, capture dash command for Host Security Audit.
Zsh audit, capture zsh command for Host Security Audit.
MySQL query SQL audit, support mysqld 5.6/5.7/8.0, and mariadDB.

%prep
Expand Down
54 changes: 54 additions & 0 deletions cli/cmd/dash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2022 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"github.com/gojue/ecapture/user/config"
"github.com/gojue/ecapture/user/module"
"github.com/spf13/cobra"
)

var dc = config.NewDashConfig()

// dashCmd represents the dash command
var dashCmd = &cobra.Command{
Use: "dash",
Short: "capture dash command",
Long: `eCapture capture dash commands for dash security audit,
Auto find the dash of the current env as the capture target.`,
Run: dashCommandFunc,
}

func init() {
dashCmd.PersistentFlags().StringVar(&dc.Dashpath, "dash", "", "$SHELL file path, eg: /bin/dash , will automatically find it from $ENV default.")
dashCmd.PersistentFlags().StringVar(&dc.Readline, "readlineso", "", "readline.so file path, will automatically find it from $BASH_PATH default.")
dashCmd.Flags().IntVarP(&dc.ErrNo, "errnumber", "e", module.DashErrnoDefault, "only show the command which exec reulst equals err number.")
rootCmd.AddCommand(dashCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// dashCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// dashCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

// dashCommandFunc executes the "dash" command.
func dashCommandFunc(command *cobra.Command, args []string) {
runModule(module.ModuleNameDash, dc)
}
54 changes: 54 additions & 0 deletions cli/cmd/zsh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2022 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"github.com/gojue/ecapture/user/config"
"github.com/gojue/ecapture/user/module"
"github.com/spf13/cobra"
)

var zc = config.NewZshConfig()

// zshCmd represents the zsh command
var zshCmd = &cobra.Command{
Use: "zsh",
Short: "capture zsh command",
Long: `eCapture capture zsh commands for zsh security audit,
Auto find the zsh of the current env as the capture target.`,
Run: zshCommandFunc,
}

func init() {
zshCmd.PersistentFlags().StringVar(&zc.Zshpath, "zsh", "", "$SHELL file path, eg: /bin/zsh , will automatically find it from $ENV default.")
zshCmd.PersistentFlags().StringVar(&zc.Readline, "readlineso", "", "readline.so file path, will automatically find it from $BASH_PATH default.")
zshCmd.Flags().IntVarP(&zc.ErrNo, "errnumber", "e", module.ZshErrnoDefault, "only show the command which exec reulst equals err number.")
rootCmd.AddCommand(zshCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all suzcommands, e.g.:
// zshCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// zshCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

// zshCommandFunc executes the "zsh" command.
func zshCommandFunc(command *cobra.Command, args []string) {
runModule(module.ModuleNameZsh, zc)
}
4 changes: 4 additions & 0 deletions kern/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#define MAX_DATA_SIZE_MYSQL 256
#define MAX_DATA_SIZE_POSTGRES 256
#define MAX_DATA_SIZE_BASH 256
#define MAX_DATA_SIZE_DASH 256
#define MAX_DATA_SIZE_ZSH 256

// enum_server_command, via
// https://dev.mysql.com/doc/internals/en/com-query.html COM_QUERT command 03
Expand All @@ -44,6 +46,8 @@
#define BASH_EVENT_TYPE_READLINE 0
#define BASH_EVENT_TYPE_RETVAL 1
#define BASH_EVENT_TYPE_EXIT_OR_EXEC 2
#define DASH_EVENT_TYPE_READLINE 3
#define ZSH_EVENT_TYPE_READLINE 4
///////// for TC & XDP ebpf programs in tc.h
#define TC_ACT_OK 0
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
Expand Down
61 changes: 61 additions & 0 deletions kern/dash_kern.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2022 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "ecapture.h"

struct event {
u32 type;
u32 pid;
u32 uid;
char comm[TASK_COMM_LEN];
u8 line[MAX_DATA_SIZE_DASH];
};

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
__uint(max_entries, 1024);
} events SEC(".maps");

// Force emitting struct event into the ELF.
const struct event *unused __attribute__((unused));

SEC("uprobe/dash_read")
int uprobe_dash_read(struct pt_regs *ctx) {
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u64 current_uid_gid = bpf_get_current_uid_gid();
u32 uid = current_uid_gid;
#ifndef KERNEL_LESS_5_2
// if target_ppid is 0 then we target all pids
if (target_pid != 0 && target_pid != pid) {
return 0;
}
if (target_uid != 0 && target_uid != uid) {
return 0;
}
#endif
u32 fd = (u32)PT_REGS_PARM1(ctx);
if (fd == 0) {
struct event event = {};
event.pid = pid;
event.uid = uid;
event.type = DASH_EVENT_TYPE_READLINE;
bpf_get_current_comm(&event.comm, sizeof(event.comm));
bpf_probe_read_user(&event.line, sizeof(event.line),(void *)PT_REGS_PARM2(ctx));
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(struct event));
}
return 0;
}
44 changes: 44 additions & 0 deletions kern/zsh_kern.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "ecapture.h"

struct event {
u32 type;
u32 pid;
u32 uid;
char comm[TASK_COMM_LEN];
u8 line[MAX_DATA_SIZE_DASH];
};

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
__uint(max_entries, 1024);
} events SEC(".maps");

// Force emitting struct event into the ELF.
const struct event *unused __attribute__((unused));

SEC("uretprobe/zsh_zleentry")
int uretprobe_zsh_zleentry(struct pt_regs *ctx) {
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u64 current_uid_gid = bpf_get_current_uid_gid();
u32 uid = current_uid_gid;
#ifndef KERNEL_LESS_5_2
// if target_ppid is 0 then we target all pids
if (target_pid != 0 && target_pid != pid) {
return 0;
}
if (target_uid != 0 && target_uid != uid) {
return 0;
}
#endif
struct event event = {};
event.pid = pid;
event.uid = uid;
event.type = ZSH_EVENT_TYPE_READLINE;
bpf_get_current_comm(&event.comm, sizeof(event.comm));
bpf_probe_read_user(&event.line, sizeof(event.line),(void *)PT_REGS_RC(ctx));
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(struct event));
return 0;
}
121 changes: 121 additions & 0 deletions user/config/config_dash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2022 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"debug/elf"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
)

// DashConfig Dashpath 与 readline 两个参数,使用时二选一
type DashConfig struct {
BaseConfig
Dashpath string `json:"dashpath"` //dash的文件路径
Readline string `json:"readline"`
ErrNo int
ElfType uint8 //
ReadlineFuncName string
}

func NewDashConfig() *DashConfig {
config := &DashConfig{}
config.PerCpuMapSize = DefaultMapSizePerCpu
return config
}

func (bc *DashConfig) Check() error {
var binaryPath string
switch bc.ElfType {
case ElfTypeBin:
binaryPath = bc.Dashpath
case ElfTypeSo:
binaryPath = bc.Readline
default:
binaryPath = "/bin/dash"
}

file, err := elf.Open(binaryPath)
if err != nil {
return err
}
defer file.Close()

symbols, err := file.DynamicSymbols()
if err != nil {
return err
}

bc.ReadlineFuncName = "read"

targetSymbol := "read"
for _, sym := range symbols {
if sym.Name == targetSymbol {
return nil
}
}

return fmt.Errorf("symbol [%s] not found in [%s]", targetSymbol, binaryPath)
}

func (bc *DashConfig) checkElf() error {
// 如果readline 配置,且存在,则直接返回。
if bc.Readline != "" || len(strings.TrimSpace(bc.Readline)) > 0 {
_, e := os.Stat(bc.Readline)
if e != nil {
return e
}
bc.ElfType = ElfTypeSo
return nil
}

//如果配置 dash的地址,且存在,则直接返回
if bc.Dashpath != "" || len(strings.TrimSpace(bc.Dashpath)) > 0 {
_, e := os.Stat(bc.Dashpath)
if e != nil {
return e
}
bc.ElfType = ElfTypeBin
return nil
}

//如果没配置,则自动查找。
dash, b := os.LookupEnv("SHELL")
if b {
soPath, e := getDynPathByElf(dash, "libreadline.so")
if e != nil {
bc.Dashpath = dash
bc.ElfType = ElfTypeBin
} else {
bc.Dashpath = soPath
bc.ElfType = ElfTypeSo
}

} else {
return errors.New("cant found $SHELL path.")
}
return nil
}

func (bc *DashConfig) Bytes() []byte {
b, e := json.Marshal(bc)
if e != nil {
return []byte{}
}
return b
}
Loading

0 comments on commit e15bb70

Please sign in to comment.