めいくりぷとのブログ

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

メイプル

gyazo.com

たまに、NGS Update Error で落とされる...

各メイプルに、1つずつNGSのプロセス生成するようにした方がええんかの...?

うほ

gyazo.com

update errorの原因何となくわかったのと、3つ?の制限無くなった^o^

cso ogl

  • o-
#define MODEL_SPAGNA	1984	// スペツナズ
#define MODEL_GIGN		2007	// GIAN
#define MODEL_ELITE		2065	// エリート要因

#define IS_MODEL_SPAGNA(VertexCount)	(VertexCount == MODEL_SPAGNA)
#define IS_MODEL_GIGN(VertexCount)		(VertexCount == MODEL_GIGN)
#define IS_MODEL_ELITE(VertexCount)		(VertexCount == MODEL_ELITE)

/////////////////////////////////////////////////////////////////////////////////

typedef struct _ENTITIES 
{
	BOOL Visible;
	POINT Head;
}ENTITIES, *PENTITIES, *LPENTITIES;

GLint Viewport[4];
GLint VertexCount3f = 0;
GLint MaxEnts = 0;
GLfloat fHighestVertex[3], fLowestVertex[3];
GLfloat fLastPosition[3];
GLdouble Depthcheck[3];
ENTITIES Entities[33] = { NULL };

void glVertex3fv_hook(__in CONST GLfloat *v)
{
	_glVertex3fv(v);

	VertexCount3f++;

	fLastPosition[0] = v[0];
	fLastPosition[1] = v[1];
	fLastPosition[2] = v[2];

	if (v[2] > fHighestVertex[2] || VertexCount3f == 1)
	{
		fHighestVertex[0] = fLastPosition[0];
		fHighestVertex[1] = fLastPosition[1];
		fHighestVertex[2] = fLastPosition[2];
	}

	if (v[2] < fLowestVertex[2] || VertexCount3f == 1)
	{
		fLowestVertex[0] = fLastPosition[0];
		fLowestVertex[1] = fLastPosition[1];
		fLowestVertex[2] = fLastPosition[2];
	}
}

void glPopMatrix_hook()
{
	_glPopMatrix();

	COpenGL *pOpenGL = COpenGL::GetInstance();

	if (VertexCount3f > pOpenGL->GetTargetModelContext() && GetDistance(fHighestVertex, fCamera) > 50.0f
		&& (fHighestVertex[2] - fLowestVertex[2]) > 30.0f)
	{
		if (pOpenGL->IsAimTarget())
		{
			switch (pOpenGL->GetAimTarget())
			{
				case 0:	// TR
				{
					if (!IS_MODEL_ELITE(VertexCount3f)) return;
					break;
				}
				case 1:	// CT
				{
					if (!IS_MODEL_GIGN(VertexCount3f)) return;
					break;
				}
				default:
					break;
			}
		}

		GLfloat pix;
		GLdouble ModelView[16];
		GLdouble ProjView[16];
		GLdouble View2D[3];

		glGetDoublev(GL_MODELVIEW_MATRIX, ModelView);
		glGetDoublev(GL_PROJECTION_MATRIX, ProjView);

		if (gluProject(fHighestVertex[0], fHighestVertex[1], fHighestVertex[2] - 4, ModelView, ProjView,
			Viewport, &View2D[0], &View2D[1], &View2D[2]) == GL_TRUE)
		{
			Head[0] = (float)View2D[0];
			Head[1] = (int)Viewport[3] - (float)View2D[1];

			if (gluProject(fHighestVertex[0], fHighestVertex[1], fHighestVertex[2] + 1, ModelView, ProjView, Viewport, &Depthcheck[0], &Depthcheck[1], &Depthcheck[2]) == GL_TRUE)
			{
				glReadPixels((int)Depthcheck[0], (int)Depthcheck[1], 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pix);

				if (MaxEnts > 32)
					MaxEnts = 0;

				if (pix >= Depthcheck[2])
					Entities[MaxEnts].Visible = 0x1;

				Entities[MaxEnts].Head.x = (LONG)Head[0];
				Entities[MaxEnts].Head.y = (LONG)Head[1];

				MaxEnts++;
			}
		}
	}
}

void glViewport_hook(__in GLint x, __in GLint y, __in GLsizei width, __in GLsizei height)
{
	Viewport[0] = x;
	Viewport[1] = y;
	Viewport[2] = width;
	Viewport[3] = height;

	_glViewport(x, y, width, height);
};

