android ebpf捕获https数据包

发布时间 2023-10-24 11:05:25作者: 怎么可以吃突突

ecapture

ebpf利用uprobe/uretprobe可以hook用户层函数,通过对https SSL层的SSL_writeSSL_read进行hook可以拦截明文数据信息。大佬的开源项目:ecapturehttps://github.com/gojue/ecapture,编译参考:https://blog.seeflower.dev/archives/172/

ecapture默认只对使用了系统路径中的ssl库(libssl.so,libgnutls.so)的进行hook,如果软件使用自带的ssl库就需要通过--libssl指定库路径。这里自编译openssl和curl静态库并链接到自己的so中进行https请求,这里编译的时候保留openssl中的符号。使用ecapture指定so库路径可以正常抓取https明文信息。

但是如果在编译的时候去除openssl中的符号程序是无法自动找到SSL_writeSSL_read等函数的偏移的,需要自己找到对应的符号偏移并指定UprobeOffset

编写脚本

这里在bcc环境下编写脚本尝试hookssl_write_internalssl_read_internal,首先找到openssl去除了符号后的SSL_writeSSL_read。查看openssl源码,SSL_write在函数头部会设置错误码,参数的值是ERR_raise(20,271)

ERR_raise(20,271)对应的函数调用指令为MOV W0, #0x14 MOV W1, #0x10F,对应的机器码为80 02 80 52 E1 21 80 52

对应的so中搜索二进制特征码,找到了几个函数。

经过筛选后得到ssl_write_internalssl_read_internal的偏移分别为0x1ADC380x1AD5C0

编写bcc脚本对ssl_write_internalssl_read_internal进行hook

import argparse
from bcc import BPF
from bcc.utils import printb

bpf_text='''
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#define MAX_SSL_PACKAGE_SIZE 16384
struct ssl_write_entry_info {
    u32 pid;
    u64 time;
    u32 size;
    char comm[TASK_COMM_LEN];
    char buf[MAX_SSL_PACKAGE_SIZE];
};

struct ssl_read_entry_info {
    u32 pid;
    u64 time;
    u64 buf_address;
    u64 readBytes_address;
};

struct ssl_read_ret_info {
    u32 pid;
    u64 time;
    u32 size;
    char comm[TASK_COMM_LEN];
    char buf[MAX_SSL_PACKAGE_SIZE];
};

BPF_ARRAY(write_entry_info, struct ssl_write_entry_info, 1);
BPF_PERF_OUTPUT(ssl_write_events);
//int ssl_write_internal(SSL *s, const void *buf, size_t num, size_t *written)
int pre_ssl_write_internal(struct pt_regs *ctx) {
    u32 zero = 0;
    struct ssl_write_entry_info *p_info = write_entry_info.lookup(&zero);
    if(p_info == 0){
        return 0;
    }
    
    u64 pid_tid = bpf_get_current_pid_tgid();
    p_info->pid = pid_tid >> 32;
    p_info->time = bpf_ktime_get_ns();

    if(bpf_get_current_comm(&p_info->comm, sizeof(p_info->comm)) != 0){
        return 0;
    }

    p_info->size = PT_REGS_PARM3(ctx);
    if(bpf_probe_read_user(&p_info->buf, min((size_t)p_info->size, (size_t)sizeof(p_info->buf)), (void*)PT_REGS_PARM2(ctx)) < 0){
        return 0;
    }
    ssl_write_events.perf_submit(ctx, p_info, sizeof(struct ssl_write_entry_info));
    return 0;
}

//key : pid_tid
BPF_HASH(read_entry_info, u64, struct ssl_read_entry_info);
int pre_ssl_read_internal(struct pt_regs *ctx) {
    struct ssl_read_entry_info _info = {};
    u64 pid_tid = bpf_get_current_pid_tgid();
    _info.pid = pid_tid >> 32;
    _info.time = bpf_ktime_get_ns();
    _info.buf_address = PT_REGS_PARM2(ctx);
    _info.readBytes_address = PT_REGS_PARM4(ctx);
    read_entry_info.update(&pid_tid, &_info);
    return 0;
}

BPF_ARRAY(read_ret_info, struct ssl_read_ret_info, 1);
BPF_PERF_OUTPUT(ssl_read_events);
//int ssl_read_internal(SSL *s, void *buf, size_t num, size_t *readbytes)
int post_ssl_read_internal(struct pt_regs *ctx) {
    if(PT_REGS_RC(ctx) == 0xffffffff){
        return 0;
    }

    u32 zero = 0;
    struct ssl_read_ret_info *p_ret_info = read_ret_info.lookup(&zero);
    if(p_ret_info == 0){
        return 0;
    }

    u64 pid_tid = bpf_get_current_pid_tgid();
    struct ssl_read_entry_info* p_entry_info;
    p_entry_info = read_entry_info.lookup(&pid_tid);
    if(p_entry_info == 0){
        return 0;
    }
    
    u32 readBytes;
    if(bpf_probe_read_user(&readBytes, sizeof(readBytes), (void*)p_entry_info->readBytes_address) < 0){
        return 0;
    }

    p_ret_info->pid = p_entry_info->pid;
    p_ret_info->time = p_entry_info->time;
    p_ret_info->size = readBytes;
    if(bpf_probe_read_user(&p_ret_info->buf, min((size_t)readBytes, (size_t)sizeof(p_ret_info->buf)), (void*)p_entry_info->buf_address) < 0){
        return 0;
    }

    if(bpf_get_current_comm(&p_ret_info->comm, sizeof(p_ret_info->comm)) != 0){
        return 0;
    }

    ssl_read_events.perf_submit(ctx, p_ret_info, sizeof(struct ssl_read_ret_info));
    read_entry_info.delete(&pid_tid);
    return 0;
}
'''

