您好,本站仅作演示所用,请勿下单付款!
商品分类

08(09)(10).类比FridaXposed学eBPF(上.中.下)

对应用程序进行了哪些修改 . 最大的作用,就是,对云服务状态的各种监控,是他的应用场景. 云原生应用, docker 的监控, 调用, 文件, 网络, 读写, 内存, 队列, socket,是他最大的应用场景.

学习, opensnoop 如何打开文件的一个系统的调用的原理.

他是 如何监控打开文件的各种系统调用. 继续学习,按照 xposed 或者 frida的语法. 如何去 hook ,如何去参数, 参数, 调用栈, 返回值 , 我们这是关心的范围 .

frida 定位的是 包–> 类–> 方法. 系统调用的是面向过程, 他没有什么高级语言的包, 和类, 方法, 只有面向过程的语言, 所以他只有方法, 我们直接hook, 他200多个固定的,调用,linux的系统调用表, 他是一个固定的系统调用表, https://filippo.io/linux-syscall-table/ 一共300多个(2023年), 400多个系统调用表(2024年), linkat, 一些常见的,用来hook, frida的系统调用 , 使用内核这样的技术,对他们进行观测, eBPF,在案桌上的核心优势,: 在应用层, 没有这样一个稳定高效的系统调用hook框架,

新手学习的还是 app的开发, xposed这样的东西, java层的, 搞完后, c 层的,搞完后, 才会搞系统调用之类的, 所以大部分连java层都没有搞定的,别抢着那么深层次的东西了, 在 linux操作系统中,万物皆是文件, 在 linux 操作系统中, 文件是最为核心的一个项目,

关键的一本书, 程序员的自我修养. 拥有 : cpu, 线程, 编译, 目标文件, 链接的接口, 符号, 静态连接, 内存,运行库, 内存, 系统调用与API, 这本书, 基本将 ,二进制的一些基础概念, 讲的, 非常清楚, 这些的基本流程到现在依然是没有变化过的. 基本把这些做好,问题就不是太大了. opensnoop基本的调用, 文件的监控, 把一份的源码的给你扣除来, 弄清晰了.基本上,对于如何写一个app, 如何去hook , 就比较的清晰了. 先要阅读别人怎么写的, 你的心里就有数了, 写frida的时候 ,找到一个类, 找到一个方法, 输出参数,变量,返回值, 然后和Frida 对比一下, 一句话 ,相当于,xposed的api , 因为上一颗,给我们快书的剖析一下.

1,如果先是, Xposed/Frida

a, 先逆向–>App: eBPF–>内核 固定 hook 系统调用, 就那个300多个 syscall table , 源码送给你了, 内核源码送给你可以直接看, 编译过,你肯定知道. 内核源码一看,固定, 300多个系统调用,

Android syscall call table

https://github.com/torvalds/linux/blob/v4.17/include/uapi/asm-generic/unistd.h 这里面 也包含了系统调用.

08(09)(10).类比FridaXposed学eBPF(上.中.下)

b, 定位: 包—> 类—-> 方法 : eBPF 是面向过程, 没有高级语言的特性, —> 挑选一个关键的系统调用 .

c, 写hook–> 输出参数, 调用栈, 返回值: eBPF 是如何, 写hook 的. 写hook 需要让他生效, 运行,挂上, 然后输出,让他生效 ,

阅读源代码,看看是如何生效的.

只要这个框架, 开发成熟, 利纳斯.towards , 就可以把他们纳入linux的内核, 他说了算,那像我们的牛马, 开发代码, 是因为兴趣而学习的.

我们搞逆向, 就是为了搞开发, 没有区别,

d, 运行–> 挂上–> 生效–> 输出:

—- eBPF 写hook的时候: Python 是个加载器 Loader, 相当于 Frida RPC时候的python loader. 具体生效的逻辑在 c 写的逻辑里面,

— hook 逻辑写在 c 里面 .使用 BPF() 创建对象, 在操作对象的方法进行hook系统调用.

