Analyzing the Anti-Analysis Logic of an Adware Installer
Recently, SANS posted a short blog post titled "Fake Adobe Flash Update OS X Malware". While the blog covers the initial infection vector, and subsequent articles provide a decent overview of the attack, here, let's briefly discuss some of the anti-analysis techniques utilized by the malware installer.
Note: if you want to follow along at home, download the malware installer (password: infect3d).
In the SANS post, the author (Johannes Ullrich), described a Facebook click-bait scheme that led naive users to a "fake Flash update":
If an unsuspecting user is tricked into installing the 'update' they will unfortunately become infected with OS X adware.
While this social-engineering approach is the most common OS X infection strategy, the fact that the binary installer is signed is somewhat interesting. SANS noted this, specifically stating, "The installer is signed with a valid Apple developer certificate issued to a Maksim Noskov."
$ codesign -dvvv /Volumes/Installer/Installer.app
Format=bundle with Mach-O thin (x86_64)
CodeDirectory v=20200 size=4065 flags=0x0(none) hashes=196+3 location=embedded
Hash type=sha1 size=20
Authority=Developer ID Application: Maksim Noskov (SHPB74W374)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Jan 20, 2016, 5:41:11 AM
Sealed Resources version=2 rules=12 files=50
Internal requirements count=1 size=184
Executing the (signed) installer, launches a GUI installer application. And if the user completes the installation, malware and (interestingly) a legitimate copy of adobe flash will be installed on their system.
Malware Installer; InstallCore
As previously mentioned, the malware installer is signed by Maksim Noskov.
Though signing malware affords a simple Gatekeeper 'bypass', it may provide attribution and also presents a simple way to stop the malware in its tracks. How? Apple can (and did) easily revoke the signing certificate, thus preventing the malware from ever executing. This is illustrated in the following image where as of February 7th, OS X blocks the malware with CSSMERR_TP_CERT_REVOKED (-2147409652):
Ok, back to Maksim Noskov. Hopping on VirusTotal, we find about 1000 files signed with this Apple Developer ID:
Several (if not all) appear to be the slight variants of the malware installer; identified as InstallCore
InstallCore is a known component of OS X adware that's been discussed before. Here however, lets briefly discuss some of its anti-analysis features.
One of the main questions an analyst often asks when analyzing a malicious installer or downloader is what the installer persistently installs and from where. Capturing some network traffic shows the malware installer connecting to various HTTP servers (presumably to download other malicious components), as well as downloading a legitimate adobe flash installer:
# tcpdump -s0 -A host 192.168.0.7 and port 80
GET /adobe_flashplayer_e2c7b.dmg HTTP/1.1
Accept-Encoding: gzip, deflate
User-Agent: Installer/1 CFNetwork/720.3.13 Darwin/14.3.0 (x86_64)
However, running 'strings' on the installer binary does not reveal these urls (e.g. appsstatic2fd4se5em.s3.amazonaws.com):
$ strings -a /Volumes/Installer/Installer.app/Contents/MacOS/tirocinium | grep appsstatic2fd4se5em.s3.amazonaws.com | wc
0 0 0
Clearly some string obfuscation is being performed. In reality, this isn't too surprising; most malware (especially on Windows platforms) will obfuscate sensitive strings.
Loading the malware installer's binary (tirocinium), into a disassembler such as IDA, makes it easy to spot string obfuscations (a simple add loop):
mov [rbp+var_8], 7Ch
mov [rbp+var_7], 80h
mov [rbp+var_6], 7Eh
mov [rbp+var_5], 6Dh
mov [rbp+var_4], 6Fh
mov [rbp+var_3], 71h
mov [rbp+var_2], 0Ch
mov [rbp+var_8], 70h
mov eax, 1
add [rbp+rax+var_8], 0F4h
cmp rax, 6
jnz short loop
mov [rbp+var_2], 0
Though one could write an IDAPython script to decode such strings, I'm lazy and would rather let the malware simply deobfuscate the strings itself, under the watchful eye of a debugger. However, before jumping into a dynamic debugger, let's note another 'anti-analysis' feature; junk code.
Many of the malware installer's methods contain copious amounts of spurious NOPs function calls that make up what is known as 'junk code'.
As the following images illustrate, there are many many code blocks and calls in the disassembly to functions such as lround, NSUserName, rint, sysconf, and more. All these calls are 'useless' - they're simply present to change the signature (hash) of identical samples and/or perhaps thwart simple AV emulators and/or hinder analysis. However, in terms of manual analysis, while somewhat annoying, in reality they don't stop us.
As previously mentioned, many of the malware's more interesting strings are obfuscated. It was surmised that the easiest way to uncover such strings was to simply let the malware run under a debugger, allowing it to un-obfuscate the strings for us. Unfortunately, attempting to debug the malware initially failed:
$ lldb /Volumes/Installer/Installer.app
(lldb) target create "/Volumes/Installer/Installer.app"
Current executable set to '/Volumes/Installer/Installer.app' (x86_64).
Process 1530 launched: '/Volumes/Installer/Installer.app/Contents/MacOS/tirocinium'
Process 1530 exited with status = 45 (0x0000002d)
I suspected some anti-debugging logic in the malware. While such anti-debugging in Windows malware is very common, on OS X malware, it is somewhat more rare.
Skimming the disassembly, nothing appears immediately obvious that would indicate debugger detection. However, as large portions of the malware (such as strings) were obfuscated, this isn't too surprising.
Starting a debug session and setting a breakpoint on the malware installer's initial code logic (-[ICAppController applicationDidFinishLaunching:]) did work. This indicated some code was (later) manually detecting the debugger and then terminating itself. Stepping over chunks of code quickly identified such a function (sub_100035C5B):
__text:0000000100035C5B sub_100035C5B proc near
__text:0000000100035C5B var_C = dword ptr -0Ch
__text:0000000100035C5B var_8 = byte ptr -8
__text:0000000100035C5B var_7 = byte ptr -7
__text:0000000100035C5B var_6 = byte ptr -6
__text:0000000100035C5B var_5 = byte ptr -5
__text:0000000100035C5B var_4 = byte ptr -4
__text:0000000100035C5B var_3 = byte ptr -3
__text:0000000100035C5B var_2 = byte ptr -2
__text:0000000100035C5B push rbp
__text:0000000100035C5C mov rbp, rsp
__text:0000000100035C5F sub rsp, 10h
__text:0000000100035C63 mov [rbp+var_8], 7Ch
__text:0000000100035C67 mov [rbp+var_7], 80h
__text:0000000100035C6B mov [rbp+var_6], 7Eh
__text:0000000100035C6F mov [rbp+var_5], 6Dh
__text:0000000100035C73 mov [rbp+var_4], 6Fh
__text:0000000100035C77 mov [rbp+var_3], 71h
__text:0000000100035C7B mov [rbp+var_2], 0Ch
__text:0000000100035C7F mov [rbp+var_8], 70h
__text:0000000100035C83 mov eax, 1
__text:0000000100035C88 add [rbp+rax+var_8], 0F4h
__text:0000000100035C8D inc rax
__text:0000000100035C90 cmp rax, 6
__text:0000000100035C94 jnz short loop
__text:0000000100035C96 mov [rbp+var_2], 0
__text:0000000100035C9A lea rsi, [rbp+var_8] ; symbol
__text:0000000100035C9E mov rdi, 0FFFFFFFFFFFFFFFFh ; handle
__text:0000000100035CA5 call _dlsym
__text:0000000100035CAA mov [rbp+var_C], 28h
__text:0000000100035CB1 add [rbp+var_C], 0FFFFFFF7h
__text:0000000100035CB5 mov edi, [rbp+var_C]
__text:0000000100035CB8 xor esi, esi
__text:0000000100035CBA xor edx, edx
__text:0000000100035CBC xor ecx, ecx
__text:0000000100035CBE call rax
__text:0000000100035CC0 add rsp, 10h
__text:0000000100035CC4 pop rbp
__text:0000000100035CC5 sub_100035C5B endp
sub_100035C5B starts by de-obfuscating some string (see the loop at 0000000100035C88). Stepping over the deobfuscation logic in a debugger, reveals the string value: "ptrace"
(lldb) x/s $rbp-0x8
Once the "ptrace" string is deobfuscated, the function then calls dlsym to resolve the address of ptrace.
(lldb) reg read
General Purpose Registers:
rax = 0x00007fff96afa0ec libsystem_kernel.dylib`__ptrace
Finally, at address 0x0000000100035CBE the function invokes ptrace with 0x1F for the request parameter. Seasoned OS X reversers should recognize 0x1F as PT_DENY_ATTACH, a common OS X-specific anti-debugging trick. Specifically, on OS X, if a process calls ptrace(PT_DENY_ATTACH, 0, 0, 0) while being actively debugged (traced), Apple notes it "will exit with the exit status of ENOTSUP;" Also, if a process has called ptrace with PT_DENY_ATTACH, attempting to attach to it with a debugger will equally fail.
Of course this anti-debugging mechanism is trivial to bypass. For example, one can skip over call, by modifying the program counter register (RIP):
(lldb) x/2i $pc
0x100035cbe: ff d0 callq *%rax
0x100035cc0: 48 83 c4 10 addq $0x10, %rsp
(lldb) reg write $rip 0x0000000100035CC0
(lldb) x/2i $pc
0x100035cc0: 48 83 c4 10 addq $0x10, %rsp
0x100035cc4: 5d popq %rbp
The malware discussed in this blog post depends on an amateur infection mechanism, and is not particularly novel or intriguing. However, it makes use of some obfuscations and anti-debugging techniques. While such anti-analysis logic is quite common in Windows malware, they are somewhat more rare on OS X...for now!