在C语言中,数组的重要性和指针是一样的。所以更深入了解数组也就更加了解C语言。

1. 一维数组

首先,我创建了一个一维数组intArray。我们看一下编译器是怎么存放这些数值的:

0.png

我们可以看到,我们的数值1,2,3,4连续存放在当前的Stack里面,就相当于连续存放的局部变量。

我们再看看intArray这个变量里面的值到底是什么?

1.png

很明显,我们的指针变量a里面存放了数组最顶部的值的地址[ebp - 18h](这个地址存放了intArray的第一个值:1)。

我们再探究一下Array是怎样取出里面的数组的呢?

2.png

我们可以看到取出一维数组中的值大概分为三个步骤:

1
2
3
4
5
6
7
8
9
//Step1:
mov eax,4 //这个步骤就是把数组的大小给eax。

//Step2:
imul ecx,eax,0 //计算数组中的offset, 这里我取第一个值,所以计算出来为0
mov edx,dword ptr [ebp+ecx-18h] //[(ebp-18h) + ecx]相当于把offset里面的值取出来。

//Step3:
mov dword ptr [ebp-30],edx //把取出的值放到局部变量中

我们可以看到数组再取出数值的时候并没有越界判断。那么我们可以利用这一点强行修改ebp+4的值达到return到指定function的功能。这种技术叫做缓冲区溢出

首先通过计算,从Array取第几个值可以达到ebp+4的位置: 我这里是取第7个值达到ebp+4:

所以我们写出下面代码模拟缓冲区溢出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

void hack() {
while (true)
{
cout << "Hey, You jump to my functioon.\n";
}
}

void check() {
int intArray[] = { 1, 2, 3, 4 };
intArray[7] = (int)&hack; //这一步是关键,把epb+4的return地址改成hack这个function的地址。
}

int main()
{
check();
}

首先不运行这一段代码,正常情况main函数只调用了check()函数,那么并不会输出任何东西到console里面。

但是如果黑客通过逆向利用缓冲区溢出修改了原函数的返回地址(例如15行)。那么就会返回到黑客自己的函数hacker()。我们编译运行看看运行结果。

3.png

我们可以看到结果,虽然main函数没有调用hacker()函数,但是仍然跳转到我们的函数了。

我们想象一下,如果hacker()函数里面放的是某些病毒函数,那么你的电脑就躺枪了。当然外挂也可以这样Hook进到进程中。这是一个技术,就看你怎么运用了。

2. 多维数组

首先看一下底层的二维数组长什么样:

4.png

其实就是一维数组。

我们再看一下,底层是怎么取出二维数组里面的值的:

5.png

其实和一维数组一样,分成三个步骤

1
2
3
Step1: 把数组中值的多少放到eax中
Step2: 计算offset
Step3: 取出offset里面的值,mov到Stack的局部变量中

所以多维数组本质上就是一维数组,但是我们可以更方便的使用多维数组(感谢编译器的工作,多维数组的工作是基于编译器的支持。如果编译器不支持多维数组,就使用不了多维数组了)。

多维数组的实现其实是编译器帮我们计算一维数组的offset,再帮我们在内存中取出来。

同时,多维数组也存在缓冲区溢出,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

void hack() {
while (true)
{
cout << "Multi Dimen Array : Hey, You jump to my functioon.\n";
}
}

void check() {
int MultiArray[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
MultiArray[3][2] = (int)&hack;//把hack()函数的地址放到ebp+4的返回地址中.
}

int main()
{
check();
}

结果如下:

6.png

3. Conclusion

  1. 一维数组是连续存放的,取出值的过程其实是编译器帮我们计算offset取出值的过程。
  2. 多维数组本质上就是一维数组。一样的,取值的过程就是编译器帮我们计算offset再取出值的过程。
  3. 一维数组和多维数组都没有Boundary Detection。所以都有可能造成缓冲区溢出的危险。