我们的攻击策略是跳转到 system() 函数,并使其执行任意命令。由于我们希望获得 shell 提示符,我们希望 system() 函数执行 "/bin/sh" 程序。因此,命令字符串 "/bin/sh" 必须首先放入内存中,我们必须知道其地址(这个地址需要传递给 system() 函数)。有很多方法可以实现这些目标,我们选择一种使用环境变量的方法。你也可以使用其他方法。
当我们从 shell 提示符执行程序时,shell 实际上会生成一个子进程来执行程序,所有被 export 的 shell 变量会成为子进程的环境变量。我们可以通过这个方法将一个任意字符串放入子进程的内存中。让我们定义一个新的 shell 变量 MYSHELL,并让它包含字符串 "/bin/sh"。从以下命令中,我们可以验证字符串进入了子进程,并由在子进程中运行的 env 命令打印出来。
$ export MYSHELL=/bin/sh
$ env | grep MYSHELL
MYSHELL=/bin/sh
我们将使用这个变量的地址作为 system() 调用的参数。这个变量在内存中的位置可以使用以下程序找到:
void main(){
char* shell = getenv("MYSHELL");
if (shell)
printf("%x\n", (unsigned int)shell);
}
将上述代码编译成名为 prtenv 的二进制文件。如果关闭了地址随机化,你会发现打印出的是相同的地址。当你在同一终端中运行易受攻击的程序 retlib 时,环境变量的地址将是相同的(见下面的特别说明)。你可以通过将上述代码放入 retlib.c 中来验证这一点。然而,程序名称的长度确实有影响。这就是我们选择 6 个字符作为程序名称 prtenv 以匹配 retlib 的原因。
当你编译上述程序时,应使用 -m32 标志,因为二进制代码 prtenv 是为 32 位机器准备的,而不是 64 位机器。易受攻击的程序 retlib 是一个 32 位二进制文件,所以如果 prtenv 是 64 位的,环境变量的地址将会不同。