Adding a recently used file menu to the existing menu of an application using a .dll by
TmcG
|
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 routineCall to "Load" 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)
: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
Call to "GetReg" function's routine: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
: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