XHtmlTreeTest中的木馬dll分析
概述
該樣本是來自于VirusShare網站提供,于2022年3月15日在VirusTotal上被首次提交。拿到樣本時只有一個dll文件,查看其資源節,發現有“XHtmlTreeTest.exe”等字符串信息,經網上搜索后懷疑其是在一個叫XHtmlTreeTest的XML和HTML解析工具上附帶的木馬dll。
詳細分析
從該木馬dll(因為該dll的原本名字未知,就先怎么稱呼了)的DllMain函數開始分析,其通過sub_1007B390函數獲取“kernel32.dll”、“ntdll.dll”,以及“msvcrt.dll”三個dll庫的基址。該函數訪問進程的ldr鏈表,遍歷其中所有的導入庫,并通過訪問 LDR_DATA_TABLE_ENTRY 結構體中的DllBase可以獲取到dll的基址。

GetProcAddress2 函數是我自己重命名的,其作用就是通過訪問前面獲得的導入庫基址,然后訪問其對應的導出表獲得目標函數地址。其中函數最后一個參數就是hash化的函數名,上面sub_1007B390函數的dll庫名同樣hash化了。此外,該木馬dll中DllMain函數里調用的幾個自定義函數都加入了大量的花指令混淆,如下圖中的 GetProcAddress2 函數所示:

花指令的特征:加或減 N*dword_100ABxxx 和 N*dword_100ABxxx << M。
發現訪問了資源節,用ResourceHack打開看看,發現一個語言未知的項里保存了一個長達0x24000字節的二進制數據。用od調試一下這個dll,該dll獲取到該資源后,申請了一個0x24000的空間,并通過memcpy函數拷貝資源的內容到該空間。發現拷貝的內容就是ResourceHack看到的二進制數據。


跳過一大堆CreateWindowExW函數后,sub_10072050函數使用密鑰”D#PSqwp>oy@S#7dEfvT1uh?XI5X<$kuIz5qEdsD#GbBoy6I+fTJmEH$sG%kSLP_6m“進行擴展。sub_100707E0函數對該資源數據進行解密,此時會發現內存里多了一個叫”Y.dll“的模塊。

對sub_100707E0函數去花后,加密代碼如下:
unsigned int __cdecl sub_100707E0(int a1, int a2, unsigned int a3)
{
v11 = 0;
v7 = 0;
for ( i = 0; i < a3; ++i )
{
v11 = ( + v11 + 1) % 21765;
v7 = (v7 + *(unsigned __int8 *)(a1 + v11)) % 21765;
v10 = *(_BYTE *)(a1 + v11);
*(_BYTE *)(a1 + v11) = *(_BYTE *)(a1 + v7);
v4 = v7;
*(_BYTE *)(a1 + v4) = v10;
v5 = (*(unsigned __int8 *)(a1 + v7) + *(unsigned __int8 *)(a1 + v11)) % 21765;
v6 = v5;
v8 = v6;
LOBYTE(v4) = *(_BYTE *)(a1 + v8);
*(_BYTE *)(a2 + i) ^= v4;
result = i + 1;
}
return result;
}
對sub_10070E70函數中僅調用sub_1007BE90函數,sub_1007BE90函數去花后,分析可知其主要是記錄前面獲得的幾個函數地址以及計算出DllRegisterServer函數的地址,其代碼如下:
DWORD *__cdecl sub_1007BE90(unsigned __int16 *a1, int a2, int (__cdecl *a3)(int, int, int, int, int), void (__cdecl *a4)(int, _DWORD, int, int), int a5, int a6, int a7, int a8)
{
v30 = 0;
if ( !sub_100701B0( a2 , 64) )
return 0;
v31 = a1;
if ( *a1 != 23117)
return 0;
if ( !sub_100701B0( a2 , *((_DWORD *)v31 + 15) + 248 ) )
return 0;
v22 = (char *)a1 + *((_DWORD *)v31 + 15);
if ( *(_DWORD *)v22 != 17744 )
return 0;
if ( *((unsigned __int16 *)v22 + 2) != 332 )
return 0;
if ( (*((_DWORD *)v22 + 14) & ( 1 != 0 )
return 0;
v9 = (int)&v22[ 24 + *((unsigned __int16 *)v22 + 10) ];
v21 = v9 ;
v27 = *((_DWORD *)v22 + 14);
v32 = 0;
while ( v32 < (unsigned int)*((unsigned __int16 *)v22 + 3) )
{
if ( *(_DWORD *)(v21 + 16) )
v20 = *(_DWORD *)(v21 + 16) + *(_DWORD *)(v21 + 12) ;
else
v20 = v27 + *(_DWORD *)(v21 + 12) ;
if ( v20 > v30 )
v30 = v20 ;
++v32;
v21 += 40;
}
v10 = &v25[0];
kernel32_GetNativeSystemInfo(&v10[0]);
v28 = sub_100703D0( *((_DWORD *)v22 + 20), v26 ) ;
v11 = sub_100703D0( v30 , v26 );
if ( v28 != v11 )
return 0;
v18 = 4 ;
v12 = 0x2000 ;
v29 = VirtualAlloc( *((_DWORD *)v22 + 13) , v28, 4096 ) | v12, v18, a8);
if ( !v29 )
{
v19 = 4 ;
v13 = 0x2000 ;
v29 = VirtualAlloc( 0, v28 , 4096 ) | ( v13 ), v19, a8);
if ( !v29 )
return 0;
}
v14 = kernel32_GetProcessHeap( 8 , 64 );
v15 = ntdll_RtlAllocateHeap(v14);
v16 = v15 ;
v24 = (_DWORD *)( v16 );
if ( v24 )
{
v24[1] = v29;
v24[5] = ( 0x2000 ) & *((unsigned __int16 *)v22 + 11)) != 0;
v24[7] = a3;
v24[8] = VirtualFree;
v24[9] = a5;
v24[10] = a6;
v24[11] = a7;
v24[13] = a8;
v24[15] = v26 ;
if ( sub_100701B0( a2 , *((_DWORD *)v22 + 21) )
&& (v33 = a3( v29, *((_DWORD *)v22 + 21) , 4096 , 4 , a8),
msvcrt_memcpy(v33,v31,*((_DWORD *)v22 + 21)),*v24 = v33 + *((_DWORD *)v31 + 15),*(_DWORD *)(*v24 + 52) = + v29,
sub_10076230(a1,a2,v22,&v24[0]))&& ((v23 =*(_DWORD *)(*v24 + 52) - *((_DWORD *)v22 + 13)) == 0 ? (v24[6] = 1) : (v17 = sub_10074F00(&v24[0],v23),v24[6] = v17),
sub_1006D090(&v24[0])
&& sub_10079BD0(&v24[0])
&& sub_1006E5C0(&v24[0])) )
{
if ( *(_DWORD *)(*v24 + 40) )
{
if ( v24[5] )
{
dword_100AE7E0 = (int (__stdcall *)(_DWORD, _DWORD, _DWORD))(v29);
v24[4] = 1;
}
else
{
v24[14] = *(_DWORD *)(*v24 + 40) + v29;
}
}
else
{
v24[14] = 0;
}
result = v24;
}
else
{
sub_1006BFA0(v24);
result = 0;
}
}
else
{
VirtualFree(v29,0,0x8000,a8);
result = 0;
}
return result;
}
該木馬dll只導出了兩個函數,分別是DllRegisterServer函數和DllUnregisterServer函數。該木馬dll的DllRegisterServer函數會去調用Y.dll的DllRegisterServer函數。sub_10073000函數的作用就是尋找Y.dll中DllRegisterServer函數的地址。

