Let's open our executable in Immunity and examine our file:
We can see the section that we added in part one of this series. Let's take note of this memory address as we will be using it later on. In my environment the memory address is 00642000.
When we open our executable file, we notice that execution is paused at the point of calling a function. This is an ideal place to start our hijack. So let's copy the first couple instructions and save them to a file. This step is important, because as you may recall we will need to return to normal execution after executing our shellcode.
Entry Point
004AC1C2 > E8 C1080000 CALL WinMerge.004ACA88
004AC1C7 .^E9 37FDFFFF JMP WinMerge.004ABF03
Let's access our buffer. We overwrite the first instruction with a jump to 00642000.
Take the jump by pressing F7. You should have landed at the beginning of our buffer.
Great!!! Highlight the changes you made above and save them to a new executable. At this point we have successfully hijacked code execution. Now, in order for us to resume normal execution after our shellcode has finished executing, we need to save the state of the registers prior to calling our shellcode. This is achieved by using the PUSHAD and PUSHFD instructions. If you are not familiar with these commands then take some time to understand them. Information can be found here.
Edit the first few bytes with the PUSHAD and PUSHFD commands. Execute the instructions and note the value of ESP. In my case that value was 0012FFA0. This step is crucial as this value will be used later on.
Fire up metasploit and generate your favourite shellcode. For this exercise I chose to use a reverse tcp shell on port 443. We then copy the generated code from metasploit and binary paste it into our buffer.
Now set a break point at the end of the shellcode you just copied and hit F9 to execute. When you have hit your breakpoint take special note of ESP. Again this is important as we will need this value to resume normal execution.
In my case, after execution ESP has an address of 0012FF00. Highlight these changes - register saving instructions and shellcode - and save them to an executable file. We are almost there.
Let's take stock of where we are at this point. So far we have:
- Hijacked execution
- Saved the state of the registers before shellcode execution
- Noted the value of ESP prior to and after shellcode execution
As we mentioned earlier once our shellcode has finished executing, we need to return to normal execution. Remember those register saving instructions I mentioned earlier ? Remember also saving the value of ESP before and after execution? Well now we are going to use them. We are going to be restoring our environment to a pre-hijacked state. To achieve this we first need to align our stack. Prior to execution, ESP had a value of 0012FFA0. And after execution it had a value of 0012FF00. In order for us to get back to 0012FA0, we need to add the result of 0012FA0 - 0012FF00 to ESP. The following command achieves this ADD ESP, 0xA0.
Having aligned our stack, we now need to restore the register values we saved prior to executing our shellcode. This we achieve by issuing the POPFD and POPAD instructions. Note the reverse order of the instructions. This of course is due to the layout of the stack.
We have now aligned the stack and restored the register values. All that is needed now is to reintroduce the initial instructions we overwrote. Recall that we overwrote the CALL WinMerge.004ACA88. We then go to the next instruction JMP 004AC1C7.
Our executable now looks like this:
Highlight those changes and save them. Now set up a listener and run the executable. BOOM!! We have shell. We are DONE!!!
Not so fast. You should have noticed that even though you received a shell, the application was not launched. Exit the shell. WTH??? What just happened? Exiting the shell launches the application ???? This is where the fun begins.
We are going to have to step through the shellcode to determine what exactly is responsible for this behaviour. Luckily I have done that for you. I spent DAYS looking and consulting with friends to get to the bottom of this. You can ask MaXe aka @InterN0t how many pms I sent him on the issue. I would venture say a thousand or so.....
As it turns out the cause of this behaviour is due to a call to WaitForSingleObject. The function takes two parameters. However the one of interest to us is the timeout parameter. And our shellcode in all its wisdom is passing a non-zero value aka wait infinitely.
It gets even more interesting. Finding this parameter is not as easy as it sounds. Trust me when I tell you it isn't. If you generated your shellcode with say msf v3.2 then it's not so bad. The parameter is actually PUSH -1. See below:
However if you used a later version like in my case msf 4.5, then the PUSH -1 which causes the shellcode to wait infinitely is implemented differently. It's implemented as series of instructions:
DEC ESI
PUSH ESI
INC ESI
DEC ESI decrements ESI by 1, resulting in ESI being equal to 00000000. The PUSH ESI pushes it to the stack, at this point that value is FFFF FFFF or -1. Finally INC ESI brings it back to 0000 0000 again. That FFFF FFFF later gets passed to the WaitForSingleObject function. You should track this value for a better understanding of what is happening. In your debugging efforts be careful not to focus on the wrong function(e.g. WaitForSingleObjectEx) as I did.
If you don't want to push -1 to the stack then you can replace DEC ESI and PUSH ESI with NOPS.
If you don't want to push -1 to the stack then you can replace DEC ESI and PUSH ESI with NOPS.
Again I have to say thanks to MaXe for not only discovering this but also for taking the time to answer ALL my questions.
Depending on your environment save those changes. Now when you run your executable, the application should launch without you having to kill your shell. Your victim will be none the wiser.
I bet you must saying there has to be a easier way than this. I am sure there is. But it never hurts to know what's happening under the hood. Do this a few times and it's really simple and straight forward. Keep in mind though that some executables have mechanisms in place to guard against this activity.
Special Thanks To:
You should "special thanks to" harder ;-) Good post
ReplyDeleteAgreed and fixed :)
ReplyDeleteThanks. I'm actually just now starting OSCE and hunting for the WaitForSingleObject call as well as finding the instruction that inserts the parameters on the stack was a PITA for me.
ReplyDeleteGlad I wasn't the only one.
Hi,
ReplyDeleteThe instructions added after the shellcode (ADD ESP,X; POPFD, POPAD, etc) are never executed. When the shell is closed a exception in ntdll module is spawned. I have followed the steps with three different executables and the behaviour is the same...
Thanks for the tutorial.
Regards
The problem was because the shell was encoded with a polymorphic encoder (by default in msfvenom)
Delete