# Malware-Analysis/SmoothOperator.md at main · dodo- sec/Malware-Analysis · GitHub **[github.com/dodo-sec/Malware-Analysis/blob/main/SmoothOperator/SmoothOperator.md](https://github.com/dodo-sec/Malware-Analysis/blob/main/SmoothOperator/SmoothOperator.md)** dodo-sec main ## Name already in use A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch? ## Malware-Analysis/SmoothOperator/SmoothOperator.md Cannot retrieve contributors at this time ## SmoothOperator This analysis is focused on the SmoothOperator payloads from Sentinel [One. They were obtained via vx-underground and comprise two DLLs. The](https://github.com/dodo-sec/Malware-Analysis/blob/main/SmoothOperator/share.vx-underground.org) first stage has the hash bf939c9c261d27ee7bb92325cc588624fca75429. ## First stage This DLL is a straightforward PE loader, with no obfuscation or encryption present. A good first step is looking for references to VirtualProtect there are two. ----- First one looks promising, given the ERW flag being passed to it. Checking the function called afterwards (__guard_dispatch_icall_fptr) leads us to an offset, which in turn leads to jmp rax. This is probably a jump to unpacked code or the next stage. Let's circle back to the start of the function where those calls to VirtualProtect are and see what exactly we're marking as executable and then jumping to. This looks promising. A DLL named d3dcompiler_47.dll and a call to ``` CreateFileW, followed by memory allocation of the same size as that file. ``` Moving on, we'll see some obvious parsing of a PE file. ----- Finally, we see a loop that starts looking for the sequence 0xFE 0xED 0xFA ``` 0xCE at the Security directory of d3dcompiler_47.dll and moves forward. ``` If we can find that sequence of bytes in a DLL file, we probably have ``` d3dcompiler_47.dll - it just so happens that sequence in present in the ``` second DLL from Sentinel One, **20d554a80d759c50d6537dd7097fed84dd258b3e. Going forward there** are several arithmetic operations followed by the aforementioned ``` VirtualProtect and jmp rax. Instead of worrying about those, just pop ``` the DLL into a debugger, rename ``` 20d554a80d759c50d6537dd7097fed84dd258b3e to d3dcompiler_47.dll ``` and run until the jmp rax. First stage is done. ## Second stage A quick glance at the debugger following the jmp to rax shows we land at some shellcode at allocated memory. ----- The dump window also shows the same memory region. One should be careful when dumping it though, since there's plenty of random data preceding the shellcode and d3dcompiler_47.dll; throwing it in Ida before getting rid of that data will make for an annoying time. On that note, even though Ida Home supports shellcode analysis, I decided to convert this stage to a PE file. The reason is twofold: first, it means I won't have to import local types manually; second, it means I can keep the dump as is, which is advantageous because we'll be able to follow direct references to the DLL that follows the shellcode. For that end, [I do a simple hack with FASM:](https://flatassembler.net/) ``` include '..\..\fasmw17330\include\win64ax.inc' .code start: file 'stage2.bin' invoke ExitProcess, 0 .end start ``` The start of the shellcode features basic position independent code (call ``` $+5 followed by pop rcx), which is used to get the address of the start of ``` the DLL read into memory by the first stage into rcx. Another displacement is applied to get a pointer to what appears to be an User-Agent string into ``` r8: ``` ----- ``` 1200 2400 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) 3CXDesktopApp/18.11.1197 Chrome/102.0.5005.167 Electron/19.1.9 Safari/537.36” ``` The next call is to a function that will be tasked with mapping ``` d3dcompiler_47.dll. Although it's already in memory, it has not been ``` mapped as an executable needs to be before it's able to run. Here's the start of it, after renaming and adjusting types for the arguments to match what was placed in the registers preceding the call. In another common practice with shellcode, API hashes are present. [HashDB identifies the algorithm employed here as one used by Metasploit.](https://hashdb.openanalysis.net/) If one decides to look into the mw_import_by_hash function, it's important to remember that this code deals with PEB64 and TEB64, structs that I [couldn't find in Ida. I recommend this resource from BITE* to create your](http://bytepointer.com/resources/tebpeb64.htm) own struct for both. Doing this will solve you a couple hours of confused cursing at the 32 bit structures. Next up the actual mapping of the DLL into memory takes place. This is made evident by several snippets of code that parse PE Section Headers relevant to the mapping process. The one below checks to see if the PE ----- being processed is indeed for a 64-bit architecture; other lines deal with the PE sections and the entry point address: A bit further down, memory is allocated to match the size of the DLL (according to the value in ``` IMAGE_NT_HEADERS64.OptionalHeader.SizeOfImage): ``` A very interesting sequence follows. It's responsible for resolving all imports of the third stage DLL by using LoadLibraryA and ``` GetProcAddress. Taking note of which fields of the PE are being parsed ``` and watching a few loops of it running will help you grasp how an import table is built when an executable is mapped. ----- A lot more code follows this, mapping sections and using VirtualProtect to assign the correct protections to each one. We're almost done now! There's then a call rbx instruction that leads to a rabbit hole of shellcode functions. Unfortunately what follows next is something no one likes to read in an analysis like this, but I have no idea what those do. My educated guess is some combination of anti-emulation/anti-sandbox, since there are multiple uses of the cpuid instruction in there and a test following that call will skip the jump to the next stage and instead just return. If anyone is curious, feel free to give it a look. After the return from the mysterious shellcode rabbit hole, we have only a few steps left. The code ensures it has mapped the DLL correctly by checking the size of its Data Directory and the exported functions (there is ----- only one, DllGetClassObject); it then maps the address of said name to ``` r8. Then the name of the export itself is checked by a simple ROR 13 ADD ``` hash function, another callback to metasploit: Finally, the arguments (remember those from ages ago??) are put back into the relevant registers and there is a jump to r8, which now holds the address of exported function of the third stage DLL. Its command line arguments are the User-Agent string from earlier and the constant 0xAA (thanks to the [Sentinel One Report for pointing out that this constant is the](https://www.sentinelone.com/blog/smoothoperator-ongoing-campaign-trojanizes-3cx-software-in-software-supply-chain-attack/) size of the User-Agent string). ## Important time-saving tip: It's only as I wrap up this write-up that I realized there is no decryption of the third stage DLL done by the shellcode, only mapping and maybe some anti-emulation shenanigans. As such, one can really speed up their analysis by extracting the full stage 2 payload and getting rid of everything before the MZ header of the third stage DLL. -----