めいくりぷとのブログ

技術的なことやゲームのことやら・・・

WDDM Hook (2)

Direct3D DDIは、OpenGL ICDとは違って、グラフィックドライバ自体にテーブルは無くd3dx.dllにドライバのテーブルがあるみたいですね...(ICD同様にグラフィックドライバ自体にテーブルが用意されているものだと勘違いしておりました。)


まずDDIの関数テーブル自体が、グラフィックドライバのテーブル関数(?) CreateDevice関数 によって初期化されますし、同じ所にあるものだと普通思いますよね...


一見どうでも良いようにも思えますが...
少し前のプロテクターにはD3D関数テーブルの整合性チェックがあっても、ドライバの関数テーブルのチェックは殆ど無かったため問題はありませんでした。ですが、最近のものはd3dx.dll自体にメモリチェックがあったりするので、どうでも良いとは言い切れません。


どうにかグラフィックドライバ自体に存在するドライバ関数?だけで完結したいですよね..


一応、DDIの各ドライバ関数の第一引数 HANDLE hDeviceにDisplay Device(関数テーブル?)のポインタが格納されており、それに対応した関数が呼ばれているので、何とかなるような気もしますけどね。


また何か進展があれば記事にしたいと思います。以上


追記:
よくよく考えれば、D3D DDIの各ドライバ関数をフックしなくても、各ドライバ関数へのテーブル自体をすり替えてしまえば、ポインタチェック?とかはされない限りは、CRCがあっても問題ありません。(.exeは別みたいですが。)


関数テーブル上の関数は、asm上では call dword ptr [eax+04] とかで呼ばれため、
テーブル自体の書き換えは、直接メモリを書き換える訳でもないので検出が難しいみたいですね。
出来るとすれば、関数ポインタ先が、メモリ内or外、またはBase Relocation (.dll+XXXX) などが挙げられるようです。


他にもポインタ自体のチェックがありますが、プロテクタがそのポインタを読み込む前に書き換えてしまえば難なく突破出来てしまいます。
良く海外フォーラム等にWallhackなどが落ちていますよね。モノによってはプロテクタの有無に関わらず、そのまま使えたりするものもありますよね。そうです、そういうことです。


少し話が逸れますが、関数テーブルの書き換えによって行うチートの検出は難しいと思いました。


更に追記:
冒頭にグラフィックドライバ自体に関数テーブルが用意されてないだとか書きましたが、OpenAdapterはグラフィックドライバの関数テーブルの他に、D3Dの何らかの関数テーブルの初期化時にも呼ばれることが分かりました。
今までは決め打ちでやっていたため、D3Dの何らかの関数テーブルの方を取得してたみたいです。

ウィンドウフレームを消して、ウィンドウを移動出来るようにする。

eventFilter(QObject* object, QEvent* event); をオーバーライドし、
event->type() から各マウスの状態から制御します。以下ソースコードです。

CMainWindow::CMainWindow(__in QWidget *parent) : QMainWindow(parent)
{
    ....
    this->installEventFilter(this);                 // eventFilterを有効にする
    this->setWindowFlags(Qt::FramelessWindowHint);  // ウィンドウの枠?を消す
    ....
}
 
bool CMainWindow::eventFilter(QObject* object, QEvent* event)
{
    if (this->m_mousePressed)
    {
        if (event->type() == QEvent::MouseMove)
        {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            this->m_currentPos = mouseEvent->pos();
            this->move(
                this->pos().x() + this->m_currentPos.x() - this->m_originPos.x(),
                this->pos().y() + this->m_currentPos.y() - this->m_originPos.y());
        }
 
        if (event->type() == QEvent::MouseButtonRelease)
        {
            this->m_mousePressed = false;
        }
    }
    else
    {
        if (event->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            this->m_originPos = mouseEvent->pos();
            this->m_mousePressed = true;
        }
    }
 
    return false;
}

NIC Mac Address Authentication

// CAuthentication.hpp
#pragma once

#include <vector>

class CAuthentication
{
public:
	CAuthentication();
	~CAuthentication();

public:
	BOOL IsUserAllowed();
	void SetWhiteList();

public:
	static CAuthentication *GetInstance()
	{
		static CAuthentication instance;
		return &instance;
	}

private:
        void RegisterMACAddress(LPCWSTR lpcwszAddress);

	std::vector<LPCWSTR> m_vData;
};
// CAuthentication.cpp
#include "stdafx.h"

#include "CAuthentication.hpp"
#include "utils.hpp"

#include <NtDDNdis.h>
#include <IPHlpApi.h>
#pragma comment(lib, "IPHlpApi")

CAuthentication::CAuthentication()
{

}
CAuthentication::~CAuthentication()
{

}

void CAuthentication::SetWhiteList()
{
	this->RegisterMACAddress(L"AA:BB:CC:DD:EE:FF");
}

BOOL CAuthentication::IsUserAllowed()
{
	IP_INTERFACE_INFO *pInterface;
	HANDLE hDevice;
	ULONG uLength;
	UCHAR uResult[6];
	WCHAR wszAdapter[MAX_PATH];
	LPVOID lpEthernet = (LPVOID)OID_802_3_CURRENT_ADDRESS;

	if (GetInterfaceInfo(NULL, &uLength) == ERROR_INSUFFICIENT_BUFFER)
	{
		pInterface = (IP_INTERFACE_INFO*)malloc(uLength);

		if (GetInterfaceInfo(pInterface, &uLength) == NO_ERROR)
		{
			for (int i = 0; i < pInterface->NumAdapters; i++)
			{
                                // NIC GUID
				std::wstring wszBuffer, wszTemp;
				wszBuffer = pInterface->Adapter[i].Name;
				wszBuffer = L"\\\\.\\" + wszBuffer.substr(wszBuffer.find_first_of('{'), wszBuffer.size());

                                // Open NIC from GUID
				hDevice = CreateFile(wszBuffer.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
				if (hDevice == INVALID_HANDLE_VALUE)
					return FALSE;

                                // Get Physical Mac-Address
				if (!DeviceIoControl(hDevice, IOCTL_NDIS_QUERY_GLOBAL_STATS,
					&lpEthernet, sizeof(lpEthernet), &uResult, sizeof(uResult), NULL, NULL))
					return FALSE;

				for (int i = 0; i < 6; i++)
					StringCchPrintf(wszAdapter, _countof(wszAdapter), _T("%s%02X:"), wszAdapter, uResult[i]);

				wszTemp = wszAdapter;
				wszTemp = wszTemp.substr(0, wszTemp.size() - 1);
				if (this->m_vData.size() > 0)
				{
					for (int i = 0; i < this->m_vData.size(); i++)
					{
						if (wcscmp(this->m_vData.at(i), wszTemp.c_str()) == NO_ERROR)
						{
							return TRUE;
						}
					}
				}
			}
		}
	}

	return FALSE;
}

void CAuthentication::RegisterMACAddress(LPCWSTR lpcwszAddress)
{
	this->m_vData.push_back(lpcwszAddress);
}
// main.cpp
#include "stdafx.h"

#include "CAuthentication.hpp"

int main()
{
        CAuthentication *pAuthentication = CAuthentication::GetInstance();
        pAuthentication->SetWhiteList();
        if (!pAuthentication->IsUserAllowed())
        {
                MessageBox(NULL, L"Authentication failed. ", L"License Error", MB_OK);
                TerminateProcess(GetCurrentProcess(), -1);
        }

        MessageBox(NULL, L"Authentication succeeded! ", L"License", MB_OK);

        return 0;
}

Wrapper opengl32.dll

// opengl32.dll export function
namespace opengl32
{
	pfnglAlphaFunc	_glAlphaFunc;
	pfnglBegin	_glBegin;
	pfnglBitmap	_glBitmap;
	pfnglBlendFunc	_glBlendFunc;
	pfnglClear	_glClear;
        ....
        ....
}

namespace opengl32
{
	typedef void (WINAPI * pfnglAlphaFunc)(GLenum func, GLclampf ref);
	typedef void (WINAPI * pfnglBegin)(GLenum mode);
	typedef void (WINAPI * pfnglBitmap)(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap);
	typedef void (WINAPI * pfnglBlendFunc)(GLenum sfactor, GLenum dfactor);
	typedef void (WINAPI * pfnglClear)(GLbitfield mask);
        ....
        ....
}

かなり面倒ですが、opengl32.dll のエクスポート関数(360個)を自動生成なりで書いていきます。
自分は自動生成という概念すら思い浮かばなかったので、1つ1つ書いていきましたが...


そして、必要な関数だけ、opengl32.dllのそれに対応したエクスポート関数のポインタを渡します。
で、フックしたい関数を __declspec(dllexport) を使ってやればオーケー


以上

ハードウェアID(PC固有)情報を調べる

Anti-Cheatプロテクターにも HWID BAN というものがありますよね。
今回は、それらを紹介していきたいと思います。


マイナーな手法として、以下のように、NIC(ネットワークインターフェースカード)のGUIDを列挙し、それに対応したMac-Addressを取得するといったものです。

#include "stdafx.h"

#include <NtDDNdis.h>
#include <string>
#include <strsafe.h>
#include <IPHlpApi.h>
#pragma comment(lib, "IPHlpApi.lib")

BOOL WINAPI GetPhysicalMacAddress()
{
	IP_INTERFACE_INFO *pInterface = { 0 };
	HANDLE hDevice;
	DWORD dwLength;

	std::wstring wsBuffer;
	WCHAR szPath[MAX_PATH];
	UCHAR uch[6];
	DWORD dwIoObj = OID_802_3_CURRENT_ADDRESS;

	if (GetInterfaceInfo(pInterface, &dwLength) == ERROR_INSUFFICIENT_BUFFER)
		pInterface = (IP_INTERFACE_INFO *)malloc(dwLength);

	if (GetInterfaceInfo(pInterface, &dwLength) == NO_ERROR)
	{
		for (int i = 0; i < pInterface->NumAdapters; i++)
		{
			wsBuffer = pInterface->Adapter[i].Name;

			StringCchPrintf(szPath, MAX_PATH, TEXT("\\\\.\\%s"), wsBuffer.substr(wsBuffer.size() - 38, 38).c_str());

			hDevice = CreateFile(szPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

			if (hDevice == INVALID_HANDLE_VALUE)
				continue;

			if (!DeviceIoControl(hDevice, IOCTL_NDIS_QUERY_GLOBAL_STATS, &dwIoObj, sizeof(dwIoObj), &uch, sizeof(uch), &dwLength, NULL))
				continue;

			_tprintf(TEXT("%s - %02X:%02X:%02X:%02X:%02X:%02X\n"), 
				szPath, uch[0], uch[1], uch[2], uch[3], uch[4], uch[5]);
		}

		return TRUE;
	}

	return FALSE;
}

int main()
{
	GetPhysicalMacAddress();

	getchar();

    return 0;
}

↑の実行結果
\\.\{XXXXXXXX-AAAA-BBBB-CCCC-YYYYYYYYYYYY} - AA:BB:CC:DD:EE:FF
というようになります。興味がある方は実行してみてください。


他にも、HDDのシリアルナンバーを取得するものがありますが、かなりメジャーな手法ですので、簡単にbypass出来てしまいます。

API Check

こちらを参考にしてAPI Checkを作ってみました。
mhllchan.hatenablog.com


www.youtube.com

比較元のDLL(copy_ntdll等)をどのように持ってくるかで強度さが変わってくるでしょう。

ラッパーAPI _GetProcAddress

GetProcAddressのラッパーAPI書いてみました。
第二引数に序数を指定した場合は対応出来ていません。

#define EXPORT_DIRECTORY OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]

FARPROC WINAPI _GetProcAddress(
    _In_ HMODULE hModule,
    _In_ LPCSTR  lpProcName)
{
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
    if (hModule == NULL) {
        pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandleW(NULL);
    }

    __try {
        if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
            SetLastError(ERROR_BAD_EXE_FORMAT);
            return NULL;
        }

        PIMAGE_NT_HEADERS pNtHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<PBYTE>(pDosHeader) + pDosHeader->e_lfanew);
        if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
            SetLastError(ERROR_INVALID_EXE_SIGNATURE);
            return NULL;
        }
        if (pNtHeader->FileHeader.SizeOfOptionalHeader == 0) {
            SetLastError(ERROR_EXE_MARKED_INVALID);
            return NULL;
        }

        PIMAGE_EXPORT_DIRECTORY pExportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(reinterpret_cast<PBYTE>(pDosHeader) + pNtHeader->EXPORT_DIRECTORY.VirtualAddress);
        LPDWORD lpdwName = reinterpret_cast<LPDWORD>(reinterpret_cast<PBYTE>(pDosHeader) + pExportDir->AddressOfNames);
        LPDWORD lpdwFunction = reinterpret_cast<LPDWORD>(reinterpret_cast<PBYTE>(pDosHeader) + pExportDir->AddressOfFunctions);
        LPWORD lpwOrdinals = reinterpret_cast<LPWORD>(reinterpret_cast<PBYTE>(pDosHeader) + pExportDir->AddressOfNameOrdinals);

        for (int i = 0; i < pExportDir->NumberOfNames; i++) {
            WORD wOrdinal = lpwOrdinals[i];
            LPSTR pszName = reinterpret_cast<LPSTR>(reinterpret_cast<PBYTE>(pDosHeader) + lpdwName[i]);
            LPVOID lpvFunction = reinterpret_cast<LPVOID>(reinterpret_cast<PBYTE>(pDosHeader) + lpdwFunction[wOrdinal]);

            if (strcmp(pszName, lpProcName) == 0) {
                SetLastError(NO_ERROR);
                return (FARPROC)lpvFunction;
            }
        }

        SetLastError(ERROR_INVALID_DATA);
        return NULL;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        SetLastError(ERROR_EXE_MARKED_INVALID);
        return NULL;
    }
}