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)
上の結果から、暗号化前および復号後の内容が取得できていることが確認できる。