百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

使用 Ftrace 对 Linux USDT进行hack

suiw9 2024-11-16 00:36 76 浏览 0 评论

我以前认为如果不添加SystemTap或LTTng,就不可能在Linux上使用用户级静态定义的跟踪(USDT)探针。Linux 的内置跟踪器、ftrace 和 perf_events 不支持 USDT。

但是我已经找到了一种方法来入侵它们。不建议这样做。不要在家里尝试这个!

# ./usdt -l /opt/node/node
PROBE
node:gc__start
node:gc__done
node:http__client__request
node:http__client__response
[...]
# ./usdt node:gc__start
Tracing uprobe node_gc__start (p:node_gc__start /opt/node/node:0x7f44b4). Ctrl-C to end.
            node-23467 [001] d... 19993502.867548: node_gc__start: (0xbf44b4)
            node-23484 [003] d... 19993502.983719: node_gc__start: (0xbf44b4)
            node-23484 [003] d... 19993503.354810: node_gc__start: (0xbf44b4)
            node-23484 [000] d... 19993503.433517: node_gc__start: (0xbf44b4)
[...]

我的 usdt 工具(在此示例中,在 Node.js上运行)是一个概念证明。重要的不是这个工具,而是在未修改的 Linux 内核上甚至可以做到这一点。在这篇文章中,我将展示它是如何完成的,尽管在我们有一个更好的前端之前,我不鼓励任何人尝试这个。

(2017年更新:自这篇文章以来,我们现在确实有更好的前端:perf现在在最近的Linux版本中支持USDT,现在bcc / eBPF USDT也是如此。)

用户级动态跟踪

USDT不应与用户级动态追踪混淆,后者可以检测任何用户级代码。例如,通过检测服务器的 dispatch_command() 函数来跟踪 MySQL 服务器查询:

# ./uprobe 'p:cmd /opt/bin/mysqld:_Z16dispatch_command19enum_server_commandP3THDPcj +0(%dx):string'
Tracing uprobe cmd (p:cmd /opt/bin/mysqld:0x2dbd40 +0(%dx):string). Ctrl-C to end.
  mysqld-2855  [001] d... 19957757.590926: cmd: (0x6dbd40) arg1="show tables"
  mysqld-2855  [001] d... 19957759.703497: cmd: (0x6dbd40) arg1="SELECT * FROM numbers"
[...]

我正在使用我的uprobe工具,该工具又使用内置的Linux功能:ftrace(tracer)和uprobes(用户级动态跟踪,您需要最新的Linux,例如4.0-ish)。其他跟踪器,包括perf_events和SystemTap,也可以做到这一点。

还可以跟踪许多其他MySQL函数以获得进一步的见解。列出并计算它们:

# ./uprobe -l /opt/bin/mysqld | more
account_hash_get_key
add_collation
add_compiled_collation
add_plugin_noargs
adjust_time_range
[...]
# ./uprobe -l /opt/bin/mysqld | wc -l
21809

这是 21,000 个函数。我们还可以跟踪库函数,甚至是单个指令偏移量。

用户级动态跟踪很棒,可以解决无数问题。但它也可能很难处理:确定要跟踪的代码、处理函数参数以及处理代码更改。

用户级静态定义的跟踪

USDT 探针(或用户级“标记”)是开发人员在感兴趣的位置向其代码添加跟踪宏的地方,具有稳定且记录在案的 API。它使跟踪更容易。(如果您是开发人员,请参阅添加用户空间探测以获取有关如何添加这些内容的示例。

使用 USDT,我可以简单地跟踪一个名为 mysql:query__start 的探测器,而不是追踪 dispatch_command() 的C++符号_Z16dispatch_command19enum_server_commandP3THDPcj。

如果我愿意,我仍然可以跟踪 dispatch_command() 和其他 21,000 个 mysqld 函数,但仅限于需要时:当 USDT 探针无法解决问题时向下钻取。

Linux中的USDT

静态示踪点,以一种或另一种形式,已经存在了几十年。它最近被Sun的DTrace实用程序流行起来,导致它们被放置在许多常见的应用程序中,包括MySQL,PostgreSQL,Node.js和Java。SystemTap开发了一种使用这些DTrace探测器的方法。

您可能正在运行已经包含 USDT 探针的 Linux 应用程序,或者可能需要重新编译(通常为 --enable-dtrace)。使用 readelf 进行检查。例如,对于节点.js:

# readelf -n node
[...]
Notes at offset 0x00c43058 with length 0x00000494:
  Owner                 Data size   Description
  stapsdt              0x0000003c   NT_STAPSDT (SystemTap probe descriptors)
    Provider: node
    Name: gc__start
    Location: 0x0000000000bf44b4, Base: 0x0000000000f22464, Semaphore: 0x0000000001243028
    Arguments: 4@%esi 4@%edx 8@%rdi
[...]
  stapsdt              0x00000082       NT_STAPSDT (SystemTap probe descriptors)
    Provider: node
    Name: http__client__request
    Location: 0x0000000000bf48ff, Base: 0x0000000000f22464, Semaphore: 0x0000000001243024
    Arguments: 8@%rax 8@%rdx 8@-136(%rbp) -4@-140(%rbp) 8@-72(%rbp) 8@-80(%rbp) -4@-144(%rbp)
[...]

这是使用 --enable-dtrace 和 systemtap-sdt-dev 包重新编译的节点,该包提供了一个“dtrace”实用程序来构建 USDT 支持。此处显示了两个探测器:node:gc__start(垃圾回收启动)和节点:http__client__request。

此时,您可以使用SystemTap或LTTng来跟踪这些内容。内置的 Linux 跟踪器、ftrace 和 perf_events 不能(尽管对 perf_events 的支持正在开发中)。然而。。。

使用 Ftrace 进行hack攻击:跟踪地址

从上面的 readelf 输出来看,node:gc__start 处于 0xbf44b4。这个地址是什么?

$ gdb node
(gdb) print/a 0xbf44b4
$1 = 0xbf44b4 <_ZN4node15dtrace_gc_startEPN2v87IsolateENS0_6GCTypeENS0_15GCCallbackFlagsE+4>
(gdb) disas _ZN4node15dtrace_gc_startEPN2v87IsolateENS0_6GCTypeENS0_15GCCallbackFlagsE
Dump of assembler code for function _ZN4node15dtrace_gc_startEPN2v87IsolateENS0_6GCTypeENS0_15GCCallbackFlagsE:
   0x0000000000bf44b0 <+0>:     push   %rbp
   0x0000000000bf44b1 <+1>:     mov    %rsp,%rbp
   0x0000000000bf44b4 <+4>:     nop
   0x0000000000bf44b5 <+5>:     pop    %rbp
   0x0000000000bf44b6 <+6>:     retq   
End of assembler dump.

这是一个nop(无操作)。Ftrace 和 uprobes 可以检测指令,因此我们可以检测此地址。

由于这不是共享库,我们需要知道并减去基本加载地址:

# objdump -x /opt/node/node | more
/opt/node/node:     file format elf64-x86-64
/opt/node/node
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000617770

Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000000400040 paddr 0x0000000000400040 align 2**3
         filesz 0x00000000000001f8 memsz 0x00000000000001f8 flags r-x
  INTERP off    0x0000000000000238 vaddr 0x0000000000400238 paddr 0x0000000000400238 align 2**0
         filesz 0x000000000000001c memsz 0x000000000000001c flags r--
    LOAD off    0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
         filesz 0x0000000000c2ada4 memsz 0x0000000000c2ada4 flags r-x
    LOAD off    0x0000000000c2bca8 vaddr 0x000000000122bca8 paddr 0x000000000122bca8 align 2**21
         filesz 0x0000000000017384 memsz 0x0000000000020cb0 flags rw-
[...]

它是0x400000(典型的x86_64)。uprobes 文档 uprobetracer.txt 从 /proc/PID/maps 获取此内容,但是,该技术需要一个运行过程。

从 0xbf44b4 = 0x7f44b4 中减去0x400000,这是用于 uprobes 的地址(因为它不是共享库)。使用我的uprobe工具(前面提到过)跟踪它:

警告:地址必须正确且与指令对齐

# ./uprobe 'p:gc_start node:0x7f44b4'
Tracing uprobe gc_start (p:gc_start /opt/node/node:0x7f44b4). Ctrl-C to end.
            node-23484 [002] d... 19993896.988891: gc_start: (0xbf44b4)
            node-23467 [003] d... 19993897.079658: gc_start: (0xbf44b4)
            node-23484 [003] d... 19993897.326284: gc_start: (0xbf44b4)
            node-23484 [003] d... 19993897.402107: gc_start: (0xbf44b4)
            node-23467 [001] d... 19993897.605167: gc_start: (0xbf44b4)
[...]

整洁。我给它起了别名“gc_start”。我们可以看到两个节点进程的 GC 正在发生。

这是我们更喜欢更好的前端,一个有更多的安全检查(例如,perf_events)。Ftrace假设你知道自己在做什么。

perf_events> look
YOU SEE A CUP OF POISON.

perf_events> drink cup
I'M AFRAID I WON'T DO THAT. BECAUSE IT IS POISON.

ftrace> drink cup
GLUG GLUG GLUG!

如果你弄错了地址,而且你很幸运,目标进程将立即崩溃(“非法指令”)。如果你运气不好,它将处于未知的损坏状态。

使用 Ftrace 进行hack攻击:已启用

让我们将前面的技术与我突出显示的第二个探针一起使用:node:http__client__request:

# ./uprobe p:node:0x7f48ff
Tracing uprobe node_0x7f48ff (p:node_0x7f48ff /opt/node/node:0x7f48ff). Ctrl-C to end.
^C
Ending tracing...

无。这个探头应该正在发射(有负载),但不是。原因在 readelf 输出中:

    Location: 0x0000000000bf48ff, Base: 0x0000000000f22464, Semaphore: 0x0000000001243024

此探测器使用信号量来实现已启用 IS:DTrace 的一项功能,其中跟踪器可以通知目标进程正在跟踪特定事件。然后,目标进程可以选择执行一些更昂贵的处理,通常是获取和格式化 USDT 探针的参数。

我们需要设置信号量。当前值应为零,并且为:

# dd if=/proc/12647/mem bs=1 count=1 skip=$(( 0x1243026 )) 2>/dev/null | xxd
0000000: 00                                       .

让我们将其设置为 1(假设开始时为零):

警告:这是一个概念证明,不打算用于实际使用

# printf "\x1" | dd of=/proc/12647/mem bs=1 count=1 seek=$(( 0x1243024 ))
1+0 records in
1+0 records out
1 byte (1 B) copied, 3.5286e-05 s, 28.3 kB/s
# dd if=/proc/12647/mem bs=1 count=1 skip=$(( 0x1243024 )) 2>/dev/null | xxd
0000000: 01                                       .

是的,我正在将 bash 的输出直接输送到目标内存上。你可能永远不应该这样做!

它确实有效,我现在可以跟踪探针:

# ./uprobe 'p:client_request /opt/node/node:0x7f48ff' | head -5
Tracing uprobe client_request (p:client_request /opt/node/node:0x7f48ff). Ctrl-C to end.
            node-12647 [001] d... 20025472.106062: client_request: (0xbf48ff)
            node-12647 [001] d... 20025472.106601: client_request: (0xbf48ff)
            node-12647 [001] d... 20025472.107240: client_request: (0xbf48ff)
            node-12647 [001] d... 20025472.107813: client_request: (0xbf48ff)

好。完成后,我应该递减信号量。

这需要一个更好的前端来正确执行信号量。我的 bash 单行代码没有错误检查。

bash|dd|/dev/mem> look
YOU ARE STANDING IN A FOREST. YOU SEE THE TARGET PROCESS TO THE NORTH.

bash|dd|/dev/mem> northh
I DON'T UNDERSTAND "NORTHH". YOU DIE.

如果你得到错误的地址,你会损坏内存。

使用 Ftrace 进行hack攻击:参数

现在我们可以追踪USDT探针,那么它们的论点呢?它们也显示在 readelf 中,例如:

    Name: gc__start
    Arguments: 4@%esi 4@%edx 8@%rdi

    Name: http__client__request
    Arguments: 8@%rax 8@%rdx 8@-136(%rbp) -4@-140(%rbp) 8@-72(%rbp) 8@-80(%rbp) -4@-144(%rbp)

这将显示它们的寄存器和堆栈位置。

要gc__start的参数可以从构建 USDT 探针的节点源确定。例如:

src/node_provider.d:
 54 provider node {
[...]
 78         probe gc__start(int t, int f, void *isolate);

src/node_dtrace.cc:
293 void dtrace_gc_start(Isolate* isolate, GCType type, GCCallbackFlags flags) {
294   // Previous versions of this probe point only logged type and flags.
295   // That's why for reasons of backwards compatibility the isolate goes last.
296   NODE_GC_START(type, flags, isolate);
297 }

所以第一个参数是“type”,存储在 4@%esi:大小 4 中,并寄存 %esi。(下面是基于汇编的语法的参考。跟踪它,并将其命名为“gctype”:

# ./uprobe 'p:client_request /opt/node/node:0x7f44b4 gctype=%si:u32'
Tracing uprobe client_request (p:client_request /opt/node/node:0x7f44b4 gctype=%si:u32). Ctrl-C to end.
            node-12617 [000] d... 20031814.978011: client_request: (0xbf44b4) gctype=0x1
            node-12647 [002] d... 20031814.985857: client_request: (0xbf44b4) gctype=0x1
            node-12647 [001] d... 20031815.323407: client_request: (0xbf44b4) gctype=0x1
            node-12647 [001] d... 20031815.412093: client_request: (0xbf44b4) gctype=0x2
            node-12617 [000] d... 20031815.535844: client_request: (0xbf44b4) gctype=0x1
[...]

伟大!来自deps/v8/include/v8.h:类型1是scavange,类型2是mark-sweep-compact。

其他论点可能会变得更加棘手,但对于真正有决心的人来说仍然是可能的。例如,获取 node:http__client__request 的 url 和方法:

# ./uprobe 'p:client_request /opt/node/node:0x7f48ff +0(+0(%ax)):string' | head -5
Tracing uprobe client_request (p:client_request /opt/node/node:0x7f48ff +0(+0(%ax)):string). Ctrl-C to end.
            node-12647 [003] d... 20031007.438200: client_request: (0xbf48ff) arg1="/"
            node-12647 [003] d... 20031007.438776: client_request: (0xbf48ff) arg1="/"
            node-12647 [003] d... 20031007.439322: client_request: (0xbf48ff) arg1="/"
            node-12647 [003] d... 20031007.439921: client_request: (0xbf48ff) arg1="/"

# ./uprobe 'p:client_request /opt/node/node:0x7f48ff +0(+8(%ax)):string' | head -5
Tracing uprobe client_request (p:client_request /opt/node/node:0x7f48ff +0(+8(%ax)):string). Ctrl-C to end.
            node-12647 [000] d... 20032205.934697: client_request: (0xbf48ff) arg1="GET"
            node-12647 [000] d... 20032205.935255: client_request: (0xbf48ff) arg1="GET"
            node-12647 [000] d... 20032205.935832: client_request: (0xbf48ff) arg1="GET"
            node-12647 [000] d... 20032205.936367: client_request: (0xbf48ff) arg1="GET"

你的参数 +0(+8(%ax)):字符串以寄存器 (%ax) 开头,然后取消引用偏移量 (+8(),然后是 +0),然后强制转换它 (:string)。这种语法类似于早期的参数表示法,但基本上仍然是brainf*ck。它记录在uprobetracer.txt和kprobetrace.txt(见FETCHARGS),但我不知道它是否有真实名称。

但真正的重点,就像其他黑客攻击一样,是我在不修改内核的情况下让它工作。我们想要的是一个更好的前端。

更安全的hack攻击

现在我已经证明了这是可能的,我将总结几种不同的方法来使其更安全。

  • 用 C 重写上述内容。包括错误和安全检查。
  • 跟踪父函数。早期的 node::gc__start 由 dtrace_gc_start() 直接调用,我们可以使用用户级动态跟踪来跟踪它,正如 ftrace、perf_events 和其他跟踪器所支持的那样。检查代码:当探测宏和函数条目之间没有分支时,这有效,因此跟踪它们本质上是相同的。
  • 跟踪另一个金丝雀函数。如果父函数不合适,则代码中可能存在先前的唯一函数调用,可以使用用户级动态跟踪进行跟踪。
  • 通过函数偏移量进行跟踪。可以使用父函数的偏移量跟踪 USDT 探测位置。例如,使用perf_events,使用 perf 探针 -x 二进制“func+偏移量”。这可能更安全,因为它可能涉及更多的错误检查(取决于跟踪器),包括确保偏移量在函数的边界内,并且它是指令对齐的。
  • 使用另一个跟踪器追踪 USDT。SystemTap或LTTng支持USDT。
  • 等待perf_events或跟踪支持。到目前为止,至少有两个人致力于适当的perf_events USDT支持。等不及!

至于我的原型 usdt 工具:

#./usdt -ip 12647 'node:http__client__request +0(+0(%ax)):string'
Tracing uprobe node_http__client__request (p:node_http__client__request /opt/node/node:0x7f48ff +0(+0(%ax)):string). Ctrl-C to end.
    node-12647 [001] d... 20047728.328532: node_http__client__request: (0xbf48ff) arg1="/"
    node-12647 [001] d... 20047728.329050: node_http__client__request: (0xbf48ff) arg1="/"
    node-12647 [001] d... 20047728.329577: node_http__client__request: (0xbf48ff) arg1="/"
    node-12647 [001] d... 20047728.330153: node_http__client__request: (0xbf48ff) arg1="/"
    node-12647 [001] d... 20047728.330689: node_http__client__request: (0xbf48ff) arg1="/"
[...]
# ./usdt -h
USAGE: usdt [-FhHisv] [-d secs] [-p PID] {[-lL] target |
            usdt_probe [filter]}
                 -F              # force. trace despite warnings.
                 -d seconds      # trace duration, and use buffers
                 -i              # enable isenabled probes. Needs -p.
                                 # WARNING: writes to target memory.
                 -l target       # list usdt probes from this executable
                 -L target       # list usdt probes and arguments
                 -p PID          # PID to match
                 -v              # view format file (don't trace)
                 -H              # include column headers
                 -s              # show user stack traces
                 -h              # this usage message
[...]

对于前面描述的警告,我认为以当前的形式分享它是鲁莽的。一旦我的公司使用较新的内核,我会考虑提高它的安全性,并且我们更有可能使用它(它使用 uprobes,这在我们运行的内核上不稳定:很多 3.13)。

perf_events USDT支持可能会在今年晚些时候到来,我可以重写uprobe来使用它。这很好,但与此同时还有其他选项,包括用于USDT的SystemTap,以及用于用户级动态跟踪的ftrace和perf_events。这并不是说我们错过了。

结论

有一种方法可以使用内置的 Linux 内核功能访问 USDT 探针:ftrace 和 uprobes。现在它是一个概念验证,我们缺乏一个像样的前端工具,可以进行错误和安全检查。(但你可能有一个迫切的需求,无论如何,了解这一点是有用的;不过,如果是这样,请查看SystemTap和LTTng。未来,内置的USDT支持只会从这里变得更好!

相关推荐

俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)

发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...

如何强制所有流量使用 HTTPS一网上用户

如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...

https和http的区别(https和http有何区别)

“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...

快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别

什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...

简述HTTPS工作原理(简述https原理,以及与http的区别)

https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...

21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)

HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...

一次安全可靠的通信——HTTPS原理

为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...

为什么有的网站没有使用https(为什么有的网站点不开)

有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...

HTTPS是什么?加密原理和证书。SSL/TLS握手过程

秘钥的产生过程非对称加密...

图解HTTPS「转」(图解http 完整版 彩色版 pdf)

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...

HTTP 和 HTTPS 有何不同?一文带你全面了解

随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...

HTTP与HTTPS的区别,详细介绍(http与https有什么区别)

HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...

一文让你轻松掌握 HTTPS(https详解)

一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...

如何在Spring Boot应用程序上启用HTTPS?

HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...

一文彻底搞明白Http以及Https(http0)

早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...

取消回复欢迎 发表评论: