In this article, we will consider an interesting, universal, and rarely used method of DLL injection into a Windows process using KnownDlls
sections. To demonstrate the method work we will develop a Windows-based sample project to inject DLL into all running processes and intercept some calls from ws2_32.dll.
Windows process injection supposes injection of a custom code into the address space of some processes. In other words, we get access to the process code, its data, the code of the system DLLs, which are loaded to the process, etc.
Why to inject DLL into process? There can be a lot of causes, both destructive โ steal passwords, hack protected application โ and peaceful ones: like antivirus analysis and protection, improvement and maintenance of an application, which source code you donโt have.
Contents
What is KnownDlls
KnownDLL
is a Windows mechanism to cache frequently used system DLLs. Initially, it was added to accelerate application loading, but also it can be considered as a security mechanism, as it prevents malware from putting Trojan versions of system DLLs to the application folders (as all main DLLs belong to KnownDLLs
, the version from the application folder will be ignored). We cannot say that this security mechanism is very strong (if you have permission to write to the application folder, you can create much more โtools of chaosโ), but still it helps to protect the system.
Letโs consider its work. When the loader comes across a record about DLL import in an executive file, it opens the file and tries to map it to the memory. But there are some nuances. In practice, before it happens, OS loader searches for the section (of MMF type) named KnownDlls<file-name-DLL>. If this section exists, then instead of opening the file the loader just use the mentioned section, i.e. maps the section to the process address space. Then it continues in accordance with the โclassicalโ DLL loading rules.
If you compare the key
KEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerKnownDLLs
with the KnownDlls
sections, youโll notice that the KnownDlls
container always includes more records than the mentioned registry key. Itโs because the sections in KnownDlls
are the result of the transitive closure of all DLLs listed in the KnownDLLs
. I.e. if a DLL is mentioned in KnownDLLs
, then all the DLLs, which are statically connected with it, are also included to the KnownDlls
sections.
Moreover, if you look closer to the KnownDLLs
registry key, youโll see that the search paths are not indicated there. Itโs because all KnownDLLs are supposed to be located in the folder, indicated in the registry key
KEY_LOCAL_MACHINESystemCurrentControlSetControlKnownDLLsDllDirectory.
This is one more security aspect of KnownDLLs
: requirement of that all KnownDLLs
are placed in the same specific folder.
When the system is loading, it looks for the path in the registry
KEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerKnownDLLs
and creates the sections KnownDlls<file-name-DLL>
for each DLL, listed in this registry key.
It should be mentioned that starting with Windows Vista itโs impossible to add directly a string parameter with the DLL path to the KnownDLLs
registry hive, as the system protects this hive from record. But if the application is started with the admin permissions, the user can get the permission to write to this hive.
This article is devoted namely to the method of Windows DLL injection using the described mechanism.
Pros of the method of intrusion using KnownDlls:
- We tested this method and confirmed its work on the Windows versions starting with XP and up to WinServer 2008 R2.
- We can hook the calls from the substituted DLL without the called function code change. It is very important for those applications, where the code integrity is checked by means of CRC.
- We can intrude into the protected processes with no problems.
- We can substitute and hook system DLLs calls.
- At the moment of this article release, this method was detected only by Kaspersky CRYSTAL Antivirus.
The main shortcoming is that the undocumented APIs are used.
Tech Aspects
Now letโs proceed to the technology in detail.
In our Windows DLL injection function example, we will inject custom code to the web browser as a target application. We work with the Windows7 x64 OS and use Mozilla Firefox, although this method will also work for any other browser that starts as the x32 application.
Expected result: replace the text โWrong_codeโ with the text โCool_code!โ in all browser requests and server responses. For example, if we enter the search request with the โWrong_codeโ text to the search line of our favorite search engine and click the button to start search, the browser forms an HTTP request to the server, and there, our sample will replace the mentioned text with the โCool_code!โ and only after that it will be sent to the server. The same actions are performed when the server response is obtained.
Scheme of function redirection.
To implement the above mentioned functionality, weโll substitute the section with the native ws2_32.dll with the section with our DLL. In the sample, the substitution is performed in the x32 process, i.e. ws2_32.dll from the C:WindowsSysWOW64 folder. To get the path to the SysWOW64
, you can call the SHGetSpecialFolderPathW
function.
In our sample, weโll hook the send
and recv
calls of winsock and provide C++ code injection.
So, letโs start.
Call Redirection
First, we create DLL for substitution during other DLL hooking. It will have the same name as the native one – ws2_32.dll.
Creating the stub (inside the DLL for substitution). The idea consists in create export table in dll stub, which functions called will be redirect in original dll. Functions which we must hook we will implement in our application and exporting them us usually.
The main goal is achieved using #pragma comment
compiler directive. It has several parameters, including EXPORT
(verbose about this instruction you can read here: http://msdn.microsoft.com/en-us/library/7f0aews7.aspx). Thus, using a string:
#pragma comment(linker, "/export:<src>=<dst>,@<ord>")
where
<src>
– name of the function to redirect;<dst>
– name of the module and function to be called after redirection;<ord>
– function ordinal number,
we can redirect all necessary calls from our .dll module to the original one.
Using this method we create the file of redirections named redirection.h. It looks as follows.
#pragma comment(linker, "/export:FreeAddrInfoEx=wsNative.FreeAddrInfoEx,@25")
#pragma comment(linker, "/export:FreeAddrInfoExW=wsNative.FreeAddrInfoExW,@26")
#pragma comment(linker, "/export:FreeAddrInfoW=wsNative.FreeAddrInfoW,@27")
#pragma comment(linker, "/export:GetAddrInfoExA=wsNative.GetAddrInfoExA,@28")
#pragma comment(linker, "/export:GetAddrInfoExW=wsNative.GetAddrInfoExW,@29")
#pragma comment(linker, "/export:GetAddrInfoW=wsNative.GetAddrInfoW,@30")
#pragma comment(linker, "/export:GetNameInfoW=wsNative.GetNameInfoW,@31")
#pragma comment(linker, "/export:InetNtopW=wsNative.InetNtopW,@32")
The first string shows that we export the FreeAddrInfoEx
function and it will be redirected to the call of FreeAddrInfoEx
from wsNative.dll
. It will be exported with the ordinal. Why wsNative
? Itโs because if we use ws2_32.FreeAddrInfoEx
, then the loader will try to load the function from our DLL. The order of listing of the #pragma comment
strings is not important, as the linker will sort the functions in the export table by names and letter case.
To create a stub for the native DLL we need to export all the function of the native DLL and moreover, the ordinals of these functions must coincide.
How weโll make the native ws2_32.dll
to be called by the name โwsNative.dllโ
? It is easy, we will create a symbolic link to the ws2_32.dll
in its directory by means of CreateSymbolicLink
. For example, if native DLL is located in C:WindowsSysWOW64ws2_32.dll
, we will create the link with the C:WindowsSysWOW64wsNative.dll
path name, which points to C:WindowsSysWOW64ws2_32.dll
.
DLL Function Hooking
Then we initialize the addresses of the hooked functions from the native DLL. To implement that, we first define the function types:
typedef int (WINAPI* PSEND)(SOCKET s, const char FAR *buf, int len, int flags);
typedef int (WINAPI* PRECV)(SOCKET s, char FAR *buf, int len, int flags);
Then we load the original DLL using LoadLibraryW, and that simply initialize them:
PSEND g_pSend = NULL;
PRECV g_pRecv = NULL;
template<typename T>
void InitAddr(T& destAdrr, LPCSTR funcName, HMODULE hModule)
{
destAdrr = (T)GetProcAddress(hModule, funcName);
CHECK_ADDRESS(destAdrr);
}
void Init(HMODULE hModule)
{
InitAddr(g_pSend, "send", hModule);
InitAddr(g_pRecv, "recv", hModule);
}
Now itโs time to implement the custom functions (analogues for the substituted ones), which weโll redirect calls to. Both functions get as parameters the buffer buf
of the length len
.
The send
function gets the buffer of the browser request to the server as buf
, and for the recv
function, the buf
buffer represents the response of server. So, itโs enough for us to search and replace the necessary text in buf
.
For example:
res = std::search(res, buf+len, &searchString[0], &searchString[0] + MAXSTRINGLEN(searchString));
if (res < buf+len)
{
memcpy(const_cast<char*>(res), replaceString, MAXSTRINGLEN(replaceString));
}
After the above described functions finished their work, we need to call the same functions from the native DLL with the modified parameters.
return g_pSend(s, buf, len, flags);
return g_pRecv(s, buf, len, flags);
And finally, we need to export our functions for hooking. Export can be performed in different ways, but in our case, itโs required that our functions are exported by the same names as in the native DLL. So, we create the def file:
LIBRARY
EXPORTS
send=_send @19
recv=_recv @16
Here we can see that send
is the name, which our function _send
will be exported with, and just like that recv=_recv
. The ordinal of the function is indicated after the ยซ@ยป symbol.
Install/uninstall
Now we can start to implement the loader for our section. We implement the service that runs at the system start and add our section. To install the section we need the Create permanent shared object permission and so the service with the system permissions fits us perfectly. Service installation is a very simple thing, so letโs start with the section creation.
Section Creation
– First we open the file of our DLL.
– Then we open the directory of KnownDlls
Object Manager using the NtOpenDirectoryObject
, function, whose parameter is the name of the directory. For the x32 process section name is KnownDLLs32
, for x64 processes – KnownDLLs
. For x64 processes, ws2_32.dllis is located in C:Windowssystem32.
– We create a section with the ws2_32.dll name in this directory. It looks as follows.
OBJECT_ATTRIBUTES sectionAttr;
UNICODE_STRING normalize_section_name;
RtlInitUnicodeString(&normalize_section_name, m_sectionPath.c_str());
InitializeObjectAttributes( §ionAttr, &normalize_section_name,
0, NULL, NULL);
stat = NtCreateSection(&hsec,
SECTION_ALL_ACCESS,
§ionAttr,
NULL,
PAGE_EXECUTE_READWRITE,
SEC_IMAGE,
hfile);
m_sectionPath
equals to “KnownDlls32ws2_32.dll”.
– Finally, we increment the reference counter for our section using NtMakePermanentObject
, and pass there the section handle. Itโs important, or the object manager will delete our section.
To remove DLL hooks, we delete the section:
UNICODE_STRING normalize_section_name;
RtlInitUnicodeString(&normalize_section_name, m_sectionPath.c_str());
if(!(hsec = OpenSection(normalize_section_name)))
{
throw std::runtime_error("DLL section name to delete does not exist!");
}
NTSTATUS stat = NtMakeTemporaryObject(hsec);
Unhooking
Then just delete symbolic link to native ws2_32.dll by DeleteFile
and restart computer. As ws2_32.dll is always present in the sections, and weโve replaced it with the custom one, we should restart to put the native DLL back. Surely, we could simple create the section for the native DLL in the same way and not restart.
Hook Testing
The project is built, and now we start
HookInstall.exe โinstall
Thus we install our server, it will run at the system start. Then we must start the service, we can do it for example typing
SC START HookInstall
or calling HookInstall.exe
without parameters. At the service start, the section is created and then the service is terminated.
First, letโs see the results of our work using for example the utility from sysinternals named WinObj
(you can also use other utilities for object view, like WinObjEx
).
Section creation result in WinObj.
Pay attention that ws2_32.dll is written in lower case while Windows at the system start creates the section with the name written in upper case.
Now letโs test the work of our code. We start a browser โ I used Mozilla Firefox โ go to the search, for example yandex.com, and type Wrong_code. Enjoy the result.
Results of hook
You can also test this sample for other applications that use sockets, for example, sending or receiving the substituted text in ICQ.
The sample can be also easily improved, for example, by adding the filtration of text change for the specified IP addresses.
Conclusion
In this article, one of the numerous techniques of process intrusion was considered. Letโs list some of the most famous approaches for hooking:
- Using debugging Tools for Windows;
- Buffers overloading;
- Executable module patching directly on the disk;
- Hooking of the Windows procedures using SetWindowHook and SetWindowLong;
- Rewriting of the process original DLL with the custom one on the disk;
- Intrusion using the legal Microsoft functions like โblock startingโ;
- Loading via the AppInit_DLLs registry hive;
- Loading using manifest;
- Creation of the remote threadโฆ
For example, take a look at another DLL hooking article in our Blog.
There are intrusion methods for user mode in this list, kernel mode hooking was not considered. But there are a lot of tricks with kernel mode.
To protect the process from intrusion via KnownDlls
you can use kernel mode hook of API function named NtCreateSection
checking the path to DLL. If it doesn’t correspond to the system one, the section creation must be blocked.
Like our work? Learn more about our System Management technologies.
Continue reading with another Dev Blog article: How to reverse engineer iOS software.