In the last issue of the Programming Journal I wrote about this prog which
adds a password dialog to EXE files and how to add functions and DLLs to the
import table of PE files. This essay deals with a completely different way to
use all API functions you need in your packer/cryptor/whatever with no need
to change the import table in any way. I'll show you how to search through the
memory in order to locate Kernel32.dll and then parse the export section to
get the API functions you want to use.
Download Source here
When an EXE file is executed, it's not (very) much more than a sub-call of
Kernel32.dll (mostly of CreateProcessA). This means that at the start [esp]
must contain the return offset to somewhere inside the Kernel32.dll and that
is our starting point. Considered that we know a valid offset inside Kernel32.dll
there must be a way to locate the beginning of this DLL and then we can easily
parse for the PE header, the Exports section and all APIs we desire to use.
And obviously there is one. Since Kernel32.dll is mapped to a certain offset
which is aligned to 1000h we can parse back and look at all offsets which are
aligned to 1000h and check for a MZ header.
mov edx, [esp] ;; Offset of function in Kernel32.dll
xor dx, dx ;; Align to 1000h
@Label:
sub edx, 1000h ;; search next page
cmp word ptr [edx], 'ZM' ;; MZ header?
jne @Label ;; If not, then keep searching
At the end of this loop EDX will point to the MZ header and we can go for the
PE header/Export table. We don't need any check if the offsets we check are
valid/initialized or even SEH because we can't miss the MZ header and there
are no invalid pages inside a (properly created) DLL. The next is locating the
Export table. As the regular reader of Programming Journal knows in the meantime,
there is a pointer to the PE header 3Ch bytes after the MZ header starts and
from there we can get the starting offset of the Export table (PE header + 78h).
I don't present the sourcecode for the simple parser I describe here, but already
the "sophisticated" one I wrote. Sophisticated means here that it
is a procedure which takes a pointer to a MZ header and a certain checksum as
parameters and locates API functions. I'll come back to that checksum later.
The parameters must be passed in EBX and EDX. Furthermore I need to declare
4 DWORD variables with obvious names: DLLStart, ExportStart and PEStart. In
my code snippet they are located in the .data section but this is not needed.
You can put them close to your code as long as you remember to enable write
access on that section. Let's get into the code now.
;; EDX must hold the offset of the MZ header, EBX the checksum
FindAPI:
push edx ;; Preserve the value in EDX
mov DLLStart, edx ;; keep the offset for later use
mov ecx, [edx+3Ch] ;; PE header
add ecx, edx ;; Normalize offset
mov PEStart, ecx ;; Keep PEStart for later use
mov ecx, [ecx+78h] ;; Pointer to Export Table
add ecx, edx ;; Normalize offset
mov ExportStart, ecx ;; Keep pointer to Export table for later use
mov edx, [ecx+18h] ;; Number of Exports
mov ecx, [ecx+20h] ;; Pointer To Pointer to Exports Names
add ecx, DLLStart ;; Normalize offset
With those few lines we have a pointer to API name table of the Exports. Now we
just have to parse all names and check if this is the one we need. This is where
the checksum appears. An average API name is about 10 bytes long, if not longer,
but if we use an unique signature to identify this API we can decrease the needed
size quite fine. You already guessed it, that's what the checksum is for. The
function contains a very simple algorithm which produces nearly unique (at least
for our purposes) results and only a DWORD value is needed to store it. When the
function now tries to locate the API function we need then it calculates a checksum
over all API names and compares it with the one that was passed to the function
in EBX. The inner loop calculates and compares the checksum, the outer loop loops
through all API names and keeps counting how many API names were already checked
(EDX) which is needed later.
@Loop:
xor edi, edi ;; EDI will hold the checksum
mov esi, [ecx] ;; Pointer To Export #n
add esi, DLLStart ;; Normalize offset
xor eax, eax
push ecx
@Loop2:
lodsb ;; Load char of API name into esi
mov ecx, eax
add edi, eax ;; checksum of API name - Part 1
rol edi, cl ;; checksum of API name - Part 2
test eax, eax ;; if byte was not the 0-byte...
jne @Loop2 ;; ...keep looping
pop ecx
cmp edi, ebx ;; Checksum found ?
je @Found ;; API found
add ecx, 4 ;; Next pointer to API Name
dec edx ;; Still APIs to go?
jne @Loop ;; If so, jump
pop edx ;; Clean the stack
xor eax, eax ;; return 0 means "API not found"
ret
@Found:
Everything's pretty simple so far and it will not get harder. In fact, we are
pretty close to the end. We just have to get back to the beginning of the export
directory and look at the offset table for offset of the API we need.
mov eax, DLLStart ;; Here we need...
mov ecx, ExportStart ;; the saved values again
mov ebx, [ecx+18h] ;; Number of Exports
mov ecx, [ecx+24h] ;; Pointer To Ordinals
add ecx, eax ;; Normalize offset
sub ebx, edx ;; EBX = Number of Ordinals - API we want
shl ebx, 1 ;; The ordinal table is made of WORD values - we have to multiply with 2
add ecx, ebx ;; Pointer to the ordinal of the API
movzx ebx, word ptr [ecx] ;; Ordinal of our API
mov ecx, ExportStart ;; And the Export Start again
mov ecx, [ecx+1Ch] ;; Offset Table
add ecx, eax ;; Normalize offset
shl ebx, 2 ;; Expand to DWORD size
add ecx, ebx ;; Point to offset where our API begins
add eax, [ecx] ;; Add that offset to the beginning of the DLL
pop edx ;; Clean the stack
ret
Now we have a working code snippet to locate all the APIs we want to use, even
those which are not located in Kernel32.dll. To be sure that the DLLs you need
are located in the EXE file's address space you can load them manually using
LoadLibrary and as this API is located inside kernel32.dll which can be parsed
easily using the return address of the EXE file everything's fine and you can
use all APIs you want nevertheless which DLL and no matter if this DLL is loaded
when the EXE is started. To be more precise: It obviously only works in DLL
files which export by name but I wrote the code only for handling the most important
system DLLs and they export by Name. Nevertheless for DLLs which export only
by ordinal you can easily make changes to my code and search for the ordinals
instead of a checksum.
Here's an example how to display a messagebox without using any directly imported
API functions in Win9X. There is another problem for WinNT/Win2K (at least proven
for Win2K and guessed for WinNT) which has nothing to do with my code, but with
the fact that those operating systems don't run EXE files that have no import
table. For compatibiliy reasons I use InitCommonControls in the example. Note
that the example file's code section must have the write-enabled flag because
I defined my variables in this section instead of the data section which is
write-enabled by default.
.386
.model flat, stdcall
option casemap:none
include \masm32\include\comctl32.inc ;; for NT/2K compatibility
includelib \masm32\lib\comctl32.lib ;; for NT/2K compatibility
.code
DLLStart dd 0 ;; used in the FindAPI proc
KernelStart dd 0 ;; used to store offset of Kernel32.dll
ExportStart dd 0 ;; used in the FindAPI proc
PEStart dd 0 ;; used in the FindAPI proc
GetProcA dd 0 ;; offset of GetProcAddress
LoadLib dd 0 ;; offset of LoadLibraryA
user32 db "User32.dll",0 ;; DLL we need for the messagebox
text db "Test, Test, 1,2,3",0 ;; MessageBox text
start:
invoke InitCommonControls ;; for NT/2K compatibility
mov edx, [esp] ;; Offset of function in Kernel32.dll
xor dx, dx ;; Align to 1000h
@Label:
sub edx, 1000h ;; search next page
cmp word ptr [edx], 'ZM' ;; MZ header?
jne @Label ;; If not, then keep searching
mov KernelStart, edx ;; Save Kernel32.dll offset
mov ebx, 0A216A185h ;; Checksum of LoadLibraryA
call FindAPI ;; Get offset of LoadLibraryA
mov LoadLib, eax ;; Save offset
push OFFSET user32 ;; "User32.dll"
call LoadLib ;; LoadLibraryA
mov ebx, 9A9C4525h ;; Checksum of MessageBoxA
mov edx, eax ;; Offset of User32.dll MZ header
call FindAPI ;; Get offset of MessageBoxA
push edx ;; Store for calling FreeLibrary
push 0
push OFFSET text
push OFFSET text
push 0
call eax ;; Show MessageBox
mov ebx, 0B36E72B3h ;; CheckSum of FreeLibrary
mov edx, KernelStart ;; API is inside Kernel32.dll
call FindAPI ;; Get API
call eax ;; Free User32.dll
ret ;; exit program
;; EDX must hold the offset of the MZ header, EBX the checksum
FindAPI:
... ;; See code above
ret
end start
The messagebox is displayed and everything seems to work fine. The only question
left is how to get the checksum of the API functions quickly? I wrote a small
C++ program where you enter a string and the checksum is calculated.
#include <iostream>
#include <stdlib>
#include <conio>
void main()
{
char name[80];
unsigned int checksum = 0;
cout << "Enter the API name: ";
cin >> name;
for (int i=0;i<strlen(name);i++)
{
checksum += name[i];
checksum = _lrotl(checksum, name[i]); // Rotate left
}
cout << "Checksum: " << hex << checksum;
getch();
}
That's all so far. As you probably saw is this code only useful for programs
which attach to EXE files and execute first. When you are already somewhere
in the EXE file you probably don't know if [ESP] still holds the return offset
of the program or if you are already in a subcall or if parameters or temporary
variables are pushed onto to the stack. Here it's still better to add the API/DLLs
you need right to the Import Table as I explained last time.
This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
|