Among Us解析報告書
*この記事ではAmong Usのアドレス情報を記載していますが悪用禁止のためStaticなアドレスは表示しません、気になれば各自調べてみて下さい。
*悪用厳禁
まえがき
このサークルでは交流の意味を込めてたまにAmong Us(通称:宇宙人狼)をやっています。
ふと気になったので今回はこのゲームのアドレス情報を解析してみようと思います。
それでは早速やってみましょう。
必要なもの
– Among Us
– Cheat Engine(その他のプロセスメモリエディタでも構いません)
– メモリ空間の簡単な知識
技術者倫理
チートは絶対やってはいけません、当たり前ですね。
チートというのは簡単に言うとゲームのメモリを書き換える行為のことです。
例えば、プロアクションリプレイ、ワザポンなどの改造ソフトは改造コードというものを用いて、メモリアドレスの書き換えを行っています。
アイテムの個数を格納しているメモリ番地が$0x0001だとしてその中身が0x05だとするとアイテムを五個持っていることを表します。
これを0xFFに書き換えるとアイテムを255個持っている状態に変更することが出来ます。
この様な仕組みでクソゴミチーター供は全ポケモンレベル100みたいなことをやっています。絶滅しろ
今回調べるアドレス情報はチートに使えます、しかしオンラインゲームで自分の環境のアドレスだけ変えてもほぼ意味がないです。
それでもチートをすることが出来てしまうので今回は悪用禁止のためstaticなアドレスは表示しません。
悪用ダメ、絶対。
本題
CheatEngineのようなプロセスメモリエディタは大抵メモリサーチという機能を備えています。
4byte型の100で検索すると“0x64 0x00 0x00 0x00” or “0x00 0x00 0x00 0x64”のような状態になっているメモリの先頭アドレスを検索しています。
*上2つの違いはリトルエンディアンかビッグエンディアンかどうかによる、アーキテクチャ依存
*十進法での100は十六進数での0x64、以後十六進法の場合は”0x”から数値を書く
最初Short_Tasksを4にして4で検索、その次に3に変更して3で検索…ということを繰り返せば徐々にヒットするメモリが絞られていきます。
今回は最後の1つに”0x07D09A4C”というアドレスが残りました。これがAmong Usで”参加可能なプレイヤー数”を表すメモリということですね,この様な作業を繰り返し様々なアドレスを見つけて行きます。
同じくことを繰り返せば他のアドレスを見つけられますがこういったメモリは隣接して確保されることが多いという知識があればこの作業もかなりへらせます。
*赤い枠のものがShort_Task
しかしここで問題発生
これらのアドレスは静的領域(static)でないためゲームを開始するたびにメモリの位置が変わってしまいます。
こんな状況の中クソゴミチーター供はどうやってチートをしているのでしょうか?
答えは簡単、staticな領域にあるポインタを利用しています。
そもそもプログラムだってポインタを利用して変数を追っているので同じことをすればいいのです。その為にはstaticな変数を見つける必要があるので頑張りましょう。
7A****BE - 83 C4 14 - add esp,14
7A****C1 - 6A 00 - push 00
7A****C3 - FF 76 2C - push [esi+2C] << ここ!
7A****C6 - 68 90000000 - push 00000090
* ESI=07D09A20
“Find out what accesses this address”で見つかった領域を逆アセンブルするとこんなコードがあります。
[esi+2C]というのはESIから0x2Cだけ進んだ所にこのメモリアドレスがあるということです。
これはESIがポインタでオフセットが2Cであるということを意味しています(0x07D09A20 + 0x2C = 0x07D09A4C)
ここで0x07D09A20を検索して見つかったポインタに対して同じことを繰り返せばこのアドレスは
( [[[static]+0x5C]+0x4]+0x2C ) = 0x07D09A4C ということが求まり後はこのようにポインタをたどるだけでOKになってしまいます。*色付きの数値はオフセット
アドレスを見つけるのはこれで大丈夫ですが少し考察してみます。
見てわかるようにこれは三重ポインタになっています、何でこんなややこしいことになっているのでしょうか?
Among UsはUnityで作られたゲームでおそらくC#が使われています。
ここで大事なのはオブジェクト指向でのインスタンスも実はポインタが使われているということです。
Cユーザーなら構造体を思い浮かべると分かりやすいと思います。
struct data {
int a;
int b;
int c;
};
struct data s1;
こんな構造体(あるいはclass)があった場合はs1.aにアクセスする際にs1のポインターが必要ですよね、
大体これと同じです。つまり
class Data{
int a;
int b;
Data2 c = new Data2();
}
public class Data2{
public int val = 0;
}
があった場合はcにはData2型のインスタンスのポインタが入るということです。
*Cの場合s1とs1.aのアドレスは同じになるが、C#の場合先頭アドレスはクラスの情報が含まれるcとc.valのオフセットはずれる
以上のことを簡単なものまとめるとフィールドにインスタンスを持っておりそこからの変数にアクセスするため三重ポインタになってしまったということです。
ちなみに大元の変数がstaticなのはstatic void main(args[]){}から参照できるのがstaticだからだと思われます。
というわけで以下の結果になりました。
config_pointer : [[[static]+0x5C]+0x4]
データ名 データ型 オフセット(config_Pointerからの)
Players 4 Bytes 8
Lunguage 2 Bytes C
Stage 4 Bytes 10
Player_Speed Float 14
Crewmate_Vision Float 18
Impostor_Vision Float 1c
Kill_Cooldown Float 20
Common_Tasks 4 Bytes 24
Long_Tasks 4 Bytes 28
Short_Tasks 4 Bytes 2c
Emergency_Meetings 4 Bytes 30
Emergency_cooldown 4 Bytes 34
Impostors 4 Bytes 38
Kill_Distance 4 Bytes 40
Discussion_Time 4 Bytes 44
Voting_Time 4 Bytes 48
Confirm_Ejects Byte 4c
Visual_Task Byte 4d
Anonymous_Vote Byte 4e
Task_Bar_Update 4 Bytes 50
*Byteというのは1Byte型のことです、BooleanもByte型で表します
オフセットというのは先頭アドレスからの距離です。
例えばconfig_pointer=0x07D09A20のときShort_Taskは0x07D09A20 + 0x2C = 0x07D09A4C となります。
Playersがいきなり8から始まっているのは先頭にクラス情報があるからだと思われます。
またメモリに使われていない隙間があるように見えますがこれはメモリのアライメントというやつです、おそらくフィールド変数も並び替えられているのでここから正確なclass構造を再現するのは難しいでしょう
調べれば色んな事が分かると思うのでみんなも是非なんか調べてみて下さい。
1ゲッツ