修改存在缓冲区溢出漏洞的C函数return地址
Modify the return address of a C function with buffer overflow vulnerability
我正在尝试修改以下 C 程序,以便 main 函数将跳过 printf("x is 1") 行,只打印 "x is 0".
void func(char *str) {
char buffer[24];
int *ret;
ret = buffer + 28; // Supposed to set ret to the return address of func
(*ret) += 32; // Add the offset needed so that func will skip over printf("x is 1")
strcpy(buffer, str);
}
int main(int argc, char **argv) {
int x;
x = 0;
func(argv[1]);
x = 1;
printf("x is 1");
printf("x is 0");
getchar();
}
正如注释所暗示的,ret 指针需要首先设置为函数的 return 地址。然后我需要添加一个偏移量,将其推到我想跳过的线上。我在 Linux 系统上 运行 此代码,配备 2 x Intel(R) Xeon(TM) CPU 3.20GHz。我正在使用 gcc 版本 4.7.2 (Debian 4.7.2-5) 进行编译。我也尝试使用此 (http://insecure.org/stf/smashstack.html) link 中的 example3.c 作为参考。这是使用 gdb 对主要函数的反汇编:
Dump of assembler code for function main:
0x0000000000400641 <+0>: push %rbp
0x0000000000400642 <+1>: mov %rsp,%rbp
0x0000000000400645 <+4>: sub [=12=]x20,%rsp
0x0000000000400649 <+8>: mov %edi,-0x14(%rbp)
0x000000000040064c <+11>: mov %rsi,-0x20(%rbp)
0x0000000000400650 <+15>: movl [=12=]x0,-0x4(%rbp)
0x0000000000400657 <+22>: mov -0x20(%rbp),%rax
0x000000000040065b <+26>: add [=12=]x8,%rax
0x000000000040065f <+30>: mov (%rax),%rax
0x0000000000400662 <+33>: mov %rax,%rdi
0x0000000000400665 <+36>: callq 0x4005ac <func>
0x000000000040066a <+41>: movl [=12=]x1,-0x4(%rbp)
0x0000000000400671 <+48>: mov [=12=]x40075b,%edi
0x0000000000400676 <+53>: mov [=12=]x0,%eax
0x000000000040067b <+58>: callq 0x400470 <printf@plt>
0x0000000000400680 <+63>: mov [=12=]x400762,%edi
0x0000000000400685 <+68>: mov [=12=]x0,%eax
0x000000000040068a <+73>: callq 0x400470 <printf@plt>
0x000000000040068f <+78>: callq 0x400490 <getchar@plt>
0x0000000000400694 <+83>: leaveq
0x0000000000400695 <+84>: retq
End of assembler dump.
使用我从示例中读到的内容,我的缓冲区长 24 个字节,我应该为 SFP 大小添加额外的 4 个字节。这意味着我要添加 28 个字节才能到达 <+41> 的 return 地址。然后看起来我想跳转到 <+73> 处的最后一个 printf 调用。这应该是 32 的偏移量。但是,当我执行代码时,仍然会打印 "x is 1"。我似乎无法找出原因。我的数学或假设有问题吗?
您也应该反汇编函数 func() 以便更好地了解事情的进展情况。此外,我不明白你对 strcpy() 的调用的作用,对我来说只是一个分段错误的原因我把它注释掉以使你的代码工作。
不要忘记,您在代码中看到的大小是以十六进制打印的,而您在代码中输入的缓冲区移位是十进制的。
因此,当您阅读以下内容时:
mov %rdi,-0x28(%rbp)
您必须想到 40 个字节(0x28 六进制 = 40 十进制),而不是 28 个字节。
上面的代码实际上是从func()函数反汇编中提取出来的。正如@cybermike 提到的,不要忘记虽然 Alpeh1 的文本仍然是关于这个主题的参考,但它现在已经很老了:体系结构和保护系统已经广泛发展到现在。
如前所述here,在 x64 架构上,编译器现在会尝试将堆栈地址与 16 字节边界对齐,因此要为 24 字符数组分配大小,它实际上会保留 32 字节,即。最近的边界。
加上为您的 "rest" 指针分配的 8 个字节,那么您就知道您的 return 地址恰好位于 40 个字节之外。
然后,看main()反汇编,正常的return地址是:
0x00000000004005fe <+41>: movl [=11=]x1,-0x4(%rbp)
我们希望它是:
0x0000000000400614 <+63>: mov [=12=]x4006bb,%edi
所以我们必须将 return 增加 63 - 41 = 22。
总而言之,我使用以下 func() 函数让您的练习按预期工作:
void func(char *str) {
char buffer[24];
int *ret;
ret = buffer + 40; // Supposed to set ret to the return address of func
(*ret) += 22; // Add the offset needed so that func will skip over printf("x is 1")
//strcpy(buffer, str);
}
执行结果:
$ ./se
x is 0
我正在尝试修改以下 C 程序,以便 main 函数将跳过 printf("x is 1") 行,只打印 "x is 0".
void func(char *str) {
char buffer[24];
int *ret;
ret = buffer + 28; // Supposed to set ret to the return address of func
(*ret) += 32; // Add the offset needed so that func will skip over printf("x is 1")
strcpy(buffer, str);
}
int main(int argc, char **argv) {
int x;
x = 0;
func(argv[1]);
x = 1;
printf("x is 1");
printf("x is 0");
getchar();
}
正如注释所暗示的,ret 指针需要首先设置为函数的 return 地址。然后我需要添加一个偏移量,将其推到我想跳过的线上。我在 Linux 系统上 运行 此代码,配备 2 x Intel(R) Xeon(TM) CPU 3.20GHz。我正在使用 gcc 版本 4.7.2 (Debian 4.7.2-5) 进行编译。我也尝试使用此 (http://insecure.org/stf/smashstack.html) link 中的 example3.c 作为参考。这是使用 gdb 对主要函数的反汇编:
Dump of assembler code for function main:
0x0000000000400641 <+0>: push %rbp
0x0000000000400642 <+1>: mov %rsp,%rbp
0x0000000000400645 <+4>: sub [=12=]x20,%rsp
0x0000000000400649 <+8>: mov %edi,-0x14(%rbp)
0x000000000040064c <+11>: mov %rsi,-0x20(%rbp)
0x0000000000400650 <+15>: movl [=12=]x0,-0x4(%rbp)
0x0000000000400657 <+22>: mov -0x20(%rbp),%rax
0x000000000040065b <+26>: add [=12=]x8,%rax
0x000000000040065f <+30>: mov (%rax),%rax
0x0000000000400662 <+33>: mov %rax,%rdi
0x0000000000400665 <+36>: callq 0x4005ac <func>
0x000000000040066a <+41>: movl [=12=]x1,-0x4(%rbp)
0x0000000000400671 <+48>: mov [=12=]x40075b,%edi
0x0000000000400676 <+53>: mov [=12=]x0,%eax
0x000000000040067b <+58>: callq 0x400470 <printf@plt>
0x0000000000400680 <+63>: mov [=12=]x400762,%edi
0x0000000000400685 <+68>: mov [=12=]x0,%eax
0x000000000040068a <+73>: callq 0x400470 <printf@plt>
0x000000000040068f <+78>: callq 0x400490 <getchar@plt>
0x0000000000400694 <+83>: leaveq
0x0000000000400695 <+84>: retq
End of assembler dump.
使用我从示例中读到的内容,我的缓冲区长 24 个字节,我应该为 SFP 大小添加额外的 4 个字节。这意味着我要添加 28 个字节才能到达 <+41> 的 return 地址。然后看起来我想跳转到 <+73> 处的最后一个 printf 调用。这应该是 32 的偏移量。但是,当我执行代码时,仍然会打印 "x is 1"。我似乎无法找出原因。我的数学或假设有问题吗?
您也应该反汇编函数 func() 以便更好地了解事情的进展情况。此外,我不明白你对 strcpy() 的调用的作用,对我来说只是一个分段错误的原因我把它注释掉以使你的代码工作。
不要忘记,您在代码中看到的大小是以十六进制打印的,而您在代码中输入的缓冲区移位是十进制的。 因此,当您阅读以下内容时:
mov %rdi,-0x28(%rbp)
您必须想到 40 个字节(0x28 六进制 = 40 十进制),而不是 28 个字节。
上面的代码实际上是从func()函数反汇编中提取出来的。正如@cybermike 提到的,不要忘记虽然 Alpeh1 的文本仍然是关于这个主题的参考,但它现在已经很老了:体系结构和保护系统已经广泛发展到现在。
如前所述here,在 x64 架构上,编译器现在会尝试将堆栈地址与 16 字节边界对齐,因此要为 24 字符数组分配大小,它实际上会保留 32 字节,即。最近的边界。
加上为您的 "rest" 指针分配的 8 个字节,那么您就知道您的 return 地址恰好位于 40 个字节之外。
然后,看main()反汇编,正常的return地址是:
0x00000000004005fe <+41>: movl [=11=]x1,-0x4(%rbp)
我们希望它是:
0x0000000000400614 <+63>: mov [=12=]x4006bb,%edi
所以我们必须将 return 增加 63 - 41 = 22。
总而言之,我使用以下 func() 函数让您的练习按预期工作:
void func(char *str) {
char buffer[24];
int *ret;
ret = buffer + 40; // Supposed to set ret to the return address of func
(*ret) += 22; // Add the offset needed so that func will skip over printf("x is 1")
//strcpy(buffer, str);
}
执行结果:
$ ./se
x is 0