なんともいえないプログラム

仕事じゃ役に立たない何とも言えないプログラムについて書いています。

discordとかLINEをCtrl+Enterで送信する1

初めに

皆さん普段からLINEやdiscord、slackといった様々なコミュニケーションツールを使用していると思います。これらのツールで送信するときLINEやdiscordはEnter、slackやtwitterはctrl+enterとツールによってバラバラです。個人的には誤爆を防ぐためにすべてCtrl+Enterで統一したいところですが送信方法を設定できないツールも多く設定できてもAlt+Enterで送信とかいう微妙な設定しかありません。
そこでツールにキー入力(Ctrl+Enter)が入ったときに入力を内部でEnterに変えてツールに送ればCtrl+Enterで送信できるはずです。

キー入力乗っ取り

あるプログラムの中で任意のプログラムを動作させるにはdllインジェクションを行います。プログラムに流れるキー入力を監視しEnterが押されたら破棄し、改行キーを送り直します。Ctrl+Etnerが来ればEnterのみを送信します。

dllインジェクション

dllをプログラムに埋め込むにはSetWindowsHookEx関数を使用します。

HHOOK SetWindowsHookExW(
  int       idHook,   //フックタイプ
  HOOKPROC  lpfn,     //コールバック関数
  HINSTANCE hmod,     //dllハンドル
  DWORD     dwThreadId//スレッド識別子
);

今回のフックタイプにはWH_KEYBOARDを指定します。このフックタイプはキーボードの入力があれば指定したコールバック関数が実行されます。 dllのエントリポイントでハンドルを取得しておきます。

BOOL APIENTRY DllMain(HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        g_hInst = (HINSTANCE)hModule;   // DLLモジュールのハンドル取得
    }
    return TRUE;
}

そしてフックする関数を以下のように定義しておきます。

DLLEXPORT void HookStart()
{
    g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, g_hInst, 0);

    if (g_hHook == NULL) {
        MessageBox(NULL, "フック開始は失敗しました", "HookStart", MB_OK);
        return;
    }
}

コールバック関数にはとりあえず適当に作っておきます。

DLLEXPORT LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) 
{
    MessageBox(NULL, std::to_string(wParam).c_str(), "Hook", MB_OK);
    return CallNextHookEx(g_hHook, code, wParam, lParam);
}

WH_KEYBOARDではwParamに入力されたキー情報が格納されています。 CallNextHookEx(g_hHook, code, wParam, lParam);では受け取った情報をそのまま親プロセスに渡します。NULL等を返せば横取りしたキー入力をそのまま破棄するのでなにも入力できなくなります。
ここまではdllに書くプログラムでしたが最初にこのdllを読み込んでHookStart()を実行しなければdllを流し込めません。 そこで、このdllを流し込むexeを作成していきます。

   MSG msg;
    HINSTANCE hinst = LoadLibrary(TEXT("CTRLSENDLL.dll"));
    if (hinst) {
        typedef void (*Install)();
        typedef void (*Uninstall)();

        Install install = (Install)GetProcAddress(hinst, "HookStart");

        install();

        while (GetMessage(&msg, NULL, 0, 0) > 0)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
   }

これを実行すれば起動しているあらゆるプログラムにdllが注入されてキー入力するたびにメッセージボックスが出現します。
ここで注意するのはLINEやdiscordは32bitソフトなので流し込むdllも32bitで作成しないとdllインジェクションは失敗します。
続きはまた書きます。dllの全文は以下に置いておきます。

#include "pch.h"
#include <string>
#pragma comment(lib, "imm32.lib")
#include "dllmain.h"
#define DLLEXPORT extern "C" __declspec(dllexport)

HINSTANCE g_hInst;
#pragma data_seg(".shared")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.shared,RWS")

DLLEXPORT LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) {
    MessageBox(NULL, std::to_string(wParam).c_str(), "Hook", MB_OK);
    return CallNextHookEx(g_hHook, code, wParam, lParam);
}
DLLEXPORT void HookStart()
{

    g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, g_hInst, 0);

    if (g_hHook == NULL) {
        MessageBox(NULL, "フック開始は失敗しました", "HookStart", MB_OK);
        return;
    }
}

DLLEXPORT void HookEnd()
{
    UnhookWindowsHookEx(g_hHook);
}

BOOL APIENTRY DllMain(HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        g_hInst = (HINSTANCE)hModule;   // DLLモジュールのハンドル取得
    }
    return TRUE;
}