Tutorial 7: Export Table
WE HAVE Learned About One Part of The Dynamic Linking, Namely The Import Table, In The Previous Tutorial. Now We Will Learn About The Other Side of the Coin, The Export Table.
THEORY:
When the PE loader runs a program, it loads the associated DLLs into the process address space. It then extracts information about the import functions from the main program. It uses the information to search the DLLs for the addresses of the functions to be patched into ....................
WHEN A DLL / EXE EXPORTS A Function To Be Used by Other DLL / EXE, IT CAN DO SO in Two Ways: It Can Export The Function By Name Or by Ordinal Only. Say Ire Is A Function Named "Getsysconfig" in A DLL , it can choose to tell the other DLLs / EXEs that if they want to call the function, they must specify it by its name, ie. GetSysConfig. The other way is to export by ordinal. What's an ordinal? An ordinal is a 16 -bit number that uniquely identifies a function in a particular DLL. This number is unique only within the DLL it refers to. For example, in the above example, the DLL can choose to export the function by ordinal, say, 16. Then the Other DLLS / EXES Which Want To Call This Function Must Specify This Number in getProcaddress. this is called export by Ordinal ONLY.
Export by ordinal only is strongly discouraged because it can cause a maintenance problem for the DLL. If the DLL is upgraded / updated, the programmer of that DLL can not alter the ordinals of the functions else other programs that depend on the DLL will break.
Now we can examine the export structure. As with import table, you can find where the export table is from looking at the data directory. In this case, the export table is the first member of the data directory. The export structure is called IMAGE_EXPORT_DIRECTORY . There are 11 members in the structure but only some of them are really used.Field NameMeaningnNameThe actual name of the module. This field is necessary because the name of the file can be changed. If it's the case, the PE loader will use this internal name.nBaseA number that you must bias against the ordinals to get the indexes into the address-of-function array.NumberOfFunctionsTotal number of functions / symbols that are exported by this module.NumberOfNamesNumber of functions / symbols that are exported by name. This value is not the number of ALL functions / symbols in the module. For that number, you need to check NumberOfFunctions. This value can be 0. in that case, the module may export by ordinal only. If there is no fun ction / symbol to be exported in the first case, the RVA of the export table in the data directory will be 0. AddressOfFunctionsAn RVA that points to an array of RVAs of the functions / symbols in the module. In short, RVAs to all functions in the module are kept in an array and this field points to the head of that array.AddressOfNamesAn RVA that points to an array of RVAs of the names of functions in the module.AddressOfNameOrdinalsAn RVA that points to a 16-bit array that contains the ORDINALS Associated with the function names in the addressofnames array letter.
Just Reading The Above Table May Not Give You The Real Picture of The Export Table. The Simplified Explanation Below Will Clarify The Concept.
The export table exists for use by the PE loader. First of all, the module must keep the addresses of all exported functions somewhere so the PE loader can look them up. It keeps them in an array that is pointed to by the field AddressOfFunctions. The number of elements in the array is kept in NumberOfFunctions. Thus if the module exports 40 functions, it must have 40 members in the array pointed to by AddressOfFunctions and NumberOfFunctions must contain a value 40. Now if some functions are exported by names, the module must keep the names in the file. It keeps the RVAs to the names in an array so the PE loader can look them up. That array is pointed to by AddressOfNames and the number of names in NumberOfNames. Think about the job of the PE loader, it knows the names of the functions, it must somehow obtain the addresses of those functions Up to now, the module has two arrays:. the names and the addresses but there is no linkage between them Thus we need something that relates the.names of the functions to their addresses. The PE specification uses indexes into the address array as that essential linkage. Thus if the PE loader finds the name it looks for in the name array, it can obtain the index into the address table for that name too. The indexes are kept in another array (the last one) pointed to by the field AddressOfNameOrdinals. Since this array exists as the linkage between the names and the addresses, it must have exactly the same number of elements as the name array, ie Each Name Can Have One and Only One Associated Address. The Reverse Is Not True: An Address May Have Several Names Associated with It. Thus we can have "aliases"
that refer to the same address. To make the linkage works, both name and index arrays must run in parallel, ie. the first element in the index array must hold the index for the first name and so on.AddressOfNamesAddressOfNameOrdinals
|
|
RVA of Name 1RVA of Name 2RVA of Name 3rva of name 4
...
RVA of Name N
<-> <-> <-> <->
...
<->
Index of name 1Index of Name 2Index of name 3index of name 4
...
Index of Name N
An Example or Two is in Order. If we have the name of an export function and we need to get it can do like this:
Go to the PE header Read the virtual address of the export table in the data directory Go to the export table and obtain the number of names (NumberOfNames) Walk the arrays pointed to by AddressOfNames and AddressOfNameOrdinals in parallel, searching for the matching name. If the name is found in the AddressOfNames array, you must extract the value in the associated element in the AddressOfNameOrdinals array. For example, if you find the RVA of the matching name in 77th element of the AddressOfNames array, you must extract the value stored in the 77th element of the AddressOfNameOrdinals array. If you walk the array until NumberOfNames elements are examined, you know that the name is not in this module. Use the value from the AddressOfNameOrdinals array as the index into the AddressOfFunctions array. Say, if the value ....................................
Now we can turn our attention to the nBase member of the IMAGE_EXPORT_DIRECTORY structure. You already know that the AddressOfFunctions array contains the addresses of all export symbols in a module. And the PE loader uses the indexes into this array to find the addresses of the functions . Let's imagine the scenario where we use the indexes into this array as the ordinals. Since the programmers can specify the starting ordinal number in .def file, like 200, it means that there must be at least 200 elements in the AddressOfFunctions array. Furthermore the first 200 elements are not used but they must exist so that the PE loader can use the indexes to find the correct addresses. This is not good at all. The nBase member exists to solve this problem. If the programmer specifies the starting ordinal of 200. The Value in NBase Would Be 200. When The Pe Loader Reads The Value in NBase, It Knows That The First 200 Elements Do Not Exist and That Should Subtract The Ordinal By The Value In N Base to obtain the true index into the AddressOfFunctions array. With the use of nBase, there is no need to 200 empty provide elements.Note that nBase does not affect the values in the AddressOfNameOrdinals array. Despite the name "AddressOfNameOrdinals", this array Contains the true indexes Into the addressoffunctions array, not the orders.
WY, WE CAN continue to the next esample.suppose That We since:
Go to the PE header Obtain the RVA of the export table from the data directory Go to the export table and obtain the value of nBase. Subtract the ordinal by the value in nBase and you have the index into the AddressOfFunctions array. Compare the index with the value in NumberOfFunctions. If the index is larger or equal to the value in NumberOfFunctions, the ordinal is invalid. Use the index to obtain the RVA of the function in the AddressOfFunctions array.Note that obtaining the address of a function from an ordinal is much easier and faster than using the name of the function. There is no need to walk the AddressOfNames and AddressOfNameOrdinals arrays. The performance gain, however, must be balanced against the difficulty in the maintaining the module.
In conclusion, if you want to obtain the address of a function from its name, you need to walk both AddressOfNames and AddressOfNameOrdinals arrays to obtain the index into the AddressOfFunctions array. If you have the ordinal of the function, you can go directly to the Addressoffunctions ArrayAfter the Ordinal Is Biased by NBase.
If a function is exported by name, you can use either its name or its ordinal in GetProcAddress. But what if the function is exported by ordinal only? We come to that now. "A function is exported by ordinal only" means the function doesn 't have entries in both AddressOfNames and AddressOfNameOrdinals arrays. Remember the two fields, NumberOfFunctions and NumberOfNames. The existence of these two fields is the evidence that some functions may not have names. The number of functions must be at least equal to the number of names. The functions that do not have names are exported by their ordinals only. For example, if there are 70 functions but only 40 entries in the AddressOfNames array, it means there are 30 functions in the module that are exported by their ordinals only . Now how can we find out which functions are exported by ordinals only? It's not easy. You must find that out by exclusion, ie. the entries in the AddressOfFunctions array that are not referenced by the AddressOfNameOrdi Nals Array Contain The Ras of The Functions That Are Exported by Ordinals Only.example: 0000-00-00 0000-00-00 0000-00-00. '
This example is similar to the one in the previous tutorial. However, it displays the values of some members of IMAGE_EXPORT_DIRECTORY structure and also lists the RVAs, ordinals, and names of the exported functions. Note that this example does not list the functions that Are exported by Ordinals Only.
.386 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/kernel32.inc include /masm32/include/comdlg32.inc include /masm32/include/user32.inc includelib / masm32 /lib/user32.lib includelib /masm32/lib/kernel32.lib includelib /masm32/lib/comdlg32.lib IDD_MAINDLG equ 101 IDC_EDIT equ 1000 IDM_OPEN equ 40001 IDM_EXIT equ 40003 DlgProc proto: DWORD,: DWORD,: DWORD,: DWORD ShowExportFunctions Proto: DWORD Showthefunctions Proto: DWORD,: DWORD APPENDTEXT Proto: DWORD,: DWORD
SEH struct PrevLink dd? CurrentHandler dd? SafeOffset dd? PrevEsp dd? PrevEbp dd? SEH ends .data AppName db "PE tutorial no.7", 0 ofn OPENFILENAME <> FilterString db "Executable Files (* .exe, * .dll) ", 0," *. EXE; *. DLL ", 0 DB" All Files ", 0," *. * ", 0, 0 FileOpenenError DB" Cannot Open THE FILE, 0 FileOpenMappingerRor DB "Cannot Open THE File for membort mapping ", 0 Filemappinger DB" Cannot Map The File Into Memory ", 0 NOTVALIDPE DB" this file is not a valid pe ", 0 NoExportTable DB" no export information in this file ", 0 CRLF DB 0DH, 0AH, 0 ExportTable DB 0DH, 0AH, "====== [image_export_directory] =======", 0DH, 0AH DB "Name of the module:% s", 0DH, 0AH DB "NBASE:% Lu", 0DH, 0AH DB "Numberoffunctions:% Lu", 0DH, 0AH DB "NumberOfnames:% Lu", 0DH, 0AH DB "Addressoffunctions:% lx", 0DH, 0AH DB "AddressOfnames:% lx", 0DH, 0AH DB "AddressOfnameRINALS :% lx ", 0DH, 0AH, 0 Header DB" RVA Ord. Name ", 0DH, 0AH DB "--------------------------------------------", 0 Template DB "% LX% U% S", 0 .data? Buffer DB 512 DUP (?) HFFER DD? HMApping DD? PMApping DD? Validpe Dd? .Code Start: Invoke GetModuleHandle, Null Invoke Dialogboxparam, Eax, IDD_MAINDLG, NULL, addr DlgProc, 0 invoke ExitProcess, 0 DlgProc proc hDlg: DWORD, uMsg: DWORD, wParam: DWORD, lParam: DWORD .if uMsg == WM_INITDIALOG invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_SETLIMITTEXT, 0,0 .elseif uMsg == WM_Close Invoke Enddialog, HDLG, 0 .ELSEIF UMSG =
= WM_COMMAND .if lParam == 0 mov eax, wParam .if ax == IDM_OPEN invoke ShowExportFunctions, hDlg .else; IDM_EXIT invoke SendMessage, hDlg, WM_CLOSE, 0,0 .endif .endif .else mov eax, FALSE ret .endif mov eax, TRUE ret DlgProc endp SEHHandler proc uses edx pExcept: DWORD, pFrame: DWORD, pContext: DWORD, pDispatch: DWORD mov edx, pFrame assume edx: ptr sEH mov eax, pContext assume eax: ptr CONTEXT push [edx] .SafeOffset pop [eax] .regEip push [edx] .PrevEsp pop [eax] .regEsp push [edx] .PrevEbp pop [eax] .regEbp mov ValidPE, FALSE mov eax, ExceptionContinueExecution ret SEHHandler endp ShowExportFunctions proc uses edi hDlg: DWORD LOCAL seh: sEH mov ofn.lStructSize, SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile, 512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax == True Invoke Createfile, AddR Buffer, Generic_Read, Fi LE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL .if eax! = INVALID_HANDLE_VALUE mov hFile, eax invoke CreateFileMapping, hFile, NULL, PAGE_READONLY, 0,0,0 .if eax! = NULL mov hMapping, eax invoke MapViewOfFile, hMapping, FILE_MAP_READ, 0,0,0 .if eax! = Null mov pmapping, eax associum fs: Nothing push fs: [0] pop seh.prevlink Mov Seh.currenthandler, Offset Sehhandler Mov Seh.SAFEOFFSET, OFFSET FINALEXIT LEA EAX, SEH MOV FS: [0], EAX MOV SEH.PREVESP, ESP MOV SEH.PREVEBP, EBP MOV EDI, PMAPPING Assume EDI: Ptr Image_dos_Header .IF [EDI] .e_magic ==
IMAGE_DOS_SIGNATURE add edi, [edi] .e_lfanew assume edi: ptr IMAGE_NT_HEADERS .if [edi] .Signature == IMAGE_NT_SIGNATURE mov ValidPE, TRUE .else mov ValidPE, FALSE .endif .else mov ValidPE, FALSE .endif FinalExit: push seh.PrevLink pop fs: [0] .if ValidPE == TRUE invoke ShowTheFunctions, hDlg, edi .else invoke MessageBox, 0, addr NotValidPE, addr AppName, MB_OK MB_ICONERROR .endif invoke UnmapViewOfFile, pMapping .else invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK MB_ICONERROR .endif invoke CloseHandle, hMapping .else invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK MB_ICONERROR .endif invoke CloseHandle, hFile .else invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK MB_ICONERROR .endif.ndif rett showex portFunctions endp AppendText proc hDlg: DWORD, pText: DWORD invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_REPLACESEL, 0, pText invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_REPLACESEL, 0, addr CRLF invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_SETSEL, -1,0 ret AppendText endp RVAToFileMap PROC uses edi esi edx ecx pFileMap: DWORD, RVA: DWORD mov esi, pFileMap assume esi: ptr IMAGE_DOS_HEADER add esi, [esi] .e_lfanew assume esi: ptr IMAGE_NT_HEADERS mov edi, RVA; edi == RVA mov edx, ESI Add Edx, SizeOf Image_NT_HEADERS MOV CX, [ESI] .fileHeader.Numberofsections Movzx ECX, CX Assume Edx: Ptr Image_section_Header .While ECX> 0 .IF EDI> =
[edx] .virtualaddress MOV EAX, [EDX] .virtualAddress Add Eax, [EDX] .SizeOfrawData .IF EDI Mov Eax, [EDX] .virtualAddress Sub EDI, EAX MOV EAX, [EDX] .pointertorawdata Add Eax, EDI Add Eax, Pfilemap RET .endif .endif Add Edx, SizeOf Image_SECTION_HEADER Dec ECX .endw Assume Edx: Nothing Assume ESI: Nothing Mov Eax, EDI RET RVATOFILEMAP ENDP Showthefunctions Proc Uses ESI ECX EBX HDLG: DWORD, PNTHDR: DWORD Local Temp [512]: BYTE Local NumberOfNames: DWORD Local Base: DWORD Mov Edi, PNTHDR Assume EDI: PTR Image_NT_HEADERS Mov Edi, [EDI] .optionalheader.DataDirectory.VirtualAddress .IF EDI == 0 Invoke Messagebox, 0, Addr NoExportTable, Addr Appname, MB_OK MB_ICONERROR RET .endif Invoke setdlgitemtext, hdlg, idc_edit, 0 Invoke AppendText, HDLG, AddR Buffer Invoke rvatofilemap, pmapping, edi Mov Edi, EAX Assume EDI: PTR image_export_directory MOV EAX, [EDI] .Numberoffunctions Invoke rvatofilemap, pmapping, [edi] .nname invoke wsprintf, addr temp, addr ExportTable, eax, [edi] .nBase, [edi] .NumberOfFunctions, [edi] .NumberOfNames, [edi] .AddressOfFunctions, [edi] .AddressOfNames, [edi] .AddressOfNameOrdinals Invoke appendtext, hdlg, addr temp Invoke appendtext, hdlg, addr header Push [EDI] .NumberOfnames Pop NumberOfnames push [edi] .nBase pop Base invoke RVAToFileMap, pMapping, [edi] .AddressOfNames mov esi, eax invoke RVAToFileMap, pMapping, [edi] .AddressOfNameOrdinals mov ebx, eax invoke RVAToFileMap, pMapping, [edi] .AddressOfFunctions mov edi, eax .while NumberOfNames> 0 invoke RVAToFileMap, pMapping, dword ptr [esi] mov dx, [ebx] movzx edx, dx mov ecx, edx shl edx, 2 add edx, edi add ecx, Base invoke wsprintf, addr temp, addr template, DWORD PTR [EDX], ECX, EAX INVOKE APPENDTEXT, HDLG, Addr Temp Dec NumberOfNames Add ESI, 4 Add EBX, 2 .Endw Ret Showthefunctions Endp End Startanalysis: mov edi, pNTHdr assume edi: ptr IMAGE_NT_HEADERS mov edi, [edi] .OptionalHeader.DataDirectory.VirtualAddress .if edi == 0 invoke MessageBox, 0, addr NoExportTable, addr AppName, MB_OK MB_ICONERROR ret .endif After the program verifies that the file is a valid PE, it goes to the data directory and obtains the virtual address of the export table. If the virtual address is zero, the file does not have any exported symbol. mov eax, [edi] .NumberOfFunctions invoke RVAToFileMap, pMapping, [edi] .nName invoke wsprintf, addr temp, addr ExportTable, eax, [edi] .nBase, [edi] .NumberOfFunctions, [edi] .NumberOfNames, [edi] .Addressoffunctions, [edi] .addressofnames, [edi] .addressofnameordinals invoke appendtext, hdlg, Addr Temp WE Display The Important Information In The Image_Export_directory Structure In The Edit Control. Push [EDI] .NumberOfnames Pop NumberOfNames Push [EDI] .nbase Pop Base . invoke RVAToFileMap, pMapping, [edi] .AddressOfNames mov esi, eax invoke RVAToFileMap, pMapping, [edi] .AddressOfNameOrdinals mov ebx, eax invoke RVAToFileMap, pMapping, [edi] .AddressOfFunctions mov edi, eaxThe addresses of the three arrays are stored in ESI, EBX, AND EDI, Ready to Be Accessed. .While NumberOfnames> 0 Continue Until All Names are processed. Invoke RVATOFILEMAP, PMAPPING, DWORD PTR [ESI] Since ESI Points to an Array of Rvas of the Exported Names, Dereference It Will Give The RVA of The Current Name. We Convert It To The Virtual Address, To BE Used In Wsprintf Later. MOV DX, [EBX] Movzx EDX, DX MOV ECX, EDX Add ECX, BASE ebx points to the array of ordinals. Its array elements are word-size. Thus we need to convert the value into a dword first. edx and ecx contain the index into the AddressOfFunctions array. We will use edx as the pointer into the AddressOfFunctions array . We add the value of nbase to ecx to obtain the orderdinal number of the function. SHL EDX, 2 Add Edx, EDI WE MULTIPLY THE INDEX BY 4 (Each Element In The Addressoffunctions Array IS 4 BYtes in size) and the address of the addressoffunctions array to it. Thus edx points to the rva of the function. Invoke WSPrintf, Addr Temp, Addr Template, DWORD PTR [EDX], ECX, EAX Invoke AppendText, HDLG, Addr Temp WE Display The RVA, Ordinal, And The Name of the Function in The Edit Control. Dec NumberOfnames Add ESI, 4 Add EBX, 2 .Endw Update The counter and the addresses of the capital Elements in addressofNames and addressofnameordinals arrays. Continue UnTil All Names are processed. [ICZelion's Win32 Assembly HomePage]