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 CONSTThis 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 - .rsrcOnce 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 DATANow 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:01001030There 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:0100102CThis 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:010010B8This 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
