
学校の夏のある夜、私はDayZ Mod(Arma 2 OA)のmaphackが必要になりました。 トピックに関する情報を検索した後、Battleyeアンチチートに関与してはならないことに気付きました。カーネル保護ドライバーをバイパスする知識も経験もないため、ゲームプロセスへのアクセスに大量のフックを配置したためです。
DayZは、ゲームプレイに重要なオブジェクトの場所が頻繁に変更されず、再起動後も残る数少ないゲームの1つです(ベースは移動せず、ほとんどの機器も長時間静止しています)。 この事実は、RAMダンプを通じて攻撃の可能性を開きます。
最後にすべてのリンク。
第1章ダンピム
アンチチートをバイパスせずにランタイムでメモリのスナップショットを取得することは成功しそうにありません。 したがって、他の戦術を探しています。 最初に見つけるのはhabrastatyaで 、そこからどこを掘るかが明らかになります。
試行1
記事に記載されているホットセットを介してメモリイメージを取得できず、Ubuntu CyberPack(IRF)から起動し、fmemを介してイメージを取得することができませんでした。
少しグーグル、同じLiME〜Linux Memory Extractor機能を持つ代替ツールを見つけます。 今では、livecdで組み立てて実行する必要がありました。
ディストリビューションの選択はTinyCore(TinyCorePure64、3 GB以上をダンプする必要がある場合)に依存していました。 それからダウンロードした後、パッケージをダウンロードしてインストールします。
tce-load -iw linux-kernel-sources-env.tcz cliorx linux-kernel-sources-env.sh
次に、ソート付きのフラッシュドライブをマウントします。ここで、ダンプをダンプし、makeで収集し、イメージを取得します
insmod ./lime.ko "path=/path/mem-image.lime format=lime"
必要なプロセスのメモリを取得するために、誰かがこのファイルをフィードする必要があります。 これを行うには、memdumpプラグインを備えたVolatility Frameworkが必要です。
vol.py -f F:\mem-image.lime format=lime pslist vol.py -f F:\mem-image.lime format=lime memdump –dump-dir ./output –p 868
または、揮発性自体とは異なり、そのフォークであり積極的に開発されているRekallフレームワーク
rekal -f F:\mem-image.lime pslist rekal -f F:\mem-image.lime memdump dump_dir="./output", pids=868
しかし、私がやらないこと、彼は始めたくなかったので、私は掘り続けました。
Windows 10でRekallを使用する場合、ダンプで何かを初めて検索すると、次のようなメッセージが表示される場合があります。
WARNING:rekall.1:Profile nt/GUID/F6F4895554894B24B4DF942361F0730D1 fetched and built. Please consider reporting this profile to the Rekall team so we may add it to the public profile repository.
そして、次回、このようなエラーが発生する可能性があります。
CRITICAL:rekall.1:A DTB value was found but failed to verify. See logging messages for more information.
これが発生した場合、起動時に、最初に取得したプロファイル値で--profileパラメーターを指定する必要があります。
試行2
メモリの最も完全なスナップショットを取得するには、Windowsが休止状態に切り替わったときにすべてのメモリを保存するhiberfil.sysファイルを使用できます。
ここでも簡単です。休止状態モードに切り替え、任意のlivecd(私の場合は同じTinyCore)から起動し、システムディスク(読み取り専用)とUSBフラッシュドライブをマウントし、目的のファイルをコピーします
TinyCoreの場合、ntfsサポートパッケージをインストールすることを忘れないでください。
tce-load -iw ntfs-3g
fdisk -lを使用して、必要な論理パーティションを見つけてマウントします
sudo ntfs-3g -o ro /dev/sda2 /tmp/a1 // Read-Only sudo ntfs-3g /dev/sdc1 /tmp/a2
コピー
cp /tmp/a1/hiberfil.sys /tmp/a2
次に、このファイルにボラティリティを与えることができます(win7以前で休止状態ファイルをサポートします)。
vol.py imagecopy -f hiberfil.sys -O win7.img
私はwin10を持っているので、このオプションは私には適していない。
このファイルを、サンドマンフレームワークであったHibr2binプログラムに渡そうとしました。
HIBR2BIN /PLATFORM X64 /MAJOR 10 /MINOR 0 /INPUT hiberfil.sys /OUTPUT uncompressed.bin
しかし、彼女は理解できない出力を提供し、分析のためのフレームワークはそれを使用することを拒否しました。
Hibernation Reconは、無料バージョンで問題を解決しました。問題なくフレームワークを読み込めるようになりました。
memdumpの出力で、プロセスメモリ自体を含むファイルと、ファイル内のアドレスに対する仮想アドレスの比率を含むファイルを取得します。
File Address Length Virtual Addr
第2章Maphackの作成。
GUIには、Qtを選択しました。
最初に、テーブルを介してファイル内の仮想メモリにアクセスするための便利なラッパーを作成します。
class MemoryAPI { public: MemoryAPI(){} MemoryAPI(QString pathDump, QString pathIDX);
idxファイルの各行を単純な構造として示します。
class MemoryRange { private: quint32 baseVirtualAddress; quint32 basePhysicalAddress; quint32 size; };
仮想アドレスによってデータを読み取るすべての機能は、必要なパラメーターを使用してこの関数を呼び出すことに限定されます。
QByteArray MemoryAPI::readVirtMem(const quint32 baseAddr, const quint32 size) { QByteArray result;
アドレス変換は、配列内の目的のオフセットを検索するだけで実行されます(バイナリ検索を使用できますが、使用できません)。
quint32 MemoryAPI::convertVirtToPhys(const quint32 virt) const { for(auto it = memoryRelations.begin(); it != memoryRelations.end(); ++it) { if((*it).inRange(virt)) { const quint32& phBase = (*it).getPhysicalAddress(), vrBase = (*it).getVirtualAddress();
それでは、ゲーム内の各オブジェクトのデータを保存する構造を作りましょう。
class EntityData { public: friend class WorldState;
次に、世界の状態(すべてのオブジェクト)を格納するクラスを作成します。
class WorldState { public:
ここでは、メモリダンプに関するすべての作業と、すべてのオブジェクトに関する情報の読み込みが行われます。
WorldState::WorldState(const QString& dumpFile, const QString& idxFile) {
オフセットの初期化
void WorldState::initOffsets() { masterOffsets.append(0x880); masterOffsets.append(0xb24); masterOffsets.append(0xdc8); tableOffsets.append(0x8); tableOffsets.append(0xb0); tableOffsets.append(0x158); tableOffsets.append(0x200); objTableAddress = 0xDAD8C0; }
ここでさらに詳しく説明します。 ゲームワールドに関するすべての情報は、ほぼ同じ構造で保存されます(フォーラムで見つかったダンプに基づきます)。
世界構造 class World { public: char _0x0000[8]; InGameUI* inGameUI;
この構造にアクセスするには、ゲームの各バージョンで静的なオフセットにあるポインターを使用します(オフセットをグーグルで検索するか、逆に自分で見つけることができますが、これはまったく別の話です)。 このオフセットを変数objTableAddressに格納します。 masterOffsetsには、この構造に関連する3つのテーブルのオフセットを格納します。
テーブルとテーブル class EntitiesDistributed { public: char _0x0000[8]; Entity* table1;
同様に、各テーブルには長さのある4つのテーブルがさらに格納されます(これらのテーブルのオフセットはtableOffsetsに格納されます)。
これで、ゲーム内のすべてのオブジェクトを反復処理できます。 各エンティティを処理する機能を分析しましょう。
void WorldState::handleEntity(quint32 entityAddress, MemoryAPI &mem) { QString objType; QString objName; float coordX; float coordY; try{ quint32 obj1 = entityAddress; quint32 pCfgVehicle = mem.readPtr(obj1 + 0x3C); quint32 obj3 = mem.readPtr(pCfgVehicle + 0x30); quint32 pObjType = mem.readPtr(pCfgVehicle + 0x6C); objType = mem.readArmaString(pObjType); objName = mem.readStringAscii(obj3 + 0x8, 25); quint32 pEntityVisualState = mem.readPtr(obj1 + 0x18); coordX = mem.readFloat(pEntityVisualState + 0x28); coordY = mem.readFloat(pEntityVisualState + 0x30); }catch(int a) { qDebug() << " ."; return; }
各エンティティはほぼそのような構造を表します
エンティティ構造 class Entity { public: char _0x0000[24]; EntityVisualState* entityVisualState;
ここでは、3つのポインターすべてに関心があります。
- EntityVisualState-ロケーション情報。
- CfgVehicle-特性(名前、タイプ、最大速度など)。
- EntityInventory-インベントリ(インベントリの読み取りは実装されていません。私の目的のためにはこれは不要です)。
CfgVehicleから名前とタイプを読み取ります。
ArmaString* entityName;
EntityVisualState class EntityVisualState { public: char _0x0000[4]; D3DXVECTOR3 dimension;
EntityVisualStateから、3つの変数の構造である座標ベクトルを読み取ります。
D3DXVECTOR3 coordinates;
struct D3DXVECTOR3 { FLOAT x; FLOAT y; FLOAT z; };
ここではxとy(実際はz)だけが必要なので、次のように読みます。
coordX = mem.readFloat(pEntityVisualState + 0x28); coordY = mem.readFloat(pEntityVisualState + 0x30);
ところで、EntityDataにあるadditionalFieldsカードには、この段階で追加情報を書き込むことができます。 たとえば、インベントリの内容や移動速度。
これで、ゲームワールドのすべてのエンティティに関する情報を受け取って分類しました。QPainterを使用したため、何らかの方法で表示する必要があります。
描画用のウィジェットクラスを作成します。
class InteractiveMap : public QWidget { Q_OBJECT public: InteractiveMap(QWidget* pwgt = nullptr); virtual ~InteractiveMap(); protected: virtual void paintEvent(QPaintEvent* pe); private:
地図のある写真の上に装備でマークを描きます。 現在のスケールのタグをQPixmapのマップにキャッシュします(カメラを移動するたびに数百または数千のオブジェクトを再描画するのは高価です)。
void InteractiveMap::paintEvent(QPaintEvent *pe) { renderMutex.lock(); painter->begin(this);
表示する必要があるエンティティのタイプやその他の設定を選択するには、サイドバーでQCheckBoxを使用します(それらの実装はgithubで表示できます)。 レンダリングを設定に接続するために、最初にベアのQSettingsを使用しましたが、設定をメモリにキャッシュせず、レジストリで直接動作することが判明したため、キャッシュを使用してラッパーシングルトンを作成する必要がありました。
class SettingsManager : public QObject { Q_OBJECT public: SettingsManager(); ~SettingsManager(); static SettingsManager& instance(); QVariant value(const QString &key, const QVariant &defaultValue = QVariant()); void setValue(const QString &key, const QVariant &value); SettingsManager(SettingsManager const&) = delete; SettingsManager& operator= (SettingsManager const&) = delete; private: QMap<QString, QVariant> data; QSettings settings; signals: void updateMap(); };
マップを見やすくするために、カーソル(マウスホイール上)とシフト(LMBを押した状態)によるスケーリングを実装しました。 もう1つの重要な機能は、エンティティの完全な特性とゲーム座標を表示することです(目的のオブジェクトの領域をクリックしたとき)。
機能の表示は、MapReduceモデルを使用して、QtConcurrentフレームワークを使用して実装されます。
入力クラスは、エンティティカテゴリへのポインタと検索するポイントで構成されます。
class CloseObjects { public: CloseObjects() {} CloseObjects(EntityRange *r, QPointF p): range(r), coords(p) {} QString findCloseObjects() const; private: EntityRange* range; QPointF coords; };
Map関数では、カテゴリ内のすべてのオブジェクトを調べます。エンティティがカーソル位置から一定の半径内にある場合、オブジェクトの完全な説明(名前+追加フィールド)とゲーム座標を返します。
QString CloseObjects::findCloseObjects() const { QString result; QTextStream stream(&result);
以上で私の話は終わりです。 それが難しくない人には、コードを見て、可能性のある妨害を指摘してください。
興味のある方は、オブジェクトの読み取り特性を追加して、プルリクエストを投げることができます。
参照: