Writing Simple DLLs

While learning to write DLLs, I found it difficult to find a simple straight forward tutorial which outlined just the basics of creating and compiling a DLL, particularly when using the Dev-C++ compiler. DLLs present some interesting possibilities to a programmer, especially when injecting code into another application's memory, or modifying a closed-source program's functionality. I will cover writing exportable functions as well as running code when the DLL is injected into a process. You should have basic knowledge of C and Win32 applications.

Tools/Resources Used

[~] Dev-C++ - Free C/C++ IDE application.
[~] DLL_EX.zip - Sample DLL files (not required).

Getting Started

First, open Dev-C++ and create a new project. Select 'DLL' under the Basic tab, and make sure the 'C Project' option is selected. Name it whatever you want and hit OK:



Create An Exportable Function

An exportable function is one which an executable can load into memory (i.e., it can be exported from the DLL) and make availiable to its code; essentially, you are creating your own API function (see my tutorial on adding DLL functions to closed-source binaries for a more detailed explanation). After performing the steps above, Dev-C++ automatically creates two skeleton files, dllmain.c and dll.h; let's take a look at the contents of dll.h:


#ifndef _DLL_H_
#define _DLL_H_
#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
#else /* Not BUILDING_DLL */
# define DLLIMPORT __declspec (dllimport)
#endif /* Not BUILDING_DLL */
DLLIMPORT void HelloWorld (void);
#endif /* _DLL_H_ */

The most important line here is: define DLLIMPORT __declspec (dllexport). This allows you to create exportable functions by preceeding the function name with 'DLLIMPORT'. Dev-C++ has also given us a sample exportable function in dllmain.c:

DLLIMPORT void HelloWorld ()
{
MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
A program can import this function, then make a call to 'HelloWorld()' which will in turn open a message box. There is also a skeleton DllMain function, which can be ignored/removed if you are writing the DLL with only the intent to create an exportable function.

Create Injectable DLLs

Once a DLL is injected into a process, its DLLMain function is called. DllMain is the equivelant to any C program's main() funciton. Dev-C++'s dllmain.c file contains a skeleton DllMain function:


BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,
DWORD reason /* Reason this function is being called. */ ,
LPVOID reserved /* Not used. */
)
{
switch (reason)
{
case DLL_PROCESS_ATTACH: /*We will be using this case*/
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
/* Returns TRUE on success, FALSE on failure */
return TRUE;
}
When a DLL is injected into an application's memory area, DllMain is called with the reason of DLL_PROCESS_ATTACH, so we will be using that particular case to initiate our code. Next, we need to load one of the DLL's functions as a thread of the application which the DLL has been injected into, so let's create a simple Hello World function:

void HelloWorldMsg ()
{
MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
To run this function as a thread, we will call CreateThread from within the DllMain function:
hThread = CreateThread(
NULL, // Security
0, // Stack size
HelloWorldMsg, // Function to create as a thread
NULL, // Parameter
0, // Disposition
&nThread // Handle value
);

The dllmain.c file should now look like this:

#include "dll.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

DLLIMPORT void HelloWorld ()
{
MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}


void HelloWorldMsg ()
{
MessageBox (0, "Hello World from an injected DLL!\n", "Hi", MB_ICONINFORMATION);
}

BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,
DWORD reason /* Reason this function is being called. */ ,
LPVOID reserved /* Not used. */ )
{

DWORD nThread; // Thread value
HANDLE hThread; // Thread handle

switch (reason)
{
case DLL_PROCESS_ATTACH:
if((hThread = CreateThread(NULL,0,HelloWorldMsg,NULL,0,&nThread)) != NULL){

// Close handle
CloseHandle(hThread);
}
break;

case DLL_PROCESS_DETACH:
break;

case DLL_THREAD_ATTACH:
break;

case DLL_THREAD_DETACH:
break;
}

/* Returns TRUE on success, FALSE on failure */
return TRUE;
}

Injecting The DLL

The easiest way to quickly test our DLL is to place it in the C:\Windows\system32 directory and add its name to the HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs key:

"The AppInit DLLs are loaded by using the LoadLibrary() function during the DLL_PROCESS_ATTACH process of User32.dll."

In other words, all programs that load User32.dll (i.e., nearly all Win32 applications) will also load any DLL specified in this registry key. Once you have added the name of your dll, run any program such as calc.exe or notepad.exe, and you should recieve a "Hello World from an injected DLL!" messagebox.

Conclusion

The ability to write exportable functions, or to injected a DLL into other processes and run code from within that proccess's memory presents some very interesting consequences. For instance, DLL injection can be used to bypass personal firewalls, and importing DLL functions is an easy way to add functionality to closed-source applications.

Copyright ©2006 craigheffner.com