Quickwords33:How uprobe works

背景

用uprobe也有一段时间了,确实是一个很有价值的工具。我这个人有个特点,就是如果不知道这件事背后的原理,即便工具很好用,但用起来始终心里不踏实,像是房本上没加名字,住着没有安全感。对于uprobe这么fancy的工具还是很有必要了解一下具体的工作机制。一方面是打消“神秘感”,一方面是看看是否能激发别的灵感。

添加uprobe之后发生了什么

我们可以猜到的是,给二进制文件添加uprobe之后,一定可以在汇编代码里看到一些改变。既然如此,我们就找个二进制文件看一下。

在uprobe界有一个“网红”示例:bash中的readline方法。该方法能够用uprobe读取出用户在bash命令行中输入的命令的字符串。

我们先看一下原始的bash二进制文件中,readline方法的汇编代码:

1
2
3
4
5
6
7
8
9
10

$ gdb --pid <target bash PID>
(gdb) disassemble /r readline
Dump of assembler code for function readline:
0x000000000049a520 <+0>: 83 3d 81 5e 26 00 ff cmpl $0xffffffff,0x265e81(%rip)
0x000000000049a527 <+7>: 53 push %rbx
0x000000000049a528 <+8>: 74 6e je 0x49a598 <readline+120>
0x000000000049a52a <+10>: e8 21 ee ff ff callq 0x499350 <rl_set_prompt>
0x000000000049a52f <+15>: e8 3c fd ff ff callq 0x49a270 <rl_initialize>
...

如果此时添加了'r:bash:readline "%s" retval这样一条uprobe之后,再来看一下现在的bash二进制文件中readline方法的情况:

1
2
3
4
5
6
7
8
9
(gdb) disassemble /r readline
Dump of assembler code for function readline:
0x000000000049a520 <+0>: cc int3
0x000000000049a521 <+1>: 3d 81 5e 26 00 cmp $0x265e81,%eax
0x000000000049a526 <+6>: ff 53 74 callq *0x74(%rbx)
0x000000000049a529 <+9>: 6e outsb %ds:(%rsi),(%dx)
0x000000000049a52a <+10>: e8 21 ee ff ff callq 0x499350 <rl_set_prompt>
0x000000000049a52f <+15>: e8 3c fd ff ff callq 0x49a270 <rl_initialize>
...

现在可以看到的是,readline方法的二进制文件中,第一个字节从0x83变成了0xcc。这个是一个叫做INT3的指令。

INT3

既然出现在了汇编代码中,INT其实也是一个汇编指令。在x86 CPU中,INT是用来产生software interrupt的指令,它后面的数字代表对应的中断表的表项,同时触发该表项对应的回调函数。

INT3其实是用来触发软件break point的指令,其实就是我们用gdb时设定的“断点”,在CPU运行到这个指令的时候触发。

同时INT3比较特殊的地方在于,它的二进制指令只有一个字节。因为足够短,它可以在程序的任意位置设置。

总体流程

在了解了INT3这个机关之后,说明一下uprobe的工作原理:

首先把原先的readline的二进制程序83 3d 81 5e 26 00 ff的第一个字节替换为cc,这样当程序运行到这里的时候,就直接进入INT3中断。

进入中断之后,首先保存当前进程的上下文信息,然后做我们给它做的事:在原二进制程序上单步前进,在运行到ret指令时保存相关寄存器里的值并记录。然后恢复进程原来的上下文信息,令程序继续执行。

如此就完成了uprobe的功能。Kprobe也是类似的原理:

© 2020 DecodeZ All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero