Adding a recently used file menu to the existing menu of an application using a .dll

by TmcG
Published by +Tsehp oct 2001

Study brings Wisdom

Date: October 2001

Target: RVA.exe Version 1. by LaZaRus

Goal: To add a recent file list to the File menu by using a .dll, as I can then use the .dll in future to add the same recent list facility to other programs. I named my .dll "MRU.dll"

Intro: Recently I have been using Lazarus RVA Converter quit frequently and as I am to lazy to go through the whole routine off opening a file, I decided to add a recently used file menu to the existing menu and to practise some areas where I do not have any experience, so please bear with me when you can think of 1000 other ways to do it in less than 20% of the space that I used, I promise that in my next project I will try and save all the bytes I can....:))

The work I have done here I could not possibly have done without the hard work that were done by Harlequin in his WDASM essays found at http://www.harlequin00.cjb.net, Amante4 for his essay on adding an entry to an existing Import Table, "My Solution To SantMat's ReverseMe #3, and Neural Noise with his unbeatable essay on adding functionality to NotePad found at http://neudump.cjb.net

Thank you very much for Ewayne who supplied us with all the source code for his ASM Edit program, of which I used the MRU.asm source code to assemble my .dll code http://www22.brinkster.com/yooper/AsmLoad.html

Tools and material used:
RVA Converter by Lazarus..:)
SnippetCreator
HexEditor
SoftIce
API reference
ASM Edit by Ewayne
WDASM
All the PE related info you can get your hands on, because they all differ...:))

Things to do:
 Step 1: Adding a new section to accommodate our recent code
 Step 2: Adding the new imports to the import table
 Step 3: Assembling the MRU.dll source code
 Step 4: Finding an entry into our code to add and update the recent file list and code the functions

Step 1. Adding a new section to accommodate our recent code
We increase the number of sections from 4 to 5 as follows:
@offset :0000003c we get a pointer to the PE header

and at the beginning of the PE header we find information as follows:
@offset :000000c0   50 45 00 00 4c 01 04 00 PE..L...
Where:
@offset :000000c0 DWord PE signature
:000000c4 Word Machine type
:000000c6 Word Number of sections ; we change this from 4 to 5

Now we need to add the additional information to the sectional headers. Use Iczelion's Snippet Creator and edit the newly added section as follows:

name: .reloc
virtual offset: 4000 + 1000 == 5000
virtual size aligned on file alignment: 200 bytes
for raw byte size we use the full 200 bytes
Raw offset we get from adding the virtual size of .RSRC to the Raw offset of .RSRC which gives us
A00h + 1800h == 2200h

Test the results in SnippetCreator, all ok...:)

Step2. Adding the new imports to the import table
To start with, we have a small problem. The API calls needed to handle our .dll are not in the import table, so lets add them manually.

From the Kernell32.dll we need the following API's:
  LoadLibraryA
  GetProcAddress
  FreeLibrary ;all this API names are case sensitive!!

Ok so at offset @PE + 80h we get the Original Import Table's pointer and size and then
@offset :00000140 78 20 00 00 50 00 00 00
which give us VA 2078 and using RVA Calculate...:)) we get RVA 00001078 and Import Table Size 50h bytes,

    :00001078  fc  20  00  00  00  00  00  00  ü ......
:00001080 00 00 00 00 0e 22 00 00 ....."..
:00001088 34 20 00 00 c8 20 00 00 4 ..È ..
:00001090 00 00 00 00 00 00 00 00 ........
:00001098 c2 22 00 00 00 20 00 00 Â"... ..
:000010a0 38 21 00 00 00 00 00 00 8!......
:000010a8 00 00 00 00 e4 22 00 00 ....ä"..
:000010b0 70 20 00 00 00 00 00 00 p ......
:000010b8 00 00 00 00 00 00 00 00 ........
:000010c0 00 00 00 00 00 00 00 00 ........

So we can see that the import table points to three .dll's (three times 5 DWord entries) and that the Import Table entries are terminated with an empty field of 5 DWords

Then for the first entry in the Import Table we get:

DWord OriginalFirstThunk  	fc 20 00 00
DWord TimeDateStamp 00 00 00 00
DWord ForwardChain 00 00 00 00
DWord .dll Name 0e 22 00 00
DWord FirstThunk 34 20 00 00
and we get:

OriginalFirstThunk
fc 20 00 00, VA 20fc == RVA 10fc
@offset :000010fc

    :000010f 8  00 00 00 00 da 21 00 00  ....Ú!..
    :00001100  52 21 00 00 bc 21 00 00  R!..¼!..
    :00001108  f6  21 00 00 ec 21 00 00  ö!..ì!..
    :00001110  ca 21 00 00 8e 21 00 00  Ê!...!..
    :00001118  ae 21 00 00 a2 21 00 00  ®!..¢!..
    :00001120  62 21 00 00 7c 21 00 00  b!..|!..
    :00001128  6e 21 00 00 02 22 00 00  n!..."..
    :00001130  40 21 00 00 00 00 00 00  @!......

TimeDateStamp
00

ForwardChain
00

.dll Name
0e 22 00 00,VA 220e == RVA 0000120e
@offset :0000120e

    :00001208  69 6e 74 66 41 00 55 53  intfA.US
:00001210 45 52 33 32 2e 64 6c 6c ER32.dll
:00001218 00 00 19 00 43 6c 6f 73 ....Clos

FirstThunk
34 20 00 00, VA 2034 == RVA 1034
@offset :00001034

    :00001030  00 00 00 00 da 21 00 00  ....Ú!..
    :00001038  52 21 00 00 bc 21 00 00  R!..¼!..
    :00001040  f6  21 00 00 ec 21 00 00  ö!..ì!..
    :00001048  ca 21 00 00 8e 21 00 00  Ê!...!..
    :00001050  ae 21 00 00 a2 21 00 00  ®!..¢!..
    :00001058  62 21 00 00 7c 21 00 00  b!..|!..
    :00001060  6e 21 00 00 02 22 00 00  n!..."..
    :00001068  40 21 00 00 00 00 00 00  @!......

For for the first entry in the thunk table pointed to VA offset :da 21 00 00 we get VA 21da == RVA 000011da
@offset :000011d8

    :000011d8  00 00 28 02 53 65 74 44  ..(.SetD
    :000011e0   6c 67 49 74 65 6d 54 65  lgItemTe
    :000011e8   78 74 41 00 31 02 53 65  xtA.1.Se

and for the second entry in the thunk table pointed to VA offset :52 21 00 00 we get VA 2152 == RVA 00001152
@offset
:00001152

    :00001150  41 00 b6 00 45 6e 61 62  A..Enab
    :00001158   6c 65 57 69 6e 64 6f  77   leWindow
    :00001160   00 00 b8 00 45 6e 64 44 ..¸.EndD

In order for us to add new entries into the import table we need to have space in the thunk tables to add the new locations of the additional API's. We can move the data in the existing import table and correct all the pointers but this is a mammoth task... So we can rather use the new .reloc's sections space and relocate the import table as follows:

Copy the existing import table's 50h bytes pointed to at offset @:00000140 giving us @:00001078 and paste them at offset @:00002250 We do not need to correct the pointers in the table as the pointers are VA's.

Change the Import Table's location pointer to point to our new location, so at offset @:00000140 we change it as follows:

RVA 2250 == VA 00405050
VA offset == VA 00405050 minus Imagebase == 00005050

and as we're adding one entry to the import table the size will increase with 20 bytes, and we correct the size as follows:
50h + 14h == 64h (Original size + 5DWords)

    :00000140  50 50 00 00 64 00 00 00  PP..d...

At the relocated import table we add the new entry as follows:

    :00002250  fc  20 00 00 00 00 00 00  ü ......
    :00002258  00 00 00 00 0e 22 00 00  ....."..
    :00002260  34 20 00 00 c8 20 00 00  4 ..È ..
    :00002268  00 00 00 00 00 00 00 00  ........
    :00002270  c2 22 00 00 00 20 00 00  Â"... ..
    :00002278  38 21 00 00 00 00 00 00  8!......
    :00002280  00 00 00 00 e4 22 00 00  ....ä"..
    :00002288  70 20 00 00 c4 50 00 00  p ..ÄP.. 	;original first thunk
    :00002290  00 00 00 00 00 00 00 00  ........ 		;TimeDateStamp, ForwardChain
    :00002298  c2 22 00 00 b4 50 00 00  Â"..´P.. 	;dll name, FirstThunk entry
    :000022a0  00 00 00 00 00 00 00 00  ........ 		;Terminate Import table
    :000022a8  00 00 00 00 00 00 00 00  ........ 		;with 5 empty double words
    :000022b0  00 00 00 00 17 50 00 00  .....P..

Next we need to add the 3 API entries to the Thunk tables, but we do not have enough space in the existing Thunk tables ..., so I made a new thunk table for the 3 new API's located in .reloc as follows:

    :000022b0  00 00 00 00 17 50 00 00 .....P..
    :000022b8  26 50 00 00 37 50 00 00 &P..7P..
    :000022c0  00 00 00 00 17 50 00 00 .....P..
    :000022c8  26 50 00 00 37 50 00 00 &P..7P..
    :000022d0  00 00 00 00 00 00 00 00 ........

Next we need to access the thunk table through a call to a "jump" API instruction. So we need to create a jump table for the 3 API's. I will show one:

I decided to place my jump table at offset @:000022d8

First we find the address of the Original First thunk entry which shows to the API name, then we need to get the VA for the Thunk entry:

So for LoadLibraryA we get name entry at (remember to include the hint bytes)

    :002210  47 65 74 52 65 67 00 00  GetReg..
    :002218  00 4c 6f 61 64 4c 69 62  .LoadLib 
    :002220  72 61 72 79 41 00 00 00  raryA...
    :002228  47 65 74 50 72 6f  63 41  GetProcA 
    :002230  64 64 72 65 73 73 00 00  ddress..
    :002238  00 46 72 65 65 4c 69 62  .FreeLib 
    :002240  72 61 72 79 00 4c 6f  61   rary.Loa
    :002248  64 00 00 00 00 00 00 00  d.......

which we find at RVA @:00002217 == VA 00405017
and we find the thunk table entry at @:000022b4, then for the jump to this location
RVA @:000022b4 == VA 004050b4 and we enter it as follows in our jump table:

    :0022d8  f f  25 b4 50 40 00 f f  25   ÿ%´P@.ÿ%
    :0022e0  b8 50 40 00 f f  25  bc 50  ¸P@.ÿ%¼P
    :0022e8  40 00 90 90 00 00 00 00  @.......

and do the same for the rest of the 3 API's.

Step 3. Assembling the MRU.dll source code
I used the MRU.asm source code supplied by Ewayne with his ASM Edit program and compiled it as a .dll After assemblimg the source code as a .dll I found that the exports are not recognized in the .dll This gave me a hard time until I found the MRU.def file in the projects folder and I manually added my export functions before assembling the source code. Now the export functions were recognized and I had a working recent file MRU.dll At this stage I added the MRU.dll's name and my three export names in the .reloc section as follows:

    :00002200  4d 72 75 2e 64 6c 6c 00  Mru.dll.
    :00002208  49 6e 69 74 74 00 00 00  Initt...
    :00002210  47 65 74 52 65 67 00 00  GetReg..
    :00002218  00 4c 6f   61 64 4c 69 62  .LoadLib
    :00002220  72 61 72 79 41 00 00 00  raryA...
    :00002228  47 65 74 50 72 6f   63 41  GetProcA 
    :00002230  64 64 72 65 73 73 00 00  ddress..
    :00002238  00 46 72 65 65 4c 69 62  .FreeLib
    :00002240  72 61 72 79 00 4c 6f   61  rary.Loa
    :00002248  64 00 00 00 00 00 00 00  d.......

Step 4: Finding an entry into our code to add to and update the recent file list
When reading the source code of the MRU.asm file we see that there are three conditions upon which the MRU will be called:

 i. On program startup, my function "Initt", to write the recent files in a recent menu list if any recent files exist in the registry, I decided to have only three recent entries
 ii. Upon successfully opening of a file through the Load menu, my function "Load", to update the recent file list saved in registry
 iii. Upon selection of one of the recent menu tabs, my function "GetReg", to open the selected recent menu tab's file

i. With SI bpx GetModuleHandleA and start RVA. SI break, F11 till we are in RVA memory space then single step. I decided to update my menu just after the SetMenu call and jumped to our code as follows:

* Reference To: USER32.SetMenu, Ord:0231h
| :004010B0 E899090000 Call 00401A4E :004010B5 E936400000 jmp 004050F0 ;our code to call MRU.dll * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00405124(U) :004010BA FF7508 push [ebp+08] ;we return to this line * Reference To: USER32.GetDlgItem, Ord:0100h | :004010BD E85C090000 Call 00401A1E

ii. With SI bpx GetOpenFileNameA and from the Load menu open a file. SI break, F11 select a file to open and enter, we are back in SI again. If we closely follow what happen here we see that there are tests done to see if the selected file is valid win32 file etc, I stepped through all the tests and decided to update the recent file list just after it was determined that the selected file is a valid file and then jump to our code as follows:

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401197(C)

:004011B1  E9793F0000			 jmp 0040512F	;our code to call the MRU.dll

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405163(U)

:004011B6  FF7508			 push [ebp+08]	;we return to this line

* Reference To: USER32.GetDlgItem, Ord:0100h
                                                                                   |
:004011B9  E860080000			 Call 00401A1E

iii. When we select a file from the recent menu tab we want to fake a GetOpenFileNameA but not execute the API. What I did here was first to see if the menu selected was the Load menu tab or one of our Recent tabs. When we see it was from the Load menu we proceed as normal and execute the GetOpenFileNameA API. When it was from one of our menu tabs then we need to execute the code right up to the API but retrieve the Menu's FileName from registry and copy the FileName to the position where GetOpenFileNameA would have done. Here follow my coding of the relevant routines:

  * Referenced by a CALL at Address:
|:0040116F
|
:004017AC C705213040004C000000	 mov dword ptr [00403021], 0000004C
:004017B6 FF3507304000		 push dword ptr [00403007]
:004017BC 8F0529304000		 pop dword ptr [00403029]
* Possible StringData Ref from Data Obj ->"EXE, DLL" | :004017C2 C7052D304000AD314000 mov dword ptr [0040302D], 004031AD :004017CC C7053D30400054324000 mov dword ptr [0040303D], 00403254 ;address of opened
;file's name
:004017D6 C7054130400004010000 mov dword ptr [00403041], 00000104 :004017E0 C7055530400004182800 mov dword ptr [00403055], 00281804 :004017EA 6821304000 push 00403021 :004017EF E9D5390000 jmp 004051C9& ;jump to our code :004017F4 83F801 cmp eax, 00000001 ;was file open successfully :004017F7 757B jne 00401874 ;jump away if unsuccessful

My code in RVA.exe's .reloc section to call the MRU.dll and handle all recent menu entries and retrievals:

Call to "Initt" function's routine
:004050F0  60				 pushad		;save all registers
:004050F1  6800504000			 push 00405000	;our dll's name
:004050F6  E8DDFFFFFF			 call LoadLibraryA
:004050FB  89C3				 mov ebx, eax	;save .dll's handle
:004050FD  6808504000			 push 00405008	;function "Initt"
:00405102  50				 push eax	;.dll's handle
:00405103  E8D6FFFFFF			 call GetProcAddress
:00405108  85C0				 test eax, eax	;test if function found
:0040510A  740F				 je 0040511B	;return if failed
:0040510C  8B7D08			 mov edi, dword ptr [ebp+08]  ;save our window
;handle for use in .dll
:0040510F FFD0 call eax ;call our function :00405111 53 push ebx ;.dll's handle :00405112 E8CDFFFFFF call FreeLibraryA :00405117 85C0 test eax, eax ;test if successful :00405119 7400 je 0040511B ;future error routine :0040511B 83EC34 sub esp, 00000034 ;correct the stack :0040511E 61 popad ;restore registers :0040511F 68B90B0000 push 00000BB9 ;replace overwritten instruction :00405124 E991BFFFFF jmp 004010BA ;and continue :00405129 000000000000 BYTE 6 DUP(0)
Call to "Load" function's routine
:0040512F  60			 pushad			;save all registers
:00405130  6800504000		 push 00405000		;.dll's name
:00405135  E89EFFFFFF		 call LoadLibraryA
:0040513A  89C3			 mov ebx, eax		;save .dll's handle
:0040513C  6845504000		 push 00405045		;"Load" function
:00405141  50			 push eax		;.dll's handle
:00405142  E897FFFFFF		 call GetProcAddress
:00405147  85C0			 test eax, eax		;test if successfull
:00405149  740F			 je 0040515A		;jump if failed
:0040514B  8B7D08		 mov edi, dword ptr [ebp+08] ;our window's handle for use in .dll
:0040514E  FFD0			 call eax			;function "Load"
:00405150  53			 push ebx		;.dll's handle
:00405151  E88EFFFFFF		 call FreeLibrary
:00405156  85C0			 test eax, eax		;test if failed
:00405158  7400			 je 0040515A		;future error routine
:0040515A  83EC34		 sub esp,00000034	;correct the stack
:0040515D  61			 popad			;restore registers
:0040515E  68B90B0000		 push 00000BB9		;replace overwritten instruction
:00405163  E94EC0FFFF		 jmp 004011B6		;and continue

Test to see if it was one of the tabs to invoke a GetOpenFileName

:00405168  663D007D		 cmp ax, 7D00		;check if Load menu
:0040516C  0F84FDBFFFFF	 je 0040116F		;continue if true
:00405172  663D7905		 cmp ax, 0579		;check if one of
:00405176  724C			 jb 004051C4		;our recent tabs
:00405178  663D7B05		 cmp ax, 057B		;and continue
:0040517C  7746			 ja 004051C4		;if not...
:0040517E  E9ECBFFFFF		 jmp 0040116F		;if one of our recent tabs, continue
							;towards GetOpenFileNameA API
Call to "GetReg" function's routine
:00405183 60			 pushad			;save all registers
:00405184 8BF8			 mov edi, eax		;eax contains menu ID
:00405186 6800504000		 push 00405000		;our dll's name
:0040518B E848FFFFFF		 call LoadLibraryA
:00405190 89C3			 mov ebx, eax		;save .dll's handle
:00405192 6810504000		 push 00405010		"GetReg" function
:00405197 50			 push eax		;dll's handle
:00405198 E841FFFFFF		 call GetProcAddress
:0040519D 85C0			 test eax, eax		;test if failed
:0040519F 740F			 je 004051B0		;jump away if failed
:004051A1 83EC00		 sub esp, 00000000	;unused bytes
:004051A4 FFD0			 call eax			;Call our function
:004051A6 50			 push eax		;eax contains address of retrieved
							;FileName from registry
:004051A7  6854324000		 push 00403254		;address of opened filename from
							;GetOpenFileNameA
:004051AC E8EBC8FFFF		 call lstrcpyA
:004051B1 53 			 push ebx		;our .dll's handle
:004051B2 E82DFFFFFF		 call FreeLibrary
:004051B7 85C0			 test eax, eax		;test if failed
:004051B9 7400			 je 004051BB		;future error routine
:004051BB 61			 popad			;restore registers
:004051BC 83C404		 add esp, 00000004	;correct stack
:004051BF E935C6FFFF		 jmp 004017F9		;and continue at the code right after the
; original GetOpenFileNameA API
:004051C4 E970C0FFFF jmp 00401239
:004051C9 663D007D cmp ax, 7D00 ;was it from Load menu :004051CD 75B4 jne 00405183 ;if not then it was one of the recent tabs :004051CF E8D4C8FFFF call GetOpenFileNameA :004051D4 83F801 cmp eax, 00000001 ;test if failed :004051D7 0F8597C6FFFF jne 00401874 ;error message :004051DD E917C6FFFF jmp 004017F9 ;success and continue

Proloque: Many thanx goes to Harlequin for all his patience and time, I really appreciate it brother...:)) and to all the guys spending time writing essays for us to learn and understand...:))

Any positive comments and suggestions are more than welcome...:))

Happy reversing
TmcG
tmcg at mail dot com