TippingPoint Digital Vaccine Laboratories
DID YOU KNOW... Aaron guest lectures each year for a Penetration Testing and Vulnerability Analysis class at NYU:Poly. The videos are made public here.

MindshaRE: Importing Multiple Modules Into a Single IDB

A question that has no doubt come up for many IDA Pro users in the past is, how can one load multiple modules into a single IDB? The question has been answered a few times on forums such as OpenRCE, and even Ilfak Guilfanov has written a 4 part blog about this. In case all of that information is not enough, today on MindshaRE we are going to cover the same topic showing you how I do it, and as usual provide plenty of examples.

MindshaRE is our weekly look at some simple reverse engineering tips and tricks. The goal is to keep things small and discuss every day aspects of reversing. You can view previous entries here by going through our blog history.

In your every day scenario, loading additional dlls into an IDB is not necessary. External library calls are usually referencing well documented APIs from Microsoft's standard libraries. Instead of having to disassemble these you can look up their MSDN documentation. But on occasion we have a complex piece of software that has their own libraries implementing functionality important to the binary we are currently focused on. In these cases it is nice to import everything into the same IDB so you can easily navigate exported libraries as if they were local functions.

First, we must understand that IDA was not designed to contain multiple text/data/import sections. Because of this we may have to make some small concessions when combining executable images. Second, we must decide if we can run the executable we are interested in, or if we need to import its libraries statically. In this post I will only be outlining the dynamic way to import loaded modules. If you do not have the option of running the target process please take a look at Atli Mar Gudmundsson's set of PE scripts. These allow you to easily load a dll into your IDB statically.

If we can run the process things are much easier for us. We will be able to easily import modules we are interested in and modify the import section of our target binary. Let's use calc.exe as an example.

Load up calc.exe into IDA, then navigate to the "Segments" window (Shift-F7). You should see the typical three segments listed .idata, .text, and .data. They will also contain many flags as can be seen below.
                            -->D L<--
.idata 01001000 01001228 R . X . L para 0001 public CODE 
.text  01001228 01013800 R . X . L para 0001 public CODE 
.data  01014000 0101501C R W . . L para 0002 public DATA 

The two flags we will be interested in when we attach to this process is the L, and D, which stand for Loader and Debug respectively.

Making sure we have calc.exe running let's attach to it with the IDA Debugger (A first for MindshaRE!). This can be done by going to the Debugger->Attach to process menu item. If you receive a confirmation box when attaching simply click Yes, and optionally check the box to prevent the same window from showing up in the future. You should only see one instance of calc.exe in the attach window. IDA will search the current process lists and match against the open IDB. Continuing on you will see IDA transform into its debugging window layout. Don't be scared, we wont be using any of these windows.

Now that we are attached go to the segments window (Shift-F7). You should see that we now have a huge list of loaded modules, it should look something like this.
debug001                   00010000 00011000 R W . D . byte 0003 public DATA 
debug002                   00020000 00021000 R W . D . byte 0004 public DATA 
debug003                   00030000 00031000 R W . D . byte 0005 public DATA 
Stack_PAGE_GUARD[000009D8] 0007C000 0007D000 R W . D . byte 0006 public STACK
Stack[000009D8]            0007D000 00080000 R W . D . byte 0007 public STACK
debug004                   00080000 00083000 R . . D . byte 0008 public CONST
debug005                   00090000 00092000 R . . D . byte 0009 public CONST
debug006                   000A0000 000B8000 R W . D . byte 000A public DATA 
debug007                   001A0000 001A6000 R W . D . byte 000B public DATA 
debug008                   001B0000 001B3000 R W . D . byte 000C public DATA 
debug009                   001C0000 001D6000 R . . D . byte 000D public CONST
debug010                   001E0000 00221000 R . . D . byte 000E public CONST
debug011                   00230000 00271000 R . . D . byte 000F public CONST
debug012                   00280000 00286000 R . . D . byte 0010 public CONST
debug013                   00290000 00299000 R . X D . byte 0011 public CODE 
debug014                   00350000 00352000 R . X D . byte 0012 public CODE 
debug015                   00360000 00361000 R W . D . byte 0013 public DATA 
debug016                   00370000 00375000 R W . D . byte 0014 public DATA 
debug017                   00380000 00382000 R . . D . byte 0015 public CONST
debug018                   00390000 00391000 R W . D . byte 0016 public DATA 
debug019                   003A0000 003A2000 R . . D . byte 0017 public CONST
debug020                   003B0000 003BE000 R W . D . byte 0018 public DATA 
debug021                   003C0000 003C8000 R W . D . byte 0019 public DATA 
debug022                   003D0000 003D4000 R W . D . byte 001A public DATA 
debug023                   003E0000 003E3000 R . . D . byte 001B public CONST
debug024                   003F0000 003F3000 R W . D . byte 001C public DATA 
debug025                   00430000 00533000 R . . D . byte 001D public CONST
debug026                   00540000 0066B000 R . X D . byte 001E public CODE 
debug027                   00840000 00841000 R W . D . byte 001F public DATA 
debug028                   008C0000 008D0000 R W . D . byte 0020 public DATA 
Stack_PAGE_GUARD[00000F98] 0090E000 0090F000 R W . D . byte 0021 public STACK
Stack[00000F98]            0090F000 00910000 R W . D . byte 0022 public STACK
debug031                   00910000 00917000 R W . D . byte 0023 public DATA 
debug032                   00990000 00992000 R W . D . byte 0024 public DATA 
calc.exe                   01000000 01001000 R . . D . byte 0025 public CONST
.idata                     01001000 01001228 R . X . L para 0001 public CODE 
.text                      01001228 01013800 R . X . L para 0001 public CODE 
calc.exe                   01013800 01014000 R . X D . byte 0026 public CODE 
.data                      01014000 0101501C R W . . L para 0002 public DATA 
calc.exe                   0101501C 01016000 R W . D . byte 0027 public DATA 
calc.exe                   01016000 0101F000 R . . D . byte 0028 public CONST
uxtheme.dll                5AD70000 5AD71000 R . . D . byte 0029 public CONST
uxtheme.dll                5AD71000 5ADA1000 R . X D . byte 002A public CODE 
uxtheme.dll                5ADA1000 5ADA2000 R W . D . byte 002B public DATA 
uxtheme.dll                5ADA2000 5ADA8000 R . . D . byte 002C public CONST
shimeng.dll                5CB70000 5CB71000 R . . D . byte 002D public CONST
shimeng.dll                5CB71000 5CB7F000 R . X D . byte 002E public CODE 
shimeng.dll                5CB7F000 5CB93000 R W . D . byte 002F public DATA 
shimeng.dll                5CB93000 5CB96000 R . . D . byte 0030 public CONST
acgenral.dll               6F880000 6F881000 R . . D . byte 0031 public CONST
acgenral.dll               6F881000 6F8B3000 R . X D . byte 0032 public CODE 
acgenral.dll               6F8B3000 6F8B4000 R W . D . byte 0033 public DATA 
acgenral.dll               6F8B4000 6F8B8000 R W . D . byte 0034 public DATA 
acgenral.dll               6F8B8000 6F8BC000 R W . D . byte 0035 public DATA 
acgenral.dll               6F8BC000 6FA4A000 R . . D . byte 0036 public CONST
msctf.dll                  74720000 74721000 R . . D . byte 0037 public CONST
msctf.dll                  74721000 74763000 R . X D . byte 0038 public CODE 
msctf.dll                  74763000 74765000 R W . D . byte 0039 public DATA 
msctf.dll                  74765000 7476C000 R . . D . byte 003A public CONST
msctfime.ime               755C0000 755C1000 R . . D . byte 003B public CONST
msctfime.ime               755C1000 755E8000 R . X D . byte 003C public CODE 
msctfime.ime               755E8000 755E9000 R W . D . byte 003D public DATA 
msctfime.ime               755E9000 755EE000 R . . D . byte 003E public CONST
imm32.dll                  76390000 76391000 R . . D . byte 003F public CONST
imm32.dll                  76391000 763A6000 R . X D . byte 0040 public CODE 
imm32.dll                  763A6000 763A7000 R W . D . byte 0041 public DATA 
imm32.dll                  763A7000 763AD000 R . . D . byte 0042 public CONST
userenv.dll                769C0000 769C1000 R . . D . byte 0043 public CONST
userenv.dll                769C1000 76A61000 R . X D . byte 0044 public CODE 
userenv.dll                76A61000 76A63000 R W . D . byte 0045 public DATA 
userenv.dll                76A63000 76A74000 R . . D . byte 0046 public CONST
winmm.dll                  76B40000 76B41000 R . . D . byte 0047 public CONST
winmm.dll                  76B41000 76B60000 R . X D . byte 0048 public CODE 
winmm.dll                  76B60000 76B61000 R W . D . byte 0049 public DATA 
winmm.dll                  76B61000 76B62000 R W . D . byte 004A public DATA 
winmm.dll                  76B62000 76B6D000 R . . D . byte 004B public CONST
oleaut32.dll               77120000 77121000 R . . D . byte 004C public CONST
oleaut32.dll               77121000 771A1000 R . X D . byte 004D public CODE 
oleaut32.dll               771A1000 771A2000 R W . D . byte 004E public DATA 
oleaut32.dll               771A2000 771A3000 R W . D . byte 004F public DATA 
oleaut32.dll               771A3000 771A4000 R W . D . byte 0050 public DATA 
oleaut32.dll               771A4000 771AB000 R . . D . byte 0051 public CONST
comctl32.dll               773D0000 773D1000 R . . D . byte 0052 public CONST
comctl32.dll               773D1000 77462000 R . X D . byte 0053 public CODE 
comctl32.dll               77462000 77463000 R W . D . byte 0054 public DATA 
comctl32.dll               77463000 774D3000 R . . D . byte 0055 public CONST
ole32.dll                  774E0000 774E1000 R . . D . byte 0056 public CONST
ole32.dll                  774E1000 77606000 R . X D . byte 0057 public CODE 
ole32.dll                  77606000 77608000 R W . D . byte 0058 public DATA 
ole32.dll                  77608000 7760D000 R W . D . byte 0059 public DATA 
ole32.dll                  7760D000 7761D000 R . . D . byte 005A public CONST
apphelp.dll                77B40000 77B41000 R . . D . byte 005B public CONST
apphelp.dll                77B41000 77B5E000 R . X D . byte 005C public CODE 
apphelp.dll                77B5E000 77B5F000 R W . D . byte 005D public DATA 
apphelp.dll                77B5F000 77B62000 R . . D . byte 005E public CONST
msacm32.dll                77BE0000 77BE1000 R . . D . byte 005F public CONST
msacm32.dll                77BE1000 77BF1000 R . X D . byte 0060 public CODE 
msacm32.dll                77BF1000 77BF2000 R W . D . byte 0061 public DATA 
msacm32.dll                77BF2000 77BF5000 R . . D . byte 0062 public CONST
version.dll                77C00000 77C01000 R . . D . byte 0063 public CONST
version.dll                77C01000 77C05000 R . X D . byte 0064 public CODE 
version.dll                77C05000 77C06000 R W . D . byte 0065 public DATA 
version.dll                77C06000 77C08000 R . . D . byte 0066 public CONST
msvcrt.dll                 77C10000 77C11000 R . . D . byte 0067 public CONST
msvcrt.dll                 77C11000 77C5D000 R . X D . byte 0068 public CODE 
msvcrt.dll                 77C5D000 77C5F000 R W . D . byte 0069 public DATA 
msvcrt.dll                 77C5F000 77C60000 R W . D . byte 006A public DATA 
msvcrt.dll                 77C60000 77C61000 R W . D . byte 006B public DATA 
msvcrt.dll                 77C61000 77C64000 R W . D . byte 006C public DATA 
msvcrt.dll                 77C64000 77C68000 R . . D . byte 006D public CONST
advapi32.dll               77DD0000 77DD1000 R . . D . byte 006E public CONST
advapi32.dll               77DD1000 77E46000 R . X D . byte 006F public CODE 
advapi32.dll               77E46000 77E47000 R W . D . byte 0070 public DATA 
advapi32.dll               77E47000 77E49000 R W . D . byte 0071 public DATA 
advapi32.dll               77E49000 77E4B000 R W . D . byte 0072 public DATA 
advapi32.dll               77E4B000 77E6B000 R . . D . byte 0073 public CONST
rpcrt4.dll                 77E70000 77E71000 R . . D . byte 0074 public CONST
rpcrt4.dll                 77E71000 77EFB000 R . X D . byte 0075 public CODE 
rpcrt4.dll                 77EFB000 77EFC000 R W . D . byte 0076 public DATA 
rpcrt4.dll                 77EFC000 77F02000 R . . D . byte 0077 public CONST
gdi32.dll                  77F10000 77F11000 R . . D . byte 0078 public CONST
gdi32.dll                  77F11000 77F54000 R . X D . byte 0079 public CODE 
gdi32.dll                  77F54000 77F55000 R W . D . byte 007A public DATA 
gdi32.dll                  77F55000 77F56000 R W . D . byte 007B public DATA 
gdi32.dll                  77F56000 77F59000 R . . D . byte 007C public CONST
shlwapi.dll                77F60000 77F61000 R . . D . byte 007D public CONST
shlwapi.dll                77F61000 77FCD000 R . X D . byte 007E public CODE 
shlwapi.dll                77FCD000 77FCE000 R W . D . byte 007F public DATA 
shlwapi.dll                77FCE000 77FD6000 R . . D . byte 0080 public CONST
secur32.dll                77FE0000 77FE1000 R . . D . byte 0081 public CONST
secur32.dll                77FE1000 77FEE000 R . X D . byte 0082 public CODE 
secur32.dll                77FEE000 77FEF000 R W . D . byte 0083 public DATA 
secur32.dll                77FEF000 77FF1000 R . . D . byte 0084 public CONST
kernel32.dll               7C800000 7C801000 R . . D . byte 0085 public CONST
kernel32.dll               7C801000 7C885000 R . X D . byte 0086 public CODE 
kernel32.dll               7C885000 7C88A000 R W . D . byte 0087 public DATA 
kernel32.dll               7C88A000 7C8F6000 R . . D . byte 0088 public CONST
ntdll.dll                  7C900000 7C901000 R . . D . byte 0089 public CONST
ntdll.dll                  7C901000 7C97B000 R . X D . byte 008A public CODE 
ntdll.dll                  7C97B000 7C97E000 R W . D . byte 008B public DATA 
ntdll.dll                  7C97E000 7C97F000 R W . D . byte 008C public DATA 
ntdll.dll                  7C97F000 7C980000 R W . D . byte 008D public DATA 
ntdll.dll                  7C980000 7C9AF000 R . . D . byte 008E public CONST
shell32.dll                7C9C0000 7C9C1000 R . . D . byte 008F public CONST
shell32.dll                7C9C1000 7CBBF000 R . X D . byte 0090 public CODE 
shell32.dll                7CBBF000 7CBCF000 R W . D . byte 0091 public DATA 
shell32.dll                7CBCF000 7CBD5000 R W . D . byte 0092 public DATA 
shell32.dll                7CBD5000 7CBDA000 R W . D . byte 0093 public DATA 
shell32.dll                7CBDA000 7CBDC000 R W . D . byte 0094 public DATA 
shell32.dll                7CBDC000 7D1D7000 R . . D . byte 0095 public CONST
user32.dll                 7E410000 7E411000 R . . D . byte 0096 public CONST
user32.dll                 7E411000 7E471000 R . X D . byte 0097 public CODE 
user32.dll                 7E471000 7E473000 R W . D . byte 0098 public DATA 
user32.dll                 7E473000 7E4A1000 R . . D . byte 0099 public CONST
debug033                   7F6F0000 7F6F7000 R . X D . byte 009A public CODE 
debug034                   7FFB0000 7FFD4000 R . . D . byte 009B public CONST
debug035                   7FFD6000 7FFD7000 R W . D . byte 009C public DATA 
TIB[00000F98]              7FFDE000 7FFDF000 R W . D . byte 009D public DATA 
TIB[000009D8]              7FFDF000 7FFE0000 R W . D . byte 009E public DATA 
debug037                   7FFE0000 7FFE1000 R . . D . byte 009F public CONST
This is similar to our IDB, except all loaded modules, and memory regions in the active process are listed. This allows the user to jump around to different modules or allocated memory regions in IDA. What we want to do now is locate the modules we want to import. For this example I will only be importing kernel32.dll.

There are two way to take a memory snapshot in IDA, by "Loader" segments or by "All" segments. Doing a snapshot of all segments is very messy, and generally to much information. So we want to take a memory snapshot of only the loader segments. The problem is only the original module is recognized as a loader segment by IDA. This is because when attaching, IDA has not loaded any modules, and thus has not set the L flag in the segment window. Because of this we must modify the segments we want to import.

In your segments window (Shift-F7) locate the kernel32.dll segments. You should see four segments listed for kernel32.dll. This will generally be the same for most of the windows system dlls and are in order like this.
kernel32.dll - 7C800000 7C801000 - PE header
kernel32.dll - 7C801000 7C885000 - .idata/.text
kernel32.dll - 7C885000 7C88A000 - .data
kernel32.dll - 7C88A000 7C8F6000 - .rsrc
Once you locate these in the segments window we are going to edit the segments properties. This can be achieved by right clicking and choosing "Edit segment" or hitting the Ctrl-E shortcut. When the Edit Segment window pops up simply click the bottom left box "Loader segment" and then the OK button. In most cases we will only need to import the .idata/.text and .data sections. After we check the loader flag on these two sections in kernel32.dll you should see that our original module, and the kernel32.dll module have the L flag specified.
.text         01001228 01013800 R . X . L para 0001 public CODE
.idata        01001000 01001228 R . X . L para 0001 public CODE
.data         01014000 0101501C R W . . L para 0002 public DATA
kernel32.dll  7C801000 7C885000 R . X D L byte 0086 public CODE
kernel32.dll  7C885000 7C88A000 R W . D L byte 0087 public DATA
Now we can import these into our original IDB by going to Debugger->Take memory snapshot. When the snapshot message pops up select Loader segments. When the import is finished you will be returned back to your debugger windows. You can now save your IDB (Ctrl-W) and detach from the process by going to Debugger->Detach from process.

Once again IDA transforms back into our good ole static analysis view. Back in our segments window we can now see we have the kernel32.dll segments.

After we take a memory snapshot we must re-analyze the binary. Otherwise the .text section of kernel32.dll will not be defined as code. To re-analyze a binary you can go to Options->General and in the Analysis tab click "Reanalyze program". Once analysis is finished we should have a functional import of kernel32.dll.

We have one problem now. Our imports from kernel32 in calc.exe are still external.
.idata:01001020 ; Imports from KERNEL32.dll
.idata:01001020 ;
.idata:01001020     extrn _imp__GetModuleHandleA:dword
.idata:01001020     
.idata:01001024     extrn _imp__LoadLibraryA:dword
.idata:01001024     
.idata:01001028     extrn _imp__GetProcAddress:dword
.idata:01001028     
.idata:0100102C     extrn _imp__GlobalCompact:dword
.idata:0100102C     
.idata:01001030     extrn _imp__GlobalAlloc:dword
.idata:01001030
There really isn't any benefit to importing modules if you cant follow calls. One thing you may not have noticed is when we took a memory snapshot we actually imported the filled in IAT of calc.exe. When you load a binary statically in IDA the bytes in .idata are non existent. This is because they get filled in by the loader when the process starts. By importing these from the debugger we can now use them with our imported kernel32.dll.

A word of warning, I wish I knew of a better way to do the following, if you know of one please leave a comment. Back in our segments window click the .idata segment and hit Ctrl-E. Let's then change the "segment class" from CODE to DATA. Back in your disassembly view you will see that our import section now contains pointers to the imported kernel32.dll exports.
.idata:01001020 ; Imports from KERNEL32.dll
.idata:01001020 ;
.idata:01001020 _imp__GetModuleHandleA dd offset kernel32_GetModuleHandleA
.idata:01001020
.idata:01001024 _imp__LoadLibraryA dd offset kernel32_LoadLibraryA
.idata:01001024
.idata:01001028 _imp__GetProcAddress dd offset kernel32_GetProcAddress
.idata:01001028
.idata:0100102C _imp__GlobalCompact dd offset kernel32_GlobalCompact
.idata:0100102C
This is a little more useful than having just the import name, now we can follow those calls as we would with local functions. You may notice some of the other exported entries are now broken offsets.
.idata:010010A4 ;
.idata:010010A4 ; Imports from USER32.dll
.idata:010010A4 ;
.idata:010010A4 _imp__GetMenu   dd 7E4314BAh
.idata:010010A4
.idata:010010A8 _imp__SetDlgItemInt dd 7E45BC09h
.idata:010010A8
.idata:010010AC _imp__GetWindowTextW dd 7E42A5CDh
.idata:010010AC
.idata:010010B0 _imp__CheckDlgButton dd 7E424DCAh
.idata:010010B0
.idata:010010B4 _imp__HideCaret dd 7E42B086h
.idata:010010B4
.idata:010010B8 _imp__CallWindowProcW dd 7E42A01Eh
.idata:010010B8
This is because we have not imported those into the current IDB and IDA simply sees an address outside of its address space. This could be fixed by going through and importing those dlls just as we did kernel32.