使用ollydump把Y.dll從內存中dump下來,用ida靜態分析一下。Y.dll中導出函數只有一個DllRegisterServer函數,一個導入函數都沒有,看來所有的導入函數都是動態獲得的。DllRegisterServer函數里有兩個函數,分別是sub_10021D63和sub_10004587。sub_10021D63函數明顯是進行了控制流混淆,里面通過各種hash計算,套利多個switch分發。分析了部分分支的邏輯,發現其最后都會通過回調的形式訪問Windows API,匯編中表現為”call eax“。所以,想到對Y.dll模塊中所有的”call eax“指令下斷點。又因為這些分支太多了,一個個下不實際,所以利用ollyscript來下斷點。

所有分支的底層函數示例如下:

分享一下od下斷腳本:
var target var add1 var add2 var count ask "輸入hex,支持??,hex必須用##,比如查找#B8??00000001db#" mov target,$RESULT mov add1,eip start: mov add2,add1 mov $RESULT,target find add2,$RESULT cmp $RESULT,0 je over add count,1 mov add1,$RESULT add add1,1 bp $RESULT jne start over: add count,"次下斷" msg count ret
經過調試發現,其通過 SHFileOperationW 函數在”C:\Windows\SysWOW64“目錄下生成一個基于系統時間隨機生成名字的子目錄,并往該目錄創建一個同樣隨機命名的文件。

這里說一下 SHFileOperationW 函數的作用,如下:

其中SHFILEOPSTRUCTA定義如下:
typedef struct _SHFILEOPSTRUCTA {
HWND hwnd;
UINT wFunc;
PCZZSTR pFrom;
PCZZSTR pTo;
FILEOP_FLAGS fFlags;
BOOL fAnyOperationsAborted;
LPVOID hNameMappings;
PCSTR lpszProgressTitle;
} SHFILEOPSTRUCTA, *LPSHFILEOPSTRUCTA;
在od調試時斷下,發現有一次 SHFILEOPSTRUCTA結構體中的pFrom指向木馬dll的文件路徑(這里我重命名該木馬dll為virus.dll),pTo則是指向如上圖所示的新路徑。

通過CreateProcessW函數調用Regsvr32.exe以安靜模式運行將上面創建的文件寫入注冊表。因為Regsvr32常用于dll注冊,那么可以知道其目的就是換個文件名把 該木馬dll注冊到操作系統里。因為該木馬dll是32位的,所以其選擇了”C:\Windows\SysWOW64“目錄。

image-20220319175122377
不過我在分析時每次調用 SHFileOperationW 去創建文件的時候都失敗了,以管理員權限運行也還是一樣,不知道是什么原因。進程在反復調用幾次SHFileOperationW 失敗后就會終止。用rundll32去加載運行,用procmon去觀察結果,依然是如此。也嘗試過把后綴改為.exe,其結果是無法運行。因為不想硬肝那個控制流混淆,所以分析就到此為止了。
IOC
文件名19c5b13e2635be7d9931a404ccbbce7015ea2414cc12d24fd27a2ab8ad7bbe3bMD501d91a225f23340268641f2a88eddf7fSHA1ec385cf0d5dc90bc2b27d31d217356271368030eSHA25619c5b13e2635be7d9931a404ccbbce7015ea2414cc12d24fd27a2ab8ad7bbe3b文件大小1,020,928 bytes文件類型PE32 executable (DLL) (GUI) Intel 80386, for MS Windows發現時間2022-03-15 02:07:01 UTC