後はwglSwapBuffersかDrvSwapBuffersフックして...

TDMで壁抜き出来るように戻ったらまたやろうかな...

cso2はもういいかなっていう...

AVA 更新

gyazo.com

何やら変なクラスが追加されましたの...
まあ avaGame/avaRules/Engine/AVANET 辺りしか触れないからどうでもいいんですけど...

それと今まではsplash画像はファイルチェックの対象外でフォルダごと消すだけで良かったけど、アプデ入ってから毎回全ファイルチェックされる糞仕様に変わったのでってことで。
ファイルチェックはPmangDownloaderに対してReadFile辺りフックしときゃなんとかなる。(知らないけど
無駄に3つくらい起動するから、proxy dllとかで全部にフックかければok

スプラッシュウィンドウ非表示

void Detour_CreateWindowExW()
{
	static decltype(&CreateWindowExW) _CreateWindowExW = CreateWindowExW;

	decltype(&CreateWindowExW) CreateWindowExW__Hook = [](
		_In_     DWORD     dwExStyle,
		_In_opt_ LPCWSTR   lpClassName,
		_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) -> HWND
	{
		HMODULE hModule;

		if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<TCHAR*>(_ReturnAddress()), &hModule))
		{
			if (hModule == GetModuleHandle(L"ava.exe"))
			{
				if (wcsstr(lpClassName, L"SPLASH"))
					return NULL;
			}
		}

		return _CreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
	};

	DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&_CreateWindowExW), CreateWindowExW__Hook);
}

誤クリックの地味にウザいアレ

VOID Detour_ShellExecuteExW()
{
	static decltype(&ShellExecuteExW) _ShellExecuteExW = ShellExecuteExW;

	decltype(&ShellExecuteExW) ShellExecuteExW__Hook = [](
		_Inout_ SHELLEXECUTEINFO *pExecInfo) -> BOOL
	{
		if (pExecInfo && wcsstr(pExecInfo->lpFile, L"IEXPLORE.EXE"))
			return FALSE;

		return _ShellExecuteExW(pExecInfo);
	};

	DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&_ShellExecuteExW), ShellExecuteExW__Hook);
}

ウィンドウフレーム以外からでもウィンドウを移動出来るようにする。

前回同じような記事を書いたのですが、継承するクラスによっては移動出来ない物もあったみたいですので、今回は nativeEvent関数をオーバーライドして実装してみました。

bool CMainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
	MSG *msg = reinterpret_cast<MSG*>(message);

	switch (msg->message)
	{
		case WM_LBUTTONDOWN:
		{
			if (!this->m_bClicked)
			{
				this->m_bClicked = true;
				this->m_originPos.setX(GET_X_LPARAM(msg->lParam));
				this->m_originPos.setY(GET_Y_LPARAM(msg->lParam));
			}
			break;
		}
		case WM_LBUTTONUP:
		{
			if (this->m_bClicked)
				this->m_bClicked = false;
			break;
		}
		case WM_MOUSEMOVE:
		{
			if (this->m_bClicked)
			{
				this->move(
					this->pos().x() + GET_X_LPARAM(msg->lParam) - this->m_originPos.x(),
					this->pos().y() + GET_Y_LPARAM(msg->lParam) - this->m_originPos.y());
			}
			break;
		}
		default:
			return QWidget::nativeEvent(eventType, message, result);
	}

	return false;
}

別プロセスのAPIのアドレスを取得する。

https://pastebin.com/QR1TUW7U

// GetProcessModuleInfo
ReadProcessMemoryで対象プロセスのPEBを読み取る -> モジュールの一覧取得 -> モジュールのベースアドレスの取得

// GetProcAddressEx
取得したモジュールのPEヘッダーからエクスポート関数のアドレス、名前、序数を列挙 -> 該当のアドレスを返す


プロセスとモジュールのメモリ共有型のアンチチート作ってみようと思って、モジュールのAPI/関数チェックに書いてみたけど、なんか微妙な感じに...また次触るときに書き直します。


全プロセスから xul.XRE_GetBootstrap (firefox), KERNELBASE.CreateFileW を列挙した画像です。
gyazo.com

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フックの考察

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の説明は省きましたが、過去記事にそれっぽいのがいくつかありますので、そちらを見てください。(参考にはなりませんが())


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