Out-of-the-box VMIでチートをした話

はじめに

こんにちは.morimolymolyです.
DeNA 21新卒×22新卒内定者 Advent Calendar 2021の2日目の記事です.
DeNAではセキュリティ技術グループでゲームのチート対策などを行っております.
詳しいことは僕のインターン参加ブログをご参照ください.

morimolymoly.hateblo.jp

そんなわけ(?)で今日はチートのかなりベーシックな手法をOut-of-the-box VMIで試したのでご紹介いたします.

Out-of-the-box VMIとは

Virtual Machine Introspectionとは,仮想マシン(VM)を監視・解析するための技術です.
VMIにはOut-of-the-boxとIn-the-boxの2種類存在し,前者はVMの外から,後者はVMの内部から監視・解析するものです.
Out-of-the-box VMIは完全にVMの外から監視・解析を行うことから,マルウェア解析などの用途に使われます.

libvmiやDRAKVUFなどが有名なプロダクトです.

https://libvmi.com/
drakvuf.com

Out-of-the-box VMIをチートに使うことにより,チートをしていると気づかれにくく,特権的な操作を行うことができます.
最近はアンチチートがカーネルドライバになっているケースがありますが(VALORANTのVanguardが有名),それはRing0つまりOSの権限で動くのですが,VMを管理するソフトウェアであるハイパーバイザは所謂Ring-1で動作します.したがって権限が絶対的に上になります.

そのためこれを用いたチートができないか調査をいたしました.

memflow

調査をした結果,memflowというライブラリがみつかりました.
以下に特徴を列挙いたします.

  • Rustで書かれている
  • Rustでツールがかける
  • KVM based
  • PCI Leech対応
  • Windowsの解析に対応

名目上,DMAライブラリとしていますが,明らかにチートツールに転用することができます.
関係ないですけどRustでかけるのがいいですね!!!!!

memflowの使い方

以下にmemflowの使い方を説明します.

まずmemflowupをします.
これはmemflowのconnectorをセットアップします.

git clone git@github.com:memflow/memflowup.git
cd memflowup
python3 memflowup.py

次に以下のリンクからReleaseされているkvmのconnector(KVMとのコミュニケーションをとるもの)をインストールします
github.com

sudo dkms install --archive memflow-0.1.4-source-only.dkms.tar.gz
sudo modprobe memflow

次にmemflow-cliのインストールをします.

git clone git@github.com:memflow/memflow-cli.git
make install

最後にKVMにWindows10なVMをつくり,接続テストをします.(VM名はwin10としました)

memflow conn new kvm -a win10

チートシナリオ

早速チートをしましょう!!!
対象となるゲーム?は以下のようなゲームです.
変数や関数のアドレスを表示しているのはデバッグ用でチートには使用しません.

f:id:morimolymoly:20211119173133p:plain
samplegame

連打してポイントを稼いで,10000000点以上になるとwin!と表示されます.
ソースコードは以下のようになっています.

#include <iostream>

void win(int counter) {
    if (counter >= 10000000) {
        std::cout << "win!!!" << std::endl;
    }
}

int main()
{
    std::cout << "Simple Counter!!!!\n";
    int counter = 0;
    std::cout << "counter address: 0x" << &counter << std::endl;
    std::cout << "win address: 0x" << &win << std::endl;

    while (1) {
        win(counter);
        getchar();
        std::cout << "counter:" << counter << std::endl;
        counter++;
    }
}

シナリオとしては以下のようなものを想定します.

  • 点数を自由に操作する
  • win!!!を毎回表示させて優勝してお酒が飲みたい

それでは行きましょう.

チートツールを書く

以下にチートツールのレポジトリを貼ります.
github.com


チートツールで行っているチートは以下のようになっています.

  • 点数を自由に操作する == スタック上の変数の改ざん
  • win!!!を毎回表示させて優勝してお酒が飲みたい == メモリ上のコード領域の改ざん

スタック上の変数の改ざんには独自のメモリ検索エンジンを実装し,スタック上から目的の変数を探して改ざんします.
メモリ上のコード領域の改ざんには優勝するために改ざんするべきコードを改ざんします.
これらはすべてOut-of-the-box VMIで行われています.
完全にVMからは不可視で改ざんを行います.

スタック上の変数の改ざん

今回は変数がスタック上にあることが事前にわかっているので,スタック領域を求めます.
これはTEBを用います.
en.wikipedia.org

TEBはThread Information Blockのことで,現在走っているスレッドの情報が格納されています.
スレッドの情報にはスタックについての情報も載っています.
memflowはTEBのアドレスを求めてくれる機能があるので,そこからオフセットを用いてメモリからスタックのベースアドレスとリミットアドレスを取得します.
その後はメモリ検索エンジンを自前で実装し,終了です.

メモリ上のコード領域の改ざん

毎回優勝したい場合,10000000点以上を取らなければいけませんが,それは面倒くさいです.
なんなら変数の改ざんをしなくても済むようにコード領域を改変しましょう!

まずはどこにターゲットになるコードがあるかを解析しましょう!
まずはGhidraをおもむろに立ち上げて,exeファイルを読み込ませます.
その後に"win!!!"文字列を検索します.

f:id:morimolymoly:20211119172510p:plain
win!

みつかりました!
XREFをみるとwin!!!文字列を参照しているコードがどこにあるかがわかります.

早速みてみましょう!

f:id:morimolymoly:20211119172506p:plain
ターゲットコード

これをみるとベースアドレス+ 0x1119 にあるJL命令を潰してやれば良さそうです.
memflowの動的メモリ改ざん機能でここを0x90(NOP)で潰します.

結果

それではOut-of-the-box VMIなツールでチートをします!!!!
チートツールは以下のようになっています.

f:id:morimolymoly:20211119173047p:plain
cheat tool

結果は以下のようになっています!!!!

f:id:morimolymoly:20211119173057p:plain
チート結果

優勝しました!!!!!!!!!!!!!!!!!!!!!!1

対策方法

流石にチートしましただけではどうしようもないので思いつく対策を以下に列挙いたします.

  • 特徴的な文字列は暗号化する
  • 点数の値を暗号化する
  • VM検知をする(アーティファクトがどこかにあります)
  • コードのインテグリティチェックをする

……あまり参考になりませんね…….
Out-of-the-box VMI cheatを倒すにはどうしたら良いのでしょうか…….
これは今後の課題としてブログに対策方法を考えて書くことにします!!!

最後に

今回はかんたんなゲーム?に対してKVM + memflowを使ったOut-of-the-box VMIでのチートを行いました.
スタック上の変数の改ざんと,メモリ上のコード領域の改ざんでゲームをチートしました.
以上で今回の記事を終わりたいと思います.
ありがとうございました.