Adding New Functions to Compiled Code



Tools used:
  • OllyDbg - free debugger - Oleh Yuschuk
  • HIEW - line assembler for hacking our compiled binary - Eugeny Suslikov
  • iidKing - add imports to PE header - SantMat
  • LordPE - PE editor/viewer - yoda
  • VC6 - create sample projects and dll
  • Winhex - hexeditor/viewer
Download example binaries and source


Note: Also check out the CaveWriter tool I made for an alternative method to applying these patchs that might be a bit easier. (Screenshot)


This tut is just a quick fun example of what you might do with your new found RCE skills on a bored and rainy day.

Seriously though, there are times when you need to tweak a compiled app of which you do not have the source for.

This trainer will walk you through an example application and show you one such example.

Source and binaries are provided in the download package so you can directly walk along and see what I am talking about and follow along with me both in the source and in a debugger.

First lets describe a problem. We have an App that has a function that takes one numeric argument. We need to alter this number at times but the program provides no way to do so.

So this source is a mock up of this situation, and shows us an answer.

In the simplest of terms, our main app has a function call with one numeric argument.

void main(void){
	int c = 0;
	normal(c);
}
The function "normal()" is also quite simple and just displays the value passed to it.

__stdcall int normal(int c){
	printf("I am normal and I see %x\n",c);
	printf("Press any key to continue...");
	getche();
}
Your goal should you choose to accept it, is to implement a way so that you can interactivly change the value passed to normal() at run time.

If we look at the asm that calls the function normal() we might see something like this:

.text:004010BA _main           proc near               
.text:004010BA
.text:004010BA varC            = dword ptr -4
.text:004010BA
.text:004010BA                 push    ebp
.text:004010BB                 mov     ebp, esp
.text:004010BD                 push    ecx
.text:004010BE                 mov     [ebp+varC], 0   ;initialize variable
.text:004010C5                 mov     eax, [ebp+varC] ;place it in eax register
.text:004010C8                 push    eax             ;push onto stack as arg
.text:004010C9                 call    normal          ;call function normal()
.text:004010CE                 xor     eax, eax
.text:004010D0                 mov     esp, ebp
.text:004010D2                 pop     ebp
.text:004010D3                 retn
.text:004010D3 _main           endp
So what is the easiest way we can insert a binary patch to the compiled executable to interactively edit this argument value?

After thinking about some ways to do this I decided the easiest way to write an external dll that will provide a basic UI (in this case a console window) which I will then call from a small asm stub I patch into the binary.

These tricks are not new, I remember reading a tut on this kinda stuff a while back. I just wanted to try it myself so thought I would write this as I went to help others along.

So here is our basic outline

  1. compiled code calls function with one numeric argument.
  2. we change this function call and hijack execution to our own code which allows us to interactively change the value.
  3. altered value is sent back to the original function and everything is restored as if nothing happened.
Why did I choose to use an external DLL to implement the main functionality? There are a couple reasons:

  1. No space limitations
  2. Less asm to debug
  3. Dll can be written in a higher level language such as C
  4. No limitations on what functions you can use in the DLL and no need to import new libraries with LoadLibrary
Before we can use our DLL function, we need to know how we are going to load it and call it. We could call LoadLibrary and GetProcAddress manually in the asm patch, or we could be extra lazy and modify the targets import table so that our dll is automatically loaded for us by the Windows Loader.

Changing the import table sounds like more fun so lets do that :)

So first thing we do is create a sample app and debug all the code to get ourselves started. Check out the source for details (Note if you recompile the apps, offsets will likely change. I have included an original copy of the exe for you to experiment with if you want to make the mods yourself as we go)

If you look at the main app, you will see the basic layout. At the bottom you also see some inline asm tests you can try to get an overview of what we are ultimately going for.

After our UI function is created and debugged, we house it in a stdcall dll and export it. With that done, our DLL is now made, and we need to add it to the import table of our compiled target. For this we use a very cool program named iidKing.

You should be able to figure it out by just playing with it, basically you just enter the dll name to add and the dll function to import and hit a button couldnt be much simpler :)

After iidKing says happy things to you, fire up LordPE again and check the import table just to double check the results. You can also load the exe up in your debugger and look at the executable modules loaded to double check that its really there. Below is how the import table now looks in LordPE:



IIDKing also generates a file that will give you the offset of the new function you added and how to call it in asm.

ConfirmPID: call dword ptr [40A044]

Ok Cool, now our UI function is in place and ready to be called in an easy to use way. Now we need to get execution there.

Lets take a look at some example asm code of what will need to happen for this to all work:

void main(void){

	int c = 0;
	
	__asm{
				mov c,2     ;set initial val for arg
				mov ebx, c
				push ebx    ;push arg on stack
				jmp hack    ;was call normal
	back:
				mov c, eax
				jmp done

	hack:
			call ConfirmPID  ;call our dll function
			push eax         ;put return value on stack
			call normal      ;call original function
			jmp back         ;resume execution as normal
	done:
		
	}
}
Lets explain the above layout. While the majority of our new code is housed in our DLL, we are still going to have to write some stub in asm to get us to it.

First, we need to hijack execution just before the real function call.

