<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 內(nèi)核監(jiān)控在 Android 攻防中的應(yīng)用

          共 38109字,需瀏覽 77分鐘

           ·

          2022-01-08 22:52

          背景

          在日常分析外部軟件時,遇到的反調(diào)試/反注入防護(hù)已經(jīng)越來越多,之前使用的基于 frida 的輕量級沙盒已經(jīng)無法滿足這類攻防水位的需要,因此需要有一種更加深入且通用的方式來對 APP 進(jìn)行全面的監(jiān)測和繞過。本文即為對這類方案的一些探索和實踐。

          為了實現(xiàn)對安卓 APP 的全面監(jiān)控,需要知道目標(biāo)應(yīng)用訪問/打開了哪些文件,執(zhí)行了哪些操作,并且可以修改控制這些操作的返回結(jié)果。一個直觀的想法是通過 libc 作為統(tǒng)一收口來對應(yīng)用行為進(jìn)行收集,比如接管 open/openat/faccess/fstatat 實現(xiàn)文件訪問監(jiān)控以及進(jìn)一步的文件重定向。

          然而,現(xiàn)今許多聰明的加固 APP 都使用了內(nèi)聯(lián)系統(tǒng)調(diào)用匯編來繞過 libc 實現(xiàn)暗度陳倉,在 binary 層再加上控制流混淆、花指令、代碼加密甚至是 VMP 等成熟的防御措施,使得識別這類隱藏調(diào)用變得十分困難。某不知名安全研究員[1]曾經(jīng)說過:

          Never to wrestle with a pig. You get dirty, and besides, the pig likes it.

          因此,我們不應(yīng)該在應(yīng)用層上和加固廠商做對抗,而是尋找其他突破點,以四兩撥千斤的方式實現(xiàn)目的。當(dāng)然,如果只是為了練手,那手撕虛擬機也是可以的 :)

          現(xiàn)有方案

          在介紹內(nèi)核監(jiān)控技術(shù)之前,我們先來看看目前已有的一些方案,以及它們的不足之處。

          strace

          strace[2] 是 Linux 中一個知名的用戶態(tài)系統(tǒng)調(diào)用跟蹤工具,可以輸入目標(biāo)進(jìn)程所執(zhí)行的系統(tǒng)調(diào)用的名稱以及參數(shù),常用于快速的應(yīng)用調(diào)試和診斷。strace 的示例輸出如下所示:

          $ strace echo evilpan
          execve("/usr/bin/echo", ["echo""evilpan"], 0x7fe55d5d18 /* 56 vars */) = 0
          brk(NULL)                               = 0x57b1bd2000
          faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
          openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
          fstat(3, {st_mode=S_IFREG|0644, st_size=19285, ...}) = 0
          mmap(NULL, 19285, PROT_READ, MAP_PRIVATE, 3, 0) = 0x79aecf8000
          close(3)                                = 0
          openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
          read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0p\16\2\0\0\0\0\0"..., 832) = 832
          fstat(3, {st_mode=S_IFREG|0777, st_size=1439544, ...}) = 0
          mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x79aecf6000
          mmap(NULL, 1511520, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x79aeb5e000
          mprotect(0x79aecb7000, 61440, PROT_NONE) = 0
          mmap(0x79aecc6000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x158000) = 0x79aecc6000
          mmap(0x79aeccc000, 12384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x79aeccc000
          close(3)                                = 0
          mprotect(0x79aecc6000, 16384, PROT_READ) = 0
          mprotect(0x5787f5f000, 4096, PROT_READ) = 0
          mprotect(0x79aecff000, 4096, PROT_READ) = 0
          munmap(0x79aecf8000, 19285)             = 0
          brk(NULL)                               = 0x57b1bd2000
          brk(0x57b1bf3000)                       = 0x57b1bf3000
          fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0x2), ...}) = 0
          write(1, "evilpan\n", 8evilpan
          )                = 8
          close(1)                                = 0
          close(2)                                = 0
          exit_group(0)                           = ?
          +++ exited with 0 +++

          對于需要監(jiān)控系統(tǒng)調(diào)用的場景,strace 是個非常合適的工具,因為它基于 PTRACE_SYSCALL 去跟蹤并基于中斷的方式去接管所有系統(tǒng)調(diào)用,因此即便目標(biāo)使用了不依賴 libc 的內(nèi)聯(lián) svc 也可以被識別到。不過這個缺點也很明顯,從名稱也看出來,本質(zhì)上該程序是基于 ptrace 對目標(biāo)進(jìn)行跟蹤,因此如果對方代碼中有反調(diào)試措施,那么就很有可能被檢測到。

          另外在 Android 系統(tǒng)中,APP 進(jìn)程都是由 zygote fork 而出,因此使用 strace 比較不容易確定跟蹤時機,而且由于許多應(yīng)用有多個進(jìn)程,就需要對輸出結(jié)果進(jìn)行額外的過濾和清洗。

          更多關(guān)于 strace 的實現(xiàn)原理可以參考: How does strace work?[3]

          jtrace

          在早期 strace 程序還不支持 arm64,因此 Jonathan Levin 在編寫 Android Internal 一書時就寫了 jtrace 這個工具,旨在用于對 Android 應(yīng)用的跟蹤。雖然現(xiàn)在 Google 也在 AOSP 中支持了 strace,但 jtrace 仍然有其獨特的優(yōu)點:

          ?支持系統(tǒng)屬性的訪問監(jiān)控 (setprop/getprop)?支持輸入事件的監(jiān)控 (InputReader)?支持 Binder 信息的解析?支持 AIDL 的解析?……

          雖然 jtrace 是閉源的,但提供了獨特的插件功能,用戶可以根據(jù)其提供的接口去編寫一個插件(動態(tài)庫),并使用 --plugin 參數(shù)或者 JTRACE_EXT_PATH 環(huán)境變量指定的路徑加載插件,從而實現(xiàn)自定義的系統(tǒng)調(diào)用參數(shù)解析處理。

          雖然優(yōu)點比 strace 多了不少,但其缺點并沒有解決,jtrace 本身依然是基于 PTRACE_SYSCALL 進(jìn)行系統(tǒng)調(diào)用跟蹤的,因此還是很容易被應(yīng)用的反調(diào)試檢測到。

          詳見: http://newandroidbook.com/tools/jtrace.html

          Frida

          frida[4] 是目前全球最為知名的動態(tài)跟蹤工具集 (Instrumentation),支持使用 js 腳本來對目標(biāo)應(yīng)用程序進(jìn)行動態(tài)跟蹤。相信讀者對于 frida 已經(jīng)不陌生,這里也就不再過多介紹。其功能之豐富毋庸置疑,但也有一些硬傷,比如:

          ?frida-gum 基于 inline-hook 對目標(biāo)跟蹤代碼進(jìn)行實時重編譯 (JIT),對于應(yīng)用本身有較大的侵入性;?frida-inject 需要依賴 ptrace 對目標(biāo)應(yīng)用進(jìn)行第一次注入并加載 agent,有一個較短的注入窗口可能會被反調(diào)試應(yīng)用檢測到;?frida 目前尚不支持系統(tǒng)調(diào)用事件級別的追蹤,雖然 frida-stalker 可以做到匯編級別,但是開銷過大;?frida 太過知名,以至于有很多針對 frida 的特征檢測;?……

          類似的 Instrumentation 工具還有 QDBI[5]hookzz[6] 等等。

          其他

          除了上面提到的這些工具,還有很多其他工具可以進(jìn)行動態(tài)監(jiān)控,比如 ltrace、gdb 等但這些工具都不能完美實現(xiàn)我的需求。既要馬兒跑得快(開銷小),又要馬兒不吃草(無侵入),那我們就只有把眼光放向內(nèi)核了。

          Kernel Tracing 101

          如果目標(biāo)是為了實現(xiàn)系統(tǒng)調(diào)用監(jiān)控,以及部分系統(tǒng)調(diào)用參數(shù)的修改(例如 IO 重定向),那么一個直觀的想法是修改內(nèi)核源碼,在我們感興趣的系統(tǒng)調(diào)用入口插入自己的代碼實現(xiàn)具體功能。但是這樣非常低效,一來我們要在不同的系統(tǒng)調(diào)用相關(guān)函數(shù)中增加代碼,引入過多修改后會導(dǎo)致更新內(nèi)核合并上游提交變得困難;二來我們每次修改后都需要重新編譯內(nèi)核以及對應(yīng)的 AOSP 代碼(因為內(nèi)核在 boot.img 中,詳見后文),再燒寫到手機或模擬器中,流程過于復(fù)雜。

          另外一個想法是通過在內(nèi)核代碼中引入一次性的 trampoline,然后在后續(xù)增加或者減少系統(tǒng)調(diào)用監(jiān)控入口時通過內(nèi)核模塊的方式去進(jìn)行修改。這樣似乎稍微合理一些,但其實內(nèi)核中已經(jīng)有了許多類似的監(jiān)控方案,這樣做純屬重復(fù)造輪子,效率低不說還可能隨時引入 kernel panic。

          大局觀

          那么,內(nèi)核中都有哪些監(jiān)控方案?這其實不是一個容易回答的問題,我們在日常運維時聽說過 kprobe、jprobe、uprobe、eBPF、tracefs、systemtab、perf,……到底他們之間的的關(guān)系是什么,分別都有什么用呢?

          這里推薦一篇文章: Linux tracing systems & how they fit together[7],根據(jù)其中的介紹,這些內(nèi)核監(jiān)控方案/工具可以分為三類:

          1.數(shù)據(jù): 根據(jù)監(jiān)控數(shù)據(jù)的來源劃分2.采集: 根據(jù)內(nèi)核提供給用戶態(tài)的原始事件回調(diào)接口進(jìn)行劃分3.前端: 獲取和解析監(jiān)控事件數(shù)據(jù)的用戶工具

          trace

          后面對這些監(jiān)控方案分別進(jìn)行簡要的介紹。

          kprobe

          簡單來說,kprobe 可以實現(xiàn)動態(tài)內(nèi)核的注入,基于中斷的方法在任意指令中插入追蹤代碼,并且通過 pre_handler/post_handler/fault_handler 去接收回調(diào)。

          使用

          參考 Linux 源碼中的 samples/kprobes/kprobe_example.c,一個簡單的 kprobe 內(nèi)核模塊實現(xiàn)如下:

          #include <linux/kernel.h>
          #include <linux/module.h>
          #include <linux/kprobes.h>

          #define MAX_SYMBOL_LEN  64
          static char symbol[MAX_SYMBOL_LEN] = "_do_fork";
          module_param_string(symbol, symbol, sizeof(symbol), 0644);

          /* For each probe you need to allocate a kprobe structure */
          static struct kprobe kp = {
              .symbol_name    = symbol,
          };
          /* kprobe pre_handler: called just before the probed instruction is executed */
          static int handler_pre(struct kprobe *p, struct pt_regs *regs)
          {
              pr_info("<%s> pre_handler: p->addr = 0x%p, pc = 0x%lx\n", p->symbol_name, p->addr, (long)regs->pc);
              /* A dump_stack() here will give a stack backtrace */
              return 0;
          }
          /* kprobe post_handler: called after the probed instruction is executed */
          static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
          {
              pr_info("<%s> post_handler: p->addr = 0x%p\n", p->symbol_name, p->addr);
          }
          /*
           * fault_handler: this is called if an exception is generated for any
           * instruction within the pre- or post-handler, or when Kprobes
           * single-steps the probed instruction.
           */

          static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
          {
              pr_info("fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr);
              /* Return 0 because we don't handle the fault. */
              return 0;
          }

          static int __init kprobe_init(void)
          {
              int ret;
              kp.pre_handler = handler_pre;
              kp.post_handler = handler_post;
              kp.fault_handler = handler_fault;

              ret = register_kprobe(&kp);
              if (ret < 0) {
                  pr_err("register_kprobe failed, returned %d\n", ret);
                  return ret;
              }
              pr_info("Planted kprobe at %p\n", kp.addr);
              return 0;
          }

          static void __exit kprobe_exit(void)
          {
              unregister_kprobe(&kp);
              pr_info("kprobe at %p unregistered\n", kp.addr);
          }

          module_init(kprobe_init)
          module_exit(kprobe_exit)
          MODULE_LICENSE("GPL");

          安裝該內(nèi)核模塊后,每當(dāng)系統(tǒng)中的進(jìn)程調(diào)用 fork,就會觸發(fā)我們的 handler,從而在 dmesg 中輸出對應(yīng)的日志信息。值得注意的是,kprobe 模塊依賴于具體的系統(tǒng)架構(gòu),上述 pre_handler 中我們打印指令地址使用的是 regs->pc,這是 ARM64 的情況,如果是 X86 環(huán)境,則對應(yīng) regs->ip,可查看對應(yīng) arch 的 struct pt_regs 實現(xiàn)。

          原理

          kprobe 框架基于中斷實現(xiàn)。當(dāng) kprobe 被注冊后,內(nèi)核會將對應(yīng)地址的指令進(jìn)行拷貝并替換為斷點指令(比如 X86 中的 int 3),隨后當(dāng)內(nèi)核執(zhí)行到對應(yīng)地址時,中斷會被觸發(fā)從而執(zhí)行流程會被重定向到我們注冊的 pre_handler 函數(shù);當(dāng)對應(yīng)地址的原始指令執(zhí)行完后,內(nèi)核會再次執(zhí)行 post_handler (可選),從而實現(xiàn)指令級別的內(nèi)核動態(tài)監(jiān)控。也就是說,kprobe 不僅可以跟蹤任意帶有符號的內(nèi)核函數(shù),也可以跟蹤函數(shù)中間的任意指令。

          另一個 kprobe 的同族是 kretprobe,只不過是針對函數(shù)級別的內(nèi)核監(jiān)控,根據(jù)用戶注冊時提供的 entry_handler 和 ret_handler 來分別在函數(shù)進(jìn)入時和返回前進(jìn)行回調(diào)。當(dāng)然實現(xiàn)上和 kprobe 也有所不同,不是通過斷點而是通過 trampoline 進(jìn)行實現(xiàn),可以略為減少運行開銷。

          有人可能聽說過 Jprobe,那是早期 Linux 內(nèi)核的的一個監(jiān)控實現(xiàn),現(xiàn)已被 Kprobe 替代。

          拓展閱讀:

          ?An introduction to KProbes[8]?Documentation/trace/kprobetrace.rst[9]?samples/kprobes/kprobe_example.c[10]?samples/kprobes/kretprobe_example.c[11]

          uprobe

          uprobe 顧名思義,相對于內(nèi)核函數(shù)/地址的監(jiān)控,主要用于用戶態(tài)函數(shù)/地址的監(jiān)控。聽起來是不是有點神奇,內(nèi)核怎么監(jiān)控用戶態(tài)函數(shù)的調(diào)用呢?

          使用

          站在用戶視角,我們先看個簡單的例子,假設(shè)有這么個一個用戶程序:

          // test.c
          #include <stdio.h>
          void foo() {
              printf("hello, uprobe!\n");
          }
          int main() {
              foo();
              return 0;
          }

          編譯好之后,查看某個符號的地址,然后告訴內(nèi)核我要監(jiān)控這個地址的調(diào)用:

          $ gcc test.c -o test
          $ readelf -s test | grep foo
              87: 0000000000000764    32 FUNC    GLOBAL DEFAULT   13 foo
          echo 'p /root/test:0x764' > /sys/kernel/debug/tracing/uprobe_events
          echo 1 > /sys/kernel/debug/tracing/events/uprobes/p_test_0x764/enable
          echo 1 > /sys/kernel/debug/tracing/tracing_on

          然后運行用戶程序并檢查內(nèi)核的監(jiān)控返回:

          $ ./test && ./test
          hello, uprobe!
          hello, uprobe!

          cat /sys/kernel/debug/tracing/trace
          # tracer: nop
          #
          # WARNING: FUNCTION TRACING IS CORRUPTED
          #          MAY BE MISSING FUNCTION EVENTS
          # entries-in-buffer/entries-written: 3/3   #P:8
          #
          #                              _-----=> irqs-off
          #                             / _----=> need-resched
          #                            | / _---=> hardirq/softirq
          #                            || / _--=> preempt-depth
          #                            ||| /     delay
          #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
          #              | |       |   ||||       |         |
                      test-7958  [006] .... 34213.780750: p_test_0x764: (0x6236218764)
                      test-7966  [006] .... 34229.054039: p_test_0x764: (0x5f586cb764)

          當(dāng)然,最后別忘了關(guān)閉監(jiān)控:

          echo 0 > /sys/kernel/debug/tracing/tracing_on
          echo 0 > /sys/kernel/debug/tracing/events/uprobes/p_test_0x764/enable
          echo > /sys/kernel/debug/tracing/uprobe_events

          原理

          上面的接口是基于 debugfs (在較新的內(nèi)核中使用 tracefs),即讀寫文件的方式去與內(nèi)核交互實現(xiàn) uprobe 監(jiān)控。其中寫入 uprobe_events 時會經(jīng)過一系列內(nèi)核調(diào)用:

          ?probes_write?create_trace_uprobe?kern_path: 打開目標(biāo) ELF 文件;?alloc_trace_uprobe: 分配 uprobe 結(jié)構(gòu)體;?register_trace_uprobe: 注冊 uprobe;?regiseter_uprobe_event: 將 probe 添加到全局列表中,并創(chuàng)建對應(yīng)的 uprobe debugfs 目錄,即上文示例中的 p_test_0x764;

          當(dāng)已經(jīng)注冊了 uprobe 的 ELF 程序被執(zhí)行時,可執(zhí)行文件會被 mmap 映射到進(jìn)程的地址空間,同時內(nèi)核會將該進(jìn)程虛擬地址空間中對應(yīng)的 uprobe 地址替換成斷點指令。當(dāng)目標(biāo)程序指向到對應(yīng)的 uprobe 地址時,會觸發(fā)斷點,從而觸發(fā)到 uprobe 的中斷處理流程 (arch_uprobe_exception_notify),進(jìn)而在內(nèi)核中打印對應(yīng)的信息。

          與 kprobe 類似,我們可以在觸發(fā) uprobe 時候根據(jù)對應(yīng)寄存器去提取當(dāng)前執(zhí)行的上下文信息,比如函數(shù)的調(diào)用參數(shù)等。同時 uprobe 也有類似的同族: uretprobe。使用 uprobe 的好處是我們可以獲取許多對于內(nèi)核態(tài)比較抽象的信息,比如 bash 中 readline 函數(shù)的返回、SSL_read/write 的明文信息等。

          拓展閱讀:

          ?Linux uprobe: User-Level Dynamic Tracing[12]?Documentation/trace/uprobetracer.rst[13]?Linux tracing - kprobe, uprobe and tracepoint[14]

          tracepoints

          tracepont 是內(nèi)核中提供的一種輕量級代碼監(jiān)控方案,可以實現(xiàn)動態(tài)調(diào)用用戶提供的監(jiān)控函數(shù),但需要子系統(tǒng)的維護(hù)者根據(jù)需要自行添加到自己的代碼中。

          使用

          tracepoint 的使用和 uprobe 類似,主要基于 debugfs/tracefs 的文件讀寫去進(jìn)行實現(xiàn)。一個區(qū)別在于 uprobe 使用的的用戶自己定義的觀察點(event),而 tracepoint 使用的是內(nèi)核代碼中預(yù)置的觀察點。

          查看內(nèi)核(或者驅(qū)動)中定義的所有觀察點:

          cat /sys/kernel/debug/tracing/available_events
          sctp:sctp_probe
          sctp:sctp_probe_path
          sde:sde_perf_uidle_status
          ....
          random:random_read
          random:urandom_read
          ...

          在 events 對應(yīng)目錄下包含了以子系統(tǒng)結(jié)構(gòu)組織的觀察點目錄:

          ls /sys/kernel/debug/tracing/events/random/
          add_device_randomness  credit_entropy_bits  extract_entropy       get_random_bytes       mix_pool_bytes_nolock  urandom_read
          add_disk_randomness    debit_entropy        extract_entropy_user  get_random_bytes_arch  push_to_pool           xfer_secondary_pool
          add_input_randomness   enable               filter                mix_pool_bytes         random_read

          ls /sys/kernel/debug/tracing/events/random/random_read/
          enable  filter  format  id  trigger

          以 urandom 為例,這是內(nèi)核的偽隨機數(shù)生成函數(shù),對其開啟追蹤:

          echo 1 > /sys/kernel/debug/tracing/events/random/urandom_read/enable
          echo 1 > /sys/kernel/debug/tracing/tracing_on
          head -c1 /dev/urandom 
          cat /sys/kernel/debug/tracing/trace_pipe
                      head-9949  [006] .... 101453.641087: urandom_read: got_bits 40 nonblocking_pool_entropy_left 0 input_entropy_left 2053

          其中 trace_pipe 是輸出的管道,以阻塞的方式進(jìn)行讀取,因此需要先開始讀取再獲取 /dev/urandom,然后就可以看到類似上面的輸出。這里輸出的格式是在內(nèi)核中定義的,我們下面會看到。

          當(dāng)然,最后記得把 trace 關(guān)閉。

          原理

          根據(jù)內(nèi)核文檔介紹,子系統(tǒng)的維護(hù)者如果想在他們的內(nèi)核函數(shù)中增加跟蹤點,需要執(zhí)行兩步操作:

          1.定義跟蹤點2.使用跟蹤點

          內(nèi)核為跟蹤點的定義提供了 TRACE_EVENT 宏。還是以 urandom_read 這個跟蹤點為例,其在內(nèi)核中的定義在 include/trace/events/random.h:

          #undef TRACE_SYSTEM
          #define TRACE_SYSTEM random

          TRACE_EVENT(random_read,
              TP_PROTO(int got_bits, int need_bits, int pool_left, int input_left),

              TP_ARGS(got_bits, need_bits, pool_left, input_left),

              TP_STRUCT__entry(
                  __field(      int,  got_bits        )
                  __field(      int,  need_bits       )
                  __field(      int,  pool_left       )
                  __field(      int,  input_left      )
              ),

              TP_fast_assign(
                  __entry->got_bits   = got_bits;
                  __entry->need_bits  = need_bits;
                  __entry->pool_left  = pool_left;
                  __entry->input_left = input_left;
              ),

              TP_printk("got_bits %d still_needed_bits %d "
                    "blocking_pool_entropy_left %d input_entropy_left %d",
                    __entry->got_bits, __entry->got_bits, __entry->pool_left,
                    __entry->input_left)
          );

          其中:

          ?random_read: trace 事件的名稱,不一定要內(nèi)核函數(shù)名稱一致,但通常為了易于識別會和某個關(guān)鍵的內(nèi)核函數(shù)相關(guān)聯(lián)。隸屬于 random 子系統(tǒng)(由 TRACE_SYSTEM 宏定義);?TP_PROTO: 定義了跟蹤點的原型,可以理解為入?yún)㈩愋??TP_ARGS: 定義了”函數(shù)“的調(diào)用參數(shù);?TP_STRUCT__entry: 用于 fast binary tracing,可以理解為一個本地 C 結(jié)構(gòu)體的定義;?TP_fast_assign: 上述本地 C 結(jié)構(gòu)體的初始化;?TP_printk: 類似于 printk 的結(jié)構(gòu)化輸出定義,上節(jié)中 trace_pipe 的輸出結(jié)果就是這里定義的;

          TRACE_EVENT 宏并不會自動插入對應(yīng)函數(shù),而是通過展開定義了一個名為 trace_urandom_read 的函數(shù),需要內(nèi)核開發(fā)者自行在代碼中進(jìn)行調(diào)用。上述跟蹤點實際上是在 drivers/char/random.c 文件中進(jìn)行了調(diào)用:

          static ssize_t
          urandom_read_nowarn(struct file *file, char __user *buf, size_t nbytes,
                      loff_t *ppos)
          {
              int ret;

              nbytes = min_t(size_t, nbytes, INT_MAX >> (ENTROPY_SHIFT + 3));
              ret = extract_crng_user(buf, nbytes);
              trace_urandom_read(8 * nbytes, 0, ENTROPY_BITS(&input_pool)); // <-- 這里
              return ret;
          }

          static ssize_t
          urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
          {
              unsigned long flags;
              static int maxwarn = 10;

              if (!crng_ready() && maxwarn > 0) {
                  maxwarn--;
                  if (__ratelimit(&urandom_warning))
                      pr_notice("%s: uninitialized urandom read (%zd bytes read)\n",
                            current->comm, nbytes);
                  spin_lock_irqsave(&primary_crng.lock, flags);
                  crng_init_cnt = 0;
                  spin_unlock_irqrestore(&primary_crng.lock, flags);
              }

              return urandom_read_nowarn(file, buf, nbytes, ppos);
          }

          值得注意的是實際上是在 urandom_read_nowarn 函數(shù)中而不是 urandom_read 函數(shù)中調(diào)用的,因此也可見注入點名稱和實際被調(diào)用的內(nèi)核函數(shù)名稱沒有直接關(guān)系,只需要便于識別和定位即可。

          根據(jù)上面的介紹我們可以了解到,tracepoint 相對于 probe 來說各有利弊:

          ?缺點是需要開發(fā)者自己定義并且加入到內(nèi)核代碼中,對代碼略有侵入性;?優(yōu)點是對于參數(shù)格式有明確定義,并且在不同內(nèi)核版本中相對穩(wěn)定,kprobe 跟蹤的內(nèi)核函數(shù)可能在下個版本就被改名或者優(yōu)化掉了;

          另外,tracepoint 除了在內(nèi)核代碼中直接定義,還可以在驅(qū)動中進(jìn)行動態(tài)添加,用于方便驅(qū)動開發(fā)者進(jìn)行動態(tài)調(diào)試,復(fù)用已有的 debugfs 最終架構(gòu)。這里有一個簡單的自定義 tracepoint 示例[15],可用于加深對 tracepoint 使用的理解。

          拓展閱讀:

          ?LWN: Using the TRACE_EVENT() macro (Part 1)[16]?Documentation/trace/tracepoints.rst[17]?Taming Tracepoints in the Linux Kernel[18]

          USDT

          USDT 表示 Userland Statically Defined Tracing,即用戶靜態(tài)定義追蹤 (幣圈同志先退下)。最早源于 Sun 的 Dtrace 工具,因此 USDT probe 也常被稱為 Dtrace probe。可以理解為 kernel tracepoint 的用戶層版本,由應(yīng)用開發(fā)者在自己的程序中關(guān)鍵函數(shù)加入自定義的跟蹤點,有點類似于 printf 調(diào)試法(誤)。

          下面是一個簡單的示例:

          #include "sys/sdt.h"
          int main() {
            DTRACE_PROBE("hello_usdt""enter");
            int reval = 0;
            DTRACE_PROBE1("hello_usdt""exit", reval);
          }

          DTRACE_PROBEn 是 UDST (systemtap) 提供的追蹤點定義+插入輔助宏,n 表示參數(shù)個數(shù)。編譯上述代碼后就可以看到被注入的 USDT probe 信息:

          $ apt-get install systemtap-sdt-dev
          $ gcc hello-usdt.c -o hello-usdt
          $ readelf  -n ./hello-usdt
          ...
          Displaying notes found in: .note.stapsdt
            Owner                Data size        Description
            stapsdt              0x0000002e       NT_STAPSDT (SystemTap probe descriptors)
              Provider: "hello_usdt"
              Name: "enter"
              Location: 0x0000000000001131, Base: 0x0000000000002004, Semaphore: 0x0000000000000000
              Arguments:
            stapsdt              0x00000038       NT_STAPSDT (SystemTap probe descriptors)
              Provider: "hello_usdt"
              Name: "exit"
              Location: 0x0000000000001139, Base: 0x0000000000002004, Semaphore: 0x0000000000000000
              Arguments: -4@-4(%rbp)

          readelf -n 表示輸出 ELF 中 NOTE 段的信息。

          在使用 trace 工具(如 BCC、SystemTap、dtrace) 對該應(yīng)用進(jìn)行追蹤時,會在啟動過程中修改目標(biāo)進(jìn)程的對應(yīng)地址,將其替換為 probe ,在觸發(fā)調(diào)用時候產(chǎn)生對應(yīng)事件,供數(shù)據(jù)收集端使用。通常添加 probe 的方式是 基于 uprobe 實現(xiàn)的。

          使用 USDT 的一個好處是應(yīng)用開發(fā)者可以在自己的程序中定義更加上層的追蹤點,方便對于功能級別監(jiān)控和分析,比如 node.js server 就自帶了 USDT probe 點可用于追蹤 HTTP 請求,并輸出請求的路徑等信息。由于 USDT 需要開發(fā)者配合使用,不符合我們最初的逆向分析需要,因此就不過多介紹了。(其實是懶得搭環(huán)境)

          拓展閱讀:

          ?Exploring USDT Probes on Linux[19]?LWN: Using user-space tracepoints with BPF[20]

          小結(jié)

          上述介紹的四種常見內(nèi)核監(jiān)控方案,根據(jù)靜態(tài)/動態(tài)類型以及面向內(nèi)核還是用戶應(yīng)用來劃分的話,可以用下表進(jìn)行概況:

          監(jiān)控方案靜態(tài)動態(tài)內(nèi)核用戶
          Kprobes
          ??
          Uprobes
          ?
          ?
          Tracepoints?
          ?
          USDT?

          ?

          準(zhǔn)確來說 USDT 不算是一種獨立的內(nèi)核監(jiān)控數(shù)據(jù)源,因為其實現(xiàn)還是依賴于 uprobe,不過為了對稱還是放在這里,而且這樣目錄比較好看。

          采集 & 前端

          上面我們介紹了幾種當(dāng)今內(nèi)核中主要的監(jiān)控數(shù)據(jù)來源,基本上可以涵蓋所有的監(jiān)控需求。不過從易用性上來看,只是實現(xiàn)了基本的架構(gòu),使用上有的是基于內(nèi)核提供的系統(tǒng)調(diào)用/驅(qū)動接口,有的是基于 debugfs/tracefs,對用戶而言不太友好,因此就有了許多封裝再封裝的監(jiān)控前端,本節(jié)對這些主要的工具進(jìn)行簡要介紹。

          ftrace

          ftrace 是內(nèi)核中用于實現(xiàn)內(nèi)部追蹤的一套框架,這么說有點抽象,但實際上我們前面已經(jīng)用過了,就是 tracefs 中的使用的方法。

          在舊版本中內(nèi)核中(4.1 之前)使用 debugfs,一般掛載到 /sys/kernel/debug/tracing;在新版本中使用獨立的 tracefs,掛載到 /sys/kernel/tracing。但出于兼容性原因,原來的路徑仍然保留,所以我們將其統(tǒng)一稱為 tracefs。

          ftrace 通常被叫做 function tracer,但除了函數(shù)跟蹤,還支持許多其他事件信息的追蹤:

          ?hwlat: 硬件延時追蹤?irqsoff: 中斷延時追蹤?preemptoff: 追蹤指定時間片內(nèi)的 CPU 搶占事件?wakeup: 追蹤最高優(yōu)先級的任務(wù)喚醒的延時?branch: 追蹤內(nèi)核中的 likely/unlikely 調(diào)用?mmiotrace: 追蹤某個二進(jìn)制模塊所有對硬件的讀寫事件?……

          Android 中提供了一個簡略的文檔指導(dǎo)如何為內(nèi)核增加 ftrace 支持,詳見: Using ftrace[21]

          perf

          perf[22] 是 Linux 發(fā)行版中提供的一個性能監(jiān)控程序,基于內(nèi)核提供的 perf_event_open[23] 系統(tǒng)調(diào)用來對進(jìn)程進(jìn)行采樣并獲取信息。Linux 中的 perf 子系統(tǒng)可以實現(xiàn)對 CPU 指令進(jìn)行追蹤和計數(shù),以及收集 kprobe、uprobe 和 tracepoints 的信息,實現(xiàn)對系統(tǒng)性能的分析。

          在 Android 中提供了一個簡單版的 perf 程序 simpleperf[24],接口和 perf 類似。

          雖然可以監(jiān)測到系統(tǒng)調(diào)用,但缺點是無法獲取系統(tǒng)調(diào)用的參數(shù),更不可以動態(tài)地修改內(nèi)核。因此對于安全測試而言作用不大,更多是給 APP 開發(fā)者和手機廠商用于性能熱點分析。值得一提的是,perf 子系統(tǒng)曾經(jīng)出過不少漏洞,在 Android 內(nèi)核提權(quán)史中也曾經(jīng)留下過一點足跡 :D

          eBPF

          eBPF 為 extended Berkeley Packet Filter 的縮寫,BPF 最早是用于包過濾的精簡虛擬機,擁有自己的一套指令集,我們常用的 tcpdump 工具內(nèi)部就會將輸入的過濾規(guī)則轉(zhuǎn)換為 BPF 指令,比如:

          $ tcpdump -i lo0 'src 1.2.3.4' -d
          (000) ld       [0]
          (001) jeq      #0x2000000       jt 2    jf 5
          (002) ld       [16]
          (003) jeq      #0x1020304       jt 4    jf 5
          (004) ret      #262144
          (005) ret      #0

          該匯編指令表示令過濾器只接受 IP 包,并且來源 IP 地址為 1.2.3.4。其中的指令集可以參考 Linux Socket Filtering aka Berkeley Packet Filter (BPF)[25]。eBPF 在 BPF 指令集上做了許多增強(extend):

          ?寄存器個數(shù)從 2 個增加為 10 個 (R0 - R9);?寄存器大小從 32 位增加為 64 位;?條件指令 jt/jf 的目標(biāo)替換為 jt/fall-through,簡單來說就是 else 分支可以默認(rèn)忽略;?增加了 bpf_call 指令以及對應(yīng)的調(diào)用約定,減少內(nèi)核調(diào)用的開銷;?……

          內(nèi)核存在一個 eBPF 解釋器,同時也支持實時編譯(JIT)增加其執(zhí)行速度,但很重要的一個限制是 eBPF 程序不能影響內(nèi)核正常運行,在 內(nèi)核加載 eBPF 程序前會對其進(jìn)行一次語義檢查,確保代碼的安全性,主要限制為:

          ?不能包含循環(huán),這是為了防止 eBPF 程序過度消耗系統(tǒng)資源(5.3 中增加了部分循環(huán)支持);?不能反向跳轉(zhuǎn),其實也就是不能包含循環(huán);?BPF 程序的棧大小限制為 512 字節(jié);?……

          具體的限制策略都在內(nèi)核的 eBPF verifier 中,不同版本略有差異。值得一提的是,最近幾年 Linux 內(nèi)核出過很多 eBPF 的漏洞,大多是 verifier 的驗證邏輯錯誤,其中不少還上了 Pwn2Own[26],但是由于權(quán)限的限制在 Android 中普通應(yīng)用無法執(zhí)行 bpf(2) 系統(tǒng)調(diào)用,因此并不受影響。

          eBPF 和 perf_event 類似,通過內(nèi)核虛擬機的方式實現(xiàn)監(jiān)控代碼過濾的動態(tài)插拔,這在許多場景下十分奏效。對于普通用戶而言,基本上不會直接編寫 eBPF 的指令去進(jìn)行監(jiān)控,雖然內(nèi)核提供了一些宏來輔助 eBPF 程序的編寫,但實際上更多的是使用上層的封裝框架去調(diào)用,其中最著名的一個就是 BCC。

          BCC

          BCC (BPF Compiler Collection)[27] 包含了一系列工具來協(xié)助運維人員編寫監(jiān)控代碼,其中使用較多的是其 Python 綁定。一個簡單的示例程序如下:

          from bcc import BPF
          prog="""
          int kprobe__sys_clone(void *ctx) {
              bpf_trace_printk("Hello, World!\\n");
              return 0;
          }
          """

          BPF(text=prog).trace_print()

          執(zhí)行該 python 代碼后,每當(dāng)系統(tǒng)中的進(jìn)程調(diào)用 clone 系統(tǒng)調(diào)用,該程序就會打印 "Hello World" 輸出信息。可以看到這對于動態(tài)監(jiān)控代碼非常有用,比如我們可以通過 python 傳入?yún)?shù)指定打印感興趣的系統(tǒng)調(diào)用及其參數(shù),而無需頻繁修改代碼。

          eBPF 可以獲取到內(nèi)核中幾乎所有的監(jiān)控數(shù)據(jù)源,包括 kprobes、uprobes、tracepoints 等等,官方 repo 中給出了許多示例程序,比如 opensnoop 監(jiān)控文件打開行為、execsnoop 監(jiān)控程序的執(zhí)行。后文我們會在 Android 系統(tǒng)進(jìn)行實際演示來感受其威力。

          img-bcc

          bpftrace

          bpftrace[28] 是 eBPF 框架的另一個上層封裝,與 BCC 不同的是 bpftrace 定義了一套自己的 DSL 腳本語言,語法(也)類似于 awk,從而可以方便用戶直接通過命令行實現(xiàn)豐富的功能,截取幾條官方給出的示例:

          # 監(jiān)控系統(tǒng)所有的打開文件調(diào)用(open/openat),并打印打開文件的進(jìn)程以及被打開的文件路徑
          bpftrace -e 'tracepoint:syscalls:sys_enter_open { printf("%s %s\n", comm, str(args->filename)); }'

          # 統(tǒng)計系統(tǒng)中每個進(jìn)程執(zhí)行的系統(tǒng)調(diào)用總數(shù)
          bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

          官方同樣也給出了許多 .bt 腳本示例,可以通過其代碼進(jìn)行學(xué)習(xí)和編寫。

          拓展閱讀:

          ?LWN: A thorough introduction to eBPF[29]?Extending the Kernel with eBPF[30] - https://www.opersys.com/downloads/cc-slides/android-debug/slides-main-211122.html#/- http://www.caveman.work/2019/01/29/eBPF-on-Android/

          SystemTap

          SystemTap(stab)[31] 是 Linux 中的一個命令行工具,可以對各種內(nèi)核監(jiān)控源信息進(jìn)行結(jié)構(gòu)化輸出。同時也實現(xiàn)了自己的一套 DSL 腳本,語法類似于 awk,可實現(xiàn)系統(tǒng)監(jiān)控命令的快速編程。

          使用 systemtap 需要包含內(nèi)核源代碼,因為需要動態(tài)編譯和加載內(nèi)核模塊。在 Android 中還沒有官方的支持,不過有一些開源的 systemtap 移植[32]

          拓展閱讀: Comparing SystemTap and bpftrace[33]

          其他

          除了上面介紹的這些,還有許多開源的內(nèi)核監(jiān)控前端,比如 LTTng、trace-cmd[34]kernelshark[35]等,內(nèi)核監(jiān)控輸出以結(jié)構(gòu)化的方式進(jìn)行保存、處理和可視化,對于大量數(shù)據(jù)而言是非常實用的。限于篇幅不再對這些工具進(jìn)行一一介紹,而且筆者使用的也不多,后續(xù)有機會再進(jìn)行研究。

          Android 移植

          上面說了那么多,終究只是 Linux 發(fā)行版上的熱鬧,那么這些 trace 方法在 Android 上行得通嗎?理論上 AOSP 的代碼是開源的,內(nèi)核也是開源的,編譯一下不就好了。但實踐起來我們會遇到幾個方面的困難:

          1.許多工具需要編譯代碼,BCC 工具還需要 Python 運行,這在默認(rèn)的 Android 環(huán)境中不存在;2.原廠提供的預(yù)編譯內(nèi)核鏡像不帶有 kprobe 等監(jiān)控功能支持,需要自行修改配置,燒寫和編譯內(nèi)核;3.Linux 舊版本對于 eBPF 的支持不完善,許多新功能都是在 5.x 后才引進(jìn),而 Android 的 Linux 內(nèi)核都比較舊,需要進(jìn)行 cherry-pick 甚至手動 backport;4.AOSP 較新版本引入了 GKI(Generic Kernel Image)[36],需要保持內(nèi)核驅(qū)動接口的兼容性,因此內(nèi)核代碼不能引入過多修改;5.……

          由于我們主要目的是進(jìn)行安卓應(yīng)用逆向分析,因此最好在真機環(huán)境運行,因為許多應(yīng)用并不支持 x86 環(huán)境。當(dāng)然 ARM 模擬器也可以,但在攻防對抗的時可能需要進(jìn)行額外的模擬器檢測繞過。

          筆者使用的是 Google Pixel 5,使用其他手機的話需要適當(dāng)進(jìn)行調(diào)整。

          1. Debian over Android

          Android 系統(tǒng)本身并不是為了開發(fā)而設(shè)計的,因此只內(nèi)置了簡單的 busybox(toybox) 工具,以及一些包管理相關(guān)的程序如 pm/am/dumpsys/input 等。為了在上面構(gòu)建完整的開發(fā)環(huán)境,我們需要能在安卓中運行 gcc/clang、python、Makefile 等,一個直觀的想法是通過沙盒等方式在上面運行一個常見的 Linux 發(fā)行版,比如 Ubuntu 或者 Debian。

          androdeb[37] 正是這個想法的一個實現(xiàn),其核心是基于 chroot 在 Android 中運行了一個 Debian aarch64 鏡像,并可以通過 apt 等包管理工具安裝所需要的編譯工具鏈,從而在上面編譯和運行 bcc 等 Linux 項目。

          在 Android 上運行 Debian 系統(tǒng)的示例如下:

          img-vm

          其中的關(guān)鍵之處在于正確掛載原生 Android 中的映射,比如 procfs、devfs、debugfs 等。

          2. 自定義內(nèi)核

          解決了在 Android 上運行開發(fā)工具的問題之后,我們還需要一個支持動態(tài)調(diào)試的內(nèi)核環(huán)境。在絕大多數(shù)官方固件中自帶的內(nèi)核都沒有開啟 KPROBES 的支持,這意味著我們自行編譯和加載內(nèi)核。

          為了能夠支持 KPROBES、UPROBES、TRACEPOINTS 等功能,需要在內(nèi)核的配置中添加以下選項:

          禁用內(nèi)核的安全特性,開啟調(diào)試支持:

          -d CONFIG_LTO \
          -d CONFIG_LTO_CLANG \
          -d CONFIG_CFI_CLANG \
          -d CFI_PERMISSIVE \
          -d CFI_CLANG \
          -e CONFIG_IRQSOFF_TRACER \
          -e CONFIG_PREEMPT_TRACER \
          -e CONFIG_DEBUG_FS \
          -e CONFIG_CHECKPOINT_RESTORE \
          -d CONFIG_RANDOMIZE_BASE \

          開啟 eBPF 支持:

          -e CONFIG_BPF \
          -e CONFIG_BPF_SYSCALL \
          -e CONFIG_BPF_JIT \
          -e CONFIG_HAVE_EBPF_JIT \
          -e CONFIG_IKHEADERS \

          開啟 kprobes 支持:

          -e CONFIG_HAVE_KPROBES \
          -e CONFIG_KPROBES \
          -e CONFIG_KPROBE_EVENT \

          開啟 kretprobe 支持:

          -e CONFIG_KRETPROBES \
          -e CONFIG_HAVE_KRETPROBES \
          -d CONFIG_SHADOW_CALL_STACK \
          -e CONFIG_ROP_PROTECTION_NONE \

          開啟 ftrace 支持:

          -e CONFIG_FTRACE_SYSCALLS \
          -e CONFIG_FUNCTION_TRACER \
          -e CONFIG_HAVE_DYNAMIC_FTRACE \
          -e CONFIG_DYNAMIC_FTRACE \

          開啟 uprobes 支持:

          -e CONFIG_UPROBES \
          -e CONFIG_UPROBE_EVENT \
          -e CONFIG_BPF_EVENTS \

          BCC 建議設(shè)置的選項:

          -e CONFIG_DEBUG_PREEMPT \
          -e CONFIG_PREEMPTIRQ_EVENTS \
          -d CONFIG_PROVE_LOCKING \
          -d CONFIG_LOCKDEP

          為了避免各類環(huán)境問題,我建議編譯環(huán)境最好選擇干凈的虛擬機英文環(huán)境,或者直接使用 Docker 鏡像,根據(jù)官方的指導(dǎo)去編譯,見: Building Kernels[38]

          編譯內(nèi)核常見的依賴:

          $ pkg --add-architecture i386
          $ apt install git ccache automake flex lzop bison \
          gperf build-essential zip curl zlib1g-dev zlib1g-dev:i386 \
          g++-multilib python-networkx libxml2-utils bzip2 libbz2-dev \
          libbz2-1.0 libghc-bzlib-dev squashfs-tools pngcrush \
          schedtool dpkg-dev liblz4-tool make optipng maven libssl-dev \
          pwgen libswitch-perl policycoreutils minicom libxml-sax-base-perl \
          libxml-simple-perl bc libc6-dev-i386 lib32ncurses5-dev \
          x11proto-core-dev libx11-dev lib32z-dev libgl1-mesa-dev xsltproc unzip

          拓展閱讀:

          ?Building a Pixel kernel with KASAN+KCOV[39]?eBPF/BCC - A better low-level Instrumentation tool on Android[40]

          3. 內(nèi)核移植

          當(dāng)你成功編譯好內(nèi)核并啟動后,很可能會發(fā)現(xiàn)有一些內(nèi)核分析工具比如 BCC 在使用上會出現(xiàn)各種問題,這通常是內(nèi)核版本的原因。由于 eBPF 目前在內(nèi)核中也在頻繁更新,因此許多新的特性并沒有增加到當(dāng)前內(nèi)核上。

          例如,在 Pixel 5 最新的支持的內(nèi)核是 4.19 版本,在這個版本中,bpf_probe_read_user (issue#3175)[41] 函數(shù)還沒添加進(jìn)內(nèi)核,因此使用 BCC 會回退到 bpf_probe_read_kernel,這在內(nèi)核直接讀取用戶空間的數(shù)據(jù)(比如系統(tǒng)調(diào)用的參數(shù))時會出現(xiàn)錯誤,因此我們需要手動去 cherry-pick 對應(yīng)的 commit,即在 Linux 5.5 中添加的 6ae08ae3dea2[42]

          BCC 所需的所有內(nèi)核特性及其引進(jìn)的版本列表可以參考: BCC/kernel-versions.md[43],部分列表如下所示:

          img-commit

          因此為了減少可能遇到的兼容性問題,盡量使用最新版本的內(nèi)核,當(dāng)然通常廠商都只維護(hù)一個較舊的 LTS 版本,只進(jìn)行必要的安全性更新,如果買機不淑的話就需要自食其力了。

          實戰(zhàn)測試

          通過在上述 Android Debian 環(huán)境編譯好 BCC 之后,我們就可以使用 Python 編寫對應(yīng)的應(yīng)用跟蹤分析腳本了。一般是通過應(yīng)用名去過濾系統(tǒng)調(diào)用,但是在 Android 中還有個特別的過濾方式就是通過用戶 ID,因為應(yīng)用是根據(jù)動態(tài)安裝獲取的 UID 去進(jìn)行沙盒隔離的。

          以某個層層加固的惡意 APK 為例,安裝后獲取其 UID 為 u0_a142,轉(zhuǎn)換成數(shù)字是 10142,對其進(jìn)行 exec 系統(tǒng)調(diào)用的監(jiān)控:

          img-exec

          可以看到目標(biāo)應(yīng)用調(diào)用了 ps、getprop、pm 等程序,用來檢測當(dāng)前系統(tǒng)的 adb 狀態(tài)以及所安裝的應(yīng)用,比如其中通過 pm path com.topjohnwu.magisk 來判斷 Magisk 工具是否存在,因此存在 root 檢測行為。上圖中 pm 實際調(diào)用了 cmd 程序進(jìn)行查找,因為 pm 本質(zhì)上只是一個 shell 腳本:

          cat `which pm`
          #!/system/bin/sh
          cmd package "$@"

          使用 UID 進(jìn)行過濾的好處是可以跟蹤所有 fork 的子進(jìn)程和孫子進(jìn)程,這是基于 PID 或者進(jìn)程名跟蹤所無法比擬的。除了 exec,我們還可以跟蹤其他內(nèi)核函數(shù),比如 root 檢測經(jīng)常用到的 openat 或 access,如下所示:

          img-su

          基于內(nèi)核級別的監(jiān)控,讓應(yīng)用中所有的加固/隱藏/內(nèi)聯(lián)匯編等防御措施形同虛設(shè),而且可以在應(yīng)用啟動的初期進(jìn)行觀察,讓應(yīng)用的一切行為在我們眼中無所遁形。

          PS: 如果在使用 BCC 的過程中發(fā)現(xiàn)沒有過濾 UID 的選項,那可能需要切換到最新的 release 版本或者 master 分支,因為這個選項是筆者最近才加上去的。

          img-pr

          :D

          拓展閱讀:

          ?eBPF super powers on ARM64 and Android (slides)[44]?eBPF - Android Reverse Engineering Superpowers[45]

          總結(jié)

          本文總結(jié)并分析了幾種內(nèi)核主要的監(jiān)控方案,它們通常用于性能監(jiān)控和內(nèi)核調(diào)試,但我們也可以將其用做安全分析,并在 Android 中進(jìn)行了實際的移植和攻防測試,并且獲得了超出預(yù)期的實戰(zhàn)效果。除了內(nèi)核級別監(jiān)控,我們還可以基于 uprobes 實現(xiàn)應(yīng)用內(nèi)任意地址的監(jiān)控,如在 SSL_read/write 地址處獲取所有 SSL 加密的數(shù)據(jù)。得益于內(nèi)核提供的豐富監(jiān)控原語,我們可以實現(xiàn)內(nèi)核級別移動端沙盒,全面監(jiān)控移動應(yīng)用行為,也可以通過內(nèi)核讀寫原語去實現(xiàn)系統(tǒng)調(diào)用參數(shù)修改,從而實現(xiàn)應(yīng)用運行環(huán)境的模擬和偽造。

          引用鏈接

          [1] 某不知名安全研究員: https://evilpan.com/about/#2018-06-30
          [2] strace: https://man7.org/linux/man-pages/man1/strace.1.html
          [3] How does strace work?: https://blog.packagecloud.io/eng/2016/02/29/how-does-strace-work/
          [4] frida: https://github.com/frida/frida
          [5] QDBI: https://qbdi.quarkslab.com/
          [6] hookzz: https://github.com/jmpews/Dobby
          [7] Linux tracing systems & how they fit together: https://jvns.ca/blog/2017/07/05/linux-tracing-systems/
          [8] An introduction to KProbes: https://lwn.net/Articles/132196/
          [9] Documentation/trace/kprobetrace.rst: https://www.kernel.org/doc/html/latest/trace/kprobes.html
          [10] samples/kprobes/kprobe_example.c: https://elixir.bootlin.com/linux/latest/source/samples/kprobes/kprobe_example.c
          [11] samples/kprobes/kretprobe_example.c: https://elixir.bootlin.com/linux/latest/source/samples/kprobes/kretprobe_example.c
          [12] Linux uprobe: User-Level Dynamic Tracing: https://www.brendangregg.com/blog/2015-06-28/linux-ftrace-uprobe.html
          [13] Documentation/trace/uprobetracer.rst: https://www.kernel.org/doc/html/latest/trace/uprobetracer.html
          [14] Linux tracing - kprobe, uprobe and tracepoint: https://terenceli.github.io/%E6%8A%80%E6%9C%AF/2020/08/05/tracing-basic
          [15] 自定義 tracepoint 示例: https://lwn.net/Articles/383362/
          [16] LWN: Using the TRACE_EVENT() macro (Part 1): https://lwn.net/Articles/379903/
          [17] Documentation/trace/tracepoints.rst: https://www.kernel.org/doc/html/latest/trace/tracepoints.html
          [18] Taming Tracepoints in the Linux Kernel: https://blogs.oracle.com/linux/post/taming-tracepoints-in-the-linux-kernel
          [19] Exploring USDT Probes on Linux: https://leezhenghui.github.io/linux/2019/03/05/exploring-usdt-on-linux.html
          [20] LWN: Using user-space tracepoints with BPF: https://lwn.net/Articles/753601/
          [21] Using ftrace: https://source.android.google.cn/devices/tech/debug/ftrace
          [22] perf: https://perf.wiki.kernel.org/index.php/Tutorial
          [23] perf_event_open: https://man7.org/linux/man-pages/man2/perf_event_open.2.html
          [24] simpleperf: https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc
          [25] Linux Socket Filtering aka Berkeley Packet Filter (BPF): https://www.kernel.org/doc/Documentation/networking/filter.txt
          [26] Pwn2Own: https://flatt.tech/assets/reports/210401_pwn2own/whitepaper.pdf
          [27] BCC (BPF Compiler Collection): https://github.com/iovisor/bcc
          [28] bpftrace: https://github.com/iovisor/bpftrace
          [29] LWN: A thorough introduction to eBPF: https://lwn.net/Articles/740157/
          [30] Extending the Kernel with eBPF: https://source.android.com/devices/architecture/kernel/bpf
          [31] SystemTap(stab): https://sourceware.org/systemtap/wiki
          [32] 開源的 systemtap 移植: https://github.com/flipreverse/systemtap-android
          [33] Comparing SystemTap and bpftrace: https://lwn.net/Articles/852112/
          [34] trace-cmd: https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/trace-cmd.git
          [35] kernelshark: https://kernelshark.org/Documentation.html
          [36] GKI(Generic Kernel Image): https://source.android.com/devices/architecture/kernel/generic-kernel-image
          [37] androdeb: https://github.com/joelagnel/adeb
          [38] Building Kernels: https://source.android.com/setup/build/building-kernels
          [39] Building a Pixel kernel with KASAN+KCOV: https://source.android.com/devices/tech/debug/kasan-kcov
          [40] eBPF/BCC - A better low-level Instrumentation tool on Android: https://blog.senyuuri.info/2021/06/30/ebpf-bcc-android-instrumentation/#_1-choose-kernel-features
          [41] bpf_probe_read_user (issue#3175): https://github.com/iovisor/bcc/issues/3175
          [42] Linux 5.5 中添加的 6ae08ae3dea2: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?id=6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47
          [43] BCC/kernel-versions.md: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
          [44] eBPF super powers on ARM64 and Android (slides): http://www.joelfernandes.org/resources/bcc-ospm.pdf
          [45] eBPF - Android Reverse Engineering Superpowers: https://www.aisp.sg/cyberfest/document/CRESTConSpeaker/eBPF.pdf


          瀏覽 263
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爽好紧别夹喷水免费视频 | 亚洲欧洲日韩综合 | 最新热播日韩女优网站 | 爽妇网怡红院 | 国产搞鸡巴 |