ソフトウェアのプライベートAPIをリバースエンジニアリングするためのチュートリアル:ソファをハッキングする
公開: 2022-03-11旅行は私の情熱であり、私はカウチサーフィンの大ファンです。 カウチサーフィンは旅行者のグローバルコミュニティであり、滞在する場所を見つけたり、自分の家を他の旅行者と共有したりすることができます。 その上、カウチサーフィンは地元の人々と交流しながら本物の旅行体験を楽しむのに役立ちます。 私は3年以上カウチサーフィンコミュニティに参加しています。 最初はミートアップに参加しましたが、ようやく人を迎えることができました。 なんて素晴らしい旅だったのでしょう。 私は世界中からたくさんの素晴らしい人々に会い、たくさんの友達を作りました。 この全体の経験は本当に私の人生を変えました。
私は実際にサーフィンしたよりもはるかに多くの旅行者を自分でホストしてきました。 フランスのリビエラの主要な観光地の1つに住んでいる間、私は膨大な量のソファのリクエストを受け取りました(ハイシーズン中は1日10回まで)。 フリーランスのバックエンド開発者として、couchsurfing.com Webサイトの問題は、このような「高負荷」のケースを実際に適切に処理できないことであることにすぐに気付きました。 カウチの可用性に関する情報はありません。新しいカウチリクエストを受け取ったときに、その時点ですでに誰かをホストしているかどうかを確認することはできません。 承認されたリクエストと保留中のリクエストを視覚的に表現して、リクエストをより適切に管理できるようにする必要があります。 また、ソファの空き状況を公開できれば、不要なソファのリクエストを回避できます。 私が考えていることをよりよく理解するために、Airbnbカレンダーを見てください。
多くの企業は、ユーザーの話を聞いていないことで有名です。 カウチサーフィンの歴史を知っていたので、すぐにこの機能を実装することを彼らに期待することはできませんでした。 ウェブサイトが営利企業になって以来、コミュニティは悪化しました。 私が話していることをよりよく理解するために、これらの2つの記事を読むことをお勧めします。
- http://www.nithincoca.com/2013/03/27/the-rise-and-fall-of-couchsurfing/
- http://mechanicalbrain.wordpress.com/2013/03/04/couchsurfing-a-sad-end-to-a-great-idea/
多くのコミュニティメンバーがこの機能を喜んで持っていることを私は知っていました。 そこで、この問題を解決するアプリを作ることにしました。 利用可能な公開CouchsurfingAPIがないことが判明しました。 サポートチームから受け取った回答は次のとおりです。
「残念ながら、APIは実際には公開されておらず、現時点では公開する予定はありません。」
私のソファに侵入
私のお気に入りのソフトウェアリバースエンジニアリング手法のいくつかを使用して、Couchsurfing.comに侵入する時が来ました。 彼らのモバイルアプリは、バックエンドをクエリするために何らかのAPIを使用する必要があると思いました。 そのため、モバイルアプリからバックエンドに送信されるHTTPリクエストをインターセプトする必要がありました。 そのために、ローカルネットワークにプロキシを設定し、iPhoneをプロキシに接続してHTTPリクエストをインターセプトしました。 このようにして、プライベートAPIのアクセスポイントを見つけ、JSONペイロード形式を把握することができました。
最後に、人々がソファのリクエストを管理し、サーファーにソファの空き状況カレンダーを表示するのを支援する目的でWebサイトを作成しました。 私はコミュニティフォーラムにそれへのリンクを公開しました(これも私の意見ではかなり細分化されており、そこで情報を見つけるのは難しいです)。 ウェブサイトにcouchsurfing.comのクレデンシャルが必要であるという考えを好まなかった人もいましたが、それは本当に信頼の問題でした。
Webサイトは次のように機能しました。couchsurfing.comのクレデンシャルを使用してWebサイトにログインすると、数回クリックすると、couchsurfing.comプロファイルに埋め込むことができるhtmlコードが表示され、出来上がりです。カレンダーは自動的に更新されます。あなたのプロフィール。 以下はカレンダーのスクリーンショットであり、ここに私がそれを作った方法に関する記事があります:
- https://github.com/nderkach/couchsurfing-python
私はカウチサーフィンのための素晴らしい機能を作成しました、そして私は当然彼らが私の仕事に感謝するだろうと思いました-おそらく彼らの開発チームで私にポジションを提供することさえあります。 ウェブサイトへのリンク、履歴書、リファレンスを記載したメールをjobs(at)couchsurfing.comに送信しました。 私のカウチサーフィンのゲストの一人が残したお礼状:
数日後、彼らは私のリバースエンジニアリングの取り組みをフォローアップしました。 返信では、彼らが懸念しているのは自分たちのセキュリティだけであることが明らかだったので、APIについて書いたブログ投稿、そして最終的にはWebサイトを削除するように依頼されました。 私の意図は、利用規約に違反したり、ユーザーの資格情報を入手したりすることではなく、カウチサーフィンコミュニティを支援することだったため、すぐに投稿を削除しました。 私は犯罪者として扱われているという印象を受けました。会社は、私のWebサイトにユーザーの資格情報が必要であるという事実のみに焦点を当てていました。
私は彼らに私のアプリを無料で提供することを提案しました。 彼らはそれを彼らの環境でホストし、Facebook認証を介して接続することができます。 結局のところ、それは素晴らしい機能であり、コミュニティはそれを必要としていました。 私が受け取った最終的な解決策は次のとおりです。
「私たちは休暇の後にここで物事のスイングに戻ってきており、フォローアップしたいと思っていました。
カウチサーフィンのユーザーがサードパーティのサイトに資格情報を入力するときに、そのデータのプライバシーとセキュリティを損なうことなく、アプリケーションの創造性とイニシアチブを尊重する方法について、社内で話し合いました。
カレンダーは、私たちのサイトの機能の穴を明確に埋めています。これは、現在取り組んでいるより大きなプロジェクトの一部である機能です。
ただし、ユーザー名とパスワードの収集の問題は残っています。 そのデータにアクセスしたり、サイトを成果物と見なしたりせずに、私たちの側でそれをホストまたはサポートできるように、簡単に設定する方法を思い付くことができませんでした。
現在利用可能なAPIは、それにアクセスするアプリケーションからの認証/承認を必要とするバージョンにまもなく置き換えられます。」
今日(イベントの1年後)このリバースエンジニアリングソフトウェアチュートリアルを書いている間、カレンダー機能はまだCouchsurfingに実装されていません。
リターン・トゥ・イノセンス-再び私のソファをハッキング
数週間前、私はプライベートAPIをリバースエンジニアリングする手法についての記事を書くように促されました。 当然、私はこのトピックについて書いた以前の記事を要約し、さらにいくつかの詳細を追加することにしました。 新しい記事を書き始めたとき、最新のAPIを使用したリバースエンジニアリングプロセスを紹介し、APIハッキングに別のスタブを取り入れたいと思いました。 私の以前の経験と、Couchsurfingが最近完全に新しいwesbiteおよびモバイルアプリhttp://blog.couchsurfing.com/the-future-of-couchsurfing-is-on-the-way/を発表したという事実に基づいて、私はAPIをもう一度ハックすることにしました。
なぜこのリバースエンジニアリングプロセスを実行しているのですか? まず第一に、ソフトウェアをリバースエンジニアリングするのはとても楽しいことです。 私が特に気に入っているのは、技術的なスキルだけでなく、直感も含まれていることです。 場合によっては、物事を理解するための最良の方法は、知識に基づいた推測を行うことです。これにより、ブルートフォースに比べて多くの時間を節約できます。 最近、独自のAPIを使用しなければならず、ドキュメントがほとんどまたはまったくない会社からの話を聞きました。 彼らは何日もの間、未知の形式でAPI応答ペイロードを復号化するのに苦労していましたが、誰かがURLの最後で?decode=true
を試してみることにし、適切なJSONを持っていました。 運が良ければ、JSON応答をきれいにするだけでよい場合もあります。
私がこのチュートリアルを行っているもう1つの理由は、一部の企業がユーザーから要求された特定の機能を採用するのに時間がかかることです。 実装されるのを待つのではなく、プライベートAPIの力を利用して自分で構築することができます。
そこで、新しいcouchsurfing.com APIを使用して、同様のアプローチから始め、最新のiOSアプリをインストールしました。
まず、中間者攻撃(MITM)を実行して、アプリからAPIに送信されるHTTPリクエストを偽造するために、LANにプロキシを設定する必要があります。
暗号化されていない接続の場合、攻撃は非常に単純です。クライアントがプロキシに接続し、着信要求を宛先サーバーとの間で中継します。 必要に応じて、ペイロードを変更できる可能性があります。 パブリックWLANでは、WiFiルーターになりすまして、偽装してこれを実行するのはかなり簡単です。
暗号化された接続の場合、わずかな違いがあります。すべての要求はエンドツーエンドで暗号化されます。 攻撃者が秘密鍵にアクセスできない限り、攻撃者がメッセージを復号化することはできません(もちろん、これらの対話中には送信されません)。 そうは言っても、API通信チャネルは安全ですが、エンドポイント(特にクライアント)はそれほど安全ではありません。
SSLが正しく機能するには、次の条件を満たす必要があります。
- サーバーの証明書は、信頼できる認証局(CA)で署名する必要があります
- 証明書内のサーバーの共通名は、サーバーのドメイン名と一致する必要があります
MITM攻撃での暗号化を克服するには、プロキシがCA(認証局)として機能し、その場で証明書を生成する必要があります。 たとえば、クライアントがwww.google.comに接続しようとすると、プロキシはwww.google.comの証明書を動的に作成し、それに署名します。 これで、クライアントはプロキシが実際にはwww.google.comであると考えます。
プライベートAPIをリバースエンジニアリングするために使用されるスニッフィングプロキシを実装するには、mitmproxyというツールを使用します。 他の透過的なHTTPSプロキシを使用できます。 Charlesは、優れたGUIを備えたもう1つの例です。 これを機能させるには、次のものを設定する必要があります。
電話機のWiFi接続のデフォルトゲートウェイをプロキシとして設定します(プロキシが中央にあり、すべてのパケットが通過するようにします)電話機にプロキシの証明書をインストールします(クライアントがトラストストアにプロキシの公開キーを持つようにします)
証明書のインストールに関するプロキシのドキュメントを確認してください。 mitmproxyの手順は次のとおりです。 そして、これがiOS用の証明書PEMファイルです。
傍受されたHTTPリクエストを監視するには、mitmproxyを起動し、携帯電話から接続します(デフォルトのポートは8080です)。
モバイルブラウザでWebサイトを開きます。 この時点で、mitmproxyでトラフィックを確認できるはずです。
すべてが計画どおりに機能することを確認したら、選択したプライベートAPIの調査を開始します。 基本的に、この時点で、アプリを開いて試してみて、APIエンドポイントとリクエスト構造についてのアイデアを得ることができます。
ソフトウェアAPIをリバースエンジニアリングする方法に厳密なアルゴリズムはありません。ほとんどの場合、直感に頼って仮定を立てます。
私のアプローチは、API呼び出しを複製し、さまざまなオプションで遊ぶことです。 良いスタートは、mitmproxyでキャッチしたリクエストを再生し、それが機能するかどうかを確認することです(リクエストを再生するには「r」を押します)。 最初のステップは、どのヘッダーが必須であるかを把握することです。 mitmproxyでヘッダーを操作するのは非常に便利です。「e」を押して編集モードに入り、「h」を押してヘッダーを変更します。 彼らが使用するショートカットを使用すると、vim中毒者は自宅にいるように感じるでしょう。 Postmanのようなブラウザ拡張機能を使用してAPIをテストすることもできますが、それらは不要なヘッダーを追加する傾向があるため、mitmproxyまたはcurlに固執することをお勧めします。
mitmproxyダンプファイルを読み取り、curl文字列を生成するスクリプトを作成しました-https://gist.github.com/nderkach/bdb31b04fb1e69fa5346
ログイン時に送信されるリクエストから始めましょう。
POST https://hapi.couchsurfing.com/api/v2/sessions ← 200 application/json
私が最初に気付いたのは、すべてのリクエストに、毎回異なる必須のヘッダーX-CS-Url-Signature
が含まれていることです。 また、しばらくしてからリクエストを再生して、サーバーにタイムスタンプチェックがあるかどうかを確認しようとしましたが、ありません。 次に行うことは、この署名がどのように計算されるかを理解することです。
この時点で、バイナリをリバースエンジニアリングして、アルゴリズムを理解することにしました。 当然のことながら、iPhone向けの開発経験があり、iPhoneを自由に使えるようにしたので、iPhone ipa(iPhoneアプリの成果物)から始めることにしました。 1つを復号化することが判明しました、私はジェイルブレイクされた電話が必要です。 やめる! ハンマータイム。

それから、彼らもAndroidアプリを持っていることを思い出しました。 私はAndroidやJavaについて何も知らないので、このアプローチを試すのを少しためらっていました。 それから、何か新しいことを学ぶ良い機会になると思いました。 高度に最適化されたiphoneマシンコードよりも、Javaバイトコードを逆コンパイルすることで、人間が読める準ソースコードを取得する方が簡単であることがわかりました。
Apk(Androidアプリの成果物)は基本的にzipファイルです。 任意のzipエクストラクタを使用して、その内容を解凍できます。 Dalvikバイトコードであるclasses.dexというファイルがあります。 Dalvikは、Androidで翻訳されたJavaバイトコードを実行するために使用される仮想マシンです。
.dexファイルを.javaソースコードに逆コンパイルするために、dex2jarというツールを使用しました。 このツールの出力はjarファイルであり、さまざまなツールを使用して逆コンパイルできます。 EclipseまたはIntelliJIDEAでjarを開くこともでき、すべての作業が自動的に行われます。 これらのツールのほとんどは、同様の結果を生成します。 コンパイルして実行できるかどうかは気にしません。単にソースコードを分析するために使用しているだけです。
これが私が試したツールのリストです:
- FernFlower(現在はIntelliJ IDEAの一部)
- CFR
- JD-GUI
- クラカタウ
- プロキオン
CFRとFernFlowerは私にとって最高の働きをしました。 JD-GUIは、コードの一部の重要な部分を逆コンパイルできず、役に立たなかったのに対し、他の部分の品質はほぼ同じでした。 幸い、Javaコードコードは難読化されていないようですが、コードの難読化を解除するのに役立つProGuardhttp://developer.android.com/tools/help/proguard.htmlなどのツールがあります。
Javaの逆コンパイルは、実際にはこのリバースエンジニアリングチュートリアルの範囲ではありません。このトピックについては多くのことが書かれているので、Javaコードの逆コンパイルと難読化に成功したと仮定しましょう。
X-CS-Url-Signatureの計算に使用されるすべての関連コードを次の要点にまとめました:https://gist.github.com/nderkach/d11540e9af322f1c1c74
まず、 RetrofitHttpClient
で見つけたX-CS-Url-Signature
言及を検索しました。 EncUtils
モジュールへの1つの特定の呼び出しは興味深いようでした。 掘り下げてみると、彼らがHMACSHA1を使用していることに気づきました。 HMACは、暗号化関数(この場合はSHA1)を使用してメッセージのハッシュを計算するメッセージ認証コードです。 これは、整合性(つまり、中間者が要求を変更するのを防ぐため)と認証を保証するために使用されます。
X-CS-Url-Signature
を計算するには、秘密鍵とエンコードされたメッセージ(おそらくHTTPリクエストのペイロードとURLのバリエーション)の2つが必要です。
final String a2 = EncUtils.a(EncUtils.a(a, s)); final ArrayList<Header> list = new ArrayList<Header>(request.getHeaders()); list.add(new Header("X-CS-Url-Signature", a2));
コードでは、 a
はメッセージであり、 s
はヘッダーa2
を計算するために使用されるキーです( EncUtils
への二重呼び出しはHMAC SHA1の16進ダイジェストを計算するだけです)。
キーの検索は問題ではありませんでした。キーはプレーンテキストでApiModule
に保存され、RetrofitHttpClientの2番目のパラメーターを初期化するために使用されました。
RetrofitHttpClient a(OkHttpClient okHttpClient) { return new RetrofitHttpClient(okHttpClient, "v3#!R3v44y3ZsJykkb$E@CG#XreXeGCh"); }
EncUtils
の呼び出しを見ると、 this.b
が定義されている場合を除いて、上記の文字列リテラルがHMACを計算するためのキーとして逐語的に使用されていることがわかります。 後者の場合、 this.b
にドットが追加されます。
String s; if (this.b == null) { s = this.a; } else { s = this.a + "." + this.b; }
さて、コードを見ただけでは、 this.b
がどこでどのように初期化されるかがわかりませんでした(私が発見できたのは、署名this.a(String b)
を持つメソッドで呼び出されていることだけです。しかし、コード内のどこにも呼び出しが見つかりませんでした)。
public void a(final String b) { this.b = b; }
逆コンパイルして自分自身を見つけることをお勧めします:)
メッセージを理解するのは非常に簡単でした。コードでは、URLパス、つまり/api/v2/sessions
とJSONペイロード(存在する場合)を含む文字列を連結したものであることがわかります。
final byte[] b = this.b(request.getUrl()); byte[] a; if (request.getBody() != null && request.getBody() instanceof JsonTypedOutput) { System.out.println("body"); // this.a(x, y) concatenates byte arrays a = this.a(b, ((JsonTypedOutput)request.getBody()).a); } else { a = b; }
コードを見ただけでは、HMAC計算の正確なアルゴリズムを理解することは困難でした。 そこで、アプリがどのように機能するかを正確に把握するために、デバッグシンボルを使用してアプリを再構築することにしました。 私はapktoolhttps://code.google.com/p/android-apktool/というツールを使用して、smalihttps://code.google.com/p/smali/を使用してDalvikバイトコードを分解しました。 https://code.google.com/p/android-apktool/wiki/SmaliDebuggingのガイドに従いました
apkをビルドしたら、署名してデバイスにインストールする必要があります。 Androidデバイスを持っていなかったので、AndroidSDKに付属のエミュレーターを使用しました。 いくつかのスプーンフィーディングで、これがあなたがそれをする方法です:
jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android <path_to_your_built_apk> androiddebugkey jarsigner -verify -verbose -certs <path_to_your_built_apk> zipalign -v 4 <path_to_your_built_apk> <path_to_your_output_signed_apk>
sdkに付属する組み込みのAndroidエミュレーターと、HAXMが有効になっているAtom x86仮想イメージを使用して、スムーズに実行できるようにしました。
tools/emulator -avd mydroid -no-boot-anim -cpu-delay 0
仮想イメージを設定する方法に関する優れたガイドは次のとおりです。http://jolicode.com/blog/speed-up-your-android-emulator
HAXMが有効になっていることを確認するために、エミュレータの起動時にHAXの行が機能し、エミュレータが高速virtモードで実行されていることを確認してください。
次に、apkをエミュレーターにインストールして、アプリを実行しました。 apktoolガイドに従って、IntelliJ IDEAリモートデバッガーを利用してエミュレーターに接続し、いくつかの行ブレークポイントを設定しました。
アプリを少し試してみると、 RetrofitHttpClient
の初期化に使用される秘密鍵がログイン要求署名のHMACの計算に使用されていることがわかりました。 ログインPOSTへの応答で、ユーザーIDとaccessToken( X-Access-Token
)を受け取ります。 アクセストークンは、次のすべてのリクエストを承認するために使用されます。 すべてのログイン後要求のHMACは、キーが元の秘密鍵に.<user_id>
を追加することによって構成されることを除いて、ログイン要求と同じ方法で構築されます。
承認されると、アプリは次のリクエストを送信します。
POST https://hapi.couchsurfing.com/api/v2/users/1003669205/registerDevice ← 200 application/json
経験的に差し引くことができたので、このリクエストは認証のオプションです。 用途がわかればボーナスポイント!
認証されると、次のように、自分(または他の人のユーザープロファイル)をフェッチするリクエストを送信できます。
GET https://hapi.couchsurfing.com/api/v2/users/1003669205 ← 200 application/json
詳細についてはあまり詳しく説明しませんでしたが、プロファイルがPUTリクエストで更新されていることに気付きました。 楽しみのために、同じリクエストで別のプロファイルを更新しようとしました。これは許可されていないため、セキュリティの基本が実装されているようです。
couchsurfing.comのクレデンシャルを使用してログインし、ユーザープロファイルを取得するための簡単なPythonスクリプトを作成しました:https://gist.github.com/nderkach/899281d7e6dd0d497533。 APIのPythonラッパーは次のとおりです。https://github.com/nderkach/couchsurfing-pythonとpypiリポジトリで利用可能なパッケージ(pip install couchsurfing)。
次のステップ
今回はAPIで何をするのか正確にはわかりません。 ユーザープロファイルのHTMLコードは許可されなくなったため、古い問題に対する別のアプローチを考え出す必要があります。 必要に応じて、Python APIラッパーの開発と拡張を続け、couchsurfing.comがそれほど多くの問題を引き起こさないと仮定します。 私はAPIをあまり調べず、いくつかの基本的な脆弱性についてテストしました。 十分に安全なようですが、Webサイトでは入手できないデータにアクセスできるかどうかを調べるのは興味深いことです。 いずれにせよ、今では私のリバースソフトウェアエンジニアリングを使用して、Windows Phone、Pebble、またはスマートカウチ用の代替クライアントを構築できます。
質問のまとめ
開きたいディスカッションがあります。APIを公開して公開してみませんか? APIをハックできなかったとしても、Webサイトをスクレイプすることは可能です。 それは遅く、維持するのがより難しいでしょう、しかし確かに彼らは消費者がウェブスクレイパーよりもAPIを使うことを好むでしょう。 APIの可用性により、サードパーティの開発者は会社の製品を改善し、その周りに付加価値サービスを構築することができます。 プライベートAPIよりもパブリックAPIを維持する方が費用がかかると主張することができます。 しかし、繰り返しになりますが、製品に加えてコミュニティ構築サービスの利点は、APIの保守コストを上回ります。
サードパーティのクライアントによるプライベートAPIの使用を完全に防ぐことは可能ですか? そうは思いません。 SSLピニングを使用すると、前述のように単純な透過プロキシ技術を使用してAPIリクエストをスニッフィングするのを防ぐことができます。 結局、バイナリを難読化したとしても、ある程度のリソースと時間を持つ意欲的なハッカーは、常にアプリバイナリをリバースエンジニアリングして、秘密鍵/証明書を取得することができます。 クライアントエンドポイントが安全であるという仮定は本質的に間違っていると思います。 APIクライアントは弱点です。
APIを非公開にすることで、企業は基本的にユーザーに不信のメッセージを伝えています。 確かに、プライベートAPIをさらに保護することを試みることができます。 ただし、悪意のある使用を防ぐために、APIの基本的なセキュリティを実装するのではないでしょうか。 代わりに、より良いユーザーエクスペリエンスを提供するために、ソフトウェアの改善にリソースを集中させますか?
カウチサーフィン、砂糖を上にして、APIを開いてください。