In the disasm of the main function we saw the call we are interested in. In hiew this looks like
004010C9: E8 BC FF FF FF                   call     .00040108A
We can see we have 5 bytes to play with (more if we have to but then we would have to execute the other overwritten instructions somewhere else)

This raises a question if we hijack execution here...where are we going to send it? We need a place for our new instructions to sit in the file.

Here comes the concept of "caves" Basically we just need a section in the binary file that is mapped with execution privileges in memory and has a spot where there is no code yet.

To find a suitable cave, lets check out its section tables. Below is another screen shot of LordPE:



This screen shot is showing you allot. So let me explain. First, look at the Back most window that lists all the PE sections. The windows on top are further describing the .text section. From these windows, we can see that the .text section starts at raw file offset 1000h, and is 5000h bytes long. The button on the middle form breaks down the flags value and brings up the topmost screen that shows what flags are set. As we can see, it is an executable section.

Looking at the main section table again, we see that the next PE section, .rdata starts at raw file offset 6000h, this is right after the .text section ends.

The intresting thing to note, is that the virtual size (the size in memory) of the .text section is not quite using up the entire 5000h bytes that are laid out for it in the file. This means that there is a slight gap between the two sections in the file.

Lets check out the file in a hexeditor:

Remember .text started at 1000h and its raw size is 5000h bytes long, 
         This means its spans the raw file offset from 1000h to 6000h
         Also note that its virtual size (size mapped into memory) is
         only 4E5Eh, which means that there is some unused space as shown
         below


Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00005E50   C0 74 06 0F B6 45 0B C9  C3 83 C8 FF C9 C3 00 00   t..E.Ã..
00005E60   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005E70   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005E80   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005E90   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005EA0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005EB0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005EC0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005ED0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005EE0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005EF0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F00   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F10   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F20   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F30   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F40   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F50   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F60   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F70   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F80   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005F90   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005FA0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005FB0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005FC0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005FD0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005FE0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00005FF0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00006000   7A 67 00 00 B2 65 00 00  D0 65 00 00 E2 65 00 00   zg..e..e..e..
Yup..thats a nice pad for us and more than enough room for us to add in our small asm stub.

This extra space is totally unused, and not loaded into memory. Next we need to assure that whatever instructions we place in there, will be loaded into memory. We do this by altering the .text section attributes in the PE header with LordPE.

Right now the virtual size of this section is only 4E5E, because that is all the compiler needed. We need a little more, so lets change the virtual size of the .text section all the way up to 4FFF which is the max size we can use seeing how the entire raw size is only 5000.

Once that is done, we now have a suitable place to store our patch code. Next comes the actual patching :)

Above we already listed where in the disassembly the actual function call occurs
004010C9: E8 BC FF FF FF                   call     .00040108A
At this point, the argument is already on the stack and stuff is ready to happen....we just need other stuff to happen.

As shown in our inline asm code above, at this point we now the program to call our new dll function, call the original function with our modified data and resume execution. Before any of this can happen, we need to transfer the execution to our base asm stub.

For this we need to patch the original function call to instead jump right to our new code.

For this example, I choose to start my asm stub at raw file offset 5E70h. To get execution there all we have to do is replace that original function call with a jump to our new code as such
.004010C9: E9 A2 4D 00 00    jmp      .000405E70   ;this was the call normal()
One thing to pay close attention to when you are patching like this is how many bytes the instructions take. In both the original call, and our new jump, the instructions take a total of 5 bytes. This is very important.

If our jmp had taken more bytes than what we were replacing, we would have been overwriting other instructions which we would have had to make up for in our asm stub. Also if you have to overwrite other instructions..make sure you overwrite them entirely and dont leave some stray bytes that will totally change the meaning of the next instructions. (NOP out loose ends danielson)

Ok, so now instead of our function call, we will be redirected to our stub location. Now we need to enter the actual asm stub. Here is what it all looks like when you put it all together using HIEW.

.004010C5: 8B45FC         mov       eax,[ebp][-0004]
.004010C8: 50             push      eax
.004010C9: E9A24D0000     jmp      .000405E70   ;this was the call to normal()
.004010CE: 33C0           xor       eax,eax
.004010D0: 8BE5           mov       esp,ebp

.00405E70: FF1544A04000   call      d,[00040A044] ;address from iidKing for dll fx
.00405E76: 50             push      eax           ;return value from our dll
.00405E77: E80EB2FFFF     call     .00040108A     ;original "normal()" function
.00405E7C: E94DB2FFFF     jmp      .0004010CE     ;jmp back to next instruction
The normal function took one int arg, which is already on the stack for us. Our dll function takes the same int arg, and just gives us a chance to change it by popping up its own console if one was not already present. This gives us a dirty simple UI to interact with the user.

With all the changes now made, all we have to do is run it in Olly and see if we screwed anything up ;)

Trace along with the example or just double click and pray :P Turns out all our hard work paid off and works just as expected.

I have included all source code used in this tut in the package. Note that if you recompile offsets will likely change.

If you want to try to make the mods yourself, I have included an original unaltered exe as well as an altered one so you can step through it all in the debugger to watch it all in action.

This is the simplest case scenario of hacking in the ability to interactively alter a function with one numeric argument.

Anyway..enjoy...it was fun and I thought it would make a good trainer :)

-dzzie <dzzie at yahoo.com>


Copyright Sandsprite.com 2003