One last problem, that I currently do not have a good answer for is symbol information. I do not know of a way to apply a PDB to the imported IDB. I have tried various things with no results. There is a workaround though.

To apply symbols lets disassemble kernel32.dll in IDA as you would a normally. Once it has been analyzed apply the PDB file if it hasn't be done so automatically. Next we need to produce an IDC file. Do this by going to File->Produce File->Dump database to IDC file. This will produce a file containing all the information in your current IDB. We need to make a modification first. Open up the saved idc file and modify the main() function. It should look like this.
static main(void) {
        Enums();                // enumerations
        Structures();           // structure types
        Bytes();                // individual bytes (code,data)
        Functions();            // function definitions
}
Since we are importing into a different binaries IDB we don't want to change any info or segment information. You can then go back to the calc.exe IDB and load it via File->IDC file. This will then populate the symbols for kernel32 to your imported kernel32 segments in your calc.exe IDB.

Importing dlls into an IDB can be very handy when looking at enterprise software. Applications like HP OpenView, CA Brightstor, and TrendMicro ServerProtect all have several layers of abstraction that make things cumbersome to navigate. By importing these custom libraries you can stay focused on the binary at hand.

-Cody
Tags:
Published On: 2008-11-13 12:37:15

Comments post a comment

No comments.
Trackback