网页木马攻防实战学习笔记二:缓冲区溢出

1 常见案例

1.1 栈溢出

栈是一个在进程映象内存的高地址内的后进先出(LIFO) 的缓冲区。

#include<stdio.h>
#include <string.h>

char evil[] =”AAAAAAAAAAAAAAAAAAAAAAAA" ;
void main()
{
  char buff[4];
  strcpy (buff,evil);
  return;
}

上述代码中预留的缓冲区只有4个字节,而strcpy函数向其中复制了24 个字节,导致了缓冲区溢出的发生,存储在栈中的函数返回地址将被覆盖。

1.2 堆溢出

这里所说的堆,不是《数据结构》里描述的堆,这里的堆(英文称做heap),是Windows系统中的一种物理结构,用来动态分配和释放对象,用在事先不知道程序所需对象的数量和大小的情况下。

#include<stdio.h>
#include <string.h>

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(函数的时候,通过精心构造恶意的字符串,就能取得程序的控制权。

1.3 .data节中的溢出

Visual C++中,你能用#pragma指令让编译器插入数据到一个节中。一个Visual C++编译出的典型程序有如下的节:

.data节中通常存放一些全局变量,因此发生在data中的溢出是完全可能的。

1.4 TEB/PEB溢出

每个TEB都有一个缓冲区被用于将ANSI字符集转换为Unicode字符集,像SetComputerNameA和GetModuleHandleA这两个API函数就会用到这个缓冲区,可以想像如果这两个函数没有对传入的字符串进行长度检查,那么溢出就会发生。同时TEB里也有许多重要的指针,例如SEH里的EXCEPTION REGISTR ATION结构体,如果能触发异常,那么就能取得程序的控制权。

1.5 格式化字符串漏洞

最常用的两种函数调用规则,第一种方法叫cdecl,调用者负责把参数从右向左压入堆栈,被调用函数返回后由调用者负责调整堆栈。使用这种调用方式的典型函数是printf 函数。它利用格式化字符串支持可变参数的传递。第二种方法叫stdcall,它与第一种方法唯一不同的一点是由被调用函数负责调整堆栈。

printf()函数的调用格式为:

printf("<格式化字符串>”,<参数表>);

其中格式化字符串包括两部分内容:一部分是正常字符,这些字符将按原样输出:另一部分是格式化规定字符, 以“%”开始,后跟一个或几个规定字符,用来确定输出内容格式。参量表是需要输出的系列参数。

除了Printf()函数,以下函数的使用错误也同样会导致恶意代码的执行:frintfo(),sprintf(),snprintf(),vfprintf(), vprintf(),vsprintf(), vsnprintf()。

1.6 整数溢出

一个整数,整型和指针的尺寸一般是相同的(在32位的系统中,例如i386,一个整数是32位长,在64位的系统中,例如SPARC, -个整数是64位长)。计算机中整数是以二进制存储的。计算机中需要负数,通过一个变量的最高位来决定正负。如果最高位置1,这个变量就被解释为负数;如果置0,这个变量就解释为整数。

既然整数有一个固定的长度,那么它能存储的最大值是固定的,当尝试去存储一一个大于这个固定的最大值时,将会导致一个整数溢出。

整数溢出引起的缓冲区溢出主要有两类,一类是“负数等于很 大的整数”,另一类是“真符号数和无符号数比较”。

负数在被当成无符号数处理的时候,就是很大的整数,32 位有符号整数-1可以表示成0xFFFFFFF,但是看成无符号数的时候,就是4294967295。

#include <stdio. h>
#include <wi ndows .h>

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 ;
}

给它传递一个超长参数便间接地导致了缓冲区溢出的产生。

1.7 Off-by-one攻击

有些字符串处理函数会自动地在字符串末尾添加\x0O的结束符,当字符串的长度大于缓冲区长度的时候,字符串处理函数将结束符写入到下一个字节。这就是所谓的Off-by one攻击。

strncat 函数会自动地在字符串的末尾加上\x00结束符,在某些特殊情况下,与缓冲区相邻的EBP寄存器将会被改写,间接地将导致栈指针ESP也被改写。

 

2 经典溢出情形

2.1 最容易发现的情形

int main(int argc,char **argv)
 {
    char buf[256] ;
    strcpy (buf ,argv[1]) ;
 }

说明:

覆盖main函数返回地址。

2.2 主程序不返回的例子

int main(int argv,char **argc) {
    char buf[256];
    strcpy(buf,argc[1]) ;
    exit(1);
}

说明:

Windows 下利用Windows的异常机制可以成功地溢出这个程序。

2.3 覆盖函数指针

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函数指针,以达到攻击的目的。

2.4 覆盖普通指针

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()。

2.5 覆盖GOT

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,从而可以覆盖到那些部分,获得控制权。

2.6 覆盖strcpy函数的返回地址

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--返回就可以获得控制权。

2.7 两次free

int main(int argv,char **argc) {
    char *pbuf1= (char*)malloc(256) ;
    char *pbuf2= (char*)malloc(256) ;
    
    gets (pbuf1) ;
    free (pbuf2) ;
    free (pbuf1) ;
}

2.8 一次free

int main(int argv,char **argc) {
    char *pbuf1= (char*)malloc(256) ;
    
    gets (pbuf1) ;
    free (pbuf1) ;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章