“控制复杂性是计算机编程的本质。”
——Brian Kernighan
HDTrans是一个轻量级的IA32到IA32动态翻译系统,它的初衷是设计成内核级的翻译器和设备驱动沙箱(sandbox),力求简洁和易修改。当Brian Kernighan首创的“hello, world”程序遇到动态翻译器会发生什么呢?
屏幕上没有悬念的显示出了“hello, world”。动态翻译系统就像一部情节曲折,但结局早已注定的电影。因为动态翻译器必须保证程序被动态执行和native执行时表现出一致性行为。这包含两层意思:一是具有相同的正确行为,这通常比较容易实现;二是原程序的错误被正确的再现,对于精确模拟这是必须的。
在我们敲入”HDTrans ./hello”时到底发生了什么事呢?秘密总是在揭开面纱后呈现出来,原来HDTrans文件只是个shell脚本,通过设置LD_PRELOAD环境变量绑架hello程序的执行。
现在另一个问题又诞生了,HDTrans在nvdebug.so动态库里面做了什么手脚,使得其能获得对hello程序的控制权呢? 我们知道通过LD_PRELOAD环境变量,能在程序执行前预先加载一些库。这样我们就能定义一些函数替换库函数,也可以预先初始化或反初始化程序达到调试或监控程序的目的。由此我们推断应该是在初始化事做了一些事情。让我们看看HDTrans下UserEntry.s文件。
init节区调研UserEntry函数,这保证了在库初始化调用UserEntry。在UserEntry中将通过一系列的压栈操作,以UserEntry函数的返回地址作为参数调用init_translation函数,init_translation执行初始化操作,它具体做了什么会在后面介绍。init_translation的返回值指向翻译机制入口地址,被保存在eax寄存器中,最后通过jmpl跳转翻译机制的入口,对hello程序进行动态翻译,完成了对hello的绑架。
最后通过objdump和readelf看看nvdebug.so如何对UserEntry的调用。
$objdump -d nvdebug.so
00007114 <_init>:
7114: 55 push %ebp
7115: 89 e5 mov %esp,%ebp
7117: 53 push %ebx
7118: 83 ec 04 sub $0x4,%esp
711b: e8 00 00 00 00 call 7120 <_init+0xc>
7120: 5b pop %ebx
7121: 81 c3 d4 be 01 00 add $0x1bed4,%ebx
7127: 8b 93 9c ff ff ff mov -0x64(%ebx),%edx
712d: 85 d2 test %edx,%edx
712f: 74 05 je 7136 <_init+0x22>
7131: e8 c6 00 00 00 call 71fc <__gmon_start__@plt>
7136: e8 35 03 00 00 call 7470 <frame_dummy>
713b: e8 fc ff ff ff call 713c <_init+0x28>
7140: e8 8b 1a 01 00 call 18bd0 <__do_global_ctors_aux>
7145: 58 pop %eax
7146: 5b pop %ebx
7147: c9 leave
$readelf -a nvdebug | grep UserEntry
00713c 00008402 R_386_PC32 000074ac UserEntry
132: 000074ac 27 FUNC GLOBAL DEFAULT 11 UserEntry
39: 00000000 0 FILE LOCAL DEFAULT ABS UserEntry.s
610: 000074ac 27 FUNC GLOBAL DEFAULT 11 UserEntry
在713b处,call调用的地址是相对偏移,它通过重定位后链接到UserEntry函数。