> > Burp Suite 日本語版インタフェース

Burp Suite 日本語版インタフェース

2019.07.01
プロフェッショナルサービス事業部
国分 裕
title1

こんにちは、自称Burpジェリストの国分です。今回も、私が普段愛用しているプロキシツール「Burp Suite」について紹介したいと思います。

Burp Suiteは、英国のPortSwigger社が開発しているアプリケーションで、全てのインタフェースやマニュアルが英語で書かれています。 国内ではBurp Suiteのユーザグループが発足し、日本語での状況共有、勉強会、初心者向けのマニュアルの執筆などを行ってきました。情報共有について、以前はCybozuLiveを活用させていただいておりましたが2019年4月にサービスが終了したため、現在はGoogle Groupに活動の場を移行しています。
https://groups.google.com/d/forum/burp-suite-japan

これらの活動もあってか徐々に日本語での情報が流通してきていますが、Burp Suite本体の日本語化について公式の対応はないままです。 そこで私は、Burp Suiteのインタフェースなどを勝手に日本語化するツールを作り、GitHubで公開しました(※PortSwiggerの承諾を得たうえで公開しています)。今回はこのツールについて、挙動などを詳しく紹介したいと思います。ツールは、以下で公開していますので、興味のある方は使ってみてください。
Belle (Burp Suite 非公式日本語化ツール) https://github.com/ankokuty/Belle/

一般的なローカライズ方法

通常、アプリケーションをローカライズをする場合は、元々そのアプリケーションで言語リソースが別ファイルなどに分離されていて、それの翻訳で実現するのではないでしょうか。OWASP ZAPは分離されており、ローカライズのプロジェクトで翻訳が進められています。
Burp Suiteにもこのような言語ファイルがあれば、勝手に翻訳してローカライズできるかと思ったのですが、インストールされたフォルダを見てもそれっぽいファイルが見あたらず、Burp SuiteのUIで使われている特徴的な文字列で検索してもヒットしませんでした。
コマンドラインからBurp Suiteを起動していると、時々Javaのスタックトレースが表示されることがあります。スタックトレースから例外が発生したクラス名がわかるのですが、burp.xxx のように意味のない名前が表示されるので、おそらく難読化がされているのでしょう。 Burp Suiteのライセンスでリバースエンジニアリングが禁止されていますし、ここはあまり深入りしない方が良さそうですので、他の方法を考えます。

Burp Suite内部動作の想像

Burp SuiteがGUIの画面を表示する際に、内部でどのような処理が行われているのか想像してみましょう。

Burp SuiteはJavaで開発されており、インストーラにはOpenJDKのJava実行環境(JRE)が内包されています(Professional版 2.0.15beta以降、Community版 2.1以降)。そこで、Burp SuiteはJava標準のGUI機能を使ってあの画面を表示していると考えました。JavaでGUIといえば、AWT・Swingなどです。Burp Suiteでは設定でルックアンドフィールを変更でき、そこでSwingのMetalやNimbusが選択できるのでまず間違いないでしょう。

例えば、AWTでウインドウのタイトルを指定する java.awt.Frame#setTitle(String title) というメソッドがあります。 Burp Suiteもウィンドウのタイトルを指定するために、内部のどこかでこのメソッドを呼び出しているのではないかと想像しました。

// ウィンドウをタイトルを"Burp"にする表示する場合
class SomeClass {
  void someMethod(java.aws.Frame frameObject){
    frameObject.setTitle("Burp");
  }
}

setTitleメソッドが呼ばれると、指定されたBurpという文字列がJREの内部で処理され、最終的にウィンドウのタイトルに反映されるのでしょう。これが普通の挙動です。

さてここで、この setTitle メソッドの挙動を変えられないか考えてみました。"Burp"と指定されたにもかかわらず、画面上には"げっぷ"と表示してしまう奇妙な setTitle になれば、Burp Suite本体を変えずにタイトルに"げっぷ"と表示できるはずです。例えば次のように、"Burp"いう文字列が引数に指定された場合に"げっぷ"に置換する処理を入れればいいのです。

