Introduction

Each DLL contains various exported functions that can be accessed by other programs. DLLs are being extensively used because the DLL is loaded only once in the physical memory, but each program that uses a particular DLL has a copy of the DLL’s data.

Let’s take a look at a simple example. The C++ code of the project that can be compiled in Visual Studio can be seen below:

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[]) {

	HANDLE hFile = CreateFile(L"C:\temp.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hFile == INVALID_HANDLE_VALUE) {
		printf("Unable to open file.");
	}
	else {
		printf("File successfully opened/created.");
	}
	CloseHandle(hFile);
	getchar();
	return 0;
}

The program is very simple and doesn’t really do anything much: it just creates the file C:temp.txt and then quits. The program uses the CreateFile function to create a file if it doesn’t already exist and opens it at the same time. The parameters we’re supplying for that function call are saying that we want to open a file for writing, that we don’t want to share the file and that we would like to use default security options. After running the program, the following will be printed in the console window:

The file was opened and created successfully. If we want to verify that, we can open the C: partition and check whether the file temp.txt is present. The picture below shows that it is, which proves that the program is working the way it should:

After that, let’s load the executable in Olly and check its executable modules. Those can be seen on the picture below:

We can see that there are the needed libraries loaded. We used the default built system in Visual Studio, which we’ll look at now. If we go to the project’s properties in Visual Studio and then to Configuration Properties – Linker – Command Line, we can see the exact parameters used when linking the executable. All those parameters can be seen on the picture below:

We can see a number of options specified when linking the executable. The documentation for all of the options can be found at [4]. Let’s summarize the options that were used above when linking the executable:

  • /OUT: specifies the output filename, which creates the createfilee.exe executable in the project’s Debug directory.
  • /INCREMENTAL: this option controls how the linked handles incremental linking. By default, incremental linking is enabled; if we would like to disable incremental linking we have to specify the option /INCREMENTAL:NO. To understand incremental linking, I must summarize what the linking is. The linking process takes all of the .obj files built from source code and all the specified .lib dependency files and creates an output .exe or .dll file. If we don’t use incremental linking, this process has to be done every time we compile the executable or dll, but with incremental linking only small changes need to be applied, so the compilation process is faster.
  • /NOLOGO: this option prevents display of copyright message and version number.
  • /MANIFEST: this option instructs the linked to create a side-by-side manifest file.
  • /ALLOWISOLATION: this option specifies the behavior for manifest lookup; in this case the option causes the operating system to do manifest lookups and loads.
  • /MANIFESTUAC: specifies if the user account control (UAC) is embedded in the program manifest.
  • /DEBUG: creates debugging information.
  • /PDB: creates program database PDB file.
  • /SUBSYSTEM:CONSOLE: tells the operating system how to run the executable .exe file.
  • /PGD: specifies the .pgd file that will be used to hold information about the running program.
  • /TLBID:1: specifies the value for the linker created type library. In our case we’re using the default resource ID of 1.
  • /DYNAMICBASE: specifies whether we would like the executable image to be randomly rebased at load time by using ASLR feature that was introduced with Windows Vista and newer Windows versions.
  • /NXCOMPAT: specifies the executable to be compatible with data execution prevention, which prevents code to be executed from data regions of memory when executable is loaded into memory.
  • /MACHINE:X86: specifies that the target platform is X86.
  • /ERRORREPORT:QUEUE: how to report errors that happened in linking process. If we use ERRORREPORT:SEND, the information about errors is automatically sent to Microsoft.

From the options above, we can also see that the executable is linked with the following import library files: kernel32.lib, user32.lib, gdi32.lib, winspool.lib, comdlg32.lib, advapi32.lib, shell32.lib, ole32.lib, oleaut32.lib, uuid.lib, odbc32.lib and odbccp32.lib. The .lib files are used to specify the dynamic libraries that will be included in the executable: the kerbel32.dl is linked into the executable by kernel32.lib. Each application needs to be linked with the import library of the DLLs whose functions it will access. The linker must take the address of each exported routine (from the .dll files) and put it into the executable’s IAT table. This is needed because when the executable is ran and loaded, the operating system must read the IAT table of the executable and load the corresponding DLL libraries, so its functions can be called. The DLLs need to be loaded into the application’s address space.

The table below presents all of the libraries that were specified to be used during the linking phase and the libraries that are actually used when we load the application into OllyDbg and display it’s Executable Modules:

Library

Linker

OllyDbg

kernel32.dll             yes            yes
user32.dll             yes
gdi32.dll             yes
winspool.dll             yes
comdlg32.dll             yes
advapi32.dll             yes
shell32.dll             yes
ole32.dll             yes
oleaut32.dll             yes
uuid.dll             yes
odbc32.dll             yes
odbccp32.dll             yes
ntdll.dll             yes
msvcr100d.dll             yes

From the table we can conclude that the linker is using the default libraries, but actually only linking the needed libraries into the executable. Since our program is using only the functions CreateFile, CloseHandle, printf and getchar and the data variables: GENERIC_WRITE, CREATE_NEW, FILE_ATTRIBUTE_NORMAL and INVALID_HANDLE_VALUE, we only need the ntdll.dll, msvcr100d.dll and kernel32.dll, as we can see in the table above. If we take a look at the MSDN documentation at [3], we can see that the CreateFile function is present in the kernel32.dll library. This can be seen on the picture below:

All those .lib files are specified in the Properties – Configuration Properties – Linker – Input as we can see on the picture below:

Let’s see what happens if we disable the additional dependencies. Let’s click on “Additional Dependencies” on the picture above and Edit them. A new window will open, where we need to disable the “Inherit from parent or project defaults”, so the inherited .libs won’t be included into the linking process.

After that, we can recompile the program and we’ll see that the program works as expected. The file is created again (of course we need to delete the C:temp.txt file before running the program). This proves that the inherited dependencies are not needed in this case. The linking command has changed, because it doesn’t contain the .lib references anymore, but the resulting executable is the same. This is because we must also configure the “Ignore All Default Libraries” as Yes, which can be seen on the picture below. This won’t include all the libraries by default into the built project.

When we try to compile the project again, we’ll receive the following error notifying us that certain functions couldn’t be found, which is not surprising, since we’re not linking with the standard libraries.

We can see that we didn’t provide the right .lib files during linking process, which is why the symbols were not found. We know that the CreateFile and CloseHandle are located in the kernel32.lib. This is why if we included that file to the additional dependencies, the two unresolved external symbol error messages would be solved. But that isn’t true for the rest of the error messages. The printf and getchar functions are included in the msvcrt.lib.

We should also add kernel32.lib and msvcrt.lib to the Additional Dependencies as seen on the picture below. The kernel32.lib is needed for the CreateFile and CloseHandle functions, while the msvcrt.lib is needed for the main function to be found.

The actual project’s properties can be seen below:

After compiling with those options, the program is loaded with the same modules as before, which can be seen on the picture below:

Want to learn more?? The InfoSec Institute Reverse Engineering course teaches you everything from reverse engineering malware to discovering vulnerabilities in binaries. These skills are required in order to properly secure an organization from today's ever evolving threats. In this 5 day hands-on course, you will gain the necessary binary analysis skills to discover the true nature of any Windows binary. You will learn how to recognize the high level language constructs (such as branching statements, looping functions and network socket code) critical to performing a thorough and professional reverse engineering analysis of a binary. Some features of this course include:

  • CREA Certification
  • 5 days of Intensive Hands-On Labs
  • Hostile Code & Malware analysis, including: Worms, Viruses, Trojans, Rootkits and Bots
  • Binary obfuscation schemes, used by: Hackers, Trojan writers and copy protection algorithms
  • Learn the methodologies, tools, and manual reversing techniques used real world situations in our reversing lab.

The same is also true for Windbg, which also loads the same libraries, which can be seen below:

Let’s traverse the Import Directory to check whether those libraries are actually being used. The import directory contains all of the functions we’re importing from other libraries. To do that, we must first figure out the Import Directory RVA, which can be done with the !dh command, as seen below:

0:002> !dh 00400000 -f

    ...
   18000 [      3C] address [size] of Import Directory
    ...

We can see that the RVA of the Import Directory is 0×18000, which means that the whole address is 0×00400000+0×18000 = 0×00418000. After that, we must dump all of the libraries we’re using to call the imported functions. We can do that by traversing the Import Directory, which contains links to the libraries until a null-terminated element is accessed. On the picture below, we can see that we dumped two IMAGE_IMPORT_DESCRIPTOR structures, which means that we’re importing the function names from two libraries. The third entry contains all zeros, which means it terminates the Import Directory.

We can print both of the library names by printing the ASCII string from the 0×00400000 + RVA stored in the Name element in each IMAGE_IMPORT_DESCRIPTOR structure. The obtained library names are the following:

This proves that we’re actually using two libraries: MSVCR100D.dll and KERNEL32.dll and no other. If we load the executable in PE Explorer, we can also see that the executable is only using the functions from two libraries as seen on the picture below:

But this gets us no closer to the answer we’re looking for. Let’s open the executable in Windbg in order to debug this. First let’s activate a noisy symbol loading with:

.sym noisy

After that, open the executable with Windbg (don’t attach to an already running process, but rather open an executable, which will start a new process). Once that is done, the following will be printed to the screen:

So far the right modules have been loaded: the kernel32.dll and the msvcr100d.dll. Additionally, the ntdll.dll has been loaded, but this is merely because the library is a dependency of the kernel32.dll. We can view all the dependencies with the Dependency Walker as we can see on the picture below:

Notice that the ntdll.dll is the only dependency of the kernel32.dll. This explains why the ntdll.dll library gets loaded.

Conclusion

In this tutorial we’ve seen how we can change various Visual Studio settings, which affects the linking process the Visual Studio does automatically. We’ve also looked at all of the compilation parameters thatVisual Studio uses to build the executable.