Vous êtes sur la page 1sur 38

The Rheingold Docs on PE Executables

Edited from Wayback Machine by t0liet_ February 2012

The articles by Rheingold can be found on the Wayback Machine by pointing at http://www.programmingjournal.com

Table of Contents
Converting virtual offsets to raw offsets and vice versa.....................................................................................2 Introduction to the IAT by coding an Imports Viewer........................................................................................4 Coding a small PE wrapper...............................................................................................................................13 Locating API function offsets in memory.........................................................................................................34

Converting virtual offsets to raw offsets and vice versa


Converting raw offsets (the one in a file you see in a HexEditor) to virtual offsets (the one you see in a debugger) is very useful if you work with the PE header. This short essay will explain how to convert them. For this purpose you need to know some values you can read from the PE header of Win32 PE files (EXE/DLL). You need to know the ImageBase of the file and you have to perform some calculations with the section definitions of the PE header. Below you see an example of a PE header from the beginning of the file (where it is actually a MZ header until offset 0x80) until the section definitions end (offset 0x23F). The example is taken from my notepad.exe.
00000000 00000010 00000020 00000030 00000040 00000050 00000060 00000070 00000080 00000090 000000A0 000000B0 000000C0 000000D0 000000E0 000000F0 00000100 00000110 00000120 00000130 00000140 00000150 00000160 00000170 00000180 00000190 000001A0 000001B0 000001C0 000001D0 000001E0 000001F0 00000200 00000210 00000220 00000230 4D5A B800 0000 0000 0E1F 6973 7420 6D6F 5045 0000 0072 0050 0400 00E0 0000 0000 0060 0000 00D0 0000 0000 0000 0000 0000 9C3E 0000 2E64 0010 0000 E80D 0000 2E72 0060 0000 9C0A 0000 9000 0000 0000 0000 BA0E 2070 6265 6465 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 6174 0000 0000 0000 0000 7372 0000 0000 0000 0000 0300 0000 0000 0000 00B4 726F 2072 2E0D 4C01 E000 0000 0000 0000 0004 0010 1000 8C00 0000 3C09 0000 0000 0000 0000 0000 0010 0000 6100 0050 4000 0060 0000 6300 0070 4000 00D0 0000 0000 0000 0000 0000 09CD 6772 756E 0D0A 0500 0E01 0000 4000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00C0 0000 0000 0000 0000 0040 0000 0000 0400 4000 0000 0000 21B8 616D 2069 2400 6591 0B01 CC10 0010 0400 D509 0000 0000 0070 0000 0000 0000 0000 E062 0000 2E74 0040 0000 4C08 0000 2E69 0010 0000 0060 0000 2E72 0010 0000 0000 0000 0000 0000 014C 2063 6E20 0000 4635 030A 0000 0000 0000 0100 1000 0000 0000 0000 0000 0000 0000 0000 0000 6578 0000 0000 0000 0000 6461 0000 0000 0000 0000 656C 0000 0000 FFFF 0000 0000 8000 CD21 616E 444F 0000 0000 0040 0010 0010 0000 0200 0010 0000 E453 0000 0000 0000 0000 4002 0000 7400 0010 2000 0050 0000 7461 0060 4000 0070 0000 6F63 00D0 4000 0000 0000 0000 0000 5468 6E6F 5320 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0060 0000 0000 0000 0000 0040 0000 0000 0000 0000 0042 MZ.............. ........@....... ................ ................ ........!..L.!Th is program canno t be run in DOS mode....$....... PE..L...e.F5.... .............@.. .r.............. .P....@......... ................ ................ ................ ................ .`.......p...S.. ................ ....<........... ................ ................ .........b..@... ................ .........text... .>.......@...... ............ ..` .data...L....P.. .....P.......... ....@....idata.. .....`.......`.. ............@..@ .rsrc....`...p.. .`...p.......... ....@..@.reloc.. ................ ............@..B

Example 1 - Converting raw offset 7800h to a virtual offset: At first we have to find out in which section raw offset 800h lies. The section definitions start F8h bytes after the PE header start, in our case 178h. It is this part:
00000170 00000180 00000190 000001A0 000001B0 000001C0 000001D0 000001E0 000001F0 9C3E 0000 2E64 0010 0000 E80D 0000 2E72 0000 0000 6174 0000 0000 0000 0000 7372 0010 0000 6100 0050 4000 0060 0000 6300 0000 0000 0000 0000 00C0 0000 0000 0000 2E74 0040 0000 4C08 0000 2E69 0010 0000 0060 6578 0000 0000 0000 0000 6461 0000 0000 0000 7400 0010 2000 0050 0000 7461 0060 4000 0070 0000 0000 0060 0000 0000 0000 0000 0040 0000 .........text... .>.......@...... ............ ..` .data...L....P.. .....P.......... ....@....idata.. .....`.......`.. ............@..@ .rsrc....`...p..

00000200 00000210 00000220 00000230

0060 0000 9C0A 0000

0000 0000 0000 0000

0070 4000 00D0 0000

0000 0040 0000 0000

0000 2E72 0010 0000

0000 656C 0000 0000

0000 6F63 00D0 4000

0000 0000 0000 0042

.`...p.......... ....@..@.reloc.. ................ ............@..B

The colored values tell us the following values: Name of the Section Virtual Size Virtual Offset Raw Size Raw Offset .text 3E9C 1000 4000 1000 .data 84C 5000 1000 5000 .idata DE8 6000 1000 6000 .rsrc 6000 7000 6000 7000 .reloc A9C D000 1000 D000 The section definitions should be easy to understand. If not - or if you want deeper knowledge about it - consult a PE reference. The orange coloured values are not of interest for the conversion but have other functions. We want to convert raw offset 7800h. It seems obvious that this offset lies in the .rsrc section because it starts at 7000h (Raw Offset) and is 6000h bytes long (Raw Size). Offset 7800h is located 800h bytes after the section starts in the file. Since the sections are copied to the memory just like they are in the file, this address will be found 800h bytes after the section starts in memory (7000h; Virtual Offset). The offset we search is at 7800h. This is absolutely not common that the raw offset equals the virtual offset (without imagebase). In this case it is only because the sections start at the same offset in memory and in the file. The general formula is: RawOffsetYouHaveRawOffsetOfTheSection+VirtualOffsetOfTheSection+ImageBaseOfTheFile The ImageBase is another value you can read from the PE header: It is a DWORD value 34h bytes after the PE header begins. In our case it is 400000h (like in 99.9% of all Win9X EXE files). The conversion from a virtual offset to a raw offset just goes the other way round: The general formula is: VirtualOffsetYouHave-ImageBase-VirtualOffsetOfSection+RawOffsetOfSection For 40A000 that is: 40A000-400000-7000+7000 = A000

Introduction to the IAT by coding an Imports Viewer


And one more article that deals with the Win32 PE format. Yes, I really work much with it ;) Anyway, this article is only a by-product of a project I am currently working on and that might provide a very interesting topic for another article. Although the Import Address Table, to reveal what IAT means for the reader that did not yet dig too much inside the PE format, and generally Imports of Win32 PE files, are very easy to learn with some good references I thought it might by a good idea to present a small description and some source code that uses the IAT in some way. Obviously, there is not much to do with the an Import Table and as an introduction a simple sourcecode for a viewer that shows which DLLs and which functions from those DLLs an EXE uses should be enough. As usual, I coded my small PE related tools in Win32 assembly (MASM) but this time - after I saw many ppl claiming that using high level syntax makes the source to C-like - I tried to avoid the high level MASM syntax and use just plain x86 commands (at least for most parts). The resulting sourcecode was better structured but lost readability because the API calls can't be read as easily as when I had used a C like calling syntax. I will switch back to my old style at the next program ;) Alright, let's start with a small introduction about the IAT, it's location, it's use and it's method of working. Location: 0x80 bytes after the PE header started is a DWORD pointer (RVA) to the beginning of the IAT. The next DWORD (PE+0x84) is the length of the IAT. As a reference I use my version of calc.exe (German/V5.001764.1/Win98). In the following hex dump, the beginning of the PE file is marked red and the beginning of the IAT and it's length is green. Use: When a program needs functions from resources (mostly DLL files) it can't rely on the hope that the current system has the same version of that DLL and all offsets for the functions it needs are the same. Relocated DLLs and other Image Bases on different operating systems (Win9X group vs. WinNT group) are other problems why it is not possible to resolve the offsets of the functions while compiling a program. Another way must be found. Method of Work: When the EXE file is loaded into memory, the Windows EXE loader parses the IAT, loads needed DLL files into the memory and resolves the offsets of the functions inside these DLLs. These offsets are written to the IAT and all function calls to - statically loaded - DLL files are redirected through this function address table.
000000C0 000000D0 000000E0 000000F0 00000100 00000110 00000120 00000130 00000140 0000 B4AF 0B01 E019 0010 0400 1F0E 0000 0000 0000 FD34 050C 0100 0000 0000 0200 1000 0000 0000 0000 001C 0010 0010 0000 0200 0010 0000 0000 0000 0100 0000 0000 0000 0000 0000 0000 5045 0000 0032 0030 0500 0070 0000 0000 2020 0000 0000 0000 0100 0000 0100 0400 0000 0100 4C01 E000 0000 0000 0500 0006 0010 1000 8C00 0300 0F03 0000 0001 0000 0000 0000 0000 0000 ........PE..L... ...4............ .........2...... .........0...... ................ .........p...... ................ ................ ........ ......

Here you can see that the beginning of the IAT is at relative virtual offset 12020 and the length is 8C. To convert the relative virtual offset to a raw offset, please see article 4 of the Programming Journal #1. For calc.exe that does not matter because here the virtual offset is the same as the raw offset. When you look at this offset in a hexeditor you see
00012020 00012030 00012040 00012050 00012060 00012070 00012080 00012090 000120A0 9C21 F010 BA23 FFFF B3C2 BC20 1010 022A 0000 0100 0000 0100 FFFF 1F37 0100 0000 0100 0000 F248 4021 9410 2E24 FFFF 72C2 A421 F810 0000 2737 0100 0000 0100 FFFF 3E35 0100 0000 0000 FFFF 1520 AC20 0010 FA25 FFFF E046 0000 0000 FFFF 2F37 0100 0000 0100 FFFF 2737 0000 0000 A222 FFFF E046 C820 1C10 2626 FFFF 0000 0100 FFFF 2737 0100 0000 0100 FFFF 0000 .!...H'7.....".. ....@!... /7.... .#....... ...F'7 .....$....... .. ...7.....%...... . ..r.>5....&&.. .....!...F'7.... .*.............. ............

This is the "complete" IAT header, starting at offset 12020h and ending 8Ch bytes later. Obviously this is not everything you need for resolving the DLL functions. An IAT has to specify which DLLs to load, which function offsets to resolve and where to write the result. This is done in the part presented above and which is specified in the PE header. The pointers to the DLL and function names and the buffers to write the result to are specified in this part which is build like this:
typedef struct tagImportDirectory { DWORD dwRVAFunctionNameList; DWORD dwUseless1; DWORD dwUseless2; DWORD dwRVAModuleName; DWORD dwRVAFunctionAddressList; }IMAGE_IMPORT_MODULE_DIRECTORY, * PIMAGE_IMPORT_MODULE_DIRECTORY;

(Taken from "The Portable Executable File Format from Top to Bottom" by Randy Kath) This struct defines a pointer to the names of the functions that need to be resolved (dwRVAFunctionNameList), a pointer to the name of the DLL where those functions are (dwRVAModuleName) and a pointer to a table where the Virtual Offsets of the resolved functions are stored (dwRVAFunctionAddressList). The struct is 20 bytes long and "repeats one after another for each imported module in the file" (taken from Randy Kath again, because of lacking English ;). When all DLL files are added 20 00-bytes are added to the end of the struct-array to indicate the end. In our example we have 6 imported DLL files and the 6 IMAGE_IMPORT_MODULE_DIRECTORIES start at 12020, 12034, 12048, 1205C, 12070 and 12084. This is actually enough information to write a simple viewer of the DLLs an EXE file loads and resolves the functions "by name". Another way to resolve functions is "by ordinal". This is the ID number of a function in a DLL. You can identify a function which should be imported "by ordinal" by looking at the highest bit of the of the pointer to the name of the dwRVAFunctionNameList table. When the highest bit is set, this value is no pointer to the function name but the ordinal ID of the function (without the highest bit; 80000045 would be ID 00000045). This behaviour is similar to the one in the resource section and loading resources by name or by ID as presented in Programming Journal 1. The rest of calc.exe's import section:
000120A0 000120B0 000120C0 000120D0 000120E0 000120F0 00012100 00012110 00012120 00012130 00012140 00012150 00012160 00012170 00012180 00012190 000121A0 000121B0 000121C0 000121D0 000121E0 000121F0 00012200 00012210 00012220 0000 0A24 1626 8424 C425 8425 4C25 1225 D824 AA24 0E23 D822 A623 6623 2C23 0423 0000 6626 AA26 EA26 2C27 6E27 AC27 EC27 3028 0000 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0000 0100 0100 0100 0100 0100 0100 0100 0100 0000 FC23 0000 5424 B225 7625 3E25 0225 C224 B624 FA22 EE23 9423 5223 2423 F022 3026 7E26 BE26 FE26 3E27 8427 BA27 FA27 3C28 0000 0100 0000 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0000 0000 6624 E825 A225 6A25 3025 F224 3C24 9624 AE22 D623 8623 4623 1623 0000 3E26 8826 D026 1227 4E27 9227 CC27 1028 5028 0000 0000 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0000 0100 0100 0100 0100 0100 0100 0100 0100 0100 1E24 0826 7624 D425 9225 6225 2025 E424 4824 0000 C422 C623 7623 3623 E622 9422 5226 9C26 DE26 2027 5C27 9C27 DC27 2028 5C28 0100 0100 0100 0100 0100 0100 0100 0100 0100 0000 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 0100 .............$.. .$...#.......&.. .&......f$..v$.. .$..T$...%...%.. .%...%...%...%.. .%..v%..j%..b%.. L%..>%..0%.. %.. .%...%...$...$.. .$...$..<$..H$.. .$...$...$...... .#..."..."...".. ."...#...#...#.. .#...#...#..v#.. f#..R#..F#..6#.. ,#..$#...#...".. .#...".......".. ....0&..>&..R&.. f&..~&...&...&.. .&...&...&...&.. .&...&...'.. '.. ,'..>'..N'..\'.. n'...'...'...'.. .'...'...'...'.. .'...'...(.. (.. 0(..<(..P(..\(..

00012230 00012240 00012250 00012260 00012270 00012280 00012290 000122A0 000122B0 000122C0 000122D0 000122E0 000122F0 00012300 00012310 00012320 00012330 00012340 00012350 00012360 00012370 00012380 00012390 000123A0 000123B0 000123C0 000123D0 000123E0 000123F0 00012400 00012410 00012420 00012430 00012440 00012450 00012460 00012470 00012480 00012490 000124A0 000124B0 000124C0 000124D0 000124E0 000124F0 00012500 00012510 00012520 00012530 00012540 00012550 00012560 00012570 00012580 00012590 000125A0 000125B0 000125C0 000125D0 000125E0 000125F0 00012600

6A28 AA28 EE28 3829 7A29 C029 0000 4100 5F43 6F6E 4861 6F6C B402 7265 5F65 7465 636D 6E61 6D00 6865 6664 6D6F 6465 7479 6861 2E64 4158 696E 5F63 6743 7565 5265 5641 7472 7265 496E 6C6C 6F63 4C69 6C65 6C65 0000 654F 6E74 0000 0000 0000 9301 9001 476C 6974 4100 636D 6565 6300 6300 7400 7373 7941 616E 7274 3332

0100 0100 0100 0100 0100 0100 0000 5348 7878 0000 6E64 6F67 7374 7600 7869 7200 646C 7267 8200 7272 6976 6465 0000 7065 6E64 6C6C 405A 666F 6F6E 6C6F 7279 674F 5049 6370 6500 7441 6F63 0000 6E65 5374 6E41 CE02 626A 0000 4A00 3100 1B00 476C 476C 6F62 6550 9602 7041 0000 8F01 8201 3E01 0000 0000 646C 7570 2E64

7A28 BC28 0229 4A29 9829 CE29 6E00 454C 5468 4900 6C65 0000 7263 9502 7400 4602 6E00 7300 5F5F 0000 0000 0000 8000 0000 6C65 0000 0000 4040 7472 7365 5661 7065 3332 7941 4701 0000 0000 CA00 4100 7269 0000 5761 6563 2B02 4372 4372 436C 6F62 6F62 616C 726F 536C 0000 8101 476C 476C 4765 C201 2601 6541 496E 6C6C

0100 0100 0100 0100 0100 0100 5368 4C33 726F 5F5F 7200 D102 6872 6D65 4800 6578 5800 0C01 7365 9B00 6900 6E00 5F5F C700 7233 1000 0E00 5541 6F6C 4B65 6C75 6E4B 2E64 0000 4765 CF01 C801 4765 4B01 6E67 F902 6974 7400 5265 6561 6561 6F73 616C 616C 4C6F 6669 6565 8801 476C 6F62 6F62 7450 4C6F 4765 0000 666F 0000

8628 D028 0E29 5C29 A829 E029 656C 322E 7745 4378 4200 746F 0000 6D6D 5F58 6974 5F5F 5F69 7475 5F61 5F5F 5F5F 7365 5F65 0000 3F3F 3F3F 4540 6670 7900 6545 6579 6C6C CC01 7450 4C6F 4C6F 7443 4765 4100 6C73 466F 6502 7365 7465 7465 6548 556E 5369 636B 6C65 7000 476C 6F62 616C 616C 726F 6164 744D 5001 4100 CD01

0100 0100 0100 0100 0100 0100 6C41 646C 7863 7846 5F45 7570 C501 6F76 6370 0000 6765 6E69 7365 646A 705F 705F 745F 7863 4D53 3340 3174 585A 0000 7B01 7841 4578 0000 4C6F 726F 6361 6361 6F6D 7450 0803 7472 7253 5365 7445 5468 4576 616E 6C6F 7A65 0000 5374 FC02 6F62 616C 5265 436F 6341 4C69 6F64 4765 4B45 5365

9828 E028 2029 6829 B429 F029 626F 6C00 6570 7261 485F 7065 5F73 6500 7446 8D00 746D 7474 726D 7573 5F63 5F66 6170 6570 5643 5941 7970 0000 5B01 5265 0000 4100 0203 6361 6669 6C52 6C41 6D61 726F 6C73 6361 696E 7445 7665 7265 656E 646C 636B 0000 ED02 7269 6C73 616C 416C 416C 6D70 6464 6272 756C 7453 524E 7442

0100 0100 0100 0100 0100 0100 7574 4100 7469 6D65 7072 7200 7472 D000 696C 5F61 6169 6572 6174 745F 6F6D 6D6F 705F 745F 5254 5850 655F B400 5265 6751 7201 4144 6C73 6C46 6C65 6541 6C6C 6E64 6669 7472 7441 676C 7665 6E74 6164 7441 6500 0000 8C01 5772 6E67 7472 4672 6C6F 6C6F 6163 7265 6172 6548 7461 454C 6B43

j(..z(...(...(.. .(...(...(...(.. .(...)...).. ).. 8)..J)..\)..h).. z)...)...)...).. .)...)...)...).. ....n.ShellAbout A.SHELL32.dll.A. _CxxThrowExcepti on..I.__CxxFrame Handler.B._EH_pr olog....toupper. ..strchr...._str rev...memmove... _exit.H._XcptFil ter.F.exit...._a cmdln.X.__getmai nargs..._initter m...__setusermat herr...._adjust_ fdiv..i.__p__com mode..n.__p__fmo de....__set_app_ type...._except_ handler3..MSVCRT .dll....??3@YAXP AX@Z....??1type_ info@@UAE@XZ.... _controlfp..[.Re gCloseKey.{.RegQ ueryValueExA..r. RegOpenKeyExA.AD VAPI32.dll....ls trcpyA....LocalF ree.G.GetProfile IntA....LocalReA lloc....LocalAll oc....GetCommand LineA.K.GetProfi leStringA...lstr lenA....lstrcatA ....WaitForSingl eObject.e.SetEve nt..+.ResetEvent ..J.CreateThread ..1.CreateEventA ....CloseHandle. ..GlobalUnlock.. ..GlobalSize.... GlobalLock....Wr iteProfileString A...Sleep...lstr cmpA....GlobalFr ee....GlobalAllo c...GlobalReAllo c...GlobalCompac t.>.GetProcAddre ss....LoadLibrar yA..&.GetModuleH andleA..P.GetSta rtupInfoA.KERNEL 32.dll....SetBkC

00012610 00012620 00012630 00012640 00012650 00012660 00012670 00012680 00012690 000126A0 000126B0 000126C0 000126D0 000126E0 000126F0 00012700 00012710 00012720 00012730 00012740 00012750 00012760 00012770 00012780 00012790 000127A0 000127B0 000127C0 000127D0 000127E0 000127F0 00012800 00012810 00012820 00012830 00012840 00012850 00012860 00012870 00012880 00012890 000128A0 000128B0 000128C0 000128D0 000128E0 000128F0 00012900 00012910 00012920 00012930 00012940 00012950 00012960 00012970 00012980 00012990 000129A0 000129B0 000129C0 000129D0 000129E0

6F6C 6F6C BE01 4469 0000 7361 6541 4973 6F67 744D 4163 4372 AB01 4368 7374 4765 0000 9E01 7661 5570 5368 7444 4368 656D 0000 7457 6673 696E 7443 6162 7444 7465 8E00 5C01 2602 6563 2F02 7373 4D65 6961 7850 646F 6543 7443 D301 8D00 5472 0000 4465 3700 6D50 546F 5175 6E48 4D65 6970 696C 4374 7874 4301 4368 2B02

6F72 6F72 4D65 7370 8202 6765 6363 4368 4D65 6573 6365 6561 4C6F 6172 6572 7453 9A01 4C6F 6C69 6461 6F77 6C67 6563 0000 1C01 696E 6574 646F 6C69 6C65 6C67 4469 4465 4765 5365 6B52 5365 6167 7373 6C6F 6172 7754 6C69 6C69 4F70 4465 6163 A601 6657 4368 6F69 436C 6974 656C 6E75 626F 6162 726C 4100 4765 6563 5365

0000 0000 7373 6174 5472 0000 656C 696C 7373 7361 6C65 7465 6164 4E65 436C 7973 4C6F 6164 6461 7465 5769 4974 6B4D 4201 4765 646F 5265 7750 656E 5769 4974 616C 7374 7457 7443 6164 7446 6542 6167 6700 616D 6578 7062 7062 656E 7374 6B50 4C6F 696E 696C 6E74 6965 4D65 7041 4974 6172 6C65 4944 A500 7453 6B44 7444

F301 4744 6167 6368 616E 7F02 6572 6400 6167 6765 7261 5769 5374 7874 6173 436F 6164 4963 7465 5769 6E64 656D 656E 4765 744D 7750 6374 6F69 7452 6E64 656D 6F67 726F 696E 7572 696F 6F63 6565 6541 9300 4100 7441 6F61 6F61 436C 726F 6F70 6164 646F 6457 0000 6E74 7373 0000 656D 6446 0000 0000 4472 7973 6C67 6C67

5365 4933 6542 4D65 736C 5472 6174 8801 6541 4100 746F 6E64 7269 4100 7345 6C6F 4375 6F6E 5265 6E64 6F77 5465 7552 7453 656E 6F73 0000 6E74 6563 6F77 0000 5061 7957 646F 736F 4275 7573 7000 0000 4469 5E02 0000 7264 7264 6970 794D 7570 4D65 7750 696E 0A02 0000 6167 B500 0000 6F72 0101 AF00 6177 436F 4275 4974

7454 322E 6F78 7373 6174 616E 6F72 4973 0000 9601 7273 6F77 6E67 F301 7841 7242 7273 4100 6374 6F77 0000 7874 6164 7562 7500 0000 B901 7300 7400 0000 4F00 7261 696E 7752 7200 7474 0000 1402 B900 616C 5365 3C00 0000 4461 626F 656E 4D65 6E75 726F 646F 5363 E001 6500 456E 8601 6D61 4765 4472 4564 6C6F 7474 656D

6578 646C 4100 6167 654D 736C 4100 4469 2A01 4C6F 4100 4578 4100 5265 0000 7275 6F72 7A01 0000 0000 2C02 4100 696F 4D65 5B02 D201 4D61 F000 B700 0201 4372 6D41 646F 6563 3600 6F6E BD01 5365 456E 6F67 7457 436C F200 7461 6172 7500 6E75 4100 6341 7746 7265 506F A602 6162 4973 7441 7444 6177 6765 7200 6F6E 496E

7443 6C00 9500 6541 6573 6174 8501 616C 4765 6164 5900 4100 2500 6769 4401 7368 4100 496E 9102 6A02 5365 3500 4974 6E75 5365 4F66 7057 4765 456E 4765 6561 0000 7700 7400 4368 0000 4D65 6E64 6444 426F 696E 6F73 4765 0000 6400 7D02 4578 8400 0000 726F 656E 7374 5769 6C65 436C 7661 6C67 5465 0000 3300 0000 7400

olor....SetTextC olor..GDI32.dll. ..MessageBoxA... DispatchMessageA ....TranslateMes sage....Translat eAcceleratorA... IsChild...IsDial ogMessageA..*.Ge tMessageA...Load AcceleratorsA.Y. CreateWindowExA. ..LoadStringA.%. CharNextA...Regi sterClassExA..D. GetSysColorBrush ....LoadCursorA. ..LoadIconA.z.In validateRect.... UpdateWindow..j. ShowWindow..,.Se tDlgItemTextA.5. CheckMenuRadioIt em..B.GetSubMenu ....GetMenu.[.Se tWindowPos....Of fsetRect....MapW indowPoints...Ge tClientRect...En ableWindow....Ge tDlgItem..O.Crea teDialogParamA.. ..DestroyWindow. \.GetWindowRect. &.SetCursor.6.Ch eckRadioButton.. /.SetFocus....Me ssageBeep...Send MessageA....EndD ialog...DialogBo xParamA.^.SetWin dowTextA..<.Clos eClipboard....Ge tClipboardData.. ..OpenClipboard. ..DestroyMenu.}. TrackPopupMenuEx ....LoadMenuA... DefWindowProcA.. 7.ChildWindowFro mPoint....Screen ToClient....Post QuitMessage...Wi nHelpA....Enable MenuItem....IsCl ipboardFormatAva ilable....GetDlg CtrlID....DrawTe xtA...DrawEdge.. C.GetSysColor.3. CheckDlgButton.. +.SetDlgItemInt.

000129F0 5E01 4765 7457 696E 646F 7754 6578 7441 ^.GetWindowTextA 00012A00 0000 5553 4552 3332 2E64 6C6C 0000 0000 ..USER32.dll....

I will use the API function CheckDlgButton as an example how to parse the import table and after that I present the source of my small IAT viewer. CheckDlgButton is located in USER32.DLL (offset 12A002). Now we have to find a dwRVAModuleName value which points to this offset. It's obvious that an import viewer does not use the way I use here to get the values of CheckDlgButton, but parses the IAT from 1st function/1st DLL1 to last function/last DLL. For finding the pointer to the pointer to USER32.DLL you start at the beginning of the IAT+12 (because dwRVAModuleName is the 4th DWORD which in the struct) and if the DWORD value there does not match 12A002 you look 20 bytes (sizeof(tagImportDirectory)) ahead and so on. The offset we need is marked red in the first IAT dump (offset 12090). Eight bytes earlier you can find the value of dwRVAFunctionNameList:121A4 (marked green). At this offset starts a table of DWORD values which point to the different functions which were imported from USER32.DLL. Now you have to check for a DWORD value which points to 129CE because the definition of the functionname does not start at the first char of the functionname but 2 bytes before. Those two bytes are the so called "hint". When the loader tries to resolve the imported functions it uses the hint - an index into the DLL's export table - to resolve the correct offset and when this fails it uses the binary string. At offset 12284 you can find the pointer to the hint of CheckDlgItem. When the exe loader has looked the functions up, it writes the resolved address to a offset which is defined in the dwRVAFunctionAddressList. For USER32.DLL it uses the RVA 10F8. This offset is not part of the code section, but of the section which contains the code of calc.exe.
000010F0 00001100 00001110 00001120 6616 6957 3D57 3E49 CE7F F5BF F5BF F5BF 0000 A257 1D13 1D2F 0000 F5BF F5BF F5BF 2E41 A820 715C 734C F5BF F5BF F5BF F5BF 3347 FC57 8C55 0F5A F5BF F5BF F5BF F5BF f........A..3G.. iW...W... ...W.. =W......q\...U.. >I.../..sL...Z..

Here you can see that the offsets calc.exe expects the imported functions to be are already written to the dwRVAFunctionAddressList. This is called "bound imports" because a small utility (normally bind.exe) does at once what the loader would do when the EXE file gets loaded. It resolves the API functions' offsets and writes them to the dwRVAFunctionAddressList. This works fine until the user has a different version of the DLL file. In that case the EXE loader has to to the work again (with the help of the dwRVAFunctionNameList). That's the reason why in most cases another way - the standard way - of import binding is used. In this way the dwRVAFunctionAddressList is nothing more but a copy of the dwRVAFunctionAddressList table. So, that is enough for a simple IAT viewer. I present the MASM source for a very simple one which only parses the DLL names and the corresponding function names. With the information above you can easily extend it to display the dwRVAFunctionAddressList, the hint or the way of imports binding. I will only copy the important code here, the rest can be downloaded.
;; --- User clicked the button - Show the OpenFile dialog --mov ofn.lStructSize, sizeof ofn push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov byte ptr [buffer], 0 mov ofn.nMaxFile, 256 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY push OFFSET ofn call GetOpenFileName ;; --- If not file was selected, return --test eax, eax jz @RETURN push call hFile CloseHandle

;; --- Open the selected file with read access --push NULL push FILE_ATTRIBUTE_NORMAL push OPEN_EXISTING push NULL push FILE_SHARE_READ push GENERIC_READ push OFFSET buffer call CreateFileA test eax, eax jnz @FILEOPENED ;; --- Show message that something did not work --push MB_OK push OFFSET caption push OFFSET cantopen push hWin call MessageBoxA ret @FILEOPENED: ;; --- If file was opened correctly, go on with checking if it's a valid PE file --mov hFile, eax push hFile call CheckForPEFileAndGetBeginningOfImportSection ;; Return Beginning of import ;; section in eax push hFile push eax call ConvertRVAToFileOffset mov beginiat, eax mov lengthiat, ebx test eax, eax jnz @VALIDPE ;; --- Show message that it is no valid PE file --push MB_OK push OFFSET caption push OFFSET nopeorimp push hWin call MessageBoxA ret @VALIDPE: ;; --- Check if the file is big enough to hold an IAT --push 0 push hFile call GetFileSize add ebx, beginiat cmp ebx, eax jbe @CORRECTFILESIZE ;; --- If the filesize can't be of a correct PE file, show a message --push MB_OK push OFFSET caption push OFFSET nope push hWin call MessageBoxA ret @CORRECTFILESIZE: ;; --- Allocate memory for reading the file --mov ebx, eax push MEM_RELEASE push 0 push allocbuff call VirtualFree push PAGE_READWRITE push MEM_COMMIT

;;

;;

;; ;; ;;

;;

push ebx push 0 call VirtualAlloc mov allocbuff, eax --- Set the file pointer and read the complete file --push FILE_BEGIN push 0 push 0 push hFile call SetFilePointer push 0 push OFFSET buffer2 push ebx push allocbuff push hFile call ReadFile mov esi, allocbuff --- ESI points to the beginning of the IAT of the loaded file --add esi, beginiat mov edi, OFFSET RVA1buffer xor eax, eax jmp @PARSELOOP --- The following loop extracts the dwRVAFunctionNameList to RVAbuffer1, the --- dwRVAModuleName to RVA2buffer and --- the dwRVAFunctionAddressList to RVA3buffer @BEGINOFPARSELOOP: mov edi, OFFSET RVA1buffer add edi, eax movsd add esi, 8 mov edi, OFFSET RVA2buffer add edi, eax movsd mov edi, OFFSET RVA3buffer add edi, eax movsd shr eax, 2 inc eax shl eax, 2 @PARSELOOP: cmp dword ptr [esi+16], 0 jnz @BEGINOFPARSELOOP --- Terminate all RVAxbuffers with a 0 DWORD --mov edi, OFFSET RVA1buffer add edi, eax mov dword ptr [edi], 0 mov edi, OFFSET RVA2buffer add edi, eax mov dword ptr [edi], 0 mov edi, OFFSET RVA3buffer add edi, eax mov dword ptr [edi], 0

;; --- Empty the listbox --mov esi, OFFSET RVA2buffer push 0 push 0 push LB_RESETCONTENT push hwndList call SendMessage jmp @FILLLOOP

;; --- This loop writes the ModuleNames in the 1st listbox --@BEGINOFFILLLOOP: mov eax, dword ptr [esi] push hFile push eax call ConvertRVAToFileOffset add eax, allocbuff push eax push 0 push LB_ADDSTRING push hwndList call SendMessage add esi, 4 @FILLLOOP: cmp dword ptr [esi], 0 jnz @BEGINOFFILLLOOP mov esi, dword ptr [RVA1buffer] push 0 push 0 push LB_SETCURSEL push hwndList call SendMessage ret @ID_LIST: cmp ax, ID_LIST1 jnz @RETURN shr eax, 16 cmp ax, LBN_SELCHANGE jnz @RETURN ;; --- User selects another DLL file --mov esi, OFFSET RVA1buffer push 0 push 0 push LB_GETCURSEL push hwndList call SendMessage shl eax, 2 add esi, eax mov esi, [esi] ;; ESI = dwRVAFunctionNameList push hFile push esi call ConvertRVAToFileOffset mov esi, eax add esi, allocbuff push 0 push 0 push LB_RESETCONTENT push hwndList2 call SendMessage jmp @DISP2LOOP @BEGINOFDISP2LOOP: ;; Write Functionnames to 2nd DLL mov eax, dword ptr [esi] push hFile push eax call ConvertRVAToFileOffset test eax, eax jnz @ImpByName ;; Jump if eax is not 0 (offset exists in file) - if not, ;; ImpByOrd mov eax, [esi] and eax, 7FFFFFFFh push eax

push OFFSET conv push OFFSET buffer call wsprintfA push OFFSET buffer push 0 push LB_ADDSTRING push hwndList2 call SendMessage ;; Give out "ImpByOrd(x)" add esi, 4 jmp @DISP2LOOP @ImpByName: add eax, allocbuff add eax, 2 push eax push 0 push LB_ADDSTRING push hwndList2 call SendMessage ;; Give out function name add esi, 4 @DISP2LOOP: cmp dword ptr [esi], 0 jnz @BEGINOFDISP2LOOP @RETURN: xor eax, eax ret MainWndProc endp

Rheingold

Coding a small PE wrapper


A former issue of the Assembly Language Journal was about a wrapper for DOS-EXE and COM files which encrypts the code of the executables and adds a command line where you enter the password to decrypt the file again at runtime and run it. For this issue of the Programming Journal I decided to show the source for something quite similar: A small program which encrypts the code of a PE-EXE file and displays a dialog where you can enter a password when you start the program after it has been wrapped. For DOS-EXE and especially COM files coding such a wrapper was quite easy compared to a PE-wrapper, because you have to make changes to the PE header to keep the program Win2K compatible and to add a section for the code. Additionaly you have to add DLL files and API functions to the import table so that you can use them in the code you add to the EXE file in for showing the dialog and reading the password. I'll proceed by showing the source I wrote, explaining my thoughts and the source. Let's get more detailed. In order to accomplish that task we have to add a new section which should contain the code to display and process the DialogBox where the user can enter the password. The definition of the section must be added to the PE header. Then we have to make sure that the API functions (in our case DialogBoxIndirectParamA, GetDlgItemTextA and EndDialog which are all in USER32.DLL) we want to use must be available from inside the encrypted file. This means we have to change the import table and add both, the DLL and the API functions. The resource data for the dialog must be added to the new section, too. Same for the code which decrypts the file when the user entered the correct password. To provide NT compatibility we have to make some other changes, to the PE header, too. But first of all we have to check if the file is a valid PE file... This example has no user interface. This means that the password can only be changed when you recompile the file.
.386 .model flat, stdcall option casemap:none AddSection AddSectionToFile AddSectionToHeader ChangeFile ConvertRVAToFileOffset ConvertRVAtoSection CreateIAT Encrypt40Bytes FillNewSectionWithData GetNewRVAandRaw GetOldIAT SetNewIAT ValidatePE include include include include PROTO PROTO PROTO PROTO PROTO PROTO PROTO PROTO PROTO PROTO PROTO PROTO PROTO :DWORD :DWORD, :DWORD, :DWORD :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD

:DWORD, :DWORD, :DWORD :DWORD :DWORD :DWORD :DWORD, :DWORD, :DWORD, :DWORD :DWORD,

:DWORD, :DWORD, :DWORD :DWORD :DWORD :DWORD, :DWORD

\masm32\include\kernel32.inc \masm32\include\comdlg32.inc \masm32\include\windows.inc \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib

The beginning is the standard code of nearly all of my MASM files: Setting up assembler options, defining

prototypes of functions which I explain later and including the different libraries I need for the API functions I need. Now the data the program needs is defined.
.data ;--- Needed for main function FilterString db "EXE",0,"*.exe",0,"DLL",0,"*.exe",0,"All file (*.*)",0,"*.*",0,0 ;; Filter for OpenFile-Box hFile dd 0 ;; File-Handle of opened file hInstance HINSTANCE 0 ;; HINSTANCE of the Program ofn OPENFILENAME <> ;; Used for the Open-FileBox ;; --- Needed for procedures Global variables needed in different procedures allocbuffer dd 0 ;; allocbuffer will point to the PE header of the file which ;; is completely read allocbuffer2 dd 0 ;; allocbuffer2 will point to the MZ header of the file ;; which is completely read cantopen db "File can not be opened - maybe it is already in use",0 ;; ;; Error message caption db "PEPass",0 ;; Name of the program dllname db "USER32.DLL",0,0 ;; The DLL files which need to be added to ;; the files IAT filesize dd 0 ;; filesize of the opened file funcnames db "MessageBoxA",0,"DialogBoxIndirectParamA",0,"GetDlgItemTextA",0,"EndDialog",0,0,0,0 ;; The API functions which need to be added to the files IAT nopefile db "This file is no valid PE file",0 ;; Error message password db "Password",0 ;; Password for encrypting the EXE file .data? ;; --- Needed for procedures buffer db 512 dup(?) ;; buffer for several purposes .code start: invoke mov

GetModuleHandle, 0 hInstance, eax

mov ofn.lStructSize, sizeof ofn push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov byte ptr [buffer], 0 mov ofn.nMaxFile, 256 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, OFFSET ofn ;; Show OpenFile-Box .IF eax==0 ;; If User selected no file ret .ENDIF invoke CreateFileA, ADDR buffer, GENERIC_READ or GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 .IF eax==-1 ;; If opening the file with Read/Write Access failed invoke MessageBoxA, 0, ADDR cantopen, ADDR caption, MB_OK ret .ENDIF mov hFile, eax invoke ChangeFile, hFile ;; Do all kind of things to the selected file invoke VirtualFree, allocbuffer, 0, MEM_RELEASE invoke CloseHandle, hFile invoke ExitProcess, 0 ;;

-----------------------------------------------------------------------------------------------------;; | ChangeFile validates the PE header and if the file is a PE file it calls AddSection ;; to wrap the file| ;; -----------------------------------------------------------------------------------------------------ChangeFile proc hfile:DWORD invoke ValidatePE, hfile .IF eax==0 ;; If file is no valid PE file invoke MessageBoxA, 0, ADDR nopefile, ADDR caption, MB_OK ret .ENDIF invoke AddSection, hfile ret ChangeFile endp ;; -----------------------------------------------------------------------------------------------------;; | ValidatePE takes the handle of the file as parameter, returns the filesize in the ;; global variable | ;; | filesize and a pointer to a buffer which holds the complete file in the global ;; variable allocbuffer | ;; | It returns 0 if the file is no valid PE file, otherwise it returns a pointer to the ;; beginning of the| ;; | PE header (in allocbuffer). ;; ;; | I validate by checking if the file is bigger than 40h bytes, get the beginning of ;; the PE header at | ;; | DWORD 3Ch and check if this offset exists in the file and if the DWORD at the ;; beginning of the PE | ;; | header is "PE"\0\0 ;; -----------------------------------------------------------------------------------------------------ValidatePE proc hfile:DWORD invoke GetFileSize, hfile, 0 ;; Get the filesize mov filesize, eax ;; Global variable filesize invoke VirtualAlloc, 0, eax, MEM_COMMIT, PAGE_READWRITE ;; Allocate enough memory to ;; read the file mov allocbuffer, eax ;; Global variable allocbuffer which will point to the PE ;; header invoke ReadFile, hfile, allocbuffer, filesize, ADDR buffer, 0 ;; Read complete file mov eax, allocbuffer mov allocbuffer2, eax ;; Global variable allocbuffer2 which will point to the MZ ;; header .IF filesize<40h ;; If filesize is less than 40h (at DWORD(3Ch) of the MZ header is ;; a pointer to the PE header) xor eax, eax ret .ENDIF mov esi, allocbuffer mov eax, dword ptr [esi+3Ch] ;; Get beginning of PE header .IF eax>filesize ;; If this offset is not in the file xor eax, eax ret .ENDIF add esi, eax ;; Point to PE header mov eax, [esi] ;; Get the DWORD at the beginning of the PE header .IF dword ptr [esi]!='EP' ;; If this DWORD is not "PE"\0\0 it's no PE file xor eax, eax

ret .ENDIF mov allocbuffer, esi ;; save pointer to PE header ret ValidatePE endp

Having validated if the file the user selected was a valid PE file we have to get the file's section alignment - the value how the sections are aligned in the file - and its section alignment - the value how sections are aligned in memory. Normally the file alignment is 200h, and the section alignment is 1000h but this is not necessary. After we have those values we can calculate the offsets where our new section must be added (GetNewRVAandRaw). Take care that you take the filesize for aligning the new section and not the last section's values. Some self extracting archives put compressed data at the end of itself and don't list those offsets in any section. For the section alignment you can take the values from the header.
;; -----------------------------------------------------------------------------------------------------;; | AddSection reads the Section Alignment and File Alignment values from the PE header ;; and calls | ;; | procedures that go on mainpulating the fille. ;; -----------------------------------------------------------------------------------------------------AddSection proc hfile:DWORD LOCAL filealignment:DWORD LOCAL sectionalignment:DWORD mov mov mov mov mov esi, allocbuffer eax, dword ptr [esi+38h] ;; dword [PE-Header + 38h] == Section Alignment sectionalignment, eax eax, dword ptr [esi+3Ch] ;; dword [PE-Header + 3Ch] == File Alignment filealignment, eax

invoke GetNewRVAandRaw, sectionalignment, filealignment invoke AddSectionToHeader, eax, ebx invoke AddSectionToFile, hfile, ebx, eax, filealignment ret AddSection endp ;; -----------------------------------------------------------------------------------------------------;; | GetNewRVAandRaw takes the section and file alignment of the file as params and ;; calculates the | ;; | addresses for the sections we need to add. The new section offset is returned in ;; eax, the new file | ;; | offset is returned in ebx. ;; -----------------------------------------------------------------------------------------------------GetNewRVAandRaw proc secalign:DWORD, filealign:DWORD LOCAL maxRaw:DWORD ;; Used to calculate the file offset of the new section LOCAL maxRVA:DWORD ;; Used to calculate the RVA of the new section LOCAL secsize:DWORD ;; size of last "old" section mov mov mov add .WHILE maxRVA, 0 secsize, 0 esi, allocbuffer ;; Point to beginning of the PE header esi, 0F8h ;; Point to beginning of the Section definitions dword ptr [esi]!=0 ;; This loop checks for the highest RVA already used in the ;; FILE

mov .IF mov mov mov .ENDIF add .ENDW mov add mov cdq idiv .IF sub mov add .ENDIF mov mov cdq idiv .IF sub mov add .ENDIF

eax, dword ptr [esi+0Ch] ;; Get RVA of section which is currently checked eax>maxRVA ;; If new RVA is bigger than biggest already known RVA maxRVA, eax ;; Save new biggest RVA eax, dword ptr [esi+08] ;; Get virtual size of section secsize, eax ;; save virtual size, too esi, 28h ;; point to beginning of new section eax, maxRVA ;; highest RVA eax, secsize ;; + Size of section = last RVA used by the program maxRVA, eax secalign ;; Divide by section alignment to check if padding bytes are needed edx!=0 ;; If section is not aligned (very unlikely) maxRVA, edx ;; Align new section correctly... edx, secalign maxRVA, edx eax, filesize ;; Do the same aligning for the file offset of the new section maxRaw, eax filealign edx!=0 ;; If section is not (file) aligned (quite likely) maxRaw, edx ;; Align new section correctly... eax, filealign maxRaw, eax

mov eax, maxRVA ;; Return new section offset in eax mov ebx, maxRaw ;; Return new file offset in ebx ret GetNewRVAandRaw endp

Now we have to add the earlier calculated section values to the PE header. I don't check if there's still space left to add new section because normally there is space for plenty of sections. If you want to be sure that everything's fine you can calculate the difference between the lowest file offset which is mentioned in the section definition and the PE-Header start + F0h + Sectionnumber*28h. If it is bigger than 28h everything's OK and you can add the section to PE-Header start + F0h + (SectionNumber+1)*28h. I have chosen "HALB" as section name (actually I wanted to take BLAH but I did not pay attention to the fact that the DWORD will be written to the memory the Little Endian way ;) Section names can take up to 8 bytes and you can choose whatever you wish. I take constant values for the size of the new section because in this prog we only need to add one DLL and 3 API functions. This means there is plenty of space if the file we encrypt does not import more than 15 DLLs. (20 when I checked last time, but I added some more code) .
;; -----------------------------------------------------------------------------------------------------;; | AddSectionToHeader takes the RVA and the Raw Offset of the new section and writes it ;; to the file | ;; | (only in memory, so far) ;; -----------------------------------------------------------------------------------------------------AddSectionToHeader proc uses eax ebx newsec:DWORD, newfile:DWORD mov esi, allocbuffer ;; Point to PE header mov ax, word ptr [esi+6] ;; Read number of sections add esi, 0F8h ;; Point to beginning of section definition

.WHILE ax>0 ;; Loop through all sections and point at free space after the last section add esi, 28h dec ax .ENDW mov dword ptr [esi], 'BLAH' ;; Name of the new section mov dword ptr [esi+8], 1000h ;; Virtual Size of the new section mov eax, newsec mov [esi+12], eax ;; Virtual Offset of the new section mov dword ptr [esi+16], 400h ;; Raw Size of the new section mov eax, newfile mov [esi+20], eax ;; Raw Offset of the new section mov dword ptr [esi+36], 0C0000060h ;; Characteristics of the new section ret AddSectionToHeader endp

Now it is time to build the new section. As I mentioned earlier it will contain the new IAT, the resource definition for the dialog and the code for decrypting the file. The size is - as written to the header - 400h bytes. Before writing to the file the new section's starting raw offset is aligned to the file alignment once again.
;; -----------------------------------------------------------------------------------------------------;; | AddSectionToFile takes the handle of the file and the offset of the new section and ;; addes a 200h | ;; | bytes large section to the end of the file. ;; -----------------------------------------------------------------------------------------------------AddSectionToFile proc hfile:DWORD, newfile:DWORD, newsec:DWORD, secalign:DWORD LOCAL valloc:DWORD invoke VirtualAlloc, 0, 400h, MEM_COMMIT, PAGE_READWRITE ;; Allocate memory to create ;; the new section mov valloc, eax invoke FillNewSectionWithData, eax, newsec, hfile ;; Create the new section mov eax, filesize ;; Align the section's file offset again (this time not for the ;; header, but for the file) cdq idiv secalign mov eax, filesize .IF edx!=0 mov ebx, secalign sub ebx, edx add eax, ebx .ENDIF invoke SetFilePointer, hfile, eax, 0, FILE_BEGIN ;; Write new section to the end of the ;; file invoke WriteFile, hFile, valloc, 400h, ADDR buffer, 0 invoke VirtualFree, valloc, 0, MEM_RELEASE ;; Release the memory where the section was ;; created invoke SetFilePointer, hfile, 0, 0, FILE_BEGIN ;; Write the complete "old" file again ;; (with changed header and encrpytion) mov esi, allocbuffer2 invoke WriteFile, hFile, esi, filesize, ADDR buffer, 0 ret AddSectionToFile endp

Now it starts to get interesting. I will build the new section in this order: IAT, Dialog resource, code. So let's start with the IAT. Luckily we don't have to recalculate the old IAT of the file. All offsets inside the IAT are relative to the Imagebase of the file but absolute to the position of the IAT. This means no matter where the IAT is, the pointers are absolute and always point to the same offset. This is a difference to the resource section, for

example, where the pointers where relative to their position as mentioned in Programming Journal #1. This means we can just read the original IAT of the file and copy it to the beginning of the new section. After that we can append the new IAT parts (the API function we want to use) which we have to calculate. In the meantime we need some more PE header manipulations. Increasing the section value by 1 and the size of image value by 1000h and changing the entry point to the byte after new IAT. GetOldIAT returns a pointer to a buffer where the old IAT was copied to in edx. After that we have to encrypt the file. I decided to encrypt only the 40 bytes following the original entrypoint. This is enough for that example. If you want to protect better, use another algorithm (not ROR/ROL) and crypt the complete code section or even the complete file. After the IAT we have to add the DIALOGTEMPLATE which should be displayed. I created it with the help of the C++ source test.cpp which comes with the source.zip file of this essay and is a slighly changed example from MSDN. We have to make sure that the beginning of the dialog template will be at a DWORD aligned value and probably we had to DWORD align again for each control, but that does not apply here because I don't create the template dynamically and so it is already DWORD aligned. Now we have to add the code which should be executed when the user starts the protected EXE file. I already have a kind of skeleton for that code (starts at NewCode) and I just have to change the several values like pointes to buffers or positions of resolved API functions according to the virtual offset of the new section. For this we get write access (VirtualProtect) to our code section and change the values which are coded to this file using db/dw/dd. After the absolute values are changed, the code gets copied to the free space after the dialog template. Here is a dump of the code which is added.
:00000000 60 :00000001 9C :00000002 6A00 :00000004 6846514000 :00000009 6A00 :0000000B 68E0504000 :00000010 6A00 :00000012 B887504000 DialogBoxIndirectParamA :00000017 FF10 :00000019 9D :0000001A 61 :0000001B B800104000 :00000020 FFE0 :00000022 55 :00000023 8BEC :00000025 53 :00000026 56 :00000027 57 :00000028 837D0C10 WM_CLOSE, jump :0000002C 7513 :0000002E 6A00 :00000030 FF7508 :00000033 B88F504000 :00000038 FF10 :0000003A C6053F514000C3 original entry point :00000041 817D0C11010000 :00000048 0F8594000000 :0000004E 66817D10E803 :00000054 0F8588000000 :0000005A 6A1E :0000005C 68BA534000 :00000061 68E8030000 :00000066 FF7508 :00000069 B88B504000 :0000006E FF10 :00000070 BE00104000 pushad ;; Save all registers pushfd ;; Save all Flags push 00000000 push 00405146 push 00000000 push 004050E0 push 00000000 mov eax, 00405087 ;; Pointer to offset of call dword ptr [eax] ;; Call DialogBoxIndirectParamA popfd ;; Restore all flags popad ;; Restore all registers mov eax, 00401000 ;; Original Entrypoint jmp eax push ebp ;; Beginning of the dialog's WNDPROC mov ebp, esp push ebx push esi push edi cmp dword ptr [ebp+0C], 00000010 ;; If msg not jne 00000041 push 00000000 push [ebp+08] mov eax, 0040508F call dword ptr [eax] ;; EndDialog mov byte ptr [0040513F], C3 ;; Overwrite the jmp to the cmp dword ptr [ebp+0C], 00000111 ;; If not WM_COMMAND jne 000000E2 ;; jump cmp word ptr [ebp+10], 03E8 ;; If not edit field jne 000000E2 ;; jump push 0000001E push 004053BA push 000003E8 push [ebp+08] mov eax, 0040508B call dword ptr [eax] ;; GetDlgItemTextA mov esi, 00401000

:00000075 BFD8534000 :0000007A B90A000000 :0000007F A5 :00000080 49 :00000081 75FC :00000083 83EF28 :00000086 33C0 :00000088 BEBA534000 :0000008D 83F828 :00000090 7411 :00000092 40 :00000093 803E00 :00000096 74F0 :00000098 8A0E :0000009A D20F the buffer :0000009C 46 :0000009D 47 :0000009E 83F828 :000000A1 75EF :000000A3 33C0 :000000A5 B90A000000 :000000AA BFD8534000 :000000AF 0307 :000000B1 83C704 :000000B4 49 :000000B5 85C9 :000000B7 75F6 :000000B9 3D2453C2B1 :000000BE 7522 :000000C0 BF00104000 :000000C5 BED8534000 :000000CA B90A000000 :000000CF A5 :000000D0 49 :000000D1 75FC :000000D3 83EF28 :000000D6 6A00 :000000D8 FF7508 :000000DB B88F504000 :000000E0 FF10 :000000E2 33C0 :000000E4 5F :000000E5 5E :000000E6 5B :000000E7 8BE5 :000000E9 5D :000000EA C3

mov edi, 004053D8 mov ecx, 0000000A movsd ;; Load encrypted files to a buffer dec ecx jne 0000007F sub edi, 00000028 xor eax, eax mov esi, 004053BA cmp eax, 00000028 je 000000A3 inc eax cmp byte ptr [esi], 00 je 00000088 mov cl, byte ptr [esi] ror byte ptr [edi], cl ;; Decrypt encrypred bytes in inc esi inc edi cmp eax, 00000028 jne 00000092 xor eax, eax mov ecx, 0000000A mov edi, 004053D8 add eax, dword ptr [edi] ;; Get checksum add edi, 00000004 dec ecx test ecx, ecx jne 000000AF cmp eax, B1C25324 ;; Compare Checksum jne 000000E2 mov edi, 00401000 mov esi, 004053D8 mov ecx, 0000000A movsd ;; Copy decrypted bytes to the entry point dec ecx jne 000000CF sub edi, 00000028 push 00000000 push [ebp+08] mov eax, 0040508F call dword ptr [eax] ;; EndDialog xor eax, eax pop edi pop esi pop ebx mov esp, ebp pop ebp ret

;; -----------------------------------------------------------------------------------------------------;; | Fills the new section with code, imports and resources ;; -----------------------------------------------------------------------------------------------------FillNewSectionWithData proc valloc:DWORD, newsec:DWORD, hfile:DWORD LOCAL oldIAToffset:DWORD LOCAL oldIATsize:DWORD LOCAL imagebase:DWORD LOCAL entrypoint:DWORD LOCAL checksum:DWORD

;; -------- Now add the imports -------mov mov inc mov mov invoke invoke mov add mov mov add mov mov mov mov mov mov mov invoke mov mov invoke ;; add shr shl push add add esi, valloc ;; Beginning of new section edi, allocbuffer ;; Beginning of PE header word ptr [edi+6] ;; Increase number of sections in the PE header eax, dword ptr [edi+80h] oldIAToffset, eax ;; Save beginning of old IAT ConvertRVAToFileOffset, eax, hfile ;; Convert RVA of old IAT to file offset GetOldIAT, OFFSET dllname, valloc, eax, hFile ;; Read old IAT oldIATsize, eax ;; save size of old IAT edx, eax ;; Point to byte after the end of the old IAT in the new section eax, newsec [edi+50h], eax dword ptr [edi+50h], 1000h ;; Add 1000h to Size of Image value of PE header eax, [edi+34h] imagebase, eax ;; Get Imagebase from PE header eax, newsec esi, [edi+28h] entrypoint, esi [edi+28h], eax ;; Change Entrypoint dword ptr [edi+80h], eax ;; Store pointer to new IAT (beginning of new section) Encrypt40Bytes, esi, hFile, OFFSET password ;; Encrypt file checksum, eax ;; save checksum esi, valloc ;; pointer to beginning of section CreateIAT, edx, ADDR dllname, ADDR funcnames, newsec, oldIATsize ;; Create new ;; IAT (Add new DLLs and APIs to old section) Align eax to DWORD boundary eax, 3 eax, 2 eax, 2 eax ;; Save Pointer to DLGTEMPLATE [edi+28h], eax ;; Change Entrypoint esi, eax ;; Point to beginning of the dialog template

;; This is the DIALOGTEMPLATE - it is created using a C++ prog and a memory dump mov dword ptr [esi], DS_3DLOOK or DS_CENTER or WS_POPUP or WS_BORDER or WS_SYSMENU or WS_CAPTION mov dword ptr [esi+4], 0 mov word ptr [esi+8], 1 mov word ptr [esi+10], 10 mov word ptr [esi+12], 10 mov word ptr [esi+14], 100 mov word ptr [esi+16], 30 mov dword ptr [esi+18], 0 mov dword ptr [esi+22], 610050h mov dword ptr [esi+26], 730073h mov dword ptr [esi+30], 6F0077h mov dword ptr [esi+34], 640072h mov word ptr [esi+38], 0 mov WS_CHILD mov mov mov dword ptr [esi+40], ES_LEFT or ES_AUTOHSCROLL or WS_BORDER or WS_VISIBLE or dword ptr [esi+44], 0 word ptr [esi+48], 10 word ptr [esi+50], 11

mov word ptr [esi+52], 80 mov word ptr [esi+54], 10 mov word ptr [esi+56], 1000 mov word ptr [esi+58], 0FFFFh mov word ptr [esi+60], 81h mov dword ptr [esi+62], 0 ;; -------- Now add the code -------add dword ptr [edi+28h], 68 ;; Add sizeof(DlgTemplate) to entrypoint mov edi, esi add edi, 68 ;; Point to new entrypoint mov esi, NewCode ;; Beginning of code that needs to be added mov ebx, eax push push push push call mov sub add add add add mov mov add add mov mov add mov add mov add mov mov mov add add mov mov sub mov mov add mov mov mov mov add add mov 0 PAGE_READWRITE 60*4 NewCode VirtualProtect eax, @WNDPROCSTART eax, NewCode eax, newsec eax, 68 eax, ebx eax, imagebase dword ptr [@WNDPROC], eax ;; == WNDPROC param of DialogBoxIndirectParamA eax, newsec ;; Newsec eax, imagebase ;; + Imagebase eax, ebx ;; + Sizeof(IAT) dword ptr [@DLGTEMPLATE], eax ;; == Beginning of DLGTEMPLATE ebx, valloc ;; Beginning of old IAT ebx, oldIATsize ;; Point to beginning of new IAT ebx, [ebx+16] ;; Get RVAFunctionAddressList of 1st new API ebx, imagebase ;; + Imagebase dword ptr [@DIALOGBOXINDIRECTPARAMA], ebx ;; == Offset of address of ;; DialogBoxIndirectParamA ebx, 2*4 ;; == Offset of address of EndDialog (3rd API) dword ptr [@ENDDIALOG1], ebx ;; Store this value where it is needed dword ptr [@ENDDIALOG2], ebx eax, newsec ;; newsec eax, imagebase ;; + imagebase eax, 3BAh ;; + 3BAh == Buffer dword ptr [@PWDBUFFER1], eax ;; Store buffer offset where needed dword ptr [@PWDBUFFER2], eax ;; Store buffer offset where needed ebx, 1*4 ;; == Offset of address of GetDlgItemTextA (2nd API) dword ptr [@GETDLGITEMTEXTA], ebx ;; Store value where it is needed eax, entrypoint ;; old entrypoint (RVA) eax, imagebase ;; + imagebase = Entrypoint (VA) dword ptr [@ENTRYPOINT1], eax dword ptr [@ENTRYPOINT2], eax dword ptr [@ENTRYPOINT3], eax eax, imagebase ;; imagebase eax, newsec ;; + newsec eax, 3D8h ;; + 3D8h == 2nd Buffer dword ptr [@CRYPTBUFFER1], eax

mov mov mov mov

dword ptr [@CRYPTBUFFER2], eax dword ptr [@CRYPTBUFFER3], eax eax, checksum dword ptr [@CHECKSUM], eax ;; Checksum

pop ebx ;; Get Pointer to DLGTEMPLATE again mov eax, @RET sub eax, NewCode add eax, newsec add eax, 68 add eax, ebx add eax, imagebase mov dword ptr [@RETBYTE], eax ;; == byte where to place a "ret" when the user exits the wrapper mov ecx, 60 ;; Copy code to new section @Blah: movsd dec ecx jnz @Blah ret ;; Here starts the code which is added to the file which will be protected NewCode: pushad pushfd push 0 ;; push LPARAM dwInitParam db 68h ;; push DLGPROC lpDialogFunc @WNDPROC: dd 0 ;; Space for DialogFunction push 0 ;; push HWND hwndParent db 68h ;; push LPCDLGTEMPLATE hDialogTemplate @DLGTEMPLATE: dd 0 ;; Space for LPDialogTemplate push 0 ;; push HINSTANCE hInstance db 0B8h ;; mov eax, PointerToOffsetOfDialogBoxIndirectParamA @DIALOGBOXINDIRECTPARAMA: dd 0 dw 010FFh ;; call [eax] (DialogBoxIndirectParamA) popfd popad @RET: db 0B8h ;; Preparing the call the Real entry point - mov eax, ;; entrypoint @ENTRYPOINT1: dd 0 dw 0E0FFh ;; jmp eax (entrypoint) @WNDPROCSTART: push ebp mov ebp, esp push edi cmp dword ptr [ebp+0Ch], WM_CLOSE jne @WMCOMMAND push 0 ;; push int nResult push dword ptr [ebp+8] ;; push HWND hDlg db 0B8h ;; mov eax, PointerToOffsetOfEndDialog @ENDDIALOG1: dd 0 dw 10FFh ;; call [eax] (EndDialog) dw 05C6h ;; mov byte ptr.. @RETBYTE:

dd 0 ;; ..[end], C3h (ret) db 0C3h @WMCOMMAND: cmp dword ptr [ebp+0Ch], WM_COMMAND jnz @Ret cmp word ptr [ebp+10h], 1000 jnz @Ret push 30 ;; push int nMaxCount db 68h ;; push LPSTR lpString @PWDBUFFER1: dd 0 ;; Buffer push 1000 ;; push nIDDlgItem (ID of edit field = 1000) push dword ptr [ebp+8] ;; push HWND hDlg db 0B8h ;; mov eax, PointerToOffsetOfGetDlgItemTextA @GETDLGITEMTEXTA: dd 0 dw 10FFh ;; call [eax] (GetDlgItemTextA) db 0BEh @ENTRYPOINT2: dd 0 ;; mov esi, entrypoint db 0BFh @CRYPTBUFFER1: dd 0 ;; mov edi, buffer2 mov ecx, 10 @CopyBytesToBuffer: ;; Copy 40 bytes from the entrypoint to the cryptbuffer movsd dec ecx jne @CopyBytesToBuffer sub edi, 40 ;; edi points to cryptbuffer xor eax, eax @InitBuffers: db 0BEh ;; mov esi, pwdbuffer @PWDBUFFER2: dd 0 cmp eax, 40 ;; if all 40 chars are crypted... je @Blah3 ;; jump @LoopHere: inc eax ;; increase counter cmp byte ptr [esi], 0 ;; if pointer to password points to terminating 0 je @InitBuffers ;; jump and reset pointer to password to password[0] mov cl, byte ptr [esi] ;; get char from password ror byte ptr [edi], cl ;; ROR byte in cryptbuffer inc esi ;; next password byte inc edi ;; next cryptbuffer byte cmp eax, 40 ;; if not all 40 chars are crypted jne @LoopHere ;; jump @Blah3: xor eax, eax mov ecx, 10 db 0BFh ;; mov edi, cryptbuffer @CRYPTBUFFER2: dd 0 @CheckSum: add eax, dword ptr [edi] ;; eax will hold the checksum - add all 10 dwords of the ;; cryptbuffer add edi, 4 dec ecx test ecx, ecx jne @CheckSum db 03Dh ;; cmp eax, checksum @CHECKSUM: dd 0

jne @Ret db 0BFh @ENTRYPOINT3: dd 0 db 0BEh @CRYPTBUFFER3: dd 0 mov ecx, 10 @CopyBytesToEP: movsd dec ecx jne @CopyBytesToEP sub edi, 40 push 0 push dword ptr [ebp+8] db 0B8h @ENDDIALOG2: dd 0 dw 10FFh @Ret: xor eax, eax pop edi mov esp, ebp pop ebp db 0C3h ret FillNewSectionWithData endp

;; if checksum is wrong, jump ;; mov edi, entrypoint ;; mov esi, buffer

;; copy decrypted bytes from cryptbuffer to entrypoint

;; push int nResult ;; push HWND hDlg ;; mov eax, PointerToOffsetOfEndDialog ;; call [eax]

;; code "ret" like this or MASM interprets it as the ret ;; of "FillNewSectionWithData"

Now we come to the most difficult task of the whole program. We need to calculate the new IAT and add it to the old one. We have to loop for every DLL and write the DLL name, the API names and the pointers to the names and the buffers to the resolved functions to the new IAT. Additionaly we have to create a header definition for every DLL (which is the easiest part, though).
;; -----------------------------------------------------------------------------------------------------;; | CreateIAT creates the new IAT by adding the new calculated IAT to the old one. It ;; returns the size | ;; | of the new IAT in eax. Parameters are buff, the output buffer for the new IAT, ;; dllnames, the list | ;; | of DLL files which should be added to the new IAT, procnames, the list of function ;; names of those | ;; | DLL files, newsec, the RVA of the new section and oldiatsize, the size of the old ;; IAT. | ;; | As you can see, this function is reusable and in no way limited to add the one DLL ;; and three | ;; | functions we need for this program. You can add as many DLL files and Function Names ;; as you want, | ;; | only by changing the dllnames and procnames buffer. ;; ;; | The syntax for the dllnames is: DLL1,0,DLL2,0,DLL3,0,0 - One nullbyte indicates the ;; end of the DLL | ;; | name and two nullbytes are used for termination of the buffer. ;; ;; | The syntax for the procnames is: ;; DLL1_Function1,0,DLL1_Function2,0,0,DLL2_Function1,0,0, | ;; | DLL3_FUNCTION3,0,0,0,0 - One nullbyte is used to separate functions in the same DLL, ;; two nullbytes | ;; | are used to skip over to the next DLL and 4 nullbytes are used to terminate the ;; buffer. |

;; | This function has the best function of the whole program, but is very badly coded as ;; I optimized | ;; | and made changes very often and slowly lost readability. I tried to improve it ;; again, but I | ;; | accidentaly deleted some parts of the function and had to recode them. Now I am fed ;; up and before | ;; | I damage this function again, I decided to leave it as it is. It seems to work fine ;; and that is | ;; | what matters ;) ;; -----------------------------------------------------------------------------------------------------CreateIAT proc uses ebx ecx edx esi edi buff:DWORD, dllnames:DWORD, procnames:DWORD, newsec:DWORD, oldiatsize:DWORD LOCAL numofdlls:DWORD LOCAL numoffunc:DWORD LOCAL charoffunc:DWORD LOCAL charofdlls:DWORD LOCAL temp1:DWORD ;; All temp variables are used for saving pointers LOCAL temp2:DWORD LOCAL temp3:DWORD LOCAL temp4:DWORD LOCAL temp5:DWORD LOCAL counter:DWORD ;; # of DLLs LOCAL lengthofIAT:DWORD ;; Length of created IAT add mov mov mov mov mov mov mov mov mov mov xor xor xor oldiatsize, 20 ;; Add space for at least 1 DLL esi, dllnames ;; Point to buffer of DLL names counter, 0 temp1, 0 temp2, 0 temp3, 0 temp4, 0 numofdlls, 0 numoffunc, 0 charoffunc, 0 charofdlls, 0 eax, eax ebx, ebx ecx, ecx

;; ----------------------@GoOn: ;| lodsb ;| inc ecx ;| test eax, eax ;| jne @GoOn ;| lodsb ;| dec esi ;| inc ebx ;| test eax, eax ;| je @RealEnd ;| jmp @GoOn ;| @RealEnd: ;| ;; ----------------------mov mov xor xor

Check how many DLLs need to be imported Count # of chars of all functions

numofdlls, ebx ;; Number of DLL files that need to be added charofdlls, ecx ;; Number of Chars of that DLL files ebx, ebx ecx, ecx

mov

esi, procnames

;; ----------------------@GoOn2: ;| xor eax, eax ;| lodsb ;| inc ecx ;| test eax, eax ;| jne @GoOn2 ;| lodsw ;| sub esi, 2 ;| inc ebx ;| test eax, eax ;| je @RealEnd2 ;| jmp @GoOn2 ;| @RealEnd2: ;| ;; ----------------------mov mov mov mov mov mov mov mov mov mov imul add .WHILE mov mov mov add add mov add mov add xor xor @Bl: inc inc cmp jne inc cmp je jmp @Bm: .IF add .ELSE dec .ENDIF mov push add shl numoffunc, ebx charoffunc, ecx esi, buff edi, procnames temp1, edi edi, dllnames temp2, edi eax, numofdlls ebx, eax ecx, 20 ecx eax, newsec ebx>0 edi, temp1 [esi], eax ecx, oldiatsize [esi], ecx esi, 4 dword ptr [esi], 0 esi, 4 dword ptr [esi], 0 esi, 4 ecx, ecx edx, edx edi edx byte ptr [edi], 0 @Bl ecx byte ptr [edi+1], 0 @Bm @Bl dword ptr [edi]!=0 edi, 2 edi temp1, edi ecx ecx, ecx ecx, 2

Check how many Functions need to be imported Count # of chars of all functions

;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;;

Number of functions that must be added number of chars of that functions Point to beginning of the buffer Point to the functionnames Store pointer Point to the DLL Names Store the pointer Save space for old IAT header Write all headers Restore Pointer to Function Names Pointer to Function Name Pointer Table Add old IAT size Point to Reserved 1 Set Reserved 1 to 0 Pointer to Reserved 2 Set Reserved 2 to 0 Pointer to FunctionName Counter for number of Functions (DLL X) Counter for chars of all Functions (DLL X) Get the number of functions of DLL X

;; ;; ;; ;;

ecx holds the number of functions in DLL X here, edx holds the # of chars If not all user defined were already added adjust pointer to point to the next function

;; save pointer ;; Calculate Space for Pointer To Name Table ;; *4 because an entry is 4 bytes long

add mov mov add add xor mov .WHILE inc inc .ENDW inc inc mov add add mov mov sub add add pop shl add pushad mov add push mov imul add mov sub add sub add mov add invoke invoke add inc mov imul add mov sub add pop mov mov sub add mov add sub sub add

eax, ecx dword ptr [esi], eax ecx, oldiatsize [esi], ecx esi, 4 ecx, ecx edi, temp2 byte ptr [edi]!=0 edi ecx edi ecx temp2, edi eax, ecx eax, 20 dword ptr [esi], eax ecx, oldiatsize ecx, 20 [esi], ecx esi, 4 ecx ecx, 2 eax, ecx

;; Add that space ;; Write Pointer DLL Name ;; Add old IAT Size ;; Pointer to Pointer to Function Address List ;; Get Length of DLL Name

;; ;; ;; ;; ;; ;;

Adjust pointer to point to the next DLL Care for terminating 0 byte Save pointer Pointer To Function Address List Size of a IAT entry Write P. T. F. A. L.

;; Point to beginning of the next header part ;; Number of DLLs ;; One of the coding flaws I mentioned in the header ;; I just push all registers because I need them later ;; again, but use them for something else now ;; Point to next function that is processed

edi, procnames edi, temp4 eax eax, 20 counter eax, buff ebx, [eax+12] ebx, oldiatsize ebx, 20 ebx, newsec ebx, buff ecx, dllnames ecx, temp3 lstrcpy, ebx, ecx lstrlen, ebx temp3, eax temp3 eax, 20 counter eax, buff eax, [eax] eax, newsec eax, buff ebx temp5, 0 ecx, oldiatsize eax, ecx eax, 20 esi, [esi-4] esi, buff esi, newsec esi, oldiatsize esi, 20

;; Point to beginning of DLL X ;; EAX = Pointer to DllName in finished EXE

;; EBX = Pointer to DLLName now ;; ECX points to next DLLName ;; Copy DLLName to correct offset ;; Point to next DLLName ;; Pointer to beginning of Section X ;; Pointer to Function Name Pointer Table in finished EXE ;; Pointer to Function Name Pointer Table now

.WHILE byte ptr [edi]!=0

;; ESI is responsible for building the ;; dwRVAToFunctionAddressList ;; EDI points to Function Names - This loop creates the

Function Name Pointer Table mov dword ptr [eax], ebx mov ecx, oldiatsize sub ecx, 20 add [eax], ecx ;; add ebx, buff sub ebx, newsec push eax mov eax, [eax] mov [esi], eax ;; add ebx, 2 invoke lstrcpy, ebx, edi ;; mov lengthofIAT, ebx invoke lstrlen, ebx add lengthofIAT, eax add temp4, eax add esi, 4 .IF dword ptr [edi]!=0 inc temp4 .ENDIF add edi, eax ;; add ebx, eax sub ebx, buff inc ebx add ebx, newsec pop eax add eax, 4 inc temp5 inc edi .ENDW .IF dword ptr [edi]!=0 inc temp4 .ENDIF popad int 03 add eax, edx inc eax mov ecx, temp5 shl ecx, 1 inc ecx add eax, ecx dec ebx inc counter .ENDW ;; Go on with next DLL @Ret: mov eax, oldiatsize sub eax, buff add eax, lengthofIAT inc eax mov lengthofIAT, eax ret CreateIAT endp

Build dwRVAToFunctionNameList

Write value to dwRVAToFunctionFunctionAddressList Write the name of the function to the correct offset

Point to the next Function

; ######################################################################### ;; -----------------------------------------------------------------------------------------------------;; | GetOldIAT takes two parameters, iatbuff, the buffer for the new IAT, and offs, the ;; file offset of | ;; | the beginning of the IAT. Furthermore it used the global variable allocbuffer2 to ;; get a pointer to |

;; | the MZ header of the file (loaded into memory). The size of the old IAT is returned ;; in eax, a | ;; | pointer to the IAT buffer is returned in edx. ;; -----------------------------------------------------------------------------------------------------GetOldIAT proc uses ebx esi edi iatbuff:DWORD, offs:DWORD mov esi, allocbuffer2 ;; Points to MZ header add esi, offs ;; ESI points to beginning of Import Table mov ebx, esi mov edi, iatbuff ;; buffer for IAT xor eax, eax .WHILE dword ptr [esi+0Ch]!=0 ;; Write the old IAT to the buffer movsd movsd movsd movsd movsd add eax, 5*4 .ENDW mov edx, iatbuff ret GetOldIAT endp ; ######################################################################### ;; -----------------------------------------------------------------------------------------------------;; | Encrypt40Bytes takes 3 parameters, entrypoint, the RVA of the file's entrypoint, ;; hFile, the handle | ;; | of the file which should be protected and pwd, a pointer to the password. As the ;; name implies, only | ;; | 40 bytes of the file get encrypted (offset entrypoint to entrypoint+40) without ;; checking if those | ;; | bytes are actually valid. If the entrypoint of the prog will be less than 40 bytes ;; away from the end| ;; | of the section crypting the prog will fail. I don't check for that, because this ;; situation is *very*| ;; | unlikely to appear. Furthermore it sets the characteristics of the section where the ;; entry point is | ;; | in to to write access, because if the password is correct, the file must be decryped ;; again. The | ;; | encryption function works like this: The xth byte of the buffer is ROLed by the x ;; %sizeof(pwd)th | ;; | byte of the buffer. For a 3 char password this means the following: ;; ;; | byte(Entrypoint+X) is crypted with ;; | 0 byte(pwd+0) ;; | 1 byte(pwd+1) ;; | 2 byte(pwd+2) ;; | 3 byte(pwd+0) Switch back to beginning of pwd because there is ;; no char left ;; | 4 byte(pwd+1) ;; | 5 byte(pwd+2) ;; | 6 byte(pwd+0) ;; | ... ... ;; | The max size of a password is 30 bytes. This is not limited here, but in the code ;; which will be | ;; | added to the file. GetDlgItemTextA of the password box only retrieves 30 bytes. ;; | To make it harder to get the password, it is not compared directly but a checksum is ;; calculated | ;; | which is returned in eax. |

;; -----------------------------------------------------------------------------------------------------Encrypt40Bytes proc uses ebx ecx edx esi edi entrypoint:DWORD, hfile:DWORD, pwd:DWORD mov esi, allocbuffer mov edi, allocbuffer2 invoke ConvertRVAToFileOffset, entrypoint, hfile add edi, eax add esi, 0F8h mov eax, ebx mov ecx, 28h imul ecx add esi, eax or dword ptr [esi+24h], 80000000h ;; Get WriteAccess on the section xor eax, eax xor ebx, ebx @CheckSum: ;; This loop calculates the checksum by adding all 10 DWORD values of the 40 ;; bytes add ebx, [edi] inc eax add edi, 4 cmp eax, 10 jne @CheckSum xor eax, eax sub edi, 40 @InitBuffers: ;; This is the same routine as for decrypting the pwd. Difference is that ;; encrypting ;; works with ROL while decrypting works with ROR. mov esi, pwd cmp eax, 40 je @Blah3 @LoopHere: inc eax cmp byte ptr [esi], 0 je @InitBuffers mov cl, byte ptr [esi] rol byte ptr [edi], cl inc esi inc edi cmp eax, 40 jne @LoopHere @Blah3: mov eax, ebx ret Encrypt40Bytes endp ; ######################################################################### ;; This function is copy/pasted from another program - See Programming Journal 1 / ;; Article 2 ConvertRVAToFileOffset proc uses ecx edx esi edi offs:DWORD, fHandle:DWORD LOCAL numberofsections:WORD LOCAL buffer2:DWORD LOCAL sectionbuffer:DWORD LOCAL pestart:DWORD invoke VirtualAlloc, 0, 512, MEM_COMMIT, PAGE_READWRITE mov buffer2, eax invoke VirtualAlloc, 0, 512, MEM_COMMIT, PAGE_READWRITE mov sectionbuffer, eax invoke SetFilePointer, fHandle, 0, 0, FILE_BEGIN invoke ReadFile, fHandle, ADDR buffer, 40h, buffer2, 0 mov eax, dword ptr [buffer+3Ch] mov pestart, eax

add eax, 6 invoke SetFilePointer, fHandle, eax, 0, FILE_BEGIN .IF eax==-1 xor eax, eax ret .ENDIF invoke ReadFile, fHandle, ADDR buffer, 2, buffer2, 0 .IF eax!=1 xor eax, eax ret .ENDIF movzx eax, word ptr [buffer] mov numberofsections, ax xor edx, edx mov ecx, 28h imul ecx mov ebx, eax .IF filesize>eax mov eax, pestart add eax, 0F8h invoke SetFilePointer, fHandle, eax, 0, FILE_BEGIN .IF eax==-1 call @VirtualFree xor eax, eax ret .ENDIF invoke ReadFile, fHandle, sectionbuffer, ebx, buffer2, 0 .IF eax!=1 call @VirtualFree xor eax, eax ret .ENDIF xor ebx, ebx mov esi, sectionbuffer add esi, 0Ch .WHILE bx<=offs add eax, [esi-4] .IF eax>offs mov eax, offs sub eax, [esi] add eax, [esi+8] push eax call @VirtualFree pop eax ret .ENDIF .ENDIF add esi, 28h inc bx .ENDW .ELSE call @VirtualFree xor eax, eax .ENDIF call @VirtualFree xor eax, eax ret @VirtualFree: invoke VirtualFree, buffer2, 0, MEM_RELEASE invoke VirtualFree, sectionbuffer, 0, MEM_RELEASE db 0C3h ConvertRVAToFileOffset endp

; ######################################################################### end start

So, that's all. Now we have a 99% functional program that allows adding password dialogs to PE files and is very reusable because I did as much as possible relative to earlier calculations. Why only 99%? I tested it with 100 programs in Win98 and Win2K but one file refuses to work on Win2K. It seems that I forget to update I already have an idea what the problem could be, but I dont have too much time left before this issue of the Programming Journal will be released, so I try to tell you in the next issue. Rheingold

Locating API function offsets in memory


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. 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.
@Label: mov xor sub cmp jne edx, [esp] dx, dx edx, 1000h word ptr [edx], 'ZM' @Label ;; Offset of function in Kernel32.dll ;; Align to 1000h ;; search next page ;; MZ header? ;; 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 mov add xor push @Loop2: lodsb mov add rol test jne pop cmp je add dec jne pop xor ret @Found: edi, esi, esi, eax, ecx edi [ecx] DLLStart eax ;; EDI will hold the checksum ;; Pointer To Export #n ;; Normalize offset

;; Load char of API name into esi ecx, eax edi, eax edi, cl eax, eax @Loop2 ecx edi, ebx @Found ecx, 4 edx @Loop edx eax, eax ;; ;; ;; ;; checksum of API name - Part 1 checksum of API name - Part 2 if byte was not the 0-byte... ...keep looping

;; Checksum found ? ;; API found ;; ;; ;; ;; ;; Next pointer to API Name Still APIs to go? If so, jump Clean the stack return 0 means "API not 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 mov mov mov add sub shl add movzx mov mov add shl add add pop ret eax, ecx, ebx, ecx, ecx, ebx, ebx, ecx, ebx, ecx, ecx, ecx, ebx, ecx, eax, edx DLLStart ExportStart [ecx+18h] [ecx+24h] eax edx 1 ebx word ptr [ecx] ExportStart [ecx+1Ch] eax 2 ebx [ecx] ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; Here we need... the saved values again Number of Exports Pointer To Ordinals Normalize offset EBX = Number of Ordinals - API we want The ordinal table is made of WORD values - we have to multiply with 2 Pointer to the ordinal of the API Ordinal of our API And the Export Start again Offset Table Normalize offset Expand to DWORD size Point to offset where our API begins Add that offset to the beginning of the DLL Clean the stack

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 KernelStart dd 0 ExportStart PEStart GetProcA LoadLib user32 text dd dd dd dd db db 0 0 0 0 "User32.dll",0 "Test, Test, 1,2,3",0

;; used in the FindAPI proc ;; used to store offset of Kernel32.dll ;; ;; ;; ;; ;; ;; used in the FindAPI proc used in the FindAPI proc offset of GetProcAddress offset of LoadLibraryA DLL we need for the messagebox MessageBox text

start: invoke InitCommonControls mov xor @Label: sub cmp jne mov mov call mov push call mov mov call edx, [esp] dx, dx edx, 1000h word ptr [edx], 'ZM' @Label KernelStart, edx ebx, 0A216A185h FindAPI LoadLib, eax OFFSET user32 LoadLib ebx, 9A9C4525h edx, eax FindAPI ;; for NT/2K compatibility ;; Offset of function in Kernel32.dll ;; Align to 1000h ;; search next page ;; MZ header? ;; If not, then keep searching ;; Save Kernel32.dll offset ;; Checksum of LoadLibraryA ;; Get offset of LoadLibraryA ;; Save offset ;; "User32.dll" ;; LoadLibraryA ;; Checksum of MessageBoxA ;; Offset of User32.dll MZ header ;; Get offset of MessageBoxA ;; Store for calling FreeLibrary

push edx push push push push call 0 OFFSET text OFFSET text 0 eax

;; Show MessageBox

mov mov call call ret

ebx, 0B36E72B3h edx, KernelStart FindAPI eax

;; CheckSum of FreeLibrary ;; API is inside Kernel32.dll ;; Get API ;; Free User32.dll ;; 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. Rheingold

Vous aimerez peut-être aussi