Skip to content

Commit

Permalink
[feat] support capture zsh command (#683)
Browse files Browse the repository at this point in the history
  • Loading branch information
SenberHu authored Dec 6, 2024
1 parent 10717d9 commit c0a5c83
Show file tree
Hide file tree
Showing 10 changed files with 546 additions and 2 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,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.
* Bash audit, capture bash 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 MariaDB.

![](./images/ecapture-help-v0.8.9.svg)
Expand Down Expand Up @@ -149,6 +150,7 @@ 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
* 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
2 changes: 1 addition & 1 deletion builder/rpmBuild.spec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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.

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
56 changes: 56 additions & 0 deletions cli/cmd/zsh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build !androidgki
// +build !androidgki

// 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.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)
}
2 changes: 2 additions & 0 deletions kern/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#define MAX_DATA_SIZE_MYSQL 256
#define MAX_DATA_SIZE_POSTGRES 256
#define MAX_DATA_SIZE_BASH 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 +45,7 @@
#define BASH_EVENT_TYPE_READLINE 0
#define BASH_EVENT_TYPE_RETVAL 1
#define BASH_EVENT_TYPE_EXIT_OR_EXEC 2
#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
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_ZSH];
};

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;
}
94 changes: 94 additions & 0 deletions user/config/config_zsh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//go:build !androidgki
// +build !androidgki

// 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"
"fmt"
"os"
"strings"
)

// ZshConfig
type ZshConfig struct {
BaseConfig
Zshpath string `json:"zshpath"` //zsh的文件路径
ErrNo int
ElfType uint8 //
ReadlineFuncName string
}

func NewZshConfig() *ZshConfig {
config := &ZshConfig{}
config.PerCpuMapSize = DefaultMapSizePerCpu
return config
}

func (zc *ZshConfig) Check() error {
var binaryPath string
switch zc.ElfType {
case ElfTypeBin:
binaryPath = zc.Zshpath
default:
binaryPath = "/bin/zsh"
}

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

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

zc.ReadlineFuncName = "zleentry"

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

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

func (zc *ZshConfig) checkElf() error {
//如果配置 zsh的地址,且存在,则直接返回
if zc.Zshpath != "" || len(strings.TrimSpace(zc.Zshpath)) > 0 {
_, e := os.Stat(zc.Zshpath)
if e != nil {
return e
}
zc.ElfType = ElfTypeBin
return nil
}

return nil
}

func (zc *ZshConfig) Bytes() []byte {
b, e := json.Marshal(zc)
if e != nil {
return []byte{}
}
return b
}
98 changes: 98 additions & 0 deletions user/event/event_zsh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//go:build !androidgki
// +build !androidgki

// 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 event

import (
"bytes"
"encoding/binary"
"fmt"
"strings"

"golang.org/x/sys/unix"
)

/*
u8 type;
u32 pid;
u32 uid;
u8 line[MAX_DATA_SIZE_BASH];
u32 retval;
char comm[TASK_COMM_LEN];
*/

const MaxDataSizeZsh = 256

type ZshEvent struct {
eventType EventType
ZshType uint32 `json:"zsh_type"`
Pid uint32 `json:"pid"`
Uid uint32 `json:"uid"`
Comm [16]byte `json:"Comm"`
Line [MaxDataSizeZsh]uint8 `json:"line"`
}

func (be *ZshEvent) Decode(payload []byte) (err error) {
buf := bytes.NewBuffer(payload)
if err = binary.Read(buf, binary.LittleEndian, &be.ZshType); err != nil {
return
}
if err = binary.Read(buf, binary.LittleEndian, &be.Pid); err != nil {
return
}
if err = binary.Read(buf, binary.LittleEndian, &be.Uid); err != nil {
return
}
if err = binary.Read(buf, binary.LittleEndian, &be.Comm); err != nil {
return
}
if err = binary.Read(buf, binary.LittleEndian, &be.Line); err != nil {
return
}
return nil
}

func (be *ZshEvent) String() string {
s := fmt.Sprintf("PID:%d, UID:%d, \tComm:%s, \tLine:\n%s", be.Pid, be.Uid, be.Comm, strings.TrimSuffix(unix.ByteSliceToString(be.Line[:]), "\n"))
return s
}

func (be *ZshEvent) StringHex() string {
s := fmt.Sprintf("PID:%d, UID:%d, \tComm:%s, \tLine:\n%s,", be.Pid, be.Uid, be.Comm, be.Line)
return s
}

func (be *ZshEvent) Clone() IEventStruct {
event := new(ZshEvent)
event.eventType = EventTypeModuleData
return event
}

func (be *ZshEvent) EventType() EventType {
return be.eventType
}

func (be *ZshEvent) GetUUID() string {
return fmt.Sprintf("%d_%d_%s", be.Pid, be.Uid, be.Comm)
}

func (be *ZshEvent) Payload() []byte {
return be.Line[:]
}

func (be *ZshEvent) PayloadLen() int {
return len(be.Line)
}
2 changes: 2 additions & 0 deletions user/module/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (

const (
ModuleNameBash = "EBPFProbeBash"
ModuleNameZsh = "EBPFProbeZsh"
ModuleNameMysqld = "EBPFProbeMysqld"
ModuleNamePostgres = "EBPFProbePostgres"
ModuleNameOpenssl = "EBPFProbeOPENSSL"
Expand All @@ -36,6 +37,7 @@ const (

const (
BashErrnoDefault int = 128
ZshErrnoDefault int = 128
)

const (
Expand Down
Loading

0 comments on commit c0a5c83

Please sign in to comment.