Eyes, JAPAN Blog > Androidゲームをチートしてみた(SECCON 2015 × CEDEC CHALLENGEにて)

Androidゲームをチートしてみた(SECCON 2015 × CEDEC CHALLENGEにて)

beko

この記事は1年以上前に書かれたもので、内容が古い可能性がありますのでご注意ください。

こんにちは、佐藤です。SECCON 2015 × CEDEC CHALLENGEは8月10日まで募集されていたゲームのクラッキングとチートのチャレンジです。

対象アプリは今回はAndroidアプリでした。私がしたチートを書きたいと思います。
[注意]今から書くことを一般のアプリに対して行った場合、犯罪になることがあります。今回は、許可されているアプリであるため検査を行いました。悪用厳禁でお願いします。

対象アプリ1つ目 「sandback」

このアプリは、下のようなサンドバッグをタップするとポイントが加算されていくゲームです。そして、右のようにランキングがでます。使っているゲームエンジンはUnity。

20150815192620

20150815193057

このゲームはぼろぼろでした。

まず、通信をのぞき見てみましたら。

20150815193916

httpリクエストは以下の通り。

POST /score/ranking/ HTTP/1.1
X-Unity-Version: 5.0.2f1
Content-Type: application/json; charset=UTF-8
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.4; SO-02G Build/23.0.B.1.59)
Host: api.sandbag2015.net
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 76

{“uuid”:”a36cfae0-1eb8-4a55-83ef-e4ac8a2fc809″,”name”:”hooters”,”point”:12345678}

これを送りつけるスクリプトにして、

#!/usr/bin/env python
import urllib2
import sys
argvs = sys.argv
names = '"' + argvs[1] + '"'
points = argvs[2]
target_url = "http://api.sandbag2015.net/score/ranking/"
data = '{"uuid":"a36cfae0-1eb8-4a55-83ef-e4ac8a2fc809","name":' + names + ',"point":' + points + '}'
cl = len(data)
heders = {'X-Unity-Version':'5.0.2f1',
'Content-Type':'application/json; charset=UTF-8',
'User-Agent':'Dalvik/1.6.0 (Linux; U; Android 4.4.4; SO-02G Build/23.0.B.1.59)',
'Host':'api.sandbag2015.net',
'Connection':'Keep-Alive',
'Accept-Encoding':'gzip',
'Content-Length': cl,
}
print data
getpoint = urllib2.Request(target_url,data,heders)
resp = urllib2.urlopen(getpoint)
logs = resp.read()
print logs
res_head = resp.info()
print res_head.getheaders("Date")
print res_head.getheaders("Server")
print res_head.getheaders("X-Powered-By")
print res_head.getheaders("Set-Cookie")
print res_head.getheaders("Expires")
print res_head.getheaders("Cache-Control")
print res_head.getheaders("Pragma")
print res_head.getheaders("Content-Length")
print res_head.getheaders("Connection")
print res_head.getheaders("Content-Type")
20150815195951

もはやゲームしなくてもスコア好きな値にできるし他の人のクッキーも予想できそうにないので次のアプリへ・・・

対象アプリ2つ目「SUNIDRA」

20150815200809

このステージの奥にいるドラゴンを倒したら勝ち。残っているタイムが自分のスコア。
通信見たが暗号化されていてめんどくさいので他のことを考える。
クライアントを改竄する。
Unityアプリ内にゲームをルールなど司るバイナリをリーバースエンジニアリングする。
このバイナリの正体は他のライブラリから直接参照できる中間言語。故に、逆コンパイル、逆アセンブラが簡単。

20150815204247
DotNetResolverという逆コンパイラツールをつかってます。上は、ゲームオーバー時の処理です。
とりあえず、nop(何もしない)で埋めてみました。

20150815204455
そして、Apk Editerというアプリを使って再ビルドします。apkを再構築するのには作者を守るため署名が必要でそれなしには偽装アプリをインストールすらできないのですがうまく偽装されています。

20150815205112

ゲームオーバーの処理が行われずスコアがマイナスになり(笑
これでは勝てないので、20150815205237
コンパイラのコードを読むとGameRuleCtrlモジュールのUpdate()に時間をカウントしているところがありました。
Update関数はUnityでワンフレームごとに実行する関数です。
これを+にします。

20150815205514
背景がピンクなのは運営側も知っているバグ出そうです。
sub というオペコードをaddに変更します。
ドラゴンの攻撃力が尋常じゃないので受けたダメージ分自分のHPに加える用に同じ容量で変更しました。

20150815210129
そしてクリア!したと思ったんですが(笑

20150815210222
対策されてた(笑
おそらくチェックサムによるクライアント認識。
こうなったら、プロセスメモリをいじるしかない。(諦め早いかも)
AndroidプロセスメモリエディタのGameGuardianを使って

20150815211103
Androidのasrlを無効(0)にして

20150815211236
メモリを素早く見つけるには少し慣れが必要。アドレス0007DD0に格納される000008DEがスコアに相当。
これを変更すれば反映されると思ったんだのですが。 うまくいかなかった。。。
指摘するところと言えばコピー作成が可能など。
例のぼろぼろアプリはこの方法でも改竄できました。

20150815211901

肝心レポートに手を抜いてしまった。ゲームをチートから守る方法が大きな点数らしいのでまずいです。
とりあえずrootの状態でゲームをさせてしまうと危険です。開発者の皆さんお気をつけください。rootだと判明したら(アプリでrootが必要なコマンドをわざと実行しErrorを得るか得ないかで判定とか)ゲームはさせない設計などもいいと思います。

以上です。

Comments are closed.