// setTitleメソッドの改造イメージ
public class Frame {
    public void setTitle(String title)) {
        if(title.equals("Burp")){
            title = "げっぷ";
        }
        //以下本来のsetTitleの処理
    }
}

Burp SuiteにはOpenJDKのJREが内包されているので、OpenJDKのソースコードを上記のように変更して再度コンパイルしてJREごと差し替えれば、これが実現できそうです。 しかし、日本語訳を直すたびに毎回JREを作り直す手間がかかります。そこで、実行時に動的にJREの挙動を変える方法を考えます。

エージェントを使った動的な挙動変更

実行時に動作を変えるには、java.lang.instrumentパッケージが利用できます。また、バイトコードの変換にJavassistを使用しました。 まずこの挙動を説明します。

例えば、"Hello world." とだけ表示する次のようなコードがあったとします。

// Hello.java
public class Hello {
  public static void main(String[] args) {
     System.out.println("Hello world.");
  }
}

これをコンパイルしてhello.classファイルを生成し実行すると、"Hello world."と表示されます。

$ java Hello
Hello world.
さてここで、hello.classはそのままに、新たに次のコードを用意します。
// MyInjection.java
 1: import java.io.*;
 2: import java.lang.instrument.*;
 3: import java.security.*;
 4: import javassist.*;
 5: public class MyInjection {
 6:   private static ClassPool classPool;
 7:   public static void premain(String agentArgs, Instrumentation instrumentation) {
 8:     classPool = ClassPool.getDefault();
 9:     instrumentation.addTransformer(new ClassFileTransformer() {
10:       public byte[] transform(ClassLoader loader, String className,
11:         Class classBeingRedefined, ProtectionDomain protectionDomain,
12:         byte[] classfileBuffer) throws IllegalClassFormatException {
13:         try {
14:           if (className.equals("Hello")) {
15:             CtClass ctClass = classPool.makeClass(
16:               new ByteArrayInputStream(classfileBuffer));
17:             CtMethod ctMethod = ctClass.getDeclaredMethod("main");
18:             ctMethod.insertBefore("System.out.println(\"Hello Injection.\");");
19:             return ctClass.toBytecode();
20:           } else {
21:             return null;
22:           }
23:         } catch (Exception ex) {
24:           IllegalClassFormatException e = new IllegalClassFormatException();
25:           e.initCause(ex);
26:           throw e;
27:         }
28:       }
29:     });
30:   }
31: }

7行目でメソッド名がpremainとなっているように、本来のmainよりも前に実行されることを想定しています。挙動を簡単に解説すると、 14行目で"Hello"クラスを指定し、17行目でmainメソッドを指定しています。 18行目で"Hello Injection."を出力する命令を、insertBeforeつまり本来のmainメソッドの前に挿入しようとしています。
このMyInjectionクラスをコンパイルし、マニフェスト属性でPremain-Classを指定したJARファイルを生成します。

MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: MyInjection
Boot-Class-Path: javassist.jar

そして、次のように-javaagentオプションをつけて実行すると、出力が変わります。

$ java -javaagent:injection.jar Hello
Hello Injection
Hello world.

元のhello.classファイルはそのままですが、実行時にHelloクラスのバイトコードを動的に書き換えることで、挙動が変わりました。 この機能を利用して、前述のjava.awt.Frame#setTitle(String title) メソッドの挙動を変えてみたいと思います。
※ Oracle社のJREでバイトコードを書き換える行為は、バイナリ・コードライセンスに違反する可能性があります。OpenJDKは、GPLリンク例外つきの GNU General Public License (GNU GPL)なので問題ないと思いますが、他のJREを使用する際は、事前にライセンスを確認してください。

AWTの挙動変更

先程と同様に今度は、「Burp」を「げっぷ」置換する命令を setTitle メソッドの先頭に勝手に挿入してみます。前述の改造イメージからちょっと発展させ、文字列内の全ての"Burp"を"げっぷ"に変換しています。

// 一部のみ抜粋
if (className.equals("java/awt/Frame")) {
    CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
    CtMethod ctMethod = ctClass.getDeclaredMethod("setTitle");
    ctMethod.insertBefore("$1=$1.replace(\"Burp\",\"げっぷ\");");
    return ctClass.toBytecode();
}

$1は、このメソッドに渡された1番目の引数という意味で、実際にはStringオブジェクトが渡ってくるはずです。
早速、先程同様に -javaagentオプションを付けて実行してみます。

$ java -javaagent:injection.jar -jar burpsuite_community.jar

ご覧の通り、タイトルの「Burp」を「げっぷ」に変えられました。

全体の日本語化

実現の目処が立ったので、後は単純作業です。

まずは、java.awt.Frame#setTitle 以外のGUI表示関連のメソッドを探します。 AWTやSwingのメソッドの中から、引数にStringを受け取るsetTitleやsetTextなどそれっぽい名前のメソッドを中心に一通り"げっぷ"化してみて、GUIに反映されるメソッドを探していきます。 このとき、欲張り過ぎないよう注意が必要でした。例えば、HTTPメッセージエディタなどHTTPメッセージが表示される部分で、「Host: burp.example.com」を「ホスト: げっぷ.example.com」に勝手に変換してしまうと、脆弱性診断がまともにできなくなります。

次に、Burp SuiteのGUI上に表示される英文を翻訳します。最初に述べたとおり言語ファイルがあるわけではないので、翻訳すべき英文の一覧がありません。一通りの画面が表示されるようBurp Suiteを操作して、手探りで翻訳対象の文字列を探しました。 チェックボックスにチェックを入れると新たな設定項目が追加表示される箇所や、異常な値を入れた場合に表示されるメッセージなどがあり、それらが表示されるよう一つ一つ全部操作する必要がありました。 ただそのおかげで、今まで使った経験がなかった機能を使ってみたり、知らなかった設定項目を見つけるなど、新たな発見が多数ありました。特殊な条件下でしか発生しないエラーメッセージを見つけたときが一番うれしく、さらにそんなレアケースのエラーハンドリングもしっかりされていることに驚きました。

最後に、それぞれのメソッドで英文を和文に変換する挙動を挿入していきます。先程は「Burp」を「げっぷ」に変換する命令をハードコードしていましたが、最終的には別途辞書ファイルを作って実行時に読み込むようにしました。ついでに、起動時のオプションを指定すると、辞書ファイルを切り替えられるようにしています。次の例だと内蔵したja.txtではなく、カレントディレクトリにあるtest.txtを読み込みます。関西弁辞書を作れば、Burp Suiteが関西弁にできるはずです。

$ java -javaagent:injection.jar=test -jar burpsuite_community.jar

ドキュメントの日本語化

もう少し日本語化を進めます。
Burp Suiteは、マニュアルドキュメントが非常に充実しています。GUI上の至るところに?マークがあり、クリックすると説明が表示されます。 このドキュメントについて、前述の方法では日本語化ができませんでした。 Burp SuiteのJARファイルの中にドキュメントのHTMLファイルが含まれており、挙動から推測する限り内部に持っているブラウザでこれらのHTMLファイルを表示しているようです。 このドキュメントの日本語表示にも挑戦しました。

再度、Burp Suiteの内部動作を想像してみましょう。 一般的にJARファイルに含まれたリソースファイルを読み込むには、java.lang.Class#getResourceAsStream(String name) メソッドなどを使います。Burp Suiteでもこのメソッドを使い、引数にHTMLファイルのパスが指定されているのだろうと考えました。
このメソッドの挙動を勝手に変え、英語ファイルのパスが指定された場合は、別のパスのファイルを読み込ませるようにします。次のようなイメージです。

// getResourceAsStreamメソッドの改造イメージ
public class Class {
    public void getResourceAsStream(String fname) {
        fname = fname.replace("resources/Documentation/", "burp-resources-ja/Documentation/");
        //以下本来のgetResourceAsStreamの処理
    }
}

injection.jarに差し替えたHTMLファイルも格納し、先程同様に -javaagentオプションを使って実行すると期待通りに、差し替えた方のHTMLが表示されました。

実現の目処が立ったので、後は単純作業です。

Burp SuiteのJARファイルには、マニュアルドキュメントとして100個弱のHTMLが含まれていました。これを一通り翻訳します。 ちなみにこの翻訳したドキュメントだけ、別途公開しています。勢いで、Burp拡張用のAPI(JavaDoc)が約40個あったので、これも翻訳し公開しています(※PortSwiggerの承諾を得たうえで公開しています)。
https://burp-resources-ja.webappsec.jp/

最後に、この日本語ドキュメントもJARファイルに格納すれば、完成です。

手動テストシミュレータ

さて、前置きはこのくらいにして、本題に入りたいと思います。 前回のブログで、手動テストシミュレータを紹介しました。今回のインタフェース日本語化でも、もちろん日本語化の対象です。むしろ一番最初に着手しました。

日本語化の作業をする際、未翻訳の文字列が対象のメソッドに渡されてきたら、それをデバッグログに出力させるようにしながら、翻訳漏れを探していました。 シミュレータの翻訳に着手した当初、デバッグログに次の文字列が出力されていることを見つけていました。

Simulator running
Requests made:
Bytes transferred:
Base value:
Modified value:
Day rate:
Time running:
Total cost:
Current Action
Manual testing simulator

赤字にした3つについて、シミュレータのGUI上に表示されていない文字列です。最初は、何か内部で使っているゴミデータが出力されてしまったと思い無視していました。ところが作業を進めるにつれて、実はデバッグログの精度が高くゴミデータがほとんど含まれていないことに気がつき、これらの見たことのない文字列が実はどこかで使われている可能性がに気がつきました。

改めてシミュレータのマニュアルを見てみましょう。一番最後に、意味深な記述があります。

Now, it wouldn't be appropriate to have a counter showing how much the simulator has earned at your standard day rate, would it? Easter Eggs, anyone?

さて、シミュレータがあなたの標準的な日給換算でいくら稼いだか示すカウンタはない方がいいですよね?隠し機能?誰か見つけた?

何やら隠し機能が示唆されています。とりあえずシミュレータの画面で上上下下......とキーボード入力してみましたが何も起こりません。試行錯誤した結果、正解は「burp」でした。

この隠し機能はマニュアルに書かれている通り、人に代わってシミュレータがどのくらい労働したのか、脆弱性診断士の日給から換算して金額で表示してくれる機能でした。シミュレーションを開始すると、起動時間に比例して金額もどんどんと増え、シミュレータが稼いでいく様子がわかります。ランチに出ている1時間ほどシミュレータを実行しておくと、$125も稼いでくれました。現在、Professional版のライセンスは年間$399ですから、数回シミュレータを利用するだけであっという間に元は取れそうです。

デフォルトで$1000という数値が入っていますが、この根拠はわかりません。PortSwigger社の脆弱性診断士の日給なのでしょうか。 シミュレータの日本語インタフェースでは、$1000を\30000と翻訳し初期表示するようにしましたので、利用の際はご自身の日給に合わせて変えていただければと思います。

まとめ

いかがでしたか?今回は、Burp Suiteを勝手に日本語化するツールを紹介しました。 日本語で表示されてなじみやすくなったでしょうか? 私は長年英語で使っていたので、最初はかなり違和感がありました。 しかし翻訳を進め完成度が高まっていくにつれ、どんどんと愛着が増してきました。 ツール本体とマニュアルを隅から隅まで触ってみるきっかけになったのが大きいですね。

日本語化は、Burp Suiteを一つ一つ操作しながら探す必要があるため、まだ私が出会っていないメッセージがあるかもしれません。もし日本語化されていない箇所を見つけた場合、あるいは翻訳の間違いやその他不具合などを見つけましたら、GitHubのIssueなどでお知らせいただけるとありがたいです。もちろんプルリクも歓迎です。