By Mike Dvorkin and James Haughom
Introduction
“Potentially Unwanted Program”, or PUP, is the ambiguous classification given to a wide variety of software that presents some malicious characteristics but falls short of justifying being categorized as outright malicious. Such applications may technically be installed with the user’s consent, but they are typically misleading regarding their true functionality and bundle additional software that the user has not been made aware of. Their developers often use them as vessels to sneak intrusive advertisements, browsing trackers, and cryptocurrency miners onto the user’s device, while using a range of persistence methods to make their removal difficult. While a common sight across all customer environments, SentinelOne’s Vigilance MDR service has recently encountered a particularly sinister wave of activity that crosses the line from potentially unwanted to unquestionably malicious.
Test Case
Our example case began late this past November, when Vigilance identified an application signed by a known adware publisher that was seen masquerading as WeChat and installing itself on an endpoint. In the process, it triggered several indicators of the agent’s behavioral engine, including indicators of code injection, anti-virtualization techniques, and activity resembling that of a penetration testing framework. This caused all processes associated with the threat to be automatically killed and quarantined.
Vigilance analysts took remediation actions in order to reverse the threat group’s effects on the endpoint, such as dropped files, registry edits, and scheduled tasks. An in-depth investigation was launched in parallel to communicating additional mitigation steps to the customer, such as blocking certain domains, addresses, and file hashes, as well as expiring the end user’s credentials.
Looking more closely into this incident, we see that the process named 微信@1547_8256.exe executed and spawned 微信.exe. Intezer’s analysis of this sample associates it with the DealPly adware family, while some other vendors identify it as Qjwmonkey. SentinelOne’s integrated EDR solution, Deep Visibility, was used to determine that the parent process originated in a binary that was downloaded from soft.jiegeng[.]com using Internet Explorer, several minutes before this threat emerged. On execution, it communicated with a long list of suspect remote addresses which VirusTotal associates with malicious communicating files.
It has also attempted to add a root signing certificate and downloaded various files onto the endpoint, among them the binary of its child process, which it placed on the desktop.
Its most salient behavior, though, is the in-memory activity that caused various code injection indicators to be triggered by the agent. What happens inside the machine’s memory to make these indicators fire during the program’s execution? Setting the behavioral engine’s internal algorithm aside, we will demonstrate an investigation guided only by publicly-available information, intending to shed more light on this sample using static analysis.
Analysis
Looking up the sample of the parent process on VirusTotal, we see that it was first submitted for analysis the same day it was first detected and mitigated by SentinelOne. Several open source Yara rules, one of them referencing Meterpreter, are listed as triggered for this binary.
These rules/signatures can be viewed by clicking “View Ruleset”. We’ll look into the ReflectiveLoader rule first to understand why it fired.
Yara Rule: ReflectiveLoader
This rule’s description gives us some background – “Detects an unspecified hack tool, crack or malware using a reflective loader – no hard match – further investigation recommended”. In Yara, the logic resides in the strings and condition sections. The strings section consists of strings that can then be used as conditions for the rule to fire. The condition section will chain together different conditions and use logical operators to decide if the rule should fire or not.
In this rule there are three strings – all variants of the “ReflectiveLoader” string. The condition states that the file must begin with the two bytes (uint16(0) means 2 bytes at offset 0) 0x5a4d or ‘MZ’, the signature of a Windows PE. Following this requirement is an and operator, which requires that any of the strings from the strings section is found in the file or that the export section of the PE contains one of the following: “ReflectiveLoader”, “_ReflectiveLoader@4”, “?ReflectiveLoader@@YGKPAX@Z”.
We can test this rule by copying the rule logic and running it locally against one of the samples we observed from a different customer. If we pass the -s
switch, Yara will provide the string(s) that caused the rule to fire, as well as its offset. We can see below that the rule triggered due to the generic “ReflectiveLoader” string that maps to the $s1 variable.
% yara code/yara/demo.yar UninstallFxVSUpd ate@81_463743.exe -s ReflectiveLoader UninstallFxVSUpdate@81_463743.exe 0x162d70:$s1: ReflectiveLoader
Yara Rule: IMPLANT_4_v7
We’ll look into the IMPLANT_4_v7 yara detection in a similar manner.
We can see that 3 of the 10 hexadecimal strings described in the rule are matched, in addition to the condition that the file begins with a PE header.
IMPLANT_4_v7 Downloads/375da28284aab4fd20303c895caf56373f12faa4932e93f0fe39062088e92246 0xe2462:$sb3: C7 45 EC 61 64 76 61 C7 45 F0 70 69 33 32 C7 45 F4 2E 64 6C 6C 0xe4368:$sb3: C7 45 EC 61 64 76 61 C7 45 F0 70 69 33 32 C7 45 F4 2E 64 6C 6C 0xfca18:$sb3: C7 45 EC 61 64 76 61 C7 45 F0 70 69 33 32 C7 45 F4 2E 64 6C 6C 0xfcc31:$sb3: C7 45 EC 61 64 76 61 C7 45 F0 70 69 33 32 C7 45 F4 2E 64 6C 6C 0x7dfa6:$sb7: C7 45 D8 6E 65 74 61 C7 45 DC 70 69 33 32 C7 45 E0 2E 64 6C 6C 0x5a57f:$sb8: C7 45 E4 76 65 72 73 C7 45 E8 69 6F 6E 2E C7 45 EC 64 6C 6C
We can investigate how these strings are used in the sample by loading it into IDA (or any other disassembler). These strings are machine code, and when searching for the detections’ offset inside the file we will see the reconstructed assembly code. These instructions access the names of DLLs and functions that the sample requires and dynamically load them via the Windows API.
Yara Rule: HKTL_Meterpreter_inMemory
We will repeat this process for the HKTL_Meterpreter_inMemory rule. The strings section in this rule also features a hexadecimal string, which lies within curly braces. The description mentions in-memory Meterpreter, but we will apply this rule to a PE on disk.
Running the rule on the sample, we can see that it triggered due to the strings WS2_32.dll
and ReflectiveLoader
– and not the hexadecimal string.
% yara code/yara/demo.yar UninstallFxVSUpdate@81_463743.exe -s ReflectiveLoader UninstallFxVSUpdate@81_463743.exe 0x162d70:$s1: ReflectiveLoader HKTL_Meterpreter_inMemory UninstallFxVSUpdate@81_463743.exe 0x177532:$s1: WS2_32.dll 0x162d70:$s2: ReflectiveLoader
In the corresponding code section, the “ReflectiveLoader” string is being passed to _strstr
, which locates a substring within a string.
This particular code block iterates through the exports section of the PE to find the ReflectiveLoader function. The decompiled pseudo-C code of this section can be searched online to see if it might be copied from an open source framework.
while ( 1 ) { --v7; v8 = sub_4576D0(*v5, (int)this); if ( strstr(&this[v8], "ReflectiveLoader") ) break; v12 += 2; ++v5; if ( !v7 ) return 0; }
It appears that this section originated from github.com/rokups/ReflectiveLdr/blob/master/ReflectiveLdr.cpp.
We can repeat this process for the WS2_32.dll
string. After examining the disassembled and decompiled pseudo-code, it appears to be copied from the libcurl project – which agrees with Intezer’s analysis of code reuse. This can be used to fetch a malicious DLL from a remote address.
v6 = (HMODULE)sub_4E1820("WS2_32.DLL"); hLibModule = v6; if ( !v6 ) { v7 = GetLastError(); sub_4D5240(v3, "failed to load WS2_32.DLL (%d)", v7); return 2; } WSACreateEvent = (HANDLE (__stdcall *)())GetProcAddress(v6, "WSACreateEvent"); if ( !WSACreateEvent ) { v21 = GetLastError(); sub_4D5240(v3, "failed to find WSACreateEvent function (%d)", v21); LABEL_8: FreeLibrary(hLibModule); return 2; } WSACloseEvent = (BOOL (__stdcall *)(HANDLE))GetProcAddress(hLibModule, "WSACloseEvent"); if ( !WSACloseEvent ) { v22 = GetLastError(); sub_4D5240(v3, "failed to find WSACloseEvent function (%d)", v22); goto LABEL_8; }
Payload Decryption and Injection
Digging into other prominent parts of the code disassembly, we encounter an interesting sequence of API calls: VirtualAllocEx
, WriteProcessMemory
, and CreateRemoteThread
. This appears to be the process performing code injection.
Disassembling the surrounding code blocks provides additional context, as we see an XOR decoding routine followed by the resulting payload being injected and executed.
// decrypt payload if ( !a2 || !a3 ) return 0; v3 = 0; if ( a3 >= 0x40 ) { do { *(__m128i *)&a2[v3] = _mm_xor_si128(*(__m128i *)&a2[v3], (__m128i)xmmword_565A30); *(__m128i *)&a2[v3 + 16] = _mm_xor_si128(*(__m128i *)&a2[v3 + 16], (__m128i)xmmword_565A30); *(__m128i *)&a2[v3 + 32] = _mm_xor_si128((__m128i)xmmword_565A30, *(__m128i *)&a2[v3 + 32]); *(__m128i *)&a2[v3 + 48] = _mm_xor_si128(*(__m128i *)&a2[v3 + 48], (__m128i)xmmword_565A30); v3 += 64; } while ( v3 < (a3 & 0xFFFFFFC0) ); } for ( ; v3 < a3; ++v3 ) a2[v3] ^= 0x50u; ---SNIP--- // inject payload v10 = GetCurrentProcess(); if ( OpenProcessToken(v10, 0x28u, &TokenHandle) ) { Luid[0].LowPart = 1; Luid[1].HighPart = 2; if ( LookupPrivilegeValueW(0, L"SeDebugPrivilege[MD3] ", (PLUID)&Luid[0].HighPart) ) AdjustTokenPrivileges(TokenHandle, 0, (PTOKEN_PRIVILEGES)Luid, 0, 0, 0); CloseHandle(TokenHandle); } pidSelf = GetCurrentProcessId(); hProc = OpenProcess(0x43Au, 0, pidSelf); *(_DWORD *)(this + 8) = hProc; if ( !hProc ) return 0; v14 = inject_code(hProc, *(LPCVOID *)(this + 4), size, v13); // seen in screenshot if ( !v14 ) ( . . . )
Conclusion
Using publicly available detection rules as our guide in investigating a wave of incidents handled by our team, we found evidence of a threat actor that implicitly incorporates open source projects and utilizes reflective DLL injection to load a locally decoded payload into memory, striving to evade AV vendors that chiefly rely on static detection. This is one example of adware authors becoming more brazen and borrowing techniques that were once reserved for penetration testing frameworks and advanced persistent threats. Left unmitigated, what at first glance appears as an annoying piece of adware downloaded from disreputable sources can leave an endpoint backdoored and a network breached.
This case not only demonstrates the importance of on-execution, in-memory mitigation of threats that purposefully minimize their file activity, but also how indispensable human analysis remains in the face of incidents that may appear ambiguous, questionable, and only potentially unwanted.
Sample SHA1: 3be080714f7fab92001b055d915805aa95fad66c