0x00 说明
“烫”这个汉字在计算机程序执行出错时老是出现,特别有意思,尤其在Windows环境下执行越界访问的程序,会打印出一串“烫烫烫…”,作者以前在VS下写程序的时候就出现过一直打印“烫”,当时没有去仔细研究研究为什么是“烫”这个汉字,现在特把这个原因说明。
0x01 “烫”的GBK编码
首先,不得不提到“烫”的GBK编码为“0xCCCC”,同时在x86系统中“0xCC”代表汇编指令”INT 3“的机器码。
INT 3:一个中断指令,例如设置软中断与这个指令相关,那么一段内存全是“CCCCCC…”,就会一直产生“烫”。
另外还有一个例子,在往单片机的液晶显示上显示汉字的时候,有时候乱码也会出现“烫”,因为对于有些液晶,如12864,其汉字库就是GBK编码。
0x02 较简单的测试程序
运行环境
VS2013C代码
#include "stdafx.h"
void foo(void)
{
int a=0, b=0, c=2;
printf("hello,world\n");
}
int main()
{
int i=0, j=0;
char array[10];//声明一个数组,没有初始化
printf("%s\n",array);//使用字符串的形式打印
foo();
return 0;
}
- 汇编程序
int main()
{
01011440 push ebp
01011441 mov ebp,esp
01011443 sub esp,0F0h
01011449 push ebx
0101144A push esi
0101144B push edi
0101144C lea edi,[ebp-0F0h]
01011452 mov ecx,3Ch
01011457 mov eax,0CCCCCCCCh ##使用0xccccc....填充
0101145C rep stos dword ptr es:[edi]
0101145E mov eax,dword ptr ds:[01018000h]
01011463 xor eax,ebp
01011465 mov dword ptr [ebp-4],eax
int i=0, j=0;
01011468 mov dword ptr [i],0
0101146F mov dword ptr [j],0
char array[10];
printf("%s\n",array);
01011476 mov esi,esp
01011478 lea eax,[array]
0101147B push eax ##将形参入栈
0101147C push 1015868h //将“%s\n”入栈
01011481 call dword ptr ds:[1019114h]
01011487 add esp,8
0101148A cmp esi,esp
0101148C call __RTC_CheckEsp (0101113Bh) ##调用类类似printf/scanf函数后,需要检查缓冲区溢出
foo();
01011491 call foo (010110F0h)
return 0;
其实从程序内存的角度去观察程序的运行过程,才能感受到程序的本质。
上面这段汇编是main函数的反汇编代码,与最上面的C语言代码对应,可以发现好多汇编语句是C语言里边发现不了的,尤其是堆栈指针的操作。对于每一个函数,它们都有一个堆栈,如下代码:
01011440 push ebp
01011441 mov ebp,esp
01011443 sub esp,0F0h
01011449 push ebx
0101144A push esi
0101144B push edi
0101144C lea edi,[ebp-0F0h]
01011452 mov ecx,3Ch
01011457 mov eax,0CCCCCCCCh ##使用0xccccc....填充
0101145C rep stos dword ptr es:[edi]
0101145E mov eax,dword ptr ds:[01018000h]
一个函数内部,esp指针和ebp指针之间的空间就是函数的堆栈区间,进入该函数后,都会使用0xCCCC填充它的堆栈区间,那么如果我们声明一个数组,不加初始化的采用printf(“%s”)的形式打印这个数组,在终端就会打印出“烫烫烫”,一个字节就是0xCC,所以如果我们声明一个char型变量,也不加初始化的打印,便是0xCC=-52,或者以十六进制打印也可。这只是验证上面的说法:函数一开始就以0xCCCC填充堆栈区,如果不初始化,就是0xCC(就看是几个字节的变量)。
(补充:在VS下做相关实验,需要关闭sdl检查)
- 内存视界
丢一个函数调用的堆栈视图: