From Italy With Love?
› finding hackingteam code in russian malware
2/17/2017
Background
On Valentine's Day, BitDefender released a short writeup title, "New Xagent Mac Malware Linked with the APT28". In the writeup, they discussed a new piece of Mac malware, (XagentOSX/Komplex.B) associated with APT28 (aka the 'Russians'). They did not provide much technical detail, but did state, "this modular backdoor with advanced cyber-espionage capabilities is most likely planted on the system via the Komplex downloader."
I'd previously analyzed the Komplex downloader (Komplex.A), and discussed it in my recent RSA talk:
It's a basic 'first-stage' implant, that as the BitDefender report notes, may (most often) be used to download more complex 'second-stage' implant (i.e XagentOSX/Komplex.B) on targets of interest.
Regardless, a claim of a new "advanced cyber-espionage" piece of Mac malware? Count me in - sounds exciting!
Although the BitDefender report didn't provide any hashes of the malware, digging around, I found the sample I believed matched. With a SHA256 hash of 2a854997a44f4ba7e307d408ea2d9c1d84dde035c5dab830689aa45c5b5746ea, this sample was submitted to VirusTotal on February 8th. At that time, it was only detected by Kaspersky (as the wise @noar pointed out, likely their heuristic signatures flagged it - nice!).
As of today, looking the VirusTotal report, we can see it's now flagged by 25 anti-virus companies.
Considering it was Valentine's day, I posted what I believed was the XagentOSX/Komplex.B sample to share the love:
Later in the day, PaloAlto Networks Unit42 (perhaps the original group that discovered the malware) posted a more technically comprehensive analysis of the malware titled, "XAgentOSX: Sofacy's XAgent macOS Tool". Their research contains a hash (matching the sample I posted) and delves into the malware's command and control (C2) communications, commands, and infrastructure. It's a great read.
However, triaging the binary, I noticed one part of the malware hadn't been discussed either by BitDefender nor PaloAltoNetwork: code injection. Hooray, something for me to dig into :)
Code Injection
One of the first things I always do with OS X malware sample is run it thru classdump. This utility, as its name implies, will extract (i.e. dump) all class information from an Objective-C binary. Due to the way the Objective-C runtime works, such binaries must retain both class and method names. This information can be highly informative and often makes reversing such binaries fairly simple:
The classdump output reveals an interesting class named 'InjectApp':
__attribute__((visibility("hidden")))
@interface InjectApp : NSObject
{
}
- (void)injectRunningApp;
- (void)sendEventToPid:(id)arg1;
- (BOOL)isInjectable:(id)arg1;
- (id)init;
Now, code injection is an excellent feature to add to one's malware creation (think stealth, security tool bypasses, etc etc). However, as an 'advanced feature', it's very rare in (known) Mac malware. As such, it was something I wanted to take a closer look at. As there are a variety of ways to perform code injection on OS X/macOS, my main goal was to see how XAgentOSX/Komplex.B implemented it.
Reversing the malware, we can see an instance of the InjectApp class is created and utilzied from the -[BootXLoader injectApplication] method:
void -[BootXLoader injectApplication](void * self, void * _cmd)
{
rbx = [[InjectApp alloc] init];
[rbx injectRunningApp];
rdi = rbx;
[rdi release];
return;
}
Unfortunately, it appears that no other code invokes the -[BootXLoader injectApplication] method:
In other words, it appears that this injection code, though present, is not utilized by the malware. Still, I wanted to dig into its internals.
When analyzing a piece of malware, I find that static and dynamic analysis, performed in parallel, are the best way to fully understand a piece of malware. Unfortunately since the malware doesn't actually invoke the injection code (i.e. nobody invokes -[BootXLoader injectApplication] method), we can't just set a debugger breakpoint and wait until it's hit. No worries! Using the debugger we can coerce the malware to call it :) Specifically we can simply change the instruction pointer, RIP, to point to the start of the -[BootXLoader injectApplication] method.
The -[BootXLoader injectApplication] method's address is at 0000000100014D8F in the binary:
We can confirm, that when loaded in memory (i.e. in a debugger) that method is still present at that address (it hasn't been ASLR'd, etc):
$ lldb XAgentOSX
(lldb) target create "Xagent"
Current executable set to 'Xagent' (x86_64).
(lldb) x/10i 0000000100014D8F
0x100014d8f: 55 pushq %rbp
0x100014d90: 48 89 e5 movq %rsp, %rbp
0x100014d93: 41 56 pushq %r14
0x100014d95: 53 pushq %rbx
0x100014d96: 48 8b 3d f3 04 02 00 movq 0x204f3(%rip), %rdi ; (void *)0x0000000100035ac0: InjectApp
0x100014d9d: 48 8b 35 2c f5 01 00 movq 0x1f52c(%rip), %rsi ; "alloc"
0x100014da4: 4c 8b 35 8d 63 01 00 movq 0x1638d(%rip), %r14 ; (void *)0x00007fffe5346b40: objc_msgSend
0x100014dab: 41 ff d6 callq *%r14
0x100014dae: 48 8b 35 23 f5 01 00 movq 0x1f523(%rip), %rsi ; "init"
0x100014db5: 48 89 c7 movq %rax, %rdi
...
To change the instruction pointer to point the this address, one can use the registry write command:
(lldb) register write $rip 0x0000000100014D8F
Normally, changing the instruction pointer to another address, for example the start of a method, causes all sorts of issues. For example stack and register values won't be what the method is expecting (on x86_64 parameters are passed via registers). Luckily the -[BootXLoader injectApplication] method doesn't take any parameters and is nicely self-contained. That is to say, it both initializes then invokes methods of the InjectApp class. Thus, changing RIP just kind of works in this instance :)
After creating an instance of the InjectApp class, the -[BootXLoader injectApplication] method invokes it's injectRunningApp method:
void -[BootXLoader injectApplication](void * self, void * _cmd)
{
rbx = [[InjectApp alloc] init];
[rbx injectRunningApp];
rdi = rbx;
[rdi release];
return;
}
Setting a breakpoint on the InjectApp's injectRunningApp method, we can hit continue ('c') in the debugger and it will break at there. Neat!
(lldb) br s -a 0x000000010000fdff
Breakpoint 2: where = Xagent`-[InjectApp injectRunningApp], address = 0x000000010000fdff
(lldb) c
Process 1045 resuming
Process 1045 stopped
* thread #1: Xagent`-[InjectApp injectRunningApp], stop reason = breakpoint 2.1
-> 0x10000fdff <+0>: pushq %rbp
0x10000fe00 <+1>: movq %rsp, %rbp
0x10000fe03 <+4>: pushq %r15
0x10000fe05 <+6>: pushq %r14
The injectRunningApp method does three things:
- get a list of running apps
- calls 'isInjectable:' on each application
- calls 'sendEventToPid:' on each injectable application
First, injectRunningApp invokes NSWorkspace's 'runningApplications'
method to get a list of running apps. This method returns a list of running apps that we can dump, after stepping over the call in the debugger:
(lldb) po $rax
<__NSArrayI 0x100206d50>(
<NSRunningApplication: 0x100205c00 (com.apple.loginwindow - 101)>,
<NSRunningApplication: 0x100205e50 (com.apple.systemuiserver - 699)>,
<NSRunningApplication: 0x100205f50 (com.apple.dock - 698)>,
<NSRunningApplication: 0x100206050 (com.apple.Spotlight - 704)>,
<NSRunningApplication: 0x100206150 (com.apple.finder - 700)>,
<NSRunningApplication: 0x100206250 (com.apple.dock.extra - 739)>,
<NSRunningApplication: 0x100206450 (com.apple.AirPlayUIAgent - 756)>,
<NSRunningApplication: 0x100206550 (com.apple.Siri - 749)>,
<NSRunningApplication: 0x100206650 (com.apple.notificationcenterui - 747)>,
<NSRunningApplication: 0x100206850 (com.apple.storeuid - 771)>,
<NSRunningApplication: 0x100206950 (com.apple.nbagent - 758)>,
<NSRunningApplication: 0x100206a50 (com.apple.lateragent - 797)>,
<NSRunningApplication: 0x100206b50 (com.apple.Terminal - 954)>,
<NSRunningApplication: 0x100206c50 (com.apple.AppleSpell - 967)>
)
Then, for each application, injectRunningApp gets the application's localized name, then invokes the 'isInjectable' method on it. The isInjectable: method is part of the same InjectApp class. From the previous classdump output, we know it takes a single argument and returns a BOOL (YES/NO):
- (BOOL)isInjectable:(id)arg1;
In the debugger, we can step into this method and then dump the passed in parameter (which will be in RDX):
(lldb) po $rdx
loginwindow
(lldb) po [$rdx class]
__NSCFConstantString
Ok, so easy to see that isInjectable: method takes a NSString (__NSCFConstantString) as its only parameter, and that this string is the name of the application to check for 'injectability'
In order to check if an application is injectable, the isInjectable: method simply checks the name of of the application that was passed in to the method, against a list of hardcoded application names:
The list of 'blacklisted' apps is named 'appBListArray' (more on this name later!) and contains the names of a variety of
'system' applications that presumably should not be injected into:
__data:00000001000369C0 _appBListArray
dq offset cfstr_Mdworker ; "mdworker"
dq offset cfstr_Systemuiserver ; "SystemUIServer"
dq offset cfstr_Dock ; "Dock"
dq offset cfstr_Launchd ; "launchd"
dq offset cfstr_Loginwindow ; "loginwindow"
dq offset cfstr_Usereventagent ; "UserEventAgent"
If the application name that was passed to the isInjectable: method matches any of the system, 'blacklisted' applications, the method will return 'NO' (0x0, false) to indicate that the application should not be injected into.
Now we know how the malware determines if an application is injectable or not. Back in the injectRunningApp method, if the isInjectable: method returns 'YES' (0x1, true) another method, sendEventToPid: is invoked:
if (!COND) {
rbx = (r14, @selector(processIdentifier), rdx);
rbx = ((@class(NSNumber), @selector(alloc), rdx), @selector(initWithInt:), rbx);
(r15, @selector(sendEventToPid:), rbx);
usleep(0x1f4);
[rbx release];
}
From the above decompilation, we can see that the code retrieves the process identifier (PID) of the 'injectable' application and passes it to the sendEventToPid: method.
For example, the first application in my VM, that the code determines is injectable (i.e. is not in the blacklist) is SpotLight. We can dump various registers to confirm this:
(lldb) po $rdi
<NSRunningApplication: 0x100206050 (com.apple.Spotlight - 704)>
(lldb) po $rbx
704
The sendEventToPid: method is where things get interesting. Or so I thought!
Reversing the method shows it using the SBApplication class to send various AppleEvent messages to the target process:
r15 = [SBApplication applicationWithProcessIdentifier:rdx];
(r15, @selector(setTimeout:), 0x1);
(r15, @selector(setSendMode:), 0x1011);
rax = (r15, @selector(sendEvent:id:parameters:), 0x61736372, 0x67647574, 0x0);
r12 = (r13)(@class(NSNumber), @selector(numberWithInt:), getpid());
(r15, @selector(setTimeout:), 0x1);
(r15, @selector(setSendMode:), 0x1011);
(r15, @selector(sendEvent:id:parameters:), 0x4f504e65, 0x6f70656e, 0x7069646f, r12, 0x0);
0x1011 (in the setSendMode: call) maps to kAENoReply|kAENeverInteract|kAEDontRecord, while the parameters to the sendEvent:id:parameters: method call can be converted to ASCII:
0x61736372 -> 'ascr'
0x67647574 -> 'gdut'
0x4f504e65 -> 'OPNe'
0x6f70656e -> 'open'
0x7069646f -> 'pido'
In turn, several of these values map to Apple constants:
#define kASAppleScriptSuite 'ascr'
#define kGetAEUT 'gdut'
Ok, so the malware is using AppleScript to inject code into a remote process. But wait a minute, we've seen this before? Where?? HackingTeam!
Copy and Pasta
In 2015, I wrote a blog titled "Building HackingTeam's OS X Implant For Fun & Profit". In that blog I detailed how to build Hackteam's RCS mac implant (while was leaked as part of a massive hack). An interesting feature of their code was their application injection logic which used AppleScript.
In fact if we google the constants extracted from the injection code that we've been analyzing, there is only one hit on Google; HackingTeam's leaked source code:
Note that before the leaks, the venerable @osxreverser analyzed HackingTeam's Mac capabilities in a talk at ShakaCon 6 titled: "F**k You Hacking Team" and discussed this injection technique:
Since @osxreverser previously covered this injection technique and source code (now) exists for it (see RCSMCore.m), we'll end our binary analysis of the injection technique here. However, we're faced with an interesting question. Does the XagentOSX/Komplex.B malware simply make use of the same injection technique? or does it use the exact same code? As we'll show, I'm 100% confident it's the latter.
So why do I think XagentOSX/Komplex.B used the HackingTeam code versus simple re-used the same technique? Well first it's alway easier (lazier?) to cut and paste from existing code than re-implement from scratch. And since the Russia supposedly bought stuff from HackingTeam or had access (like everybody else) to the leaked source code online - this seems the logical route.
We can make a much more compelling argument, that to me, proves without a doubt that it's the same code, by comparing the compiled code in the XagentOSX/Komplex.B with the leaked HackingTeam source code.
First injection-related method names and parameters count precisely match. HackingTeam's source code (specifically RCSMCore.m) contains the following methods:
- (BOOL)isInjectable:(NSString*)appName;
- (void)sendEventToPid:(NSNumber *)thePid;
- (void)injectRunningApp;
It's easy to see that XagentOSX/Komplex.B contains the same methods:
Also global variable names and their values match. For example, the 'appBListArray' that was discussed earlier. Below is the array in HackingTeam's code, followed by a dump of the array from the disassembly the malware. Note that the variable name ('appBListArray') and its values are the same in both:
We can also look at HackingTeam's source code and see that the compiled code in the malicious binary, matches almost verbatim. First, the Objective-C code of HackingTeam's injectRunningApp method:
Now here is the decompiled injectRunningApp method from XagentOSX/Komplex.B. Notice the same logic flow, API calls, etc:
Finally we can also see that 'mistakes' in HackingTeam's code made it directly into XagentOSX/Komplex.B. This, IMHO, is a very strong indicator that HackingTeam's injection code was directly used (versus the malware author's reimplementing the logic).
The following is from HackingTeam's code:
This code, from the isInjectable method, is broken! How? Well if appName is nil, then and only then, will the second part of the if statement (![appName isKindOfClass: [NSString class]]) be executed. However, if appName is nil, executing that code makes no sense. Sending the isKindOfClass message to a nil object, will always just return nil.
As this code is at the start of the method, it appears to be an attempt at a sanity check on the method's parameter. The check should be checking for a nil parameter, or for a parameter that is not an NSString. Which it would, if an || was used instead of an &&:
if (appName == nil ||
![appName isKindOfClass: [NSString class]])
Why is this relevant? Well because this 'bug' from HackingTeam's code ended up in the malware. Let's look at a decompilation of the malware's isInjectable method:
char -[InjectApp isInjectable:](void * self, void * _cmd, void * arg1)
{
r12 = [arg1 retain];
if (r12 != 0x0) goto continue;
;this will only be executed if r12 is nil, wtf!?
if ([r12 isKindOfClass:[NSString class]] == 0x0) goto leave;
continue:
;do injection
It's actually 'easier' to see this bug in the malware's decompiled code (vs. the HackingTeam source code).
The register, $r12 contains the parameter, appName. First it's checked to make sure it's not 0x0 (nil). If it's not nil, the code jumps logic that continues. Otherwise, if the parameter appName is nil, the code will execute the second half of the if statement (![appName isKindOfClass: [NSString class]])). Again, I don't see any reason why one would call isKindOfClass on a nil object.
At this point it should be pretty clear that the injection code in the XAgent/Komplex.B malware is HackingTeam's original code, most likely copied and pasted. However, if we continue our analysis, we can see that certain parts of HackingTeam's code (i.e. code not related to the app injection) didn't make it into the malware's binary. In other words, it was a selected copy and paste. How can we tell? Let's take a closer look.
Take for example, the following disassembled code from the malware's -(void)sendEventToPid:(NSNumber *)thePid method:
A few odd things; first there is this:
[*_gControlFlagLock lock];
[*_gControlFlagLock unlock];
A lock of a global variable (gControlFlagLock), immediately followed by an unlock seems pointless. Normally one locks a variable, then executes some critical section of code, then unlocks the variable.
Morever the _gControlFlagLock variable only is referenced in the lock/unlock calls and nowhere else in the malware's binary. This means it is never initialized, and thus will be be nil.
Following this pointless locking (which wouldn't do anything anyways, as _gControlFlagLock is always nil), there is a string comparison that makes no sense:
rdx = @"STOP";
if ([@"START" isEqualToString:rdx] == 0x0) {
"STOP" will never equal "START" so again this check pointless, leaving one wondering why it's even in the binary.
Both of these 'issues' be explained by a 'bad' copy and paste job, where some unneeded code was deleted, while other code was still left in (though it could have been removed, as it was now spurious). Looking at HackingTeam's code we see:
The taskManager variable is of type __m_MTaskManager. This appears to be a class that, as its name implies, deals with delegating tasks and/or otherwise controlling various components of the entire HackingTeam implant. Obviously if one is looking just to rip out (off?) the injection part of the code, the 'Task Manager' code/logic isn't needed and thus would be removed.
The removal of 'Task Manager' code/logic explains both why the code in the compiled XAgent/Komplex.B malware had a call to [gControlFlagLock lock] immediately followed by a call to [gControlFlagLock unlock], as well as comparison between two hard-code strings (that would never match).
Conclusion
Sometimes reversing sessions take interesting twists and turns. Here, I started by looking that the 'app injection' logic in XAgent/Komplex.B with the goal of uncovering the injection mechanism. However, during this investigation, we discovered that the malware was clearly utilizing code from HackingTeam's leaked Mac implant. By comparing various pieces of HackingTeam's source code with code compiled into the binary, hopefully it's quite obvious that the XAgent/Komplex.B authors used the HackingTeam's code directly - versus reimplementing it themselves.
If I had to guess, I'd say that even though Russia supposedly bought stuff from HackingTeam (which could have included source code?), the malware authors likely just copied and pasted from the HackingTeam leaked source code. Though we'll likely never know all the answers, its surprising how much one can ascertain by simple reversing a binary!