Site in American English
Password
User name
 
 
« An intellectual is a person who has discovered something more interesting than sex »
Aldous Huxley
FAR (Function Address Retrieving)

In a previous article, DAR (DLL Address Retrieving), we saw how to get addresses of loaded DLLs directly from the Process Environment Block (PEB). It's time now to deal with functions...

Introduction

Actually, there is not much work to do! Once the base address of a DLL is known, the last stage is to poke about in the PE file format (PEF). That's all! The entire "bad and dirty" job is done by the loader during the loading process of a DLL, last operations are very easy.

PE Format Basics

PE stands for Portable Executable. It's the executable file format introduced by Microsoft with the Windows NT 3.1 operating system. Of course, no question to detail the whole PE format here, it would be a huge task and would end with a very very long text! Some useful references are provided in the biography below.

It's sufficient to say that the PEF is organized of several headers, followed by sections (a logical unit containing code or data). Be also aware that once a PE file (executable or DLL; from a logic point of view, there is not much difference between both file types) is loaded, all its content is mapped into memory. Two important concepts must be briefly described anyway...

Relative Virtual Address (RVA)

Simply speaking, it's an offset in the process memory space relative to where the image file has been mapped. Hence, when you read that the DLL kernel32.dll is loaded at address 77E40000h and that its PE signature is located at address 77E4003Ch, then the RVA of this field is 77E4003Ch - 77E40000h = 3Ch.

Virtual Address (VA)

It's the same as above except that the load address is not subtracted. Hence, 3Ch is a RVA whereas 77E4003Ch is a VA.

All right, it's time now to delve into the numerous headers! I must add that all the following data structures are taken from the file WinNT.h, located somewhere in your DDK, SDK or Developer Studio directories.

DOS Header

The first header met is called DOS header and it's the same one since MS-DOS version 2! It consists of 64 bytes of data and it's followed by a real-mode stub program. This stub is a genuine MS-DOS application more often printing a warning message like "This program cannot be run in DOS mode" when the executable file is run from Windows 3.1 or MS-DOS 2.x.

Two fields are important, the first one and the last one:

00h
...
3Ch
USHORT
...
LONG
e_magic
...
e_lfanew
 

Figure 1. DOS Header fields of interest.

The first field is used to identify MS-DOS compatible executable files. It holds the value 4D5Ah, ASCII characters "MZ" (initials of Mark Zbikowski from the DOS development team, creator of the DOS header). Let's call it the MS-DOS signature.

The last one is used to locate the PE header in the file. From a Windows NT point of view, it's the only field of importance, because the "new" (comparative to the DOS header, known for ages) executable header really starts there.

PE Header

Well, "PE header" is the common name, but internally, the structure involved is called IMAGE_NT_HEADERS. We just need to seek inside WinNT.h to find:

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

...

typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS;
 

Figure 2. IMAGE_NT_HEADERS internals.

It begins with a 4-byte field, the PE signature, a DWORD holding the ASCII text "PE\0\0" (455000h). Open an executable file with a hexadecimal file editor and check it by yourself: both signatures, MS-DOS and PE, can easily be spotted.

Two headers follow: FileHeader is relating to the physical layout of the PE file (the number of sections for instance) and it's 20 bytes in size whereas OptionalHeader (not optional at all!) deals with the logical part (file and section alignments, address of entry point, etc.). The optional header of a 32-bit PE file is 224 bytes in length.

The last field of OptionalHeader deserves our attention:

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

...

typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;

    ...

    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
 

Figure 3. The optional header guts (excerpt).

It's an array of IMAGE_DATA_DIRECTORY structures. Each entry is connected with an important table of data located somewhere into the image sections. This array allows the loader to quickly access these data without scanning the whole sections. To check the number of entries of DataDirectory, just look the NumberOfRvaAndSizes field content.

Here is the IMAGE_DATA_DIRECTORY structure detail:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD    VirtualAddress;
    DWORD    Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
 

Figure 4. The IMAGE_DATA_DIRECTORY structure.

The most important thing for us is that the first entry of DataDirectory is relating to the Export Table (ET) that is, symbols that other applications can dynamically import. Usually, the involved section is named .edata.

The Export Table

Of course a lot of structures and tables are involved again, but don't worry, the end is near... The ET begins with the Export Directory Table (EDT). Only a few fields are relevant to us:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD    Characteristics;       // Offset: 00h
    DWORD    TimeDateStamp;         // Offset: 04h
    WORD     MajorVersion;          // Offset: 08h
    WORD     MinorVersion;          // Offset: 0Ah
    DWORD    Name;                  // Offset: 0Ch
    DWORD    Base;                  // Offset: 10h
    DWORD    NumberOfFunctions;     // Offset: 14h
    DWORD    NumberOfNames;         // Offset: 18h
    DWORD    AddressOfFunctions;    // Offset: 1Ch
    DWORD    AddressOfNames;        // Offset: 20h
    DWORD    AddressOfNameOrdinals; // Offset: 24h
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
 

Figure 5. The IMAGE_EXPORT_DIRECTORY structure (with offsets).

If the name of the DLL is of importance to you, just use the Name field; it holds the RVA to an ASCII string containing the wanted name. The next important field is Base, the starting ordinal number for exports. Usually, this field is set to 1, but don't assume it's always the case.

Last (I swear it!), three tables must be commented on a little. The first one is the Export Address Table (EAT), an array of exported symbols RVAs (AddressOfFunctions field). The second one is the Export Name Table (ENT), an array of RVAs to ASCII strings corresponding to the names of exported symbols (AddressOfNames field); this table is sorted lexically. The last one is the Export Ordinal Table (EOT), an array of 16-bits (WORD) values, simply indexes into the EAT (AddressOfNameOrdinals field); don't forget that theses indexes are biased by the Base value we have seen above!

The EAT must be indexed using an ordinal number (don't forget to subtract the ordinal base before), the one provided by an EOT entry; don't assume that ordinals are successive numbers, as shown below:

Figure 6

Figure 6. EOT ordinals may use a weird logic...

All right! I know that all these fields, structures and table are a little bit obscure when you are not used to play with the PEF, but it was not possible to go further in detail: it may have been even worse! In order to clear up your brain, let's go through an example.

Living Example

Turn your WinDbg on and just open any executable. Now, let's say you want information about the GetProcAddress() function. First, we need to locate the IMAGE_EXPORT_DIRECTORY of kernel32.dll. Well, it's not that much difficult to find the DLL mapping address, we saw that in the previous article!

dt _PEB Ldr Ldr. 7FFDF000

  0x00c Ldr 0x00241ea0
    0x000 Length 0x28
      0x004 Initialized 0x1 ''
      0x008 SsHandle (null)
      0x00c InLoadOrderModuleList _LIST_ENTRY [ 0x241ee0 - 0x242010 ]
      0x014 InMemoryOrderModuleList _LIST_ENTRY [ 0x241ee8 - 0x242018 ]
      0x01c InInitializationOrderModuleList _LIST_ENTRY [ 0x241f58 - 0x242020 ]
      0x024 EntryInProgress : (null)
 

Figure 7. Tracking the PEB's Ldr field.

If you remember well, the emphasized value is about ntdll.dll, we need to go one step further to drive out kernel32.dll in the double linked list:

dc 241F58

  00241f58 00242020 00241ebc 77f40000 00000000 $...$....w....
  00241f68 000ae000 0208003a 77fb2618 00140012 ....:....&.w....
  00241f78 77f41380 00004004 0000ffff 77fb47c8 ...w.@.......G.w
  00241f88 77fb47c8 3eb1b460 00000000 abababab .G.w`..>........
 

Figure 8. The kernel32.dll LIST_ENTRY element.

Okay! We can now locate the mapping address (third DWORD starting from address 242020h):

dc 242020

  00242020 00241ebc 00241f58 77e40000 77e5ae60 ..$.X.$....w`..w
  00242030 000f6000 00420040 00241fb0 001a0018 .`..@.B...$.....
  00242040 00241fd8 00084004 0000ffff 77fb47b0 ..$..@.......G.w
  00242050 77fb47b0 3d6e6b99 00000000 abababab .G.w.kn=........
 

Figure 9. kernel32.dll load address.

Some Checks...

We have the opportunity to check some fields described above; the MS-DOS signature, first field from the image base at address 77E40000h:

dc 77E40000

  77e40000 00905a4d 00000003 00000004 0000ffff MZ..............
  77e40010 000000b8 00000000 00000040 00000000 ........@.......
  77e40020 00000000 00000000 00000000 00000000 ................
  77e40030 00000000 00000000 00000000 000000f8 ................
 

Figure 10. The MS-DOS signature.

The e_magic bytes are there! To reach the PE header, we just look at offset 3Ch, location of the e_lfanew field:

dc 77E40000+3C

  77e4003c 000000f8 0eba1f0e cd09b400 4c01b821 ............!..L
  77e4004c 685421cd 70207369 72676f72 63206d61 .!This program c
  77e4005c 6f6e6e61 65622074 6e757220 206e6920 annot be run in
  77e4006c 20534f44 65646f6d 0a0d0d2e 00000024 DOS mode....$...
 

Figure 11. Content of the e_lfanew field.

It means that the PE header starts at 77E400F8h. The first field there is the PE signature, let's check it out:

dc 77E40000+F8

  00004550 0004014c 3d6e6b99 00000000 PE..L....kn=....
  00000000 210e00e0 0007010b 00075600 .......!.....V..
  0007d600 00000000 0001ae60 00001000 ........`.......
  00072000 77e40000 00001000 00000200 . .....w........
 

Figure 12. The PE signature.

Export Table Spelunking

The IMAGE_EXPORT_DIRECTORY hunting is open now! Go back to figure 2:

- The PE signature is 4 bytes long.

- The image file header is 20 bytes long.

- The optional header is 224 byte long, but the last array, DataDirectory[], occupies 16 x 8 = 128 bytes.

Hence the export table (first entry of the IMAGE_DATA_DIRECTORY array) is located at 4 + 20 + 224 - 128 = 120 (78h) bytes from the PE header.

dc 77E40000+F8+78

  77e40170 0006d040 00006b39 00073b7c 00000028 @...9k..|;..(...
  77e40180 0007a000 00075c38 00000000 00000000 ....8\..........
  77e40190 00000000 00000000 000f0000 00005354 ............TS..
  77e401a0 000763fc 00000038 00000000 00000000 .c..8...........
 

Figure 13. Welcome to the ET!

Let's go back to figure 5 now:

- The ordinal base is located at offset 10h.

- The EAT is located at offset 1Ch.

- The ENT is located at offset 20h.

- The EOT is located at offset 24h.

Let's read these values:

dc 77E40000+6D040+10

  77ead050 00000001 000003ae 000003ae 0006d068 ............h...
  77ead060 0006df20 0006edd8 000137e8 000093fe ........7......
  77ead070 0000d496 000607c5 0006078e 0004e0a1 ................
  77ead080 0004df8c 00035098 000738ad 00036909 .....P...8...i..

dc 77E40000+6D040+1C

  77ead05c 0006d068 0006df20 0006edd8 000137e8 h... ........7..
  77ead06c 000093fe 0000d496 000607c5 0006078e ................
  77ead07c 0004e0a1 0004df8c 00035098 000738ad .........P...8..
  77ead08c 00036909 000520ce 0000df51 0000261a .i... ..Q....&..

dc 77E40000+6D040+20

  77ead060 0006df20 0006edd8 000137e8 000093fe ........7......
  77ead070 0000d496 000607c5 0006078e 0004e0a1 ................
  77ead080 0004df8c 00035098 000738ad 00036909 .....P...8...i..
  77ead090 000520ce 0000df51 0000261a 00060cce . ..Q....&......

dc 77E40000+6D040+24

  77ead064 0006edd8 000137e8 000093fe 0000d496 .....7..........
  77ead074 000607c5 0006078e 0004e0a1 0004df8c ................
  77ead084 00035098 000738ad 00036909 000520ce .P...8...i... ..
  77ead094 0000df51 0000261a 00060cce 0004c126 Q....&......&...
 

Figure 14. Export tables RVA's hunting...

Et voilą! We got all we need to track our function down:

- The ordinal base is 1.

- The Export Table (ET) is located at RVA 6D040h.

- The Export Address Table (EAT) is located at RVA 6D068h.

- The Export Name Table (ENT) is located at RVA 6DF20h.

- The Export Ordinal Table (EOT) is located at RVA 6EDD8h.

Now, it's not that much difficult to find what we are looking for...

GetProcAddress() Put to the Question

First we need to scan the ENT, beginning at RVA 6DF20h:

dc 77E40000+6DF20

  77eadf20 0006f541 0006f550 0006f559 0006f562 A...P...Y...b...
  77eadf30 0006f573 0006f584 0006f5a3 0006f5c2 s...............
  77eadf40 0006f5cf 0006f5eb 0006f5f8 0006f612 ................
  77eadf50 0006f622 0006f63b 0006f649 0006f654 "...;...I...T...
 

Figure 15. First RVAs of the Export Name Table.

The first function name found is ActivateActCtx. I bet you have never seen this one before today...:

dc 77E40000+6F541

  77eaf541 69746341 65746176 43746341 41007874 ActivateActCtx.A
  77eaf551 74416464 00416d6f 41646441 576d6f74 ddAtomA.AddAtomW
  77eaf561 64644100 736e6f43 41656c6f 7361696c .AddConsoleAlias
  77eaf571 64410041 6e6f4364 656c6f73 61696c41 A.AddConsoleAlia
 

Figure 16. First function name of kernel32.dll.

Because Intel processors use "little endian" architecture, bytes ordering in memory may seem odd to you. Just remember that the least significant byte is stored in the lower memory address, thus "direction of reading" is right to left:

Figure 17

Figure 17. Under the little endian hood...

Skim through each RVA of the Export Name Table one by one until you find the GetProcAddress() function name. It's the 401th one. What's its ordinal value? Well, the EOT starts at 6EDD8h, we look at the 401th (191h) position (each entry is two bytes long):

dw 77E40000+6EDD8+191*2

  77eaf0fa 0191 0192 0193 0194 0195 0196 0197 0198
  77eaf10a 0199 019a 019b 019c 019d 019e 019f 01a0
  77eaf11a 01a1 01a2 01a3 01a4 01a5 01a6 01a7 01a8
  77eaf12a 01a9 01aa 01ab 01ac 01ad 01ae 01af 01b0
 

Figure 18. GetProcAddress() ordinal.

Its value is 401 (191h). The last step is to access the EAT using this ordinal (once the ordinal base subtracted, of course) as index (remember figure 6); hold out, there we are!

dc 77E40000+6D068+190*4

  77ead6a8 0001b332 00009473 00054216 0001b77f 2...s....B......
  77ead6b8 00037478 00053e67 000541ee 000541bb xt..g>...A...A..
  77ead6c8 00053fd7 00032c3d 0001b78c 00002c4b .?..=,......K,..
  77ead6d8 0000b1a8 0000a56c 000502fb 00034a3c ....l.......<J..
 

Figure 19. GetProcAddress() RVA.

Thus, the VA of the GetProcAddress() function is 77E40000h+1B332h, that is 77E5B332h. That's all folks! Simple and easy uh? No? Do not panic anyway, the following figure will make all we have done even more clear:

Figure 20

Figure 20. It's a long way to the top if you wanna Rock 'n' Roll...

Example Of Use

Of course, the first thing coming to your mind is obfuscation! All we have seen can be of great help if you want to prevent somebody analysing your code or, at least, making newbie's disassembling job a hard task.

The following source code is funny. The resulting application is simply a classic dialog box displaying the famous "Hello World!" message but the interesting part is inside... Using a disassembler won't show any function import! All the functions needed are explicitly loaded at run-time and only ordinals are used; LoadLibrary() and GetProcAddress() are mapped from the PEB.

Well, a nice mess if you are not accustomed to this "style" of coding: happy descrambling!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
.686

.MODEL FLAT,STDCALL

.DATA

  szMessage DB "Hello World !",0
  szCaption DB "Application",0
  szUser32 DB "User32.dll",0
  szMessageBoxA DB "MessageBoxA",0

  hUser32 DWORD 0
  hKernel32 DWORD 0

  pFuncVA DWORD 5 DUP(0)
  pFuncOrd DWORD 175,233,400,570

.CODE

  @Start:

    ; Let's go to PEB.Ldr

    mov edx,7FFDF000h
    mov edx,DWORD PTR [edx+0Ch]

    ; Welcome to InInitializationOrderModuleList

    mov edx,DWORD PTR [edx+01Ch]

    ; We need the LIST_ENTRY of kernel32.dll

    mov edx,DWORD PTR [edx]

    ; Let's get the DLL load address

    mov edx,DWORD PTR [edx+08h]
    mov DWORD PTR hKernel32,edx

    ; What about l_fanew field?

    mov edx,DWORD PTR [edx+3Ch]
    add edx,DWORD PTR hKernel32

    ; Jump to the Export Table

    mov edx,DWORD PTR [edx+78h]
    add edx,DWORD PTR hKernel32

    ; We save the Export Address Table VA into eax

    mov eax,DWORD PTR [edx+1Ch]
    add eax,DWORD PTR hKernel32

    ; We now get VA of functions we need, from kernel32.dll.
    ; We use unbiased ordinals:
    ;
    ;   ExitProcess: 175
    ;   FreeLibrary: 233
    ;   GetProcAddress: 400
    ;   LoadLibraryA: 570

    mov ecx,4
    mov esi,OFFSET pFuncOrd
    mov edi,OFFSET pFuncVA

  @Next:

    mov ebx,DWORD PTR [esi]
    shl ebx,2
    mov edx,DWORD PTR [eax+ebx]
    add edx,DWORD PTR hKernel32
    mov DWORD PTR [edi],edx
    add esi,4
    add edi,4
    loop @Next

    ; It's time to map User32.dll

    mov edx,OFFSET szUser32
    push edx
    call pFuncVA[3*4] ; LoadLibraryA()
    mov DWORD PTR hUser32,eax

    ; Retrieving of MessageBoxA address

    mov edx,OFFSET szMessageBoxA
    push edx
    push hUser32
    call pFuncVA[2*4] ; GetProcAddress()
    mov DWORD PTR pFuncVA[4*4],eax

    ; The application code

    push 40h
    push offset szCaption
    push offset szMessage
    push 00h
    call pFuncVA[4*4] ; MessageBoxA()

    ; User32.dll unloading

    push hUser32
    call pFuncVA[1*4] ; FreeLibrary()

    ; Bye!

    push 00h
    call pFuncVA[0*4] ; ExitProcess()

  END @Start
 

Figure 21. The "obfuscate my function names" application source code

Bibliography

Papers

- DAR (DLL Address Retrieving), Arnold McDonald - 2004.
- Executable-File Header Format, Microsoft Knowledge Base (Q65122) - 2003.
- An In-Depth Look into the Win32 Portable Executable File Format, Matt Pietrek - 2002.
- Microsoft Portable Executable and Common Object File Format Specification, Microsoft MSDN - 1999.

Credits

Children of Bodom - Follow the Reaper (musical inspiration).

Enjoy, cya!

(Written 04/30/2004, revised 10/16/2009)

Click here to go to the Windows page.
Link to this page
Page #7300002, generated in 149.21 ms
 
Copyright © 2003-2017 Arnold McDonald. All rights reserved.
W3C HTML conformity
W3C CSS conformity