信息安全课的一个作业是拿到一个大概是用C写的exe文件,然后用缓冲区溢出跳过其中的字符比较,用了OllyDbg在虚拟机中实践了一下,感觉起来很简单。实际上也确实不难

本文在 Windows XP 虚拟机上进行。

我不确定在Windows 10 内是否依然能做到这些,然后又发现半年前VirtualBox 留了一个分布式对象实验的虚拟机,所以正好可以使用已经配置好的分辨率和共享文件夹。

我们拿到的题目是一个 Overflow.zip的文件夹:

1
2
3
4
E:\Downloads\overflow>dir /B
bo.jpg
bo2.exe
README.txt

bo2.exe就是我们的目标了。bo.jpg中给了一张图片,显示了密码,但是这个图片所显示的内容在ida查看之后并不是完全正确的。(有一些常数并不一样)

从图片中能读到的东西是它的密码是 654N321S。输入这段字符,程序会输出一个Serial number is correct.

1
2
3
4
5
C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe

Enter Serial Number
654N321S
Serial number is correct.

在随便输入一些字符可以见到,这个程序的逻辑是直接退出了。

然后我们的目的是要跳过这个所谓Serial Number的字符的比较,直接输出Serial number is correct.

确定漏洞存在

在输入一定数量的字符后,发现输出了Error! Input must be < 100 characters.

1
2
3
4
5
6
C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe

Enter Serial Number
12345678901234567890123456789012345678901234567890123456789012345678901234567890
123456789015234567890
Error! Input must be < 100 characters.

让我们给多更多的字符,看看会发生什么。

1
2
3
4
5
6
7
8
9
10
C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe

Enter Serial Number
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111

程序没有给出任何的结果,就直接退出了,由此可见,漏洞是存在的。

我们还是来想一个简单的方式来获取输入吧,完全手动输入实在有点麻烦了:

1
2
3
4
E:\Downloads\overflow>python -c "print('a'*200)" | .\bo2.exe

Enter Serial Number
Error! Input must be < 100 characters.

在虚拟机外的主机上,我们是有Python的存在的,那么就可以通过使用python的程序生成大量的字符,然后通过管道输出给这个程序。不过因为我们是在虚拟机中进行这样的操作,所以我们是首先将python输出的内容重定向到一个文件中,然后再在虚拟机中将这个文件重定向到bo2.exe程序的输入。

动态调试工具

首先下载一个OllyDbg,所使用的是52pojie提供的版本:在这里下载。

然后在虚拟机中打开bo2.exe,可以看到这个程序的二进制代码(CPU界面)。在这界面的左下角的面板中可以看到,内存地址中就有那几句话:Serial Number is correct。实际上如果没有,就在左下使用右键-> search -> binary string 进行搜索也能找到这段话。可以看到这段话的地址:00408030

OllyDbg 1

然后再上面的汇编代码的面板中搜索这个地址:右键 -> Search for -> constant,然后搜索得到的结果就是在调用这段话的附近。实际上,因为这是用C语言编译器编译的程序,所以其主函数的代码都会在00401000附近,直接跳到这里也就好了。

OllyDbg

现在我们看到了整个程序的结构,如果写成C的话大概是这样的。其中N是一个常数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main() {
char str[N];
printf("\nEnter Serial Number\n");
scanf("%s", str);
// 出题人貌似漏了 == 100 的情况
if (strlen(str) > 100) {
printf("Error! Input must be < 100 characters.\n");
}
if (strcmp(s, "654N321S") == 0) {
printf("Serial number is correct.\n");
}
}

缓冲区溢出

缓冲区溢出的原理就是简单的输入数量足够大的字符,然后覆盖程序的返回地址,也就是EIP寄存器。然后让我们再来做一次我们刚才所做的事情,也就是输入足够多的a,然后看看结果。

1
python -c "print('a'*300)" > temp1.txt

a*300

这个时候程序理所当然的崩溃了,崩溃的原因是EIP这个地址不可读,因为这个时候EIP的地址已经被我们的aaaa给填满了,它的地址就是61616161也就是四个a的ascii码。现在,通过缓冲区溢出,我们已经能操控EIP也就是程序跳转的方向了。

然后根据我们的目标,我们需要将这个EIP指向我们想让它出现的语句,也就是push bo2.00408030。这是在调用printf函数之前的装载使用参数。在这之后就是调用printf了。而这句话的地址就是00401060,所以我们现在需要将EIP给覆盖成00401060即可。

这个时候当然其实本来应该是进一步通过不同的输入,找到具体是哪一个位置的aaaa变成了EIP的值。但是我比较蠢,懒得花费这种智力,所以就莽了过去:

因为python的print会自动以人类可见的格式输出,\x00就真的是'\x00'这样输出了,所以得使用sys.stdout.buffer.write()

1
python -c "import sys; sys.stdout.buffer.write(b'\x00\x40\x10\x60'*100)" > temp2.txt

简单说一下,因为这是四个完全不相同的字符,因为EIP只有四个字符的位置,然后我只要确定这四个字符的相对位置,补出相应的前缀,这样总能保证EIP被正确地填上。

1
2
3
C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe <temp2.txt

Enter Serial Number

然后发现并没有取得相应的效果。(这就有点尴尬)

然后再次用OllyDbg发现EIP的值为\x60\x10\x40\x00,确实是我们给出的字符,没有位移,但是看来顺序反了。这是端序的问题,导致结果完全不一样。不过不会有什么影响,再来一遍逆序的即可。

1
python -c "import sys; sys.stdout.buffer.write(b'\x60\x10\x40\x00'*100)" > temp3.txt

然后就能得到正确的结果:

1
2
3
4
5
C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe <temp3.txt

Enter Serial Number
Serial number is correct.
Serial number is correct.

这样就直接绕过了所谓序列码直接给出了结果。