android ebpf捕获https数据包
ecapture
ebpf
利用uprobe/uretprobe
可以hook用户层函数,通过对https SSL
层的SSL_write
和SSL_read
进行hook可以拦截明文数据信息。大佬的开源项目:ecapture
https://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_write
和SSL_read
等函数的偏移的,需要自己找到对应的符号偏移并指定UAddress
。
BCC脚本
这里在bcc
环境下编写脚本尝试hookssl_write_internal
和ssl_read_internal
,首先找到openssl
去除了符号后的SSL_write
和SSL_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_internal
和ssl_read_internal
的偏移分别为0x1ADC38
和0x1AD5C0
编写bcc
脚本对ssl_write_internal
和ssl_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;
}
'''
# 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,page_cnt=32)
b["ssl_read_events"].open_perf_buffer(print_ssl_read,page_cnt=32)
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明文。
libbpf
CO-RE
不同linux内核版本的内部结构体会有差异,如果想要编写支持CO-RE
(一次编译,到处运行)的程序需要对不同的内核版本的结构体差异做兼容。这就和windows平台编写一些内核rookit驱动或者ark工具时需要访问一些例如KPROCESS
这样的结构体一样,不同的windows版本其内核对应的结构体都可能有差异,大多数ark工具都是通过从微软的符号服务器中获取对应内核版本的符号去兼容不同的内核版本。而linux上的符号文件就是BTF,BTF
是一个描述内核数据结构的元数据格式,只有在编译打开配置CONFIG_DEBUG_INFO_BTF=y
后才能生成对应的BTF文件。libbpf
就是通过在BPF
运行的时候去解析BTF
文件来兼容各个linux内核版本结构体的差异,而BCC
则是在编译的时候获取此BTF
文件去解析内核结构,所以不支持一次编译到处运行。编写程序的时候需要vmlinux.h
头文件,此文件包含了所有内核结构的定义,可以通过bpftool
工具从BTF
文件中提取对应内核版本的头文件bpftool btf dump file /sys/kernel/btf/vmlinux format c
。
编写libbpf程序
这里通过wsl + vscode + clangd
编写并交叉编译能够在android
平台运行的ebpf
程序(支持代码提示和跳转),因为交叉编译使用的编译工具是gcc-aarch64-linux-gnu
,其会依赖并静态链接linux
平台的libc
标准库,所以在编写ebpf
用户态程序的时候小心调用具有平台差异的libc函数(例如:localtime
函数linux平台会去访问/etc/localtime
文件,而android并不存在此文件),大多数libc
函数都是没有关系的。具体请参考:https://github.com/revercc/libbpf-bootstrap-for-android.git和https://blog.csdn.net/youzhangjing_/article/details/132671989
编写libbpf
程序利用uprobe
实现ssl_write_internal/ssl_read_internal
的hook,内核层代码如下
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include "sslfilter.h"
// BPF_MAP_TYPE_ARRAY类型map是数组类型
// max_entries相当于数组长度(元素个数)
// key是数组索引(8个字节)
// value是数组元素
// 因为数组map创建后,相当于所有的key(索引)都是默认存在的,和hash map类型不一样
struct{
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct SSL_FILTER_INFO);
} ssl_filter_info SEC(".maps");
struct READ_ENTRY_ARGS{
u64 buf_address;
u64 readbytes_address;
};
struct{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 100);
__type(key, u64);
__type(value, struct READ_ENTRY_ARGS);
}read_entry_args SEC(".maps");
// BPF_MAP_TYPE_PERF_EVENT_ARRAY类型map的max_entries等与cpu数量
// value是perfbuf map的文件句柄
struct{
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
} events SEC(".maps");
//int ssl_write_internal(SSL *s, const void *buf, size_t num, size_t *written)
SEC("uprobe")
int BPF_KPROBE(ssl_write_internal_entry, void *SSL, void *buf, size_t num, size_t *written)
{
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid;
u64 index = 0;
struct SSL_FILTER_INFO *p_filter_info = bpf_map_lookup_elem(&ssl_filter_info, &index);
if(p_filter_info == NULL){
return 0;
}
p_filter_info->bWrite = true;
p_filter_info->pid = pid;
p_filter_info->size = num;
bpf_get_current_comm(&p_filter_info->comm, sizeof(p_filter_info->comm));
if(bpf_probe_read_user(&p_filter_info->buf, num < sizeof(p_filter_info->buf) ? num : sizeof(p_filter_info->buf), buf) < 0){
return 0;
}
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
p_filter_info, sizeof(*p_filter_info));
return 0;
}
//int ssl_read_internal(SSL *s, void *buf, size_t num, size_t *readbytes)
SEC("uprobe")
int BPF_KPROBE(ssl_read_internal_entry, void *SSL, void *buf, size_t num, size_t *readbytes)
{
struct READ_ENTRY_ARGS args = {};
args.readbytes_address = (u64)readbytes;
args.buf_address = (u64)buf;
u64 pid_tgid = bpf_get_current_pid_tgid();
bpf_map_update_elem(&read_entry_args, &pid_tgid, &args, BPF_ANY);
return 0;
}
SEC("uretprobe")
int BPF_KRETPROBE(ssl_read_internal_exit, int ret)
{
if(ret == -1){
return 0;
}
u64 index = 0;
struct SSL_FILTER_INFO *p_filter_info = bpf_map_lookup_elem(&ssl_filter_info, &index);
if(p_filter_info == NULL){
return 0;
}
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid;
struct READ_ENTRY_ARGS *p_args = bpf_map_lookup_elem(&read_entry_args, &pid_tgid);
if(p_args == NULL){
return 0;
}
size_t readbytes;
if(bpf_probe_read_user(&readbytes, sizeof(readbytes), (size_t*)p_args->readbytes_address) < 0){
return 0;
}
p_filter_info->pid = pid;
p_filter_info->size = readbytes;
p_filter_info->bWrite = false;
bpf_get_current_comm(&p_filter_info->comm, sizeof(p_filter_info->comm));
if(bpf_probe_read_user(&p_filter_info->buf,
readbytes < sizeof(p_filter_info->buf) ? readbytes : sizeof(p_filter_info->buf),
(void*)p_args->buf_address) < 0){
return 0;
}
bpf_printk("read : %d", readbytes);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
p_filter_info, sizeof(*p_filter_info));
return 0;
}
char LICENSE[] SEC("license") = "GPL";
用户层核心代码如下:加载并验证bpf程序--->附加uprobe/uretprobe
运行./sslfilter -l /data/app/~~jHc9IVYjkaSDTGeAKNfakQ==/com.reverccqin.logintest-HoULxuV9FDpqJa8D2ZWmEA==/lib/arm64/liblogintest.so -w 0x22AC38 -r 0x22A5C0 -p 23396
测试效果:
-h
参数支持输出hex格式,./sslfilter -h -l /data/app/~~jHc9IVYjkaSDTGeAKNfakQ==/com.reverccqin.logintest-HoULxuV9FDpqJa8D2ZWmEA==/lib/arm64/liblogintest.so -w 0x22AC38 -r 0x22A5C0 -p 23396
代码和配置已上传github:https://github.com/revercc/libbpf-bootstrap-for-android.git
参考:
http://blog.silence.pink/p/create-btf-from-debuginfo/
https://blog.csdn.net/youzhangjing_/article/details/132671989
https://nakryiko.com/posts/libbpf-bootstrap/
https://github.com/iovisor/bcc/tree/3162551b0b93ac9b663c14fdaae5c1503b68fc65/libbpf-tools
https://www.kernel.org/doc/html/latest/bpf/libbpf/libbpf_overview.html
https://github.com/libbpf/libbpf-bootstrap.git