Sie sind auf Seite 1von 5

PE Files Import Table Rebuilding -------------------------------Written 7/18/99 By TiTi/BLiZZARD

1. For what is it useful ? ========================== Hihoo all :) Well i wrote this essay because i was working on a process dumper, when I saw that many compressors/encrypters make the Import table unusable, and then, the dumped executables needed to have their import table rebuilt. I saw no essay about this on common win32asm sites, so here is a little help if you are interested in. For example, any Petite v2.1 compressed executable, after having been dumped from memory, need to have its import table rebuilt (more precisely corrected), in order for this .exe to run properly. (That's the same for aspack, pepack, pesentry...etc). That's why Imp table rebuilding functions are needed in any dumping soft. (for example Phoenix Engine by G-RoM/UCF (included in ProcDump), or PE Rebuilder by Virogen/PC and me). Well as this subject is very specific, and quite complicated, i'll assume that you already know PE files structure. (You should have read docs about PE files). 2. Some preliminary comments ============================ Firstly, some quick informations about import tables and rva/va. The Import table relative virtual offset (RVA) is stored in the corresponding directory entry in the PE Header. (Its offset is [offset peheader+80h]. As it is a virtual offset, it wont match the file offset (VA) of the import table (except if the file has just been purely dumped from memory). So, the first thing you have to do to find the import table in a PE file is to convert this RVA to the corresponding VA. For that, there are some different solutions : you can write a personnal routine that parses the sections directory and calculates the VA, but the easiest way to do it is to use an API that is especially designe d to do that. This API is in IMAGEHLP.DLL (a library used on both win9x and NT systems), and its name is ImageRvaToVa. Here is its description (get full detail in the msdn library): # LPVOID ImageRvaToVa( # IN PIMAGE_NT_HEADERS NtHeaders, # IN LPVOID Base, # IN DWORD Rva, # IN OUT PIMAGE_SECTION_HEADER *LastRvaSection #); # #Parameters : # NtHeaders # Pointer to an IMAGE_NT_HEADERS structure. This structure # can be obtained by calling the ImageNtHeader function. # Base # Specifies the base address of an image that is mapped # into memory through a call to the MapViewOfFile function.

# # # # # # # #

Rva Specifies the relative virtual address to locate. LastRvaSection Pointer to an IMAGE_SECTION_HEADER structure that specifies the last RVA section. This is an optional parameter. When specified, it points to a variable that contains the last section value used for the specified image to translate an RVA to a VA.

You see it's pretty simple to use. You just have to map your pe file in memory and call this function to get the valid VA to the import table. Note that i'll skip all RVA/VA remarks in the following, but dont forget to convert one to the other when you read/write RVAs from/to the PE file you are rebuilding.

3. Full explanations ==================== Here is a full sample of an altered import table (This is the import table of a PE file that has been compressed with petite v2.1, and then that has been directly dumped from memory) : 00 are represented by '' non-strings are represented by '-' 0000C1E8h 0000C1F8h 0000C208h 0000C218h 0000C228h 0000C238h 0000C248h 0000C258h 0000C268h 0000C278h 0000C288h 0000C298h 0000C2A8h 0000C2B8h 0000C2C8h 0000C2D8h : : : : : : : : : : : : : : : : 00 38 C5 00 00 7F 1A 00 41 45 61 74 47 00 4E 33 00 C2 C2 00 00 89 38 00 00 78 64 50 65 00 45 32 00 00 00 00 00 E7 F1 00 00 69 4C 72 74 55 4C 2E 00 00 00 00 00 77 77 00 00 74 69 6F 4F 53 33 64 00 00 44 D2 00 4C 10 00 77 50 62 63 70 45 32 6C 00 00 C2 C2 00 BC 40 00 73 72 72 41 65 52 2E 6C 00 00 00 00 00 E8 F1 4D 70 6F 61 64 6E 33 64 00 00 00 00 00 00 77 77 65 72 63 72 64 46 32 6C 00 00 00 00 54 00 00 00 73 69 65 79 72 69 2E 6C 00 00 00 00 C2 00 00 00 73 6E 73 41 65 6C 64 00 00 00 00 00 00 00 00 00 61 74 73 00 73 65 6C 63 00 00 00 00 00 00 00 00 67 66 00 00 73 4E 6C 6F 00 BA 00 00 00 00 E6 4F 65 41 00 00 00 61 00 6D 00 C2 00 00 00 00 9F 1E 42 00 00 00 00 6D 4B 64 00 00 00 00 00 00 F1 D8 6F 00 4C 47 00 65 45 6C 00 00 00 00 00 00 77 77 78 00 6F 65 00 41 52 67 00 ------- -------- -------- ----------------------MessageBox AwsprintfA ExitProcessLo adLibraryAGe tProcAddress GetOpenFileNameA USER32.dllKER NEL32.dllcomdlg 32.dll

Well as you can see this import table is divided into three main parts: - From C1E8h->C237h : The array of IMAGE_IMPORT_DESCRIPTOR structures, each one corresponds to one imported DLL file. This array is ended by a struct filled with 0. IMAGE_IMPORT_DESCRIPTOR struct OriginalFirstThunk dd 0 ;RVA to original unbound IAT TimeDateStamp dd 0 ;not used here ForwarderChain dd 0 ;not used here Name dd 0 ;RVA to DLL name sring

FirstThunk dd 0 ;RVA to IAT array IMAGE_IMPORT_DESCRIPTOR ends - From C238h->C25Bh : The ArrayS of Dwords called 'IAT' pointed by FirstThunk members of the IMAGE_IMPORT_DESCRIPTOR structs. Each DWORD of this array corresponds to an imported function.

- From C25Ch->C2DDh : These are the strings of the imported functions and DLL files. On problem is that there is no predefined order : sometimes the dll names are before functions, sometimes that is the contrary, and sometimes they are mixed up. Little explanation about the import tables -----------------------------------------The OriginalFirstThunk is the array of IAT the PE loader first searches for. If it is present, the pe loader will use it to correct eventual problems in the FirstThunk IAT array. Once loaded into memory, each dword of the FirstThunk array, containing an RVA to the function name string, is replaced by a RVA to the real function's address. (The location into memory that'll be executed when calling this function). So, basically, there is no import table problem, provided that the OriginalFirstThunk stays unchanged. Here we come to our problem --------------------------Well, after this short description, we come to the problem. If you try to run the executable containing the import table showed above, it won't load, and windows will display an error message. Why ?, simply because the OriginalFirstThunk array has been deleted. Actually, you can notice that, for each IMAGE_IMPORT_DESCRIPTOR struct of this import table, the OriginalFirstThunk member is 00000000h. Hmm, so we deduce that, when launching the exe, the pe loader will try to get imported functions names from the FirstThunk array. BUT, as you can notice, this array doesn't contain RVA to functions name strings anymore, but RVA to function address in memory. What we have to do -----------------Now, what we have to do to get this executable to work is to rebuild the FirstThunk array members, to make them point again to functions name strings we can see in the 3rd part of the import table. Basically, that's not a very hard task, but, we need to know which IAT corresponds to which functions, as the functions strings are not sorted the same way as the FirstThunk members. So, for each IAT, we need to identify the function name it corresponds to. (actually, we already have the DLL name, because of the IMAGE_IMPORT_ DESCRIPTOR.Name dword, that havn't been changed of course.). How to identify each function -----------------------------

As we saw above, each corrupted IAT is an RVA to the function's address in memory. These addresses do not change from one session to another, so we just have to retrieve the function whose address is pointed by the corrupted IAT, and make it point to the function name string. For this, there is a very useful API in Kernel32.dll GetProcAddress. It allows you to get the address of a given function. Here is its description : GetProcAddress( HMODULE LPCSTR ); So, basically, for a given corrupted IAT, we just have to parse all function names contained in the 3rd part of the import table, until the GetProcAddress returns the address of the function we are looking for. - The hModule parameter is the handle of the DLL module (that is to say the Base Address of the module's image in memory), that we can get using the well known GetModuleHandleA API : HMODULE GetModuleHandle( LPCTSTR lpModuleName ); // address of module name to return handle for hModule, lpProcName // handle to DLL module // name of function

(The lpModuleName just have to point to the DLL filename string we get from the IMAGE_IMPORT_DESCRIPTOR.Name member) - the lpProcName just points to the function name string. Note that sometimes function are imported by ordinal number. These numbers are WORDS in each [offset functionname - 2]. So, your parsing routine will have to check if each function is imported by name or by ordinal. Example using the import table above -----------------------------------I'll explain how to fix the first imported function of the first imported dll if the sample import table above. 1. We look at the first IMAGE_IMPORT_DESCRIPTOR struct of the array (C1E8h), and get the DLL name, pointed by the .Name member (C1E4h, that points to C1BAh). We see that it's USER32.dll. 2. We look at the .FirstThunk member, that points to an array of IAT; each one corresponding to 1 imported function from this DLL (user32.dll). In this case, that is C1F8h, that points to C238h. So, at C238h, we have our corrupted IATs to fix. (You can notice that this IAT array contains 2 dwords, so 2 functions are imported from this DLL). 3. Let's get the first corrupted IAT. It's value is 77E7897Fh. This is the address the the function in memory. 4. For each function name in the 3rd part of the import table, we call the GetProcAddress API. When this API returns 77E7897Fh, that's it, we reached the right function. So we make the corrupted IAT point to the right function name. (in this case that is 'wsprintfA').

5. Now we just have to make the IAT point to : offset(function Name string)-2. Why -2 ? because of ordinalsorting of fucntions that is sometimes used. So, in this case, we change the content of the addr C238h to make it point to C26Ah (instead of 77E7897Fh). 6. That's it, this function is fixed, now you just have to repeat this process on all IATs. Last Notes ---------Well i described the general process. Of course it will only work on DLLs that are currently loaded into memory. For the others, you'll have to load them, or t o traverse their export table to find right functions addresses.

Das könnte Ihnen auch gefallen