# initialize BPF
b = BPF(text=bpf_text)   # 这是一个构造函数 , 构造了一个对象, 对象传递给了,  b的这个变量, 
if not is_support_kfunc:
    b.attach_kprobe(event=fnname_open, fn_name="syscall__trace_entry_open")
    b.attach_kretprobe(event=fnname_open, fn_name="trace_return")

    b.attach_kprobe(event=fnname_openat, fn_name="syscall__trace_entry_openat") # 这是 c定义的回调, syscall__trace_entry_openat, 这是系统调用定义的变量. fnname_openat
    b.attach_kretprobe(event=fnname_openat, fn_name="trace_return")

    if fnname_openat2:
        b.attach_kprobe(event=fnname_openat2, fn_name="syscall__trace_entry_openat2")
        b.attach_kretprobe(event=fnname_openat2, fn_name="trace_return")

initial_ts = 0

attach_kprobe()

以上已经开始hook 系统调用了. BPF() 就是一个类,通过调用, 这个类的方法, 我们需要b.attach_kprobe 看到这个, attach_kprobe() 的功能和作用是什么?

attach_kprobe() 这是 python 语言的功能的函数的API ,不要混淆. 混合编程, 最怕搞乱了,

Python 里面的对象和方法:

BPF(). attach_kprobe()

—事件监控: attach_kprobe() 使得 eBPF 程序能够在指定的内核函数被调用执行. 这对于监控系统行为, 性能分析或者调试内核非常有用.

—函数挂钩: 通过挂钩内核函数, eBPF 程序可以在函数开始执行时(即 kprobe) 或函数返回前(即 kretprobe) 运行.

—-相当于 xposed的 的 before 和 after . 返回前

1. attach_kprobe()

Syntax: BPF.attach_kprobe(event="event", fn_name="name")

Instruments the kernel function event() using kernel dynamic tracing of the function entry, and attaches our C defined function name() to be called when the kernel function is called.
使用函数入口的内核动态跟踪来检测内核函数 event(),并附加我们的 C 定义的函数 name() 以便在调用内核函数时调用。
For example:

b.attach_kprobe(event="sys_clone", fn_name="do_trace")

This will instrument the kernel sys_clone() function, which will then run our BPF defined do_trace() function each time it is called.
这将检测内核 sys_clone() 函数,然后每次调用 BPF 定义的 do_trace() 函数时,该函数都将运行该函数。
You can call attach_kprobe() more than once, and attach your BPF function to multiple kernel functions. You can also call attach_kprobe() more than once to attach multiple BPF functions to the same kernel function.
您可以多次调用 attach_kprobe(),并将 BPF 函数附加到多个内核函数。您还可以多次调用 attach_kprobe() 以将多个 BPF 函数附加到同一个内核函数。
See the previous kprobes section for how to instrument arguments from BPF.
有关如何检测 BPF 中的参数,请参阅前面的 kprobes 部分。
Examples in situ: search /examples, search /tools

fnname_openat , 这个函数查找的定义在, openshooppy里面.

fnname_openat( )

b = BPF(text='')
# open and openat are always in place since 2.6.16
fnname_open = b.get_syscall_prefix().decode() + 'open'
fnname_openat = b.get_syscall_prefix().decode() + 'openat'  # 通过 fnname_openat 这个函数来查找到的出处.
fnname_openat2 = b.get_syscall_prefix().decode() + 'openat2'

get_syscall_prefix( )

08(09)(10).类比FridaXposed学eBPF(上.中.下)

get_syscall_fnname()

Syntax: BPF.get_syscall_fnname(name : str)

Return the corresponding kernel function name of the syscall. This helper function will try different prefixes and use the right one to concatenate with the syscall name. Note that the return value may vary in different versions of linux kernel and sometimes it will causing trouble. (see #2590)
他会跟着正确的前缀拼接起来.  返回值会不停的变化, 在不同的版本和linux 的内核上,  可能还会出现问题.  在最新的api上可能已经不是这个方式了.   

print("The function name of %s in kernel is %s" % ("clone", b.get_syscall_fnname("clone")))  # 应该使用这个api 来或者 真正系统调用的名字. 
# sys_clone or __x64_sys_clone or ...

Examples in situ: search /examples, search /tools

他的功能就是拼接一个完整系统调用的名称, 如果在arm64下, x86下, mips下, 都有一个共用的名称, 这样的不会因为架构不同,而不同.

— 系统调用名称: b.get_syscall_fnname(“openat”) 最为优雅. 系统架构的变量. arm ,x86, mips, rsvc 等等. 可以自动适应.

08(09)(10).类比FridaXposed学eBPF(上.中.下)

以上是参考, 通过不断的, bpf_text ,他们会到最后,进行合并成为一个 C 文件.

C里面的数据章节的API

bpf_get_current_pid_tgid() -> 获得当前调用系统调用的进程,线程 ID
bpf_get_current_uid_gid() —>获得当前调用, 系统调用,的用户组 ID .

08(09)(10).类比FridaXposed学eBPF(上.中.下)

为什么你要 写入 data, 呢? 不写入其他呢? 因为你想要开发 eBPF 的话, 你只能这样写,这是固定格式, 你没有办法,创新. 标准就是别人创立的,你没有选择.

10. 类比Frida Xposed 学习 eBPF(下)

attach_uprobe()

b.attach_uprobe(name="c", sym="strlen", fn_name="count")   // 这里可以直接 hook 一个 strlen C 的回调函数.

Syntax: BPF.attach_uprobe(name="location", sym="symbol", fn_name="name" [, sym_off=int]), BPF.attach_uprobe(name="location", sym_re="regex", fn_name="name"), BPF.attach_uprobe(name="location", addr=int, fn_name="name")

Instruments the user-level function symbol() from either the library or binary named by location using user-level dynamic tracing of the function entry, and attach our C defined function name() to be called whenever the user-level function is called. If sym_off is given, the function is attached to the offset within the symbol.
使用函数入口的用户级动态跟踪,从按位置命名的库或二进制文件中检测用户级函数 symbol(),并附加我们的 C 定义的函数 name(),以便在调用用户级函数时调用。如果给出了 sym_off ,则函数将附加到 symbol 内的偏移量。
The real address addr may be supplied in place of sym, in which case sym must be set to its default value. If the file is a non-PIE executable, addr must be a virtual address, otherwise it must be an offset relative to the file load address.
可以提供实际地址 addr 来代替 sym,在这种情况下,sym 必须设置为其默认值。如果文件是非 PIE 可执行文件,则 addr 必须是虚拟地址,否则它必须是相对于文件加载地址的偏移量。
Instead of a symbol name, a regular expression can be provided in sym_re. The uprobe will then attach to symbols that match the provided regular expression.
可以在 sym_re 中提供正则表达式,而不是元件名称。然后,uprobe 将附加到与提供的正则表达式匹配的符号。
Libraries can be given in the name argument without the lib prefix, or with the full path (/usr/lib/...). Binaries can be given only with the full path (/bin/sh).
库可以在 name 参数中给出,不带 lib 前缀,也可以给出完整路径 (/usr/lib/...)。二进制文件只能使用完整路径 (/bin/sh) 提供。

https://github.com/iovisor/bcc/blob/4f8454cc77b89987960c2f40c88922ee4fe2a1a6/tools/sslsniff.py

这里是这个版本的 sslsniff 的这个 py 文件.

https://github.com/iovisor/bcc/blob/master/tools/sslsniff_example.txt

我们在幽兰本上, 运行, sudo ./sslsniff.py 可以看到 一对的 ssl 读写, 全部都是密文的形式.

太多输出, 需要按照进程, 或者 逻辑来进行过滤.
sudo killall sslsniff.py # 这里开始报错
sudo kill -9 7890 # 可以被终止.

sudo ./sslsniff.py -c curl | tee -a sslsniff.py.log

Linux 下Shell 命令的 输出信息同时显示在屏幕和保存到日志文件中
# 直接覆盖日志文件
ls -l | tee ./t.log
# 将输出内容附加到日志文件
ls -l | tee -a ./t.log

sslsniff.py hook 到了 ssl_write和ssl_read 等等, 必须

def attach_openssl(lib):
    b.attach_uprobe(name=lib, sym="SSL_write",
                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
    b.attach_uretprobe(name=lib, sym="SSL_write",
                       fn_name="probe_SSL_write_exit", pid=args.pid or -1)
    b.attach_uprobe(name=lib, sym="SSL_read",
                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
    b.attach_uretprobe(name=lib, sym="SSL_read",
                       fn_name="probe_SSL_read_exit", pid=args.pid or -1)
    if args.latency and args.handshake:
        b.attach_uprobe(name="ssl", sym="SSL_do_handshake",
                        fn_name="probe_SSL_do_handshake_enter", pid=args.pid or -1)
        b.attach_uretprobe(name="ssl", sym="SSL_do_handshake",
                           fn_name="probe_SSL_do_handshake_exit", pid=args.pid or -1)

https://github.com/gojue/ecapture/blob/71b4d41c1aef8a7953851c455b64c128ee26a868/kern/openssl.h#L253

/***********************************************************
 * BPF probe function entry-points
 ***********************************************************/

// Function signature being probed:
// int SSL_write(SSL *ssl, const void *buf, int num);
SEC("uprobe/SSL_write")                           // 这里即使 u --> user -->  代表用户的空间的探针
int probe_entry_SSL_write(struct pt_regs* ctx) {
    u64 current_pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = current_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


SEC("uretprobe/SSL_write")  // uretprobe/SSL_write 这里是 用户空间探针--> SSL_write
int probe_ret_SSL_write(struct pt_regs* ctx) {
    u64 current_pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = current_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
    debug_bpf_printk("openssl uretprobe/SSL_write pid :%d\n", pid);
    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;
        s32 version = active_ssl_buf_t->version;
        bpf_probe_read(&buf, sizeof(const char*), &active_ssl_buf_t->buf);
        process_SSL_data(ctx, current_pid_tgid, kSSLWrite, buf, fd, version);
    }
    bpf_map_delete_elem(&active_ssl_write_args_map, &current_pid_tgid);
    return 0;
}

这里的回调有, uprobe/SSL_write 等等. . .

python 所给的形式,就死后 Loader 就是, attach_uprobe 就是. 又没有这种可能.

eCapture: 交叉比对, 核心位置地址相同.

https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#4-uprobes

4. uprobes

These are instrumented by declaring a normal function in C, then associating it as a uprobe probe in Python via BPF.attach_uprobe() (covered later).

Arguments can be examined using PT_REGS_PARM macros.

For example:

int count(struct pt_regs *ctx) {
    char buf[64];
    bpf_probe_read_user(&buf, sizeof(buf), (void *)PT_REGS_PARM1(ctx));
    bpf_trace_printk("%s %d", buf, PT_REGS_PARM2(ctx));
    return(0);
}

This reads the first argument as a string, and then prints it with the second argument as an integer.

Examples in situ: code

他的这个框架 BCC , 还是得用 python 来加载. 使用了 eCapture 比对一下, 核心都是一样的.

我们 主要, 看, 核心的参数, 调用栈, 返回值. 从这里看 , 你的回调是什么?

返回的时候需要看 uretprobe() , 回调是这个.

这里面好像没有调用栈, 我们要的是用户态的调用栈.

对于这个我们 已经讲完了, 可以先看看 ebpf ,

Linux上的eBPF抓包工具sslsniff核心原理:bpf_probe_read_user(&data->buf, buf_copy_size, (char )bufp)与安卓上的eBPF抓包工具eCapture的核心原理bpf_probe_read(&buf, sizeof(const char*), &active_ssl_buf_t->buf);是一模一样的

https://github.com/gojue/ecapture/blob/71b4d41c1aef8a7953851c455b64c128ee26a868/kern/openssl.h#L276C9-L276C75

https://github.com/iovisor/bcc/blob/394adb0738b410cdb91777849a9df7097597e2cd/tools/sslsniff.py#L178

重要参考

https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#1-attach_kprobe
https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#4-uprobes

https://github.com/iovisor/bcc/blob/003b00374b052ed0da59776eb2be812fa1bbcb79/tools/opensnoop.py
https://github.com/iovisor/bcc/blob/4f8454cc77b89987960c2f40c88922ee4fe2a1a6/tools/sslsniff.py
https://github.com/iovisor/bcc/blob/394adb0738b410cdb91777849a9df7097597e2cd/tools/sslsniff.py#L178
https://github.com/gojue/ecapture/blob/71b4d41c1aef8a7953851c455b64c128ee26a868/kern/openssl.h#L276C9-L276C75
https://wx.zsxq.com/search/ecapture?groupId=15285125185812&searchUid=0.6089012070415906

https://github.com/iovisor/bcc/blob/master/tools/opensnoop_example.txt
https://github.com/iovisor/bcc/blob/master/tools/sslsniff_example.txt

https://github.com/torvalds/linux/blob/v4.17/include/uapi/asm-generic/unistd.h

https://cloud.tencent.com/developer/article/2081128

https://www.52pojie.cn/thread-1987327-1-1.html

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

联系我们

888-888-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部