栈是一个在进程映象内存的高地址内的后进先出(LIFO) 的缓冲区。
#include
#include
char evil[] =”AAAAAAAAAAAAAAAAAAAAAAAA" ;
void main()
{
char buff[4];
strcpy (buff,evil);
return;
}
上述代码中预留的缓冲区只有4个字节,而strcpy函数向其中复制了24 个字节,导致了缓冲区溢出的发生,存储在栈中的函数返回地址将被覆盖。
这里所说的堆,不是《数据结构》里描述的堆,这里的堆(英文称做heap),是Windows系统中的一种物理结构,用来动态分配和释放对象,用在事先不知道程序所需对象的数量和大小的情况下。
#include
#include
int foo(char *buf) ;
int main(int argc, char *argv[])
{
HMODULE 1 = LoadLibrary ("msvcrt.d11") ;
printf (" \n\nHeapoverflow Poc\n") ;
if (argc != 2)
return printf("缺少参数");
foo(argv[1]) ;
return 0;
}
int foo(char *buf)
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
hp = HeapCreate (0, 0x1000, 0x10000) ;
if(!hp)
return printf("Failed to create heap. \n") ;
h1 = HeapAlloc (hp, HEAP_ZERO_MEMORY,4) ;
printf ("HEAP: %.8X %.8X\n" ,h1, &h1) ;
strcpy (h1,buf) ;// Heap Overflow occurs here:
h2 = HeapAlloc (hp, HEAP_ZERO_MEMORY,260) ;//第二次调用HeapAlloc()将使我们获得程序的控制权
printf("hello") ;
return 0;
}
在foo()函数中,HeapAlloc 函数申请了4个字节大小的堆用于存放变量,而strcpy函数又对buf变量的长度没有检查,如果将一个超长字符串复制到堆中,将破坏原有的堆控制数据结构,当第二次调用HeapAlloc(函数的时候,通过精心构造恶意的字符串,就能取得程序的控制权。
Visual C++中,你能用#pragma指令让编译器插入数据到一个节中。一个Visual C++编译出的典型程序有如下的节:
.data节中通常存放一些全局变量,因此发生在data中的溢出是完全可能的。
每个TEB都有一个缓冲区被用于将ANSI字符集转换为Unicode字符集,像SetComputerNameA和GetModuleHandleA这两个API函数就会用到这个缓冲区,可以想像如果这两个函数没有对传入的字符串进行长度检查,那么溢出就会发生。同时TEB里也有许多重要的指针,例如SEH里的EXCEPTION REGISTR ATION结构体,如果能触发异常,那么就能取得程序的控制权。
最常用的两种函数调用规则,第一种方法叫cdecl,调用者负责把参数从右向左压入堆栈,被调用函数返回后由调用者负责调整堆栈。使用这种调用方式的典型函数是printf 函数。它利用格式化字符串支持可变参数的传递。第二种方法叫stdcall,它与第一种方法唯一不同的一点是由被调用函数负责调整堆栈。
printf()函数的调用格式为:
printf("<格式化字符串>”,<参数表>);
其中格式化字符串包括两部分内容:一部分是正常字符,这些字符将按原样输出:另一部分是格式化规定字符, 以“%”开始,后跟一个或几个规定字符,用来确定输出内容格式。参量表是需要输出的系列参数。
除了Printf()函数,以下函数的使用错误也同样会导致恶意代码的执行:frintfo(),sprintf(),snprintf(),vfprintf(), vprintf(),vsprintf(), vsnprintf()。
一个整数,整型和指针的尺寸一般是相同的(在32位的系统中,例如i386,一个整数是32位长,在64位的系统中,例如SPARC, -个整数是64位长)。计算机中整数是以二进制存储的。计算机中需要负数,通过一个变量的最高位来决定正负。如果最高位置1,这个变量就被解释为负数;如果置0,这个变量就解释为整数。
既然整数有一个固定的长度,那么它能存储的最大值是固定的,当尝试去存储一一个大于这个固定的最大值时,将会导致一个整数溢出。
整数溢出引起的缓冲区溢出主要有两类,一类是“负数等于很 大的整数”,另一类是“真符号数和无符号数比较”。
负数在被当成无符号数处理的时候,就是很大的整数,32 位有符号整数-1可以表示成0xFFFFFFF,但是看成无符号数的时候,就是4294967295。
#include
#include
int foo(char *buf) ;
int main(int argc, char *argv[] )
{
printf (" \n\nFormat String Error Poc\n") ;
if(argc !=2)
return printf("缺少参数");
foo(argv[1]) ;
return 0;
}
int foo(char *buf)
{
char s[8];
strncat (s, buf, sizeof (s)-strlen(buf)-1) ;
printf ("Integer overflow!") ;
return 0 ;
}
给它传递一个超长参数便间接地导致了缓冲区溢出的产生。
有些字符串处理函数会自动地在字符串末尾添加\x0O的结束符,当字符串的长度大于缓冲区长度的时候,字符串处理函数将结束符写入到下一个字节。这就是所谓的Off-by one攻击。
strncat 函数会自动地在字符串的末尾加上\x00结束符,在某些特殊情况下,与缓冲区相邻的EBP寄存器将会被改写,间接地将导致栈指针ESP也被改写。
int main(int argc,char **argv)
{
char buf[256] ;
strcpy (buf ,argv[1]) ;
}
说明:
覆盖main函数返回地址。
int main(int argv,char **argc) {
char buf[256];
strcpy(buf,argc[1]) ;
exit(1);
}
说明:
Windows 下利用Windows的异常机制可以成功地溢出这个程序。
int main(int argv,char **argc) {
extern system, puts;
void (*fn) (char*)= (void(*) (char*) )&system;
char buf[256];
fn= (void(*) (char*)) &puts;
strcpy (buf,argc[1]) ;
fn(argc[2]) ;
exit(1) ;
}
说明:
可利用缓冲区溢出覆盖fn函数指针,以达到攻击的目的。
int main(int argv,char **argc) {
char *pbuf=malloc (strlen (argc[2])+1) ;
char buf[256] ;
fn= (void(*) (char*) ) &puts;
strcpy (buf,argc[1]) ;
strcpy (pbuf,argc[2]) ;
fn(argc[3]) ;
while(1) ;
}
说明:
可第一个strcpy时候,可覆盖到pbuf指针,可使pbuf指向fn地址,所以第二次strcpy的时候就会覆盖到fn 指针,结果在运行fn()函数的时候就可以执行任意函数调用,比如system()。
int main(int argv, char**argc) {
char *pbuf=malloc (strlen(argc[2])+1) ;
char buf[256] ;
strcpy (buf, argc[1]) ;
for (; *pbuf++=*(argc[2]++);) ;
exit(1) ;
}
说明:
第一个strcpy时候,可覆盖到pbuf指针,可使pbuf指向exit的GOT或者.dotrs地址+4,从而可以覆盖到那些部分,获得控制权。
int main(int argv,char **argc) {
char *pbuf=malloc (strlen (argc[21)+1) ;
char buf[256];
strcpy (buf,argc[1]) ;
strcpy (pbuf,argc[2]) ;
while(1) ;
}
说明:
第一个strcpy时候,可覆盖到pbuf指针,可使pbuf指向第二个strcpy函数的返回地址,从而可以覆盖到该地址,第二个stcpy--返回就可以获得控制权。
int main(int argv,char **argc) {
char *pbuf1= (char*)malloc(256) ;
char *pbuf2= (char*)malloc(256) ;
gets (pbuf1) ;
free (pbuf2) ;
free (pbuf1) ;
}
int main(int argv,char **argc) {
char *pbuf1= (char*)malloc(256) ;
gets (pbuf1) ;
free (pbuf1) ;
}