Windows APIの隠し関数を使ってみた

# 目次
- [はじめに](#はじめに)
- [Windows APIとは?](#windows-apiとは)
- [Windowsのウィンドウ表示の仕組み](#windowsのウィンドウ表示の仕組み)
- [CreateWindowInBandとは?](#createwindowinbandとは)
- [実際にやってみる](#実際にやってみる)
    - [普通にdllからインポート](#普通にdllからインポートして使ってみる失敗)
    - [GitHubにあったコードをそのまま使ってみる](#githubにあったコードをそのまま使ってみる失敗)
    - [dllをシステムの実行ファイルに差し込む](#dllをシステムの実行ファイルに差し込む成功)
- [最後に](#最後に)

# はじめに
今回は、Windows APIを使用して、特殊なウィンドウを作成します。

# Windows APIとは?
Windows上でコンソールアプリケーションやデスクトップアプリケーションを作成するためのAPIです。
ウィンドウを作成したりシステムの情報を取得したりいろいろ自由です。
Microsoft Learnにほとんどの関数の情報が存在します。
今回は、Microsoft Learnに掲載されていない、`CreateWindowInBand`という関数について紹介します。

# Windowsのウィンドウ表示の仕組み
Windowsのウィンドウには、「バンド」と「Zオーダー」というものが存在します。
「バンド」とは簡単に言うと、階層です。
Windowsでは、バンドが高ければ高いほどウィンドウが上位に表示されます。タスクマネージャーの最上位表示ウィンドウは、普通のウィンドウより上位のバンドに存在します。
そして、「Zオーダー」は、階層の中での高さです。
同じバンドの中でも、Zオーダーが高ければ高いほどウィンドウが上位に表示されます。
また、Zオーダーは、`SetWindowPos`関数や、アクティブウィンドウにすることで変更できますが、 ~~バンドは変更できません~~。
**訂正** 2024/12/26: `SetWindowBand`という非公開の関数によってバンドを変更することができます。

# `CreateWindowInBand`とは?
`CreateWindowInBand`関数は、Windowsのシステムファイル、`user32.dll`に定義されている関数です。
この関数は、普通の`CreateWindow`系の関数と違って、Zオーダーだけでなく、バンドも指定することができます。

# 実際にやってみる
`CreateWindowInBand`関数を実際に使ってみます。

### 普通にdllからインポートして使ってみる(失敗)
普通に`LoadLibrary`と`GetProcAddress`で`CreateWindowInBand`を取得して、使用してみます。

```c++:cwinband.cpp
// インクルード
#include <windows.h>

// 定義
constexpr const wchar_t *szClassName = L"cwinband";
typedef HWND(WINAPI* CreateWindowInBand)(_In_ DWORD dwExStyle, _In_opt_ ATOM atom, _In_opt_ LPCWSTR lpWindowName, _In_ DWORD dwStyle, _In_ int X, _In_ int Y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam, DWORD band);
enum ZBID {
	ZBID_DEFAULT = 0,
	ZBID_DESKTOP = 1,
	ZBID_UIACCESS = 2,
	ZBID_IMMERSIVE_IHM = 3,
	ZBID_IMMERSIVE_NOTIFICATION = 4,
	ZBID_IMMERSIVE_APPCHROME = 5,
	ZBID_IMMERSIVE_MOGO = 6,
	ZBID_IMMERSIVE_EDGY = 7,
	ZBID_IMMERSIVE_INACTIVEMOBODY = 8,
	ZBID_IMMERSIVE_INACTIVEDOCK = 9,
	ZBID_IMMERSIVE_ACTIVEMOBODY = 10,
	ZBID_IMMERSIVE_ACTIVEDOCK = 11,
	ZBID_IMMERSIVE_BACKGROUND = 12,
	ZBID_IMMERSIVE_SEARCH = 13,
	ZBID_GENUINE_WINDOWS = 14,
	ZBID_IMMERSIVE_RESTRICTED = 15,
	ZBID_SYSTEM_TOOLS = 16,
	ZBID_LOCK = 17,
	ZBID_ABOVELOCK_UX = 18,
};

HWND hWnd;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch(msg) {
        case WM_CLOSE: {
            DestroyWindow(hWnd);
            break;
        }
        case WM_DESTROY: {
            PostQuitMessage(0);
            break;
        }
    }
    return DefWindowProcW(hWnd, msg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, PWSTR pwCmdLine, int nCmdShow) {
    // ウィンドウクラスの登録
    WNDCLASSEXW wc;
    wc.cbSize = sizeof(WNDCLASSEXW);
    wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = GetModuleHandleW(NULL);
	wc.hIcon = (HICON)LoadIconW(NULL, IDI_APPLICATION);
	wc.hCursor = (HCURSOR)LoadCursorW(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = szClassName;
	wc.hIconSm = (HICON)LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
	ATOM regclsres = RegisterClassExW(&wc);

    // user32.dllからCreateWindowInBand関数を取得
    HMODULE hModule = LoadLibraryW(L"user32.dll"); 
    CreateWindowInBand pCreateWindowInBand = CreateWindowInBand(GetProcAddress(hpath, "CreateWindowInBand"));

    // ウィンドウの作成、表示、アップデート
    hWnd = pCreateWindowInBand(
        WS_EX_TOPMOST,
        regclsres,
        L"Created with the \"CreateWindowInBand\" function.",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, LPVOID(regclsres),
        ZBID_ABOVELOCK_UX
    );
    ShowWindow(hWnd, SW_SHOW);
    UpdateWindow(hWnd);

    // メッセージループ
    MSG msg;
    while(GetMessageW(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return (int)msg.wParam;
}
```

このコードをコンパイルして実行してみます。

```batch
:: コンパイル
g++.exe -o cwinband.exe cwinband.cpp -mwindows -municode
:: 実行
cwinband.exe
```

しかし、これでは`pCreateWindowInBand`関数で失敗してしまいます。
理由は良くわかりませんでしたが、プロセス自体に、Microsoftの署名が必要なのでは、と考えている方がいました。

### GitHubにあったコードをそのまま使ってみる(失敗)
`CreateWindowInBand`と検索すると、とあるGitHubのページがヒットします。
[Example of creating a window using a private api on a dll-injected immersive process · GitHub](https://gist.github.com/ADeltaX/a0b5366f91df26c5fa2aeadf439346c9)
(作成者様に感謝!)
このページを参考に、コードを書いてみます。

```c++:cwinband.dll.cpp
// インクルード
#include <windows.h>
#pragma comment(lib, "gdi32.lib")

enum ZBID {
	ZBID_DEFAULT = 0,
	ZBID_DESKTOP = 1,
	ZBID_UIACCESS = 2,
	ZBID_IMMERSIVE_IHM = 3,
	ZBID_IMMERSIVE_NOTIFICATION = 4,
	ZBID_IMMERSIVE_APPCHROME = 5,
	ZBID_IMMERSIVE_MOGO = 6,
	ZBID_IMMERSIVE_EDGY = 7,
	ZBID_IMMERSIVE_INACTIVEMOBODY = 8,
	ZBID_IMMERSIVE_INACTIVEDOCK = 9,
	ZBID_IMMERSIVE_ACTIVEMOBODY = 10,
	ZBID_IMMERSIVE_ACTIVEDOCK = 11,
	ZBID_IMMERSIVE_BACKGROUND = 12,
	ZBID_IMMERSIVE_SEARCH = 13,
	ZBID_GENUINE_WINDOWS = 14,
	ZBID_IMMERSIVE_RESTRICTED = 15,
	ZBID_SYSTEM_TOOLS = 16,
	ZBID_LOCK = 17,
	ZBID_ABOVELOCK_UX = 18,
};

LRESULT CALLBACK TrashParentWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch (message) {
    	case WM_CREATE:
    		break;
    	case WM_DESTROY:
    		PostQuitMessage(0);
    		break;
    	case WM_WINDOWPOSCHANGING:
    		return 0;
    	case WM_CLOSE:
    		HANDLE myself;
    		myself = OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId());
    		TerminateProcess(myself, 0);
    		return true;
    	default:
    		break;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

HWND hwnd = NULL;
typedef HWND(WINAPI* CreateWindowInBand)(_In_ DWORD dwExStyle, _In_opt_ ATOM atom, _In_opt_ LPCWSTR lpWindowName, _In_ DWORD dwStyle, _In_ int X, _In_ int Y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam, DWORD band);

void CreateWin(HMODULE hModule, UINT zbid, const wchar_t* title, const wchar_t* classname) {
	{
		HINSTANCE hInstance = hModule;
		WNDCLASSEXW wndParentClass = {};
		wndParentClass.cbSize = sizeof(WNDCLASSEXW);
		wndParentClass.cbClsExtra = 0;
		wndParentClass.hIcon = NULL;
		wndParentClass.lpszMenuName = NULL;
		wndParentClass.hIconSm = NULL;
		wndParentClass.lpfnWndProc = TrashParentWndProc;
		wndParentClass.hInstance = hInstance;
		wndParentClass.style = CS_HREDRAW | CS_VREDRAW;
		wndParentClass.hCursor = LoadCursor(0, IDC_ARROW);
		wndParentClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
		wndParentClass.lpszClassName = classname;

		auto res = RegisterClassExW(&wndParentClass);

		const auto hpath = LoadLibraryW(L"user32.dll");
		const auto pCreateWindowInBand = CreateWindowInBand(GetProcAddress(hpath, "CreateWindowInBand"));

		auto hwndParent = pCreateWindowInBand(WS_EX_TOPMOST,
			res,
			title,
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
			NULL,
			NULL,
			wndParentClass.hInstance,
			LPVOID(res),
			zbid
       );
		ShowWindow(hwndParent, SW_SHOW);
		UpdateWindow(hwndParent);

		if (hwndParent != nullptr)
			hwnd = hwndParent;
	}
}

DWORD WINAPI Thrd(LPVOID lpParam) {
	CreateWin(NULL, ZBID_ABOVELOCK_UX, L"Created by CreateWindowInBand Function!!", L"cwinband");

	MSG msg;
	while (GetMessageW(&msg, nullptr, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessageW(&msg);
	}

	return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		CreateThread(nullptr, 0, Thrd, hModule, NULL, NULL);
	break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}

	return TRUE;
}
```
```c++:cwinband.cpp
#include <windows.h>

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, PWSTR pwCmdLine, int nCmdShow) {
    HMODULE hDll = LoadLibraryW(L"cwinband.dll");
    FreeLibrary(hDll);
    return 0;
}
```

このコードをコンパイルして実行してみます。
```batch
:: コンパイル
g++.exe -o cwinband.dll cwinband.dll.cpp -shared -static -lgdi32 -mwindows -municode
g++.exe -o cwinband.exe cwinband.cpp -static -luser32 -lkernel32 -mwindows -municode
:: 実行
cwinband.exe
```
しかし、これでも失敗してしまいます。理由は先ほどと同じくだと思うんですけどね。

### dllをシステムの実行ファイルに差し込む(成功)
先ほどのGitHubのページと作成者様のコメントを参考にして、コードを改良しました。
dllのコードは先ほどと同じなので省略します。
ChatGPTからも知恵を分けてもらいました。

```c++:cwinband.cpp
#include <windows.h>
#include <tlhelp32.h>

const wchar_t* DLL_PATH = L"cwinband.dll";

// 外部プロセスにdllを差し込む関数
bool InjectDLL(DWORD processId, const wchar_t* dllPath) {
    HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
    if (process == NULL) return false;

    LPVOID alloc = VirtualAllocEx(process, NULL, (wcslen(dllPath) + 1) * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (alloc == NULL) {
        CloseHandle(process);
        return false;
    }

    WriteProcessMemory(process, alloc, dllPath, (wcslen(dllPath) + 1) * sizeof(wchar_t), NULL);

    HANDLE thread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, alloc, 0, NULL);
    if (thread == NULL) {
        VirtualFreeEx(process, alloc, 0, MEM_RELEASE);
        CloseHandle(process);
        return false;
    }

    WaitForSingleObject(thread, INFINITE);
    CloseHandle(thread);
    VirtualFreeEx(process, alloc, 0, MEM_RELEASE);
    CloseHandle(process);
    return true;
}

int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, PWSTR pwCmdLine, int nCmdShow) {
    // RuntimeBroker.exeを起動
    STARTUPINFOW si = {sizeof(si)};
    PROCESS_INFORMATION pi;
    if (!CreateProcessW(L"RuntimeBroker.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        MessageBoxW(NULL, L"Failed to start RuntimeBroker.exe\n", L"Error", MB_ICONERROR);
        return 1;
    }

    // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEフラグを設定
    HANDLE job = CreateJobObject(NULL, NULL);
    if (job == NULL) {
        MessageBoxW(NULL, L"Failed to create job object\n", L"Error", MB_ICONERROR);
        return 1;
    }

    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
    jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo));

    // プロセスをジョブに追加
    AssignProcessToJobObject(job, pi.hProcess);

    // dllをRuntimeBroker.exeに差し込む
    if (!InjectDLL(pi.dwProcessId, DLL_PATH)) {
        MessageBoxW(NULL, L"DLL injection failed\n", L"Error", MB_ICONERROR);
        TerminateProcess(pi.hProcess, 1);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        return 1;
    }

    WaitForSingleObject(pi.hProcess, INFINITE);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    CloseHandle(job);

    return 0;
}
```
このコードをコンパイルして実行してみます。
```batch
:: コンパイル
g++.exe -o cwinband.dll cwinband.dll.cpp -shared -static -lgdi32 -mwindows -municode
g++.exe -o cwinband.exe cwinband.cpp -static -luser32 -lkernel32 -mwindows -municode
:: 実行
cwinband.exe
```
なんと成功です！他のウィンドウより上に表示されてますし、タスクマネージャー(最上位表示)より上に表示されています。
やったね！
ちなみにタスクマネージャーより上じゃなくても普通に最上位表示するだけなら`CreateWindowExW(WS_EX_TOPMOST, /* 省略 */)`でいいんですけどね...

# 最後に
今回は、`CreateWindowInBand`というWindows APIの隠し関数を使用して、ウィンドウを作成してみました。
参考になってくれたことを願います。