読者です 読者をやめる 読者になる 読者になる

めいくりぷとのブログ

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

AVA D3D hook

AVA C++

AVA SDK + WDDM Hook


まあWDDM hookなんてしないでも、普通にIDirect3DDevice9のfuction table書き換えでもいいのと、それのほうがBeginSceneとEndSceneに処理突っ込めれてやりやすいんだけど、書き直すのが面倒だから、また次やる気出たときに作り直します。


AVA SDKはUE3 SDK Generatorを用いて各自用意してください。
AVA SDKからクラスやら取得するのが割りと重いから(処理的に)、PFND3DDDI_PRESENTに入れるのはちょっとアレかな...?


余談ですが、オブジェクトの座標取得の基本的な流れとしては、プレイヤーを描画する時は他より比較的に頂点数が多い(?)ので、それを基に3D座標を取得して2D(スクリーン座標)に変換する~って感じなので、OpenGLの場合はglVertex3f/v関数でVertexCount(頂点数)が一定数以上の時に座標を格納して、変換するだけで簡単に取れるのですが、D3Dは自分が知っている限りでは、少し手間のかかる方法しか知りません...


DrawIndexedPrimitive関数にオブジェクトの種類によって渡される引数(PrimitiveCount?)があり、その値から判断するという方法もありますが、Loggerなどを用いてログを調べないといけないのと、ゲームによって違うというのが面倒です...
何か良い方法ありませんかね..? もう少し調べてみます。

FLOAT APIENTRY GetDistance(__in FLOAT *LocationA, __in FLOAT *LocationB)
{
	FLOAT DistanceX = LocationA[0] - LocationB[0];
	FLOAT DistanceY = LocationA[1] - LocationB[1];
	FLOAT DistanceZ = LocationA[2] - LocationB[2];

	return (FLOAT)sqrt((DistanceX * DistanceX) + (DistanceY * DistanceY) + (DistanceZ * DistanceZ));
}

HRESULT APIENTRY DrawBox(IDirect3DDevice9 *pDevice, FLOAT x, FLOAT y, FLOAT Width, FLOAT Height, D3DCOLOR Color)
{
	D3DRECT Rect = { x, y, x + Width, y + Height };

	return pDevice->Clear(1, &Rect, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, Color, 0, 0);
}

HRESULT APIENTRY DrawLine(ID3DXLine *pLine, FLOAT x, FLOAT y, FLOAT x2, FLOAT y2, D3DCOLOR Color)
{
	D3DXVECTOR2 vLine[2];
	vLine[0].x = x;
	vLine[0].y = y;
	vLine[1].x = x2;
	vLine[1].y = y2;

	if (pLine)
	{
		pLine->SetWidth(1);
		pLine->SetAntialias(FALSE);
		pLine->SetGLLines(FALSE);
		pLine->Begin();
		pLine->Draw(vLine, 2, Color);
		pLine->End();
	}

	return S_OK;
}

HRESULT APIENTRY DDICreateQuery(__in HANDLE hDevice, __in_opt D3DDDIARG_CREATEQUERY *pCreateQuery)
{
	if (pCreateQuery && pCreateQuery->QueryType == D3DDDIQUERYTYPE_OCCLUSION)
		pCreateQuery->QueryType = D3DDDIQUERYTYPE_EVENT;

	return _DDICreateQuery(hDevice, pCreateQuery);
}

HRESULT APIENTRY DDIDrawIndexedPrimitive(__in HANDLE hDevice, __in_opt const D3DDDIARG_DRAWINDEXEDPRIMITIVE *pDPI)
{
	CGraphicsContext *pGraphicsContext = CGraphicsContext::GetInstance();
	IDirect3DDevice9 *pDevice = Direct3D9::GetInstance()->GetDevice();

	// get entity 2D position
	for (int i = 0; i < MaxEnts; i++)
	{
		D3DXMATRIX mProjection, mViewProj, mWorldProj;
		D3DXVECTOR3 vScreen2D, vWorld3D;

		vWorld3D.x = Ents[i].Origin[0];
		vWorld3D.y = Ents[i].Origin[1];
		vWorld3D.z = Ents[i].Origin[2];

		if (pDevice)
		{
			pDevice->GetViewport(&Viewport);
			pDevice->GetVertexShaderConstantF(0, mProjection, 4);
			pDevice->GetVertexShaderConstantF(231, mViewProj, 4);

			D3DXMatrixIdentity(&mWorldProj);
			D3DXVec3Project(&vScreen2D, &vWorld3D, &Viewport, &mProjection, &mViewProj, &mWorldProj);

			if (vScreen2D.z < 1.0f && mProjection._44 > 1.0f)
			{
				Ents[i].ESP.x = vScreen2D.x;
				Ents[i].ESP.y = vScreen2D.y;
			}
		}
	}

	// draw esp
	for (int i = 0; i < MaxEnts; i++)
	{
		FLOAT x = Ents[i].ESP.x;
		FLOAT y = Ents[i].ESP.y;
		UCHAR r, g, b, a;

		if (Ents[i].IsVisible == TRUE)
		{
			r = 20; g = 255; b = 20; a = 200;
		}
		else
		{
			r = 255; g = 255; b = 20; a = 200;
		}

		// draw esp box on target
		if (pGraphicsContext->IsDrawEspBoxActivated())
		{
			DrawBox(pDevice, Ents[i].ESP.x, Ents[i].ESP.y, 5, 5, D3DCOLOR_RGBA(r, g, b, a));
		}

		// draw esp line on foot to target
		if (pGraphicsContext->IsDrawEspLineActivated())
		{
			ID3DXLine *pLine = NULL;

			D3DXCreateLine(pDevice, &pLine);
			if (pLine)
			{
				DrawLine(pLine, x, y, Viewport.Width / 2.0f, Viewport.Height, D3DCOLOR_RGBA(r, g, b, a));
			}
		}
	}

	// reset
	MaxEnts = 0;

	return _DDIDrawIndexedPrimitive(hDevice, pDPI);
}

HRESULT APIENTRY DDIPresent(__in HANDLE hDevice, __in_opt const D3DDDIARG_PRESENT *pPresent)
{
	CGameManager *pGameManager = CGameManager::GetInstance();
	UGameEngine* pGameEngine;
	AavaPlayerController* pAvaPC;
	AavaPawn* pAvaPawn;

	pGameEngine = pGameManager->GetEngine();

	if (pGameEngine && pGameEngine->GamePlayers.Data)
	{
		pAvaPC = (AavaPlayerController*)pGameEngine->GamePlayers.Data[0]->Actor;

		if (pAvaPC && pAvaPC->IsA(AavaPlayerController::StaticClass()))
		{
			pAvaPawn = (AavaPawn*)pAvaPC->Pawn;

			// Validate the pawn and the WorldInfo pointer required for pawn looping
			if (pAvaPawn && pAvaPawn->IsA(AavaPawn::StaticClass()) && pAvaPawn->WorldInfo)
			{
				if (MaxEnts = 0)
				{
					// Loop through the pawns
					for (APawn* pTarget = pAvaPawn->WorldInfo->PawnList; pTarget; pTarget = pTarget->NextPawn)
					{
						// Validate the current target
						if (pTarget != pAvaPawn && pTarget->IsA(AavaPawn::StaticClass()) 
							&& pGameManager->IsValidTarget(pTarget))
						{
							// player hp
							Ents[MaxEnts].Health = (INT)pTarget->Health;

							// player position
							Ents[MaxEnts].Origin[0] = pTarget->Location.X;
							Ents[MaxEnts].Origin[1] = pTarget->Location.Y;
							Ents[MaxEnts].Origin[2] = pTarget->Location.Z;

							// Next Entity
							MaxEnts++;
						}
					}
				}
			}
		}
	}

	return _DDIPresnet(hDevice, pPresent);
}

ゴミコード投下2

C++ CodeMon

3,4ヶ月前に書いた雑&糞コード、現在動くかは不明

まあ糞コードだけど、一応 creditつけておく
[C++] CodeMon copy module bypass - Pastebin.com

VirtualAllocをフックして、コピーされるモジュールのサイズから決め打ちで判別して、各APIをオリジナルのAPIに飛ばします。
コピーされるタイミングで飛ばしてるから、コピーされたモジュールに対してのCRCには引っかからない。
あとは各検出に使われてるオリジナルのAPIにフックかけて...
クライアントにかけられてるCRCは前記事参考に。
基本的に何もかもがマニュアルマッピングでロードされるから、色々と手間

ゴミコード投下

C++ CodeMon

3,4ヶ月前に書いたコードだからあんまり覚えてないけど、CodeMonのCRCが最初からあるんじゃなくて、CodeMonの初期化時にUrlからCRCモジュール?がダウンロードされてきて、マニュアルマッピング?かなんかでロード(MapViewOfFileExフックすればモジュール取得できたと思う)されるから、そのタイミングでフックしてクライアントのCRCだけ潰せば、CodeMon自体のCRCはどうにかしなくても大丈夫だったはず。。?
(crc bypassのコードはテンプレです。)

(DWORD)lpCrcModule + 0xFDBB このオフセットは前回から更新がなければそのままだと思うけど、まあすぐ見つかるから自分で探してください。

#include "stdafx.h"

#include "CodeMonCRC.hpp"
#include "detour.hpp"

DWORD dwMemoryStart = NULL, dwMemoryEnd = NULL, dwFakeMemory = NULL;
DWORD CMCRC_Client_1_Ret, CMCRC_Client_2_Ret;

void __declspec(naked) CMCRC_Client_1_Asm() 
{
	__asm 
	{
		cmp edi,[dwMemoryStart]
		jb Ending
		cmp edi,[dwMemoryEnd]
		ja Ending
		sub edi,[dwMemoryStart]	
		add edi,[dwFakeMemory]
Ending:
		mov eax,[edi]
		xor eax,ecx
		add edi,04
		jmp dword ptr[CMCRC_Client_1_Ret]
	}
}

void __declspec(naked) CMCRC_Client_2_Asm()
{
	__asm
	{
		cmp esi,[dwMemoryStart]
		jb Ending
		cmp esi,[dwMemoryEnd]
		ja Ending
		sub esi,[dwMemoryStart]
		add esi,[dwFakeMemory]
Ending:
		mov eax,[esi]
		imul eax, eax, 5BD1E995h
		jmp dword ptr[CMCRC_Client_2_Ret]
	}
}

BOOL WINAPI Detour_MapViewOfFileEx()
{
	typedef LPVOID(WINAPI *pfnMapViewOfFileEx)(
		_In_     HANDLE hFileMappingObject,
		_In_     DWORD  dwDesiredAccess,
		_In_     DWORD  dwFileOffsetHigh,
		_In_     DWORD  dwFileOffsetLow,
		_In_     SIZE_T dwNumberOfBytesToMap,
		_In_opt_ LPVOID lpBaseAddress
		);

	static LPVOID lpCrcModule = NULL;

	static pfnMapViewOfFileEx _MapViewOfFileEx =
		reinterpret_cast<pfnMapViewOfFileEx>(GetProcAddress(GetModuleHandle(_T("KERNELBASE.dll")), "MapViewOfFileEx"));
	pfnMapViewOfFileEx MapViewOfFileEx_Hook = [](
		_In_     HANDLE hFileMappingObject,
		_In_     DWORD  dwDesiredAccess,
		_In_     DWORD  dwFileOffsetHigh,
		_In_     DWORD  dwFileOffsetLow,
		_In_     SIZE_T dwNumberOfBytesToMap,
		_In_opt_ LPVOID lpBaseAddress) -> LPVOID
	{
		LPVOID lpvAddress = _MapViewOfFileEx(hFileMappingObject, 
			FILE_MAP_ALL_ACCESS, dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap, lpBaseAddress);

		if (lpvAddress)
		{
			if (lpCrcModule == NULL)
			{
				lpCrcModule = lpvAddress;

                                DWORD CMCRC_Client_1 = (DWORD)lpCrcModule + 0xFDBB;
	                        DWORD CMCRC_Client_2 = (DWORD)lpCrcModule + 0xFE34;

				CMCRC_Client_1_Ret = CMCRC_Client_1 + 0x7;
				CMCRC_Client_2_Ret = CMCRC_Client_2 + 0x8;

				DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&CMCRC_Client_1), CMCRC_Client_1_Asm);
				DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&CMCRC_Client_2), CMCRC_Client_2_Asm);
			}
		}

		return lpvAddress;
	};

	return DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&_MapViewOfFileEx), MapViewOfFileEx_Hook);
}