#255,44,112
# Get args
class HexIntAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, int(values, 16))

parser = argparse.ArgumentParser(description="file relocate by bcc")
parser.add_argument("--pid", required=True, type=int, help="process pid")
parser.add_argument("--libssl", required=True, type=str, help="ssl so path")
parser.add_argument("--write", required=True, type=str, action=HexIntAction, help="ssl_write_internal offset")
parser.add_argument("--read", required=True, type=str, action=HexIntAction, help="ssl_read_internal offset")
args = parser.parse_args()

# load BPF program
b = BPF(text=bpf_text)
b.attach_uprobe(
    name=args.libssl,
    addr=args.write,
    fn_name='pre_ssl_write_internal',
    pid=args.pid,
)

b.attach_uprobe(
    name=args.libssl,
    addr=args.read,
    fn_name='pre_ssl_read_internal',
    pid=args.pid
)

b.attach_uretprobe(
    name=args.libssl,
    addr=args.read,
    fn_name='post_ssl_read_internal',
    pid=args.pid
)


g_data = {"pid_tid" : ['buf', 0]}
# read events
def print_ssl_write(cpu, data, size):
    event = b["ssl_write_events"].event(data)
    print("\033[36m")
    print("SSL_write------------------------------------------------------")
    print("%-10d %-32s %-10d" % (event.pid, event.comm, event.size))
    printb(event.buf)
    print("\033[0m")
def print_ssl_read(cpu, data, size):
    event = b["ssl_read_events"].event(data)
    print("\033[32m")
    print("SSL_read------------------------------------------------------")
    print("%-10d %-32s %-10d" % (event.pid, event.comm, event.size))
    printb(event.buf)
    print("\033[0m")

b["ssl_write_events"].open_perf_buffer(print_ssl_write)
b["ssl_read_events"].open_perf_buffer(print_ssl_read)
while True:
    try:
        #b.trace_print()
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

运行脚本python ssl_hook.py --libssl /data/app/~~GWzeE8KPBIy0kjA1H2YxPQ==/com.reverccqin.logintest--SlKqgwTivBrodcAIX72vw==/lib/arm64/liblogintest.so --write 0x1ADC38 --read 0x1AD5C0 --pid 22226 捕获https明文。