eCapture 一個神奇的開源工具

一 前言

前段時間,一直對eBPF感興趣戏仓,也寫了一些eBPF的入門介紹文章疚宇,朋友就發(fā)來個連接說這是一個好東西竞惋,我看,是利用eBPF來實現(xiàn)的灰嫉,剛好最近在研究,所以就有了這篇的介紹文章嗓奢。

二 eCapture 是什么

官方介紹如下:

eBPF HOOK uprobe實現(xiàn)的各種用戶態(tài)進程的數(shù)據(jù)捕獲讼撒,無需改動原程序。
SSL/HTTPS數(shù)據(jù)導出功能股耽,針對HTTPS的數(shù)據(jù)包抓取根盒,不需要導入CA證書。
bash的命令捕獲物蝙,HIDS的bash命令監(jiān)控解決方案炎滞。
mysql query等數(shù)據(jù)庫的數(shù)據(jù)庫審計解決方案。

地址:ecapture/README_CN.md at master · ehids/ecapture (github.com)

eBPF程序可以對內(nèi)核的靜態(tài)跟蹤點诬乞,動態(tài)跟蹤點册赛,設置鉤子,然后當內(nèi)核執(zhí)行到這些點的時候震嫉,會自動調用我們設置好的鉤子森瘪,這樣我們就可以對一些數(shù)據(jù)進行監(jiān)控,網(wǎng)絡流量也可以進行轉發(fā)票堵,甚至對數(shù)據(jù)包進行修改扼睬,簡直是黑魔法;不光可以對內(nèi)核的進行動態(tài)追蹤悴势,對用戶態(tài)進程也可以窗宇,我記得我上篇文章就介紹對簡單的c程序的代碼的hook。

eCapture就是這個原理特纤,對用戶態(tài)程序的具體追蹤军俊,具體原理如下:

eCapture系統(tǒng)用戶態(tài)程序使用Golang語言開發(fā),具有良好的系統(tǒng)兼容性捧存,無依賴快速部署蝇完,更適合云原生場景。 內(nèi)核態(tài)代碼使用C編寫矗蕊,使用clang/llvm編譯短蜕,生產(chǎn)bpf字節(jié)碼后,采用go-bindata轉化為golang語法文件傻咖,之后采用ehids/ebpfmanager類庫朋魔,調用bpf syscall進行加載、HOOK卿操、map讀取警检。 golang編譯后孙援,無其他任何依賴即可運行,兼容linux kernel 4.18以上所有版本扇雕。

架構圖如下:


ecapture架構圖

三 安裝和使用

我曾經(jīng)想在centos 8.x 上嘗試拓售,發(fā)現(xiàn)充滿了挫折,真不建議用這個系統(tǒng)作為ebpf的實驗環(huán)境,又發(fā)現(xiàn)不少朋友采用 ubuntu服務器镶奉,遂下載 ubuntu-21.10-live-server-amd64.iso安裝體驗了一把础淤,可以說是非常順利,幾乎使用到的安裝命令都是一把過哨苛,強烈推薦鸽凶,而且沒有的命令,會直接提供安裝命令建峭,非常的人性化玻侥;
當然如果你就喜歡挑戰(zhàn)困難,可以試試centos 8.x上編譯內(nèi)核亿蒸,打開一些選項后凑兰,編譯起來常常報錯,安裝相關依賴又困難重重边锁,我遇到錯誤如下票摇,如果有大神對這方便比較精通可以指點下:

vim .config
# 添加: CONFIG_DEBUG_INFO_BTF=y

# 編譯
make  -j4
LD [M]  kernel/bpf/preload/bpf_preload.o
ld: kernel/bpf/preload/bpf_preload_kern.o: unable to initialize decompress status for section .debug_addr
ld: kernel/bpf/preload/bpf_preload_kern.o: unable to initialize decompress status for section .debug_addr
ld: kernel/bpf/preload/bpf_preload_kern.o: unable to initialize decompress status for section .debug_addr
ld: kernel/bpf/preload/bpf_preload_kern.o: unable to initialize decompress status for section .debug_addr
kernel/bpf/preload/bpf_preload_kern.o: file not recognized: 不可識別的文件格式
make[3]: *** [scripts/Makefile.build:483:kernel/bpf/preload/bpf_preload.o] 錯誤 1
make[2]: *** [scripts/Makefile.build:549:kernel/bpf/preload] 錯誤 2
make[1]: *** [scripts/Makefile.build:549:kernel/bpf] 錯誤 2
make: *** [Makefile:1846:kernel] 錯誤 2
make: *** 正在等待未完成的任務....

3.1 ubuntu下環(huán)境準備

3.1.1 安裝編譯和依賴頭文件工具等

sudo apt-get install -y  make clang llvm libelf-dev libbpf-dev bpfcc-tools libbpfcc-dev linux-tools-$(uname -r) linux-headers-$(uname -r)

3.1.2 安裝git和golang

sudo apt-get git
sudo apt-get golang

指定golang使用代理,不然下載不了github上的庫:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

3.1.3 編譯eCapture

非常順利的一氣呵成的編譯:

git clone https://github.com/ehids/ecapture.git
cd ecapture
make

3.2 運行使用

root@ubuntu-lab:/home/miao/ecapture-master# ./bin/ecapture 
NAME:
    ecapture - capture text SSL content without CA cert by ebpf hook.

USAGE:
    ecapture [flags]

VERSION:
    --

COMMANDS:
    bash    capture bash command
    help    Help about any command
    mysqld  capture sql queries from mysqld 5.6/5.7/8.0 .
    tls alias name:openssl , use to capture tls/ssl text content without CA cert.

DESCRIPTION:
    eCapture is a tool that can capture plaintext packets 
    such as HTTPS and TLS without installing a CA certificate.
    It can also capture bash commands, which is suitable for 
    security auditing scenarios, such as database auditing of mysqld, etc.
    
    Repository: https://github.com/ehids/ecapture

OPTIONS:
  -d, --debug[=false]   enable debug logging
  -h, --help[=false]    help for ecapture
      --hex[=false] print byte strings as hex encoded strings
  -p, --pid=0       if target_pid is 0 then we target all pids

通過命令來看砚蓬,核心的三個功能矢门,一個是bash的輸入內(nèi)容的記錄,一個是對mysql的sql命令的記錄灰蛙,一個是對tls 明文數(shù)據(jù)捕獲祟剔。

  1. bash記錄,可以用在入侵檢測摩梧,或者我們打開功能后可以監(jiān)看黑客的入侵過程物延;
  2. mysql的sql命令記錄,可以用在數(shù)據(jù)庫審計仅父,數(shù)據(jù)防泄露等場景叛薯;
  3. tls明文數(shù)據(jù)捕獲,可以用在比較機器被入侵了笙纤,通信是加密的耗溜,我們可以通過這些命令查看傳遞的是什么內(nèi)容,這點真的很酷省容。

好了抖拴,下面進入演示環(huán)節(jié):

3.2.1 bash命令記錄

root@ubuntu-lab:/home/miao/ecapture-master# ./bin/ecapture  bash
2022/04/09 04:45:48 start to run EBPFProbeBash module
2022/04/09 04:45:48 pid info :8130
2022/04/09 04:45:48 HOOK binrayPath:/bin/bash, FunctionName:readline
2022/04/09 04:45:48 target all process. 
2022/04/09 04:45:54  PID:3687,  Comm:bash,  Line: pwd
2022/04/09 04:46:03  PID:3687,  Comm:bash,  Line: cat /etc/passwd

如上,我們可以在記錄中查看到命令執(zhí)行的時間腥椒,命令名為bash,命令id為3687,具體命令行為pwd虏两,第二個命令為cat /etc/passwd.

3.2.2 mysql命令記錄

# 安裝server 注意這個程序不支持mysql,所以只能安裝mariadb
miao@ubuntu-lab:~/ehids-agent-master$ sudo apt-get  install mariadb-server 

# mysql命令執(zhí)行:
sudo mysql -u root
[sudo] password for miao: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 30
Server version: 10.5.15-MariaDB-0ubuntu0.21.10.1 Ubuntu 21.10

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
3 rows in set (0.001 sec)

MariaDB [(none)]> create database test;
Query OK, 1 row affected (0.000 sec)

MariaDB [(none)]> use test;
Database changed
MariaDB [test]> CREATE TABLE user(
    ->            id int(10) unsigned not null auto_increment,
    ->            name varchar(25),
    ->            sex varchar(5),
    ->            age int(10),
    ->            password varchar(25),
    ->            primary key(id))engine=Innodb;
Query OK, 0 rows affected (0.013 sec)

MariaDB [test]> create table test(id  int(10),name varchar(64),aget int(10));
Query OK, 0 rows affected (0.010 sec)
MariaDB [test]> insert test values(1,'abc',14);
Query OK, 1 row affected (0.003 sec)
MariaDB [test]> insert test values(2,'edf',14);
Query OK, 1 row affected (0.001 sec)
MariaDB [test]> insert test values(3,'dfg',18);
Query OK, 1 row affected (0.001 sec)
MariaDB [test]> select * from test;
+------+------+------+
| id   | name | aget |
+------+------+------+
|    1 | abc  |   14 |
|    2 | edf  |   14 |
|    3 | dfg  |   18 |
+------+------+------+
3 rows in set (0.000 sec)

MariaDB [test]> delete from test where id=3;
Query OK, 1 row affected (0.001 sec)
MariaDB [test]> commit;
Query OK, 0 rows affected (0.000 sec)
MariaDB [test]> drop table user;
Query OK, 0 rows affected (0.003 sec)
MariaDB [test]> quit
Bye

演示了增刪改查和創(chuàng)建刪除表等命令雌澄,我們來看看審計的情況:

root@ubuntu-lab:/home/miao/ecapture-master# ./bin/ecapture mysqld
2022/04/09 04:57:32 start to run EBPFProbeMysqld module
2022/04/09 04:57:32 pid info :11307
2022/04/09 04:57:32 Mysql Version:mysqld-5.6, binrayPath:/usr/sbin/mariadbd, FunctionName:_Z16dispatch_command19enum_server_commandP3THDPcjbb ,UprobeOffset:0
2022/04/09 04:57:48  PID:10185, Comm:mariadbd, Time:3827419930458,  length:(32/32),  Line:select @@version_comment limit 1
2022/04/09 04:58:06  PID:10185, Comm:mariadbd, Time:3845780980800,  length:(14/14),  Line:show databases
2022/04/09 04:58:16  PID:10185, Comm:mariadbd, Time:3855625666790,  length:(20/20),  Line:create database test
2022/04/09 04:58:20  PID:10185, Comm:mariadbd, Time:3859549915643,  length:(17/17),  Line:SELECT DATABASE()
2022/04/09 04:58:20  PID:10185, Comm:mariadbd, Time:3859551387926,  length:(14/14),  Line:show databases
2022/04/09 04:58:20  PID:10185, Comm:mariadbd, Time:3859551716349,  length:(11/11),  Line:show tables
2022/04/09 05:00:39  PID:10185, Comm:mariadbd, Time:3998880500344,  length:(228/228),  Line:CREATE TABLE user(
           id int(10) unsigned not null auto_increment,
           name varchar(25),
           sex varchar(5),
           age int(10),
           password varchar(25),
           primary key(id))engine=Innodb
2022/04/09 05:02:51  PID:10185, Comm:mariadbd, Time:4131399810197,  length:(60/60),  Line:create table test(id  int(10),name varchar(64),aget int(10))
2022/04/09 05:03:09  PID:10185, Comm:mariadbd, Time:4148536516108,  length:(30/30),  Line:insert test values(1,'abc',14)
2022/04/09 05:03:16  PID:10185, Comm:mariadbd, Time:4156272316044,  length:(30/30),  Line:insert test values(2,'edf',14)
2022/04/09 05:03:25  PID:10185, Comm:mariadbd, Time:4165122488367,  length:(30/30),  Line:insert test values(3,'dfg',18)
2022/04/09 05:03:32  PID:10185, Comm:mariadbd, Time:4172032070611,  length:(18/18),  Line:select * from test
2022/04/09 05:04:00  PID:10185, Comm:mariadbd, Time:4199712261102,  length:(27/27),  Line:delete from test where id=3
2022/04/09 05:04:02  PID:10185, Comm:mariadbd, Time:4202319566537,  length:(6/6),  Line:commit
2022/04/09 05:04:17  PID:10185, Comm:mariadbd, Time:4217159993175,  length:(15/15),  Line:drop table user

審計記錄的內(nèi)容包括:時間、進程id、進程名、命令長度妖异、命令行;
注意:沒有執(zhí)行結果的展示煌茴,也沒有命令的返回值,如果能做到輸入的命令和返回的內(nèi)容就好了日川,不過這個改造起來難度應該也不大蔓腐,如果返回的select的內(nèi)容很多,可能map記錄不一定存的下龄句,但是我覺得執(zhí)行成功失敗還是要有的回论,有興趣的同學可以繼續(xù)完善下。

3.2.2 tls 通信明文還原

本來我是用編譯的程序來還原的分歇,發(fā)現(xiàn)不能用傀蓉,原因是:

root@ubuntu-lab:/home/miao/ecapture-master/bin# ./ecapture  tls
2022/04/09 07:33:55 pid info :17222
2022/04/09 07:33:55 start to run EBPFProbeOPENSSL module
2022/04/09 07:33:55 start to run EBPFProbeGNUTLS module
2022/04/09 07:33:55 start to run EBPFProbeNSPR module
2022/04/09 07:33:55 HOOK type:2, binrayPath:/lib/x86_64-linux-gnu/libnspr4.so
2022/04/09 07:33:55 target all process. 
2022/04/09 07:33:55 HOOK type:2, binrayPath:/lib/x86_64-linux-gnu/libssl.so.1.1
2022/04/09 07:33:55 libPthread so Path:/lib/x86_64-linux-gnu/libpthread.so.0
2022/04/09 07:33:55 target all process. 
2022/04/09 07:33:55 HOOK type:2, binrayPath:/lib/x86_64-linux-gnu/libgnutls.so.30
2022/04/09 07:33:55 target all process. 
2022/04/09 07:33:55 couldn't start bootstrap manager: error:1 error occurred:
    * error:opening uprobe: symbol connect: not found , isRet:%!d(bool=false), {UID:, EbpfFuncName:probe_connect}

, probes activation validation failed

看樣子是ebpf的函數(shù)名竟然找不到,可能是我裝的ebpf版本太新了职抡≡崃牵看還有一個編譯好的版本,所以嘗試下:

wget https://github.do/https://github.com/ehids/ecapture/releases/download/v0.1.5/ecapture_v0.1.5.zip

解壓后運行缚甩,果然沒報錯:

root@ubuntu-lab:/home/miao# ./ecapture tls
2022/04/09 07:39:15 pid info :17414
2022/04/09 07:39:15 start to run EBPFProbeOPENSSL module
2022/04/09 07:39:15 start to run EBPFProbeGNUTLS module
2022/04/09 07:39:15 start to run EBPFProbeNSPR module
2022/04/09 07:39:15 HOOK type:2, binrayPath:/lib/x86_64-linux-gnu/libssl.so.1.1
2022/04/09 07:39:15 target all process. 
2022/04/09 07:39:15 HOOK type:2, binrayPath:/lib/x86_64-linux-gnu/libgnutls.so.30
2022/04/09 07:39:15 target all process. 
2022/04/09 07:39:15 HOOK type:2, binrayPath:/lib/x86_64-linux-gnu/libnspr4.so
2022/04/09 07:39:15 target all process. 
2022/04/09 07:40:38  PID:17458, Comm:wget, TID:17458, TYPE:Send, DataLen:126 bytes, Payload:
GET / HTTP/1.1
User-Agent: Wget/1.21
Accept: */*
Accept-Encoding: identity
Host: www.baidu.com
Connection: Keep-Alive


2022/04/09 07:40:38  PID:17458, Comm:wget, TID:17458, TYPE:Recived, DataLen:116 bytes, Payload:
HTTP/1.1 200 OK
Content-Length: 2443
Content-Type: text/html
Server: bfe
Date: Sat, 09 Apr 2022 07:40:38 GMT


2022/04/09 07:40:38  PID:17458, Comm:wget, TID:17458, TYPE:Recived, DataLen:1063 bytes, Payload:
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下谱净,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度
2022/04/09 07:40:38  PID:17458, Comm:wget, TID:17458, TYPE:Recived, DataLen:1380 bytes, Payload:
下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新聞</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地圖</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>視頻</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>貼吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登錄</a> </noscript> <script>document.write('<a + encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登錄</a>');
                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多產(chǎn)品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>關于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必讀</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意見反饋</a>&nbsp;京ICP證030173號&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

這是我在另外一個終端運行,抓獲的結果擅威。

wget https://www.baidu.com 

同時發(fā)現(xiàn)個問題壕探,我在服務器上部署一個https的服務器,通過windows下的瀏覽器訪問這個地址郊丛,沒有看到抓獲的明文內(nèi)容李请,所以我猜應該是只處理了客戶端命令,回頭看下原理驗證下厉熟。

四 原理分析

原理比較簡單导盅,其實就是去找用戶空間的運行程序或庫的特定函數(shù)的地址信息,然后在這個函數(shù)地址安裝hook揍瑟,這樣就可以在程序進入到這個函數(shù)的時候认轨,執(zhí)行我們的代碼,就可以把得到的報文信息簡單打印出來月培。

這些函數(shù)位置如何找那嘁字,參考官網(wǎng)的文章恩急,以tls模塊為例子說明下。

4.1 tls 明文抓包的原理

ecapture tls 命令啟動tls/ssl 模塊纪蜒,支持三個常用的tls庫衷恭,分別是:

  1. openssl ,動態(tài)鏈接庫名字為libssl.so
  2. gnutls 纯续,動態(tài)鏈接庫名字為libgnutls.so
  3. nss/nspr 随珠,動態(tài)鏈接庫名字為libnspr4.so


    圖來自官網(wǎng),侵權刪除

比如我們測試用的是wget猬错,我們先查看wget使用什么tls庫窗看,如下命令:

root@ubuntu-lab:/home/miao/apache-tomcat-9.0.0.M20/bin# ldd `which wget` |grep -E "tls|ssl|nspr|nss"
    libssl.so.1.1 => /lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007f9099d0a000)

我們可以看到依賴的libssl庫是ecapture 可以hook的,所以我們可以使用wget進行https請求倦炒,同時進行明文抓包显沈。這樣看來,只要程序進行通信的時候使用了這三類tls庫逢唤,就可以進行明文抓包拉讯。

下一步就是定位函數(shù)了,定位函數(shù)需要對libssl庫有所了解鳖藕,知道哪個是解密出明文的魔慷,然后通過:

objdump -T /lib/x86_64-linux-gnu/libssl.so.1.1

這個命令查看符號的地址,做hook即可著恩,這是我猜測的院尔,看了下代碼:

func (this *MOpenSSLProbe) setupManagers() error {
    var binaryPath, libPthread string
    switch this.conf.(*OpensslConfig).elfType {
    case ELF_TYPE_BIN:
        binaryPath = this.conf.(*OpensslConfig).Curlpath
    case ELF_TYPE_SO:
        binaryPath = this.conf.(*OpensslConfig).Openssl
    default:
        //如果沒找到
        binaryPath = "/lib/x86_64-linux-gnu/libssl.so.1.1"
    }

    libPthread = this.conf.(*OpensslConfig).Pthread
    if libPthread == "" {
        libPthread = "/lib/x86_64-linux-gnu/libpthread.so.0"
    }
    _, err := os.Stat(binaryPath)
    if err != nil {
        return err
    }

    this.logger.Printf("HOOK type:%d, binrayPath:%s\n", this.conf.(*OpensslConfig).elfType, binaryPath)
    this.logger.Printf("libPthread so Path:%s\n", libPthread)

    this.bpfManager = &manager.Manager{
        Probes: []*manager.Probe{
            {
                Section:          "uprobe/SSL_write",
                EbpfFuncName:     "probe_entry_SSL_write",
                AttachToFuncName: "SSL_write",
                BinaryPath:       binaryPath,
            },
            {
                Section:          "uretprobe/SSL_write",
                EbpfFuncName:     "probe_ret_SSL_write",
                AttachToFuncName: "SSL_write",
                BinaryPath:       binaryPath,
            },
            {
                Section:          "uprobe/SSL_read",
                EbpfFuncName:     "probe_entry_SSL_read",
                AttachToFuncName: "SSL_read",
                BinaryPath:       binaryPath,
            },
            {
                Section:          "uretprobe/SSL_read",
                EbpfFuncName:     "probe_ret_SSL_read",
                AttachToFuncName: "SSL_read",
                BinaryPath:       binaryPath,
            },
            {
                Section:          "uprobe/connect",
                EbpfFuncName:     "probe_connect",
                AttachToFuncName: "connect",
                BinaryPath:       libPthread,
            },
        },

        Maps: []*manager.Map{
            {
                Name: "tls_events",
            },
            {
                Name: "connect_events",
            },
        },
    }

    this.bpfManagerOptions = manager.Options{
        DefaultKProbeMaxActive: 512,

        VerifierOptions: ebpf.CollectionOptions{
            Programs: ebpf.ProgramOptions{
                LogSize: 2097152,
            },
        },

        RLimit: &unix.Rlimit{
            Cur: math.MaxUint64,
            Max: math.MaxUint64,
        },
        // 填充 RewriteContants 對應map
        ConstantEditors: this.constantEditor(),
    }
    return nil
}

可以看到就是在SSL_read和SSL_write的函數(shù)上建立hook,hook函數(shù)看樣子是probe_ret_SSL_read和probe_ret_SSL_write喉誊,我們繼續(xù)看下這兩個函數(shù)是干嘛的召边。

我們只看openssl的代碼,這部分是通過c代碼來實現(xiàn)的裹驰,結構很清晰:

SEC("uprobe/SSL_write")
int probe_entry_SSL_write(struct pt_regs* ctx) {
    uint64_t current_pid_tgid = bpf_get_current_pid_tgid();
    uint32_t pid = current_pid_tgid >> 32;

    // if target_ppid is 0 then we target all pids
    if (target_pid != 0 && target_pid != pid) {
        return 0;
    }

    void * ssl = (void *) PT_REGS_PARM1(ctx);
    // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/crypto/bio/bio_local.h
    struct ssl_st  ssl_info;
    bpf_probe_read_user(&ssl_info, sizeof(ssl_info), ssl);

    struct BIO  bio_w;
    bpf_probe_read_user(&bio_w, sizeof(bio_w), ssl_info.wbio);

    // get fd ssl->wbio->num
    u32 fd = bio_w.num;
    debug_bpf_printk("uprobe SSL_write FD:%d\n", fd);

    const char* buf = (const char*)PT_REGS_PARM2(ctx);
    struct active_ssl_buf active_ssl_buf_t;
    __builtin_memset(&active_ssl_buf_t, 0, sizeof(active_ssl_buf_t));
    active_ssl_buf_t.fd = fd;
    active_ssl_buf_t.buf = buf;
    bpf_map_update_elem(&active_ssl_write_args_map, &current_pid_tgid, &active_ssl_buf_t, BPF_ANY);

    return 0;
}

SEC("uretprobe/SSL_write")
int probe_ret_SSL_write(struct pt_regs* ctx) {
    uint64_t current_pid_tgid = bpf_get_current_pid_tgid();
    uint32_t pid = current_pid_tgid >> 32;

    // if target_ppid is 0 then we target all pids
    if (target_pid != 0 && target_pid != pid) {
        return 0;
    }

    struct active_ssl_buf* active_ssl_buf_t = bpf_map_lookup_elem(&active_ssl_write_args_map, &current_pid_tgid);
    if (active_ssl_buf_t != NULL) {
        const char* buf;
        u32 fd = active_ssl_buf_t->fd;
        bpf_probe_read(&buf, sizeof(const char *), &active_ssl_buf_t->buf);
        process_SSL_data(ctx, current_pid_tgid, kSSLWrite, buf, fd);
    }
    bpf_map_delete_elem(&active_ssl_write_args_map, &current_pid_tgid);
    return 0;
}

// Function signature being probed:
// int SSL_read(SSL *s, void *buf, int num)
SEC("uprobe/SSL_read")
int probe_entry_SSL_read(struct pt_regs* ctx) {
    uint64_t current_pid_tgid = bpf_get_current_pid_tgid();
    uint32_t pid = current_pid_tgid >> 32;

    // if target_ppid is 0 then we target all pids
    if (target_pid != 0 && target_pid != pid) {
        return 0;
    }

    void * ssl = (void *) PT_REGS_PARM1(ctx);
    // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/crypto/bio/bio_local.h
    struct ssl_st  ssl_info;
    bpf_probe_read_user(&ssl_info, sizeof(ssl_info), ssl);

    struct BIO  bio_r;
    bpf_probe_read_user(&bio_r, sizeof(bio_r), ssl_info.rbio);

    // get fd ssl->rbio->num
    u32 fd = bio_r.num;
    debug_bpf_printk("uprobe SSL_read FD:%d\n", fd);

    const char* buf = (const char*)PT_REGS_PARM2(ctx);
    struct active_ssl_buf active_ssl_buf_t;
    __builtin_memset(&active_ssl_buf_t, 0, sizeof(active_ssl_buf_t));
    active_ssl_buf_t.fd = fd;
    active_ssl_buf_t.buf = buf;
    bpf_map_update_elem(&active_ssl_read_args_map, &current_pid_tgid, &active_ssl_buf_t, BPF_ANY);
    return 0;
}

SEC("uretprobe/SSL_read")
int probe_ret_SSL_read(struct pt_regs* ctx) {
    uint64_t current_pid_tgid = bpf_get_current_pid_tgid();
    uint32_t pid = current_pid_tgid >> 32;

    // if target_ppid is 0 then we target all pids
    if (target_pid != 0 && target_pid != pid) {
        return 0;
    }

    struct active_ssl_buf* active_ssl_buf_t = bpf_map_lookup_elem(&active_ssl_read_args_map, &current_pid_tgid);
    if (active_ssl_buf_t != NULL) {
        const char* buf;
        u32 fd = active_ssl_buf_t->fd;
        bpf_probe_read(&buf, sizeof(const char *), &active_ssl_buf_t->buf);
        process_SSL_data(ctx, current_pid_tgid, kSSLRead, buf, fd);
    }
    bpf_map_delete_elem(&active_ssl_read_args_map, &current_pid_tgid);
    return 0;
}

最終都調用process_SSL_data來處理的隧熙,代碼如下:

static int process_SSL_data(struct pt_regs* ctx, uint64_t id, enum ssl_data_event_type type,
                            const char* buf, u32 fd) {
    int len = (int)PT_REGS_RC(ctx);
    if (len < 0) {
        return 0;
    }

    struct ssl_data_event_t* event = create_ssl_data_event(id);
    if (event == NULL) {
        return 0;
    }

    event->type = type;
    event->fd = fd;
    // This is a max function, but it is written in such a way to keep older BPF verifiers happy.
    event->data_len = (len < MAX_DATA_SIZE_OPENSSL ? (len & (MAX_DATA_SIZE_OPENSSL - 1)) : MAX_DATA_SIZE_OPENSSL);
    bpf_probe_read(event->data, event->data_len, buf);
    bpf_get_current_comm(&event->comm, sizeof(event->comm));
    bpf_perf_event_output(ctx, &tls_events, BPF_F_CURRENT_CPU, event,sizeof(struct ssl_data_event_t));
    return 0;
}

通過bpf的函數(shù)讀取解密后的數(shù)據(jù),進程命令幻林,然后通過事件的形式輸出贞盯。
整體邏輯沒細看,大差不差吧沪饺,下次有空好好分析下代碼吸收下躏敢。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市整葡,隨后出現(xiàn)的幾起案子件余,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啼器,死亡現(xiàn)場離奇詭異旬渠,居然都是意外死亡,警方通過查閱死者的電腦和手機端壳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門告丢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人损谦,你說我怎么就攤上這事岖免。” “怎么了照捡?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵颅湘,是天一觀的道長。 經(jīng)常有香客問我栗精,道長闯参,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任术羔,我火速辦了婚禮赢赊,結果婚禮上乙漓,老公的妹妹穿的比我還像新娘级历。我一直安慰自己,他們只是感情好叭披,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布寥殖。 她就那樣靜靜地躺著,像睡著了一般涩蜘。 火紅的嫁衣襯著肌膚如雪嚼贡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天同诫,我揣著相機與錄音粤策,去河邊找鬼。 笑死误窖,一個胖子當著我的面吹牛叮盘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霹俺,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼柔吼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丙唧?” 一聲冷哼從身側響起愈魏,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后培漏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溪厘,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年北苟,在試婚紗的時候發(fā)現(xiàn)自己被綠了桩匪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡友鼻,死狀恐怖傻昙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情彩扔,我是刑警寧澤妆档,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站虫碉,受9級特大地震影響贾惦,放射性物質發(fā)生泄漏。R本人自食惡果不足惜敦捧,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一须板、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兢卵,春花似錦习瑰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窃款,卻和暖如春课兄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晨继。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工烟阐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人紊扬。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓蜒茄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親珠月。 傳聞我的和親對象是個殘疾皇子扩淀,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容