BOOL codemon_crc_bypass()
{
        HMODULE hModule = GetModuleHandle(NULL);

	IMAGE_NT_HEADERS32 *pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS32*>(
		reinterpret_cast<PBYTE>(hModule) + PIMAGE_DOS_HEADER(hModule)->e_lfanew);

        if (pNtHeaders)
        {
		DWORD dwSize = pNtHeaders->OptionalHeader.SizeOfImage;
		dwMemoryStart = pNtHeaders->OptionalHeader.ImageBase;
		dwMemoryEnd = pNtHeaders->OptionalHeader.ImageBase + pNtHeaders->OptionalHeader.SizeOfImage;

        	if (dwMemoryStart && dwMemoryEnd)
        	{
	        	BYTE *pbFakeMemory = (BYTE*)malloc(dwSize);
                	RtlCopyMemory(pbFakeMemory, (LPVOID)dwMemoryStart, dwSize);

	        	dwFakeMemory = (DWORD)pbFakeMemory;

	        	return Detour_MapViewOfFileEx();
        	}
	}

	return FALSE;
}

記事ネタが無いので、ゴミコード投下ということでした。

久しぶりにCSO

CSO C++ OpenGL

なぜ今更こんな糞ゲーについての記事を書くと言いますと...

いつのアップデートかは知りませんが、OpenGLでのプレイヤーの描画方法に少し変更があったそうな。

何のためにかはハッキリ分かりませんが、jacky14, delayzero? などのOpenGLをベースにしたチート対策でもあるのかな?..
処理速度云々も考えられなくもないですが、おそらく前者でしょう。

で、何がどう変更したかって言いますと、今まではプレイヤーを描画(プレイヤーに限らず)する際にglVertex3fで描画していましたが、変更後はプレイヤーの無敵時間時は今まで通りglVertex3fで描画して、通常時(ダメージが通る時)はglVertex3fvで描画されるようになりました。

この変更で、jacky14, delayzeroは試していませんが使えなくなったのではないでしょうか..(?)
(根拠はありませんが、自作してる物が使えなくなったので...)

解決策

コードにもよりますが、オブジェクトを描画するのは基本的にはglVertex3fが使われているため、オブジェクトの座標を取得するのもそれをフックすればいいだけです。

なので、glVertex3fvも同じようにフックして取得するだけです。

gluProjectで3D座標から2D座標へ変換して、足場の座標、体、頭、、と取得しますが、glVertex3fが全く使われなくなった訳ではないため、VertexCount(頂点数)は関数別で分けて処理させたほうが良いでしょう。

gyazo.com

Have fun! XD

QXmlStreamReader/Writerでxmlにウィジェットのステータスを保存する

サンプルコード

CConfigManager.hpp

#pragma once

#include <qwidget.h>
#include <map>

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

public:
	// 
	void WriteXmlElement(__in QXmlStreamWriter &writer);
	void ReadFromXml(__in QXmlStreamReader &reader);

	// for qwidget
	QCheckBox *CreateCheckBox(__in const QString &name, __in QWidget *parent = 0);
	QSpinBox *CreateSpinBox(__in const QString &name, __in QWidget *parent = 0);
	QLineEdit *CreateLineEdit(__in const QString &name, __in QWidget *parent = 0);
	QComboBox *CreateComboBox(__in const QString &name, __in QWidget *parent = 0);
	QPushButton *CreatePushButton(__in const QString &name, __in bool checkable, __in QWidget *parent = 0);
	QRadioButton *CreateRudioButton(__in const QString &name, __in QWidget *parent = 0);
	QSlider *CreateSlider(__in const QString &name, __in QWidget *parent = 0);

	static CConfigManager *GetInstance()
	{
		static CConfigManager instance;
		return &instance;
	}

private:
	void WriteXmlSettingsElement(__in QXmlStreamWriter &writer);
	void ReadXmlSettings(__in QXmlStreamReader &reader);

	void ReadXmlCheckBox(__in QXmlStreamReader &reader);
	void ReadXmlSpinBox(__in QXmlStreamReader &reader);
	void ReadXmlLineEdit(__in QXmlStreamReader &reader);
	void ReadXmlComboBox(__in QXmlStreamReader &reader);
	void ReadXmlPushButton(__in QXmlStreamReader &reader);
	void ReadXmlRadioButton(__in QXmlStreamReader &reader);
	void ReadXmlSlider(__in QXmlStreamReader &reader);

	// for qwidget
	std::map<QString, QCheckBox*> m_mCheckBox;
	std::map<QString, QSpinBox*> m_mSpinBox;
	std::map<QString, QLineEdit*> m_mLineEdit;
	std::map<QString, QComboBox*> m_mComboBox;
	std::map<QString, QPushButton*> m_mPushButton;
	std::map<QString, QRadioButton*> m_mRadioButton;
	std::map<QString, QSlider*> m_mSlider;
};

CConfigManager.cpp

#include "stdafx.h"

#include "CConfigManager.hpp"

CConfigManager::CConfigManager()
{
}

CConfigManager::~CConfigManager()
{
}

void CConfigManager::WriteXmlElement(__in QXmlStreamWriter &writer)
{
	// settings
	this->WriteXmlSettingsElement(writer);
}

void CConfigManager::WriteXmlSettingsElement(__in QXmlStreamWriter &writer)
{
	writer.writeStartElement("settings");

	foreach(auto it, this->m_mCheckBox)
	{
		if (it.second && it.first.size() > 1)
		{
			writer.writeStartElement("checkbox");
			writer.writeTextElement("name", it.first);
			writer.writeTextElement("state", QString::number(it.second->isChecked()));
			writer.writeEndElement();
		}
	}

	writer.writeEndElement();
}

void CConfigManager::ReadFromXml(__in QXmlStreamReader &reader)
{
	while (!reader.atEnd() && !reader.hasError())
	{
		QXmlStreamReader::TokenType token = reader.readNext();
		if (token == QXmlStreamReader::StartElement)
		{
			if (reader.name() == "settings")
			{
				// settings
				this->ReadXmlSettings(reader);
			}
		}
	}
}

void CConfigManager::ReadXmlSettings(__in QXmlStreamReader &reader)
{
	while (!reader.atEnd() && !reader.hasError())
	{
		QXmlStreamReader::TokenType token = reader.readNext();
		if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == "settings")
		{
			// element item end
			break;
		}
		if (token == QXmlStreamReader::StartElement)
		{
			if (reader.name() == "checkbox")
				this->ReadXmlCheckBox(reader);
		}
	}
}

void CConfigManager::ReadXmlCheckBox(__in QXmlStreamReader &reader)
{
	QString name = QString();
	while (!reader.atEnd() && !reader.hasError())
	{
		QXmlStreamReader::TokenType token = reader.readNext();
		if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == "checkbox")
		{
			// element item end
			break;
		}
		if (token == QXmlStreamReader::StartElement)
		{
			if (reader.name() == "name")
				name = reader.readElementText();
			else if (reader.name() == "state" && name.length() > 1)
				this->m_mCheckBox[name]->setChecked(reader.readElementText().toInt());
		}
	}
}

// thanks to mahorori
QCheckBox *CConfigManager::CreateCheckBox(__in const QString &name, __in QWidget *parent)
{
	QCheckBox *checkBox = new QCheckBox(name, parent);
	this->m_mCheckBox[name] = checkBox;
	return checkBox;
}
//

...

使い方

CMainWindow::CMainWindow(__in QWidget *parent) : QMainWindow(parent)
{
        CConfigManager *pConfig = CConfigManager::GetInstance();
        QCheckBox *chkTestBox = pConfig->CreateCheckBox("Test Box");

        connect(chkTestBox, &QCheckBox::toggled, [=](bool toggled)
        {
                //
        }
        ...
}

void CMainWindow::OnLoad()
{
	QSettings settings;
	QString sFileName = QFileDialog::getOpenFileName(this, QString(), settings.value("filename").toString(), tr("Xml Files (*.xml)"));
	if (sFileName.isEmpty())
	{
		QMessageBox::warning(this, QString(), tr("Unknown error occurred."), QMessageBox::Ok);
		return;
	}

	QFile file(sFileName);
	if (!file.open(QIODevice::ReadOnly))
	{
		QMessageBox::warning(this, QString(), file.errorString(), QMessageBox::Ok);
		return;
	}

	CConfigManager::GetInstance()->ReadFromXml(QXmlStreamReader(&file));

	this->m_sXmlFileName = sFileName;
}

void CMainWindow::OnSave()
{
	if (this->m_sXmlFileName.isEmpty())
	{
		// no file exists, try to save as...
		this->OnSaveAs();
		return;
	}

	QFile file(this->m_sXmlFileName);
	if (!file.open(QIODevice::WriteOnly))
	{
		QMessageBox::warning(this, QString(), file.errorString(), QMessageBox::Ok);
		return;
	}

	QXmlStreamWriter writer(&file);
	writer.setAutoFormatting(true);
	writer.setAutoFormattingIndent(4);
	writer.writeStartDocument();

	// write elements
	writer.writeStartElement("XmlTester");
	CConfigManager::GetInstance()->WriteXmlElement(writer);
	writer.writeEndElement();

	file.close();

	QMessageBox::information(this, QString(), tr("Done."), QMessageBox::Ok);
}

void CMainWindow::OnSaveAs()
{
	//
	QString sFileName = QFileDialog::getSaveFileName(this, QString(), QString(), tr("Xml Files (*.xml)"));
	if (sFileName.isEmpty())
	{
		// QMessageBox::warning(this, QString(), tr("Unknown error occurred."), QMessageBox::Ok);
		return;
	}

	this->m_sXmlFileName = sFileName;
	this->OnSave();
}

プロテクターに検出されないD3D・OpenGLフックの考察

C++ DirectX

FPS等でWallhackやD3Dフックをベースにしたチートを行うにも殆どのゲームでXignCodeやHackShield, NGS(BlackChiper)などのプロテクターが導入されているため、チート検出されてしまいます。

ですが、海外フォーラム等に落ちている物などで、検出されずに使えるものも存在します。
検出される物とされない物の違いをプロテクター別で考察していきます。

チート検出される物

[XIGNCODE, nProtect, NGS(BlackCipher), HackShield]

・IDirect3DDevice9インターフェースなどのメンバ関数を示すアドレスを書き換えてフックする方法 (関数テーブルの書き換え)
IDirect3DDevice9の場合だと、インターフェースが示すアドレスがD3D9.DLLの範囲に収まるかどうかをチェックしていますので、これに当たらないアドレスがある場合はチート検出されます。

・D3Dxx.DLL/OpenGL32.DLLの特定部分にJMPコードを仕込ませてフックする方法
基本的に1Byteでも書き換えるとチート検出されます。

HackShield(v5.6.34.449, v5.7.6.502)は D3Dxx.DLL, OpenGL32.DLLにCRCを実装しているため、
適当なアドレスをCEのRead Accessで調べてみると...(何故か仮想化されていないので、検出関数がすぐ見つかります。)

チート検出されない物

何度か記事で上げましたが、グラフィックドライバにつけ込んだ物がまだ生き残っているようです。(XignCodeで確認済み)
D3Dxx.DLLはWDDM, OpenGLはICDを...(省略

唯一NGSだけはグラフィックドライバもチェックしています。
・関数テーブル上の関数が示すアドレスの配置範囲のチェック
・関数テーブル上の関数が示すアドレス分のメモリのチェック
がありますが、Mid function? (関数の途中にJMPコードを仕込ませてフックする方法)でどうにかなります。

まあこれくらいです..

WDDM, OpenGL ICDの説明は省きましたが、過去記事にそれっぽいのがいくつかありますので、そちらを見てください。(参考にはなりませんが())


以上...と説明していた夢の内容でした。

:D

C++ DirectX

gyazo.com

前回よりも大分まとまった...?

D3Dの深い部分の勉強も兼ねて地道に...