在 Linux 中,当程序运行时,libc 库将被加载到内存中。当内存地址随机化关闭时,对于相同的程序,库总是被加载到相同的内存地址中(对于不同的程序, libc 库的内存地址可能不同)。因此,我们可以使用调试工具,如 gdb,找出 system() 的地址。也就是说,我们可以调试目标程序 retlib。尽管程序是一个 root 拥有的 Set-uid 程序,我们仍然可以调试它,只是权限将被丢弃(即,有效用户 ID 将与真实用户 ID 相同)。在 gdb 中,我们需要输入 run 命令来执行一次目标程序。否则,库代码将不会被加载。我们使用 p 命令(或 print)打印出 system() 和 exit() 函数的地址(我们稍后将需要 exit())。
$ touch badfile
$ gdb -q retlib ⬅ 使用"安静"模式
Reading symbols from ./retlib...
(No debugging symbols found in ./retlib)
gdb-peda$ break main
Breakpoint 1 at 0x1327
gdb-peda$ run
......
Breakpoint 1, 0x56556327 in main ()
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e12420 <system>
gdb-peda$ p exit
$2 = {<text variable, no debug info>} 0xf7e04f80 <exit>
gdb-peda$ quit
应当注意,即使对于相同的程序,如果我们将其从 Set-uid 程序更改为非 Set-uid 程序,libc 库可能不会被加载到同一位置。因此,当我们调试程序时,需要调试目标 Set-uid 程序,否则,我们得到的地址可能是错误的。
如果你更喜欢在批处理模式下运行 gdb,你可以将 gdb 命令放入一个文件中,然后让 gdb 执行这个文件中的命令:
$ cat gdb_command.txt
break main
run
p system
p exit
quit
$ gdb -q -batch -x gdb_command.txt ./retlib
...
Breakpoint 1, 0x56556327 in main ()
$1 = {<text variable, no debug info>} 0xf7e12420 <system>
$2 = {<text variable, no debug info>} 0xf7e04f80 <exit>