Your7Maxx/FlowGod

SSL_read读取的响应内容为空

Closed this issue · 9 comments

jpgong commented

使用attach_uprobe在SSL_read挂载,读取的内容为0字节。SSL_read和SSL_write的逻辑应该是相通的,不清楚为啥不能读取响应内容。

代码如下:
`#!/usr/bin/env python

coding: utf-8

from bcc import BPF

bpf_source = """
#include <uapi/linux/ptrace.h>
#define MAX_BUF_SIZE 4000
struct event_t {
u32 pid;
u32 len;
u8 buf[MAX_BUF_SIZE];
};

BPF_PERF_OUTPUT(events);

BPF_HASH(bpf_context_1, u64, struct event_t, 2048);
BPF_ARRAY(bpf_context_gen_1, struct event_t, 1);

static struct event_t *make_event_1() {
int zero = 0;
struct event_t *bpf_ctx = bpf_context_gen_1.lookup(&zero);
if (!bpf_ctx) return 0;
u64 id = bpf_get_current_pid_tgid();
bpf_context_1.update(&id, bpf_ctx);
return bpf_context_1.lookup(&id);
}

int trace_SSL_read_return(struct pt_regs *ctx) {

struct event_t *event = make_event_1();
if (!event) return 0;

event->pid = bpf_get_current_pid_tgid() >> 32;

int len = PT_REGS_RC(ctx);
event->len = (u32)len;
 
u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len);
void *buf_arg_ptr = (void *)PT_REGS_PARM2(ctx);
bpf_probe_read_user(&event->buf, buf_copy_size, (char *)buf_arg_ptr);
events.perf_submit(ctx, event, sizeof(struct event_t));
return 0;

}
"""

def print_event(cpu, data, size):
event = bpf["events"].event(data)
buf_size = event.len
payload_str = bytearray(event.buf[:buf_size]).decode()
payload_str = bytes(payload_str, encoding='utf-8')
print("PID: %d read %d bytes: %s" % (event.pid, event.len, payload_str))

bpf = BPF(text=bpf_source)
ssl_lib_path = '/usr/lib64/libssl.so.10'
bpf.attach_uprobe(name=ssl_lib_path, sym="SSL_read", fn_name="trace_SSL_read_return")

bpf["events"].open_perf_buffer(print_event)

while True:
try:
bpf.perf_buffer_poll()
except KeyboardInterrupt:
exit()

`
结果
image

利用你提供的probe_SSL_rw_enter和probe_SSL_read_exit也不行

首先flowgod默认是不支持获取响应内容的。其次看上去你试图用SSL_read去获取响应内容,但我不太能理解你所说的“SSL_read和SSL_write的逻辑应该是相通的”这句话的含义是什么?回到具体的问题上来,你提供的截图反馈出你能获取到函数返回值大小以及全0的缓冲区内容,除了这条信息,有没有其他返回的响应内容?我建议这边可以通过在你的eBPF程序中加上对缓冲区内容的判断逻辑,譬如判断缓冲区的前四个字节是否为'HTTP'等。

jpgong commented

首先flowgod默认是不支持获取响应内容的。其次看上去你试图用SSL_read去获取响应内容,但我不太能理解你所说的“SSL_read和SSL_write的逻辑应该是相通的”这句话的含义是什么?回到具体的问题上来,你提供的截图反馈出你能获取到函数返回值大小以及全0的缓冲区内容,除了这条信息,有没有其他返回的响应内容?我建议这边可以通过在你的eBPF程序中加上对缓冲区内容的判断逻辑,譬如判断缓冲区的前四个字节是否为'HTTP'等。

“SSL_read和SSL_write的逻辑应该是相通的”,我的意思是指利用SSL_write可以获取https的请求明文,那SSL_read是不是就可以获取https的响应明文。因为我看ecapture在TLS处理read和write其实是相似的。
image

第二个问题:函数返回值大小以及全0的缓冲区内容,除了这条信息,有没有其他返回的响应内容?
返回的内容格式是{pid,length, response},所以他返回的响应内容就是\x00,这也是我困惑的地方。
image

因为我看到flowgod好像也处理了read操作。所以不太清楚为啥我这边read读取的响应值是0值
image

第一个问题,你的理解没有错,用SSL_read的确是可以获取到响应明文的。第二个问题,我的意思是指除了这条{pid,length, response},有没有输出其他的{pid,length, response}?因为SSL_read函数的响应可能会被分成多个分片传输,可能你这次读取的是之前的内容,所以这也是为什么我让你在BPF程序中加一些过滤逻辑在里面,目的就是为了筛出匹配的响应内容。第三个,你所指的flowgod中的这段代码,很遗憾并没有被用到,这是由于当时在设计的时候的确把响应捕获这个功能考虑在内,但最后没有实现,你可以看下user部分并没有去hook函数SSL_read来应证这一点。

ping,如果没有其他flowgod相关的问题,那这个issue我就关闭啦?

jpgong commented

向过滤tcp和udp流量,我想把他们合在一起进行分析,就会报错
image
代码如下:
`#include <linux/if_ether.h>
#include <linux/in.h>
#include <bcc/proto.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/sched.h> /* For TASK_COMM_LEN */

#define IP_TCP 6
#define ETH_HLEN 14

struct data_key {
u8 proto;
u32 src_ip;
u32 dst_ip;
u16 src_port;
u16 dst_port;
};

struct data_value {
u8 proto;
u32 pid;
u32 uid;
u32 gid;
char task[TASK_COMM_LEN];
};

BPF_HASH(tl_datas, struct data_key, struct data_value, 20480);

BPF_PERF_OUTPUT(events_tl);

/bypass 512 limit/
BPF_HASH(bpf_context_1, u64, struct data_value, 2048);
BPF_ARRAY(bpf_context_gen_1, struct data_value, 1);

static struct data_value *make_event_1(){
int zero = 0;
struct data_value *bpf_ctx = bpf_context_gen_1.lookup(&zero);
if (!bpf_ctx) return 0;
u64 id = bpf_get_current_pid_tgid();
bpf_context_1.update(&id, bpf_ctx);
return bpf_context_1.lookup(&id);
}

int tl_filter(struct __sk_buff *skb){
u8 *cursor = 0;
struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
//filter IP packets (ethernet type = 0x0800) IPv6 0x86DD
if(ethernet->type == 0x0800) { //type保存了二层协议类型,ETH_P_IP、ETH_P_ARP,ETH_P_ALL
struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
//IP_TCP(0x06)、IPPROTO_UDP(0x11)
bpf_trace_printk("%d\n", ip->nextp);
if (ip->nextp == IP_TCP){
struct tcp_t *tcp = cursor_advance(cursor, sizeof(*tcp));
// 过滤掉不携带数据的包
if (!tcp->flag_psh) { // Control when packets are send
return 0;
}

        struct data_key key = {};
        key.proto = 6;
        key.src_ip = ip->src;
        key.dst_ip = ip->dst;
        key.src_port = tcp->src_port;
        key.dst_port = tcp->dst_port;

        struct data_value *value = tl_datas.lookup(&key);
        if (!value) {
            bpf_trace_printk("No value found for key\n");
            return 0;
        }

        events_tl.perf_submit_skb(skb,skb->len,value, sizeof(struct data_value));
        return 0;

    } else if(ip->nextp == IPPROTO_UDP) {
        struct udp_t *udp = cursor_advance(cursor, sizeof(*udp));

        struct data_key key = {};
        key.proto = 17;
        key.src_ip = ip->src;
        key.dst_ip = ip->dst;
        key.src_port = udp->sport;
        key.dst_port = udp->dport;

        struct data_value *value = tl_datas.lookup(&key);
        if (!value) return 0;

        events_tl.perf_submit_skb(skb,skb->len,value, sizeof(struct data_value));
        return 0;
    }
}
return 0;

}

static int trace_sendmsg(struct pt_regs *ctx, struct sock *sk, int protocol) {
// u16 sport, dport;
// bpf_probe_read(&sport, sizeof(u16), &sk->__sk_common.skc_num);
// bpf_probe_read(&dport, sizeof(u16), &sk->__sk_common.skc_dport);
//
// u32 saddr, daddr;
// bpf_probe_read(&saddr, sizeof(u32), &sk->__sk_common.skc_rcv_saddr);
// bpf_probe_read(&daddr, sizeof(u32), &sk->__sk_common.skc_daddr);

u16 sport = sk->sk_num;
u16 dport = sk->sk_dport;

u32 saddr = sk->sk_rcv_saddr;
u32 daddr = sk->sk_daddr;

u64 pid_tgid = bpf_get_current_pid_tgid();
u64 uid_gid = bpf_get_current_uid_gid();

struct data_key key = {.proto = protocol};
key.src_ip = htonl(saddr);
key.dst_ip = htonl(daddr);
key.src_port = sport;
key.dst_port = htons(dport);

bpf_trace_printk("%d\\n", key.proto);

// struct data_value *value = make_event_1();
// if (!value){
// bpf_trace_printk("No value found for make_event_1\n");
// return 0;
// }
// value->proto = protocol;
// value->pid = pid_tgid >> 32;
// value->uid = (u32)uid_gid;
// value->gid = uid_gid >> 32;
// bpf_get_current_comm(value->task, sizeof(value->task));
//
// tl_datas.update(&key,value);
struct data_value value = {.proto=protocol};
value.pid = pid_tgid >> 32;
value.uid = (u32)uid_gid;
value.gid = uid_gid >> 32;
bpf_get_current_comm(&value.task, sizeof(value.task));
// bpf_get_current_comm(value.task, 64);
//Writing the value into the eBPF table:
tl_datas.update(&key, &value);

return 0;

}

int trace_tcp_sendmsg(struct pt_regs *ctx, struct sock *sk) {
return (trace_sendmsg(ctx,sk, 6));

}

int trace_udp_sendmsg(struct pt_regs *ctx, struct sock *sk){
return (trace_sendmsg(ctx,sk, 17));
}`
但是把他们拆开,就是TCP的写一个,UDP的写一个就没有任何问题。看报错是堆栈的问题,实在搞不清楚。请问您有了解吗?

jpgong commented

第一个问题,你的理解没有错,用SSL_read的确是可以获取到响应明文的。第二个问题,我的意思是指除了这条{pid,length, response},有没有输出其他的{pid,length, response}?因为SSL_read函数的响应可能会被分成多个分片传输,可能你这次读取的是之前的内容,所以这也是为什么我让你在BPF程序中加一些过滤逻辑在里面,目的就是为了筛出匹配的响应内容。第三个,你所指的flowgod中的这段代码,很遗憾并没有被用到,这是由于当时在设计的时候的确把响应捕获这个功能考虑在内,但最后没有实现,你可以看下user部分并没有去hook函数SSL_read来应证这一点。

这个响应没有被分片,它的内容全是0。
代码如下:
`#!/usr/bin/env python

coding: utf-8

from bcc import BPF

bpf_source = """
#include <uapi/linux/ptrace.h>
#define MAX_BUF_SIZE 4000
struct event_t {
u32 pid;
u32 len;
u8 buf[MAX_BUF_SIZE];
};

BPF_PERF_OUTPUT(events);

BPF_HASH(bpf_context_1, u64, struct event_t, 2048);
BPF_ARRAY(bpf_context_gen_1, struct event_t, 1);

static struct event_t *make_event_1() {
int zero = 0;
struct event_t *bpf_ctx = bpf_context_gen_1.lookup(&zero);
if (!bpf_ctx) return 0;
u64 id = bpf_get_current_pid_tgid();
bpf_context_1.update(&id, bpf_ctx);
return bpf_context_1.lookup(&id);
}

int trace_SSL_read_return(struct pt_regs *ctx) {

struct event_t *event = make_event_1();
if (!event) return 0;

event->pid = bpf_get_current_pid_tgid() >> 32;

int len = PT_REGS_RC(ctx);
event->len = (u32)len;
 
u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len);
void *buf_arg_ptr = (void *)PT_REGS_PARM2(ctx);
bpf_probe_read_user(&event->buf, buf_copy_size, (char *)buf_arg_ptr);
events.perf_submit(ctx, event, sizeof(struct event_t));
return 0;

}
"""

def print_event(cpu, data, size):
event = bpf["events"].event(data)
buf_size = event.len
payload_str = bytearray(event.buf[:buf_size]).decode()
payload_str = bytes(payload_str, encoding='utf-8')
print("PID: %d read %d bytes: %s" % (event.pid, event.len, payload_str))

bpf = BPF(text=bpf_source)
ssl_lib_path = '/usr/lib64/libssl.so.10'
bpf.attach_uprobe(name=ssl_lib_path, sym="SSL_read", fn_name="trace_SSL_read_return")

bpf["events"].open_perf_buffer(print_event)

while True:
try:
bpf.perf_buffer_poll()
except KeyboardInterrupt:
exit()
`
我请求curl https://www.baidu.com,请求内容可以正常分析,但是响应内容就不行。请问您最后没实现是遇到什么问题了吗

看上去这还是你的个人实现需求,非flowgod的设计缺陷或bug,,所以这个issue我就先关闭了。具体的一些技术讨论,可以加我的微信联系,微信与github平台ID同名。

done

jpgong commented

谢谢