CTF未経験の僕が30分でバイナリ解析を学び、練習問題を解いた話【備忘録】
[kattene]
“image”: “https://images-na.ssl-images-amazon.com/images/I/410bb-z1SJL._SX396_BO1,204,203,200_.jpg”,
“title”: “セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方”,
“description”: “コンピュータセキュリティ技術を競うコンテストCTFで戦うための知識技能を鍛えよう。”,
“バイナリ とは sites”: [
“color”: “orange”,
“url”: “https://www.amazon.co.jp/%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%82%B3%E3%83%B3%E3%83%86%E3%82%B9%E3%83%88%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8%E3%83%96%E3%83%83%E3%82%AF-CTF%E3%81%A7%E5%AD%A6%E3%81%BC%E3%81%86-%E6%83%85%E5%A0%B1%E3%82%92%E5%AE%88%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E6%88%A6%E3%81%84%E6%96%B9-%E7%A2%93%E4%BA%95-%E5%88%A9%E5%AE%A3/dp/4839956480/ref=as_li_ss_tl?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=CTF&qid=1575462737&sr=8-1&linkCode=ll1&tag=kashiwabayu0c-22&linkId=b5f72e35795bdd8594c540a25d0a3fe4&language=ja_JP”,
“label”: “Amazon”,
“main”: “true”
>,
“color”: “red”,
“url”: “https://hb.afl.rakuten.co.jp/hgc/17ca09af.8067910e.17ca09b0.17069ed7/?pc=https%3A%2F%2Fitem.rakuten.co.jp%2Fbook%2F13410740%2F&m=http%3A%2F%2Fm.rakuten.co.jp%2Fbook%2Fi%2F17626636%2F&link_type=hybrid_url&ut=eyJwYWdlIjoiaXRlbSIsInR5cGUiOiJoeWJyaWRfdXJsIiwic2l6ZSI6IjI0MHgyNDAiLCJuYW0iOjEsIm5hbXAiOiJyaWdodCIsImNvbSI6MSwiY29tcCI6ImRvd24iLCJwcmljZSI6MSwiYm9yIjoxLCJjb2wiOjEsImJidG4iOjEsInByb2QiOjB9”,
“label”: “楽天”
>
]
>
[/kattene]
バイナリ解析とは
そもそもバイナリ解析とはなんでしょうか。
なんとなく、「バイナリ」を「解析」することだとわかります。
解析
https://kotobank.jp/word/%E8%A7%A3%E6%9E%90-457741
[名](バイナリ とは スル)
1 事物の構成要素を細かく理論的に調べることによって、その本質を明らかにすること。「調査資料を解析する」
2 数学的論法の一。Aの事柄を証明するために、Aが成立するためにはBが成立しなければならないことを示し、Bが成立するためにCが成立しなければならないことを示し、以下順次これを繰り返して既知の事柄に帰着させること。
3 「解析学」の略。
つまりは、 「バイナリ解析」とは、『機械語で書かれたソースを細かく調べて、その本質を明らかにすること』である と言えます。
なぜそんなことをするかと言えば、『実行ファイルから、そのプログラムの本質を知るため』です。
もちろんソースコードを読めれば一番ですが、ウイルスを含め、世の中に出回っているプログラムの大半のソースコードは公開されていません。
バイナリ解析に使うツール
バイナリ解析を行うためには、解析に適したツールが必要になります。
なぜなら、バイナリファイルは機械語で書かれているため、人間が読んでも全く理解することができないためです。
・fileコマンド:ファイルの種類などの情報を見られるLinuxのコマンドです。
・ghex:Linux上で動くGUIベースのバイナリエディタです。
・readelf:ELFファイルの情報を表示してくれるLinuxのツールです。
練習問題にチャレンジ!
問題は、『ファイルを渡されたがクリックしても実行できない!頑張って実行してくれ!』という内容でした。
さて、上記のコマンドを実行した出力結果には、『ELF 64-bit LSB executable』という文言が確認できます。
バイナリエディタの使い方や見方(「アドレス」や「テキスト」とは?)
(図132)
●③Stirlingを開く
↓
●④バイナリデータを開く
●その他:編集方法
<目次> (1) Windows10でアイコンの間隔が広くなる不具合の対処 (1-1) 発生状況・エラーメッセージ等 (1-2) 原因・対処 (1) Windows10でアイコンの間 …
<目次> (1) Celonisとは?プロセスマイニングの概要やCelonisの特徴 (1-1) プロセスマイニングとは? (1-2) Celonisとは? (1-3) Celon …
<目次> (1) RESTful APIとは?概要や6つの原則についてご紹介 (1-1) RESTful APIの概要 (1-2) RESTの6つの原則 (1) RESTful AP …
<目次> (1) OutlookでSkypeボタンが押せない時の復旧方法について (1-1) 事象概要 (1-2) 原因 (1) OutlookでSkypeボタンが押せない時の復旧方 …
<目次> (1) 画面設計の項目一覧の書き方とサンプルのご紹介 (1-1) 項目一覧とは? (1-2) 項目一覧の書き方は? (1-3) 項目一覧のサンプル (バイナリ とは 1) 画面設計の項目 …
バイナリ、文字列、文字リスト
Unicode は私たちが使う多くの文字にコードポイントを割り振っています。例えば、 a という文字は 97 のコードポイントを持っていますが、 ł という文字は 322 のコードポイントを持っています。ディスクに "hełło" という文字列を書き込む際に、私たちはこれら文字の連なりをバイトに変換しなければならないのですが、1バイトが一つのコードポイントを表現するというルールに習った場合、 "hełło" を表現することができません。コードポイント 322 は ł の為に使用していますが、1バイトでは 0 から 255 の数値を表現することしかできないのです。とはいえ、実際には "hełło" をスクリーン上で読めるのですから、 何らかの方法 でそれを表現する必要があります。そこでエンコーディングの出番です。
バイトでコードポイントを表現する際にそれらをどうにかエンコードする必要があります。Elixir はデフォルトのエンコード方式として UTF-8 を採用しています。文字列は UTF-8 でエンコードされたバイナリだと述べました。あの意味は、文字列が UTF-8 で指定された通りのコードポイントを表す為に編成されるバイトの一塊りだという意味です。
コードポイント 322 が割り振られている ł のような文字があるので、実際にはそれを表現する為に 1 バイト以上が必要になります。 String.length/1 と byte_size/1 で比較し、違いを見てみます。
ほら。 byte_size/1 は根本的にバイト数を計算しますが、 String.length/1 は文字数を計算していますね。
Note: Windows ではターミナルがデフォルトで UTF-8 が使えないことがあります。 iex ( iex.bat )を起動する前に chcp 65001 を実行して現在のセッションのエンコードを変更できます。
UTF-8 は バイナリ とは バイナリ とは h 、 e 、 o を表現する為にそれぞれ 1 バイトを必要としますが、 バイナリ とは ł の表現には 2 バイトです。Elixir では ? を使って文字のコードポイントを得られます。
Elixir が優れた文字列操作をサポートしていることをお分かりいただけると思います。また同時に多くの Unicode バイナリ とは 操作もサポートしています。実際、“文字列型は壊れている(英語)”という記事で提示されているすべてのテストを Elixir はパスしています。
しかし、文字列型はこの話におけるほんの一部分にでしかありません。文字列がバイナリであり、 is_binary/1 関数を使った時、Elixir には文字列を強化する為に基礎的な型が必要です。というわけで、それをやるとしましょう。今こそバイナリについてお話する時です!
バイナリとビット文字列
Elixir では > を使ってバイナリを定義できます。
Elixir では、文字列の内部的なバイナリ表現を確かめる為に空のバイト > ` を連結させるというテクニックをよく使います。
バイナリに与えたれている各数字はバイトを表す為であり、255 以下でなければいけません。バイナリは 255 より大きな数字を保持したり、コードポイントを UTF-8 に変換する為に修飾子を受け付けることができます。
1byte(8bit) に 1bit を渡すとどうなるでしょうか。
値はもはやバイナリではありませんが、ビット文字列、つまりビットの塊です。よって、バイナリはビット数が 8 で割り切ることのできるビット文字列です。
バイナリパターンの各エントリはちょうど 8bit にマッチすることを期待されています。サイズが分からないバイナリでマッチさせたい時には、パターンマッチの最後にバイナリ修飾子を置くことによって可能です。
バイナリとビット文字列のコンストラクタに関する詳細な資料はin the Elixir documentationを参照してください。これにて文字列、バイナリ、ビット文字列のツアーは終了します。文字列とは UTF-8 でエンコードされたバイナリであり、バイナリとはビット数が 8 で割り切ることのできるビット文字列でした。ここで Elixir がビットとバイトを用いた作業の為の柔軟性が用意されていることを示しましたが、99% はバイナリ操作と is_binary/1 と byte_size/1 を使うことになります。
文字リスト
文字リストはバイトの代わりに文字のコードポイントを包含していることが分かりますね (IEx は、いずれかの整数が ASCII の範囲を超える場合のみ、デフォルトでコードポイントを出力します)。ダブルクォーテーションが文字列(i.e. バイナリ) を表現するのに対して、シングルクォーテーションは文字リストを表現します(i.e. リスト)。
実際には、文字リストは特に引数としてバイナリを受け付けない古いErlangライブラリとの、インターフェイスとして使われます。 to_string/1 や to_charlist/1 関数を使って、文字リストを文字列に変換したり、文字列から文字リストに変換したりできます。
binascii --- バイナリデータと ASCII データとの間での変換¶
binascii モジュールにはバイナリと ASCII コード化されたバイナリ表現との間の変換を行うための多数のメソッドが含まれています。通常、これらの関数を直接使う必要はなく、 uu 、 base64 や binhex といった、ラッパ (wrapper) モジュールを使うことになるでしょう。 binascii モジュールは C で書かれた高速な低水準関数を提供していて、それらは上記の高水準なモジュールで利用されます。
a2b_* 関数は ASCII 文字だけを含むユニコード文字列を受け取ります。他の関数は ( bytes や bytearray またはバッファープロトコルをサポートするその他のオブジェクトのような) バイナリ とは bytes-like オブジェクト だけを受け取ります。
バージョン 3.3 で変更: a2b_* 関数は ASCII のみのユニコード文字列を受け取るようになりました。
binascii. a2b_uu ( string ) ¶
uuencode された 1 バイナリ とは バイナリ とは 行のデータをバイナリに変換し、変換後のバイナリデータを返します。最後の行を除いて、通常 1 行には (バイナリデータで) 45 バイトが含まれます。入力データの先頭には空白文字が連続していてもかまいません。
binascii. b2a_uu ( data , * , backtick = False ) ¶
Convert binary data to a line of ASCII characters, the return value is the converted line, including a newline char. The length of data should be at most 45. If backtick is true, zeros are represented by '`' instead of spaces.
バージョン 3.7 で変更: backtick パラメータを追加しました.
base64 でエンコードされたデータのブロックをバイナリに変換し、変換後のバイナリデータを返します。一度に 1 行以上のデータを与えてもかまいません。
binascii. b2a_base64 ( data , * , newline = True ) ¶
バイナリデータを base64 でエンコードされた 1 行の ASCII 文字列に変換します。戻り値は変換後の 1 バイナリ とは バイナリ とは 行の文字列で、newline が真の場合改行文字を含みます。この関数の出力は RFC 3548 を遵守します。
バージョン 3.6 で変更: パラメータに newline を追加しました。
quoted-printable 形式のデータをバイナリに変換し、バイナリデータを返します。一度に 1 行以上のデータを渡すことができます。オプション引数 header が与えられており、かつその値が真であれば、アンダースコアは空白文字にデコードされます。
binascii. b2a_qp ( data , quotetabs = False , istext = True , header = False ) ¶
バイナリデータを quoted-printable 形式でエンコードして 1 行から複数行の ASCII 文字列に変換します。変換後の文字列を返します。オプション引数 quptetabs が存在し、かつその値が真であれば、全てのタブおよび空白文字もエンコードされます。オプション引数 istext が存在し、かつその値が真であれば、改行はエンコードされませんが、行末の空白文字はエンコードされます。オプション引数 header バイナリ とは バイナリ とは が存在し、かつその値が真である場合、空白文字は RFC 1522 にしたがってアンダースコアにエンコードされます。オプション引数 header が存在し、かつその値が偽である場合、改行文字も同様にエンコードされます。そうでない場合、復帰 (linefeed) 文字の変換によってバイナリデータストリームが破損してしまうかもしれません。
binascii. a2b_hqx ( バイナリ とは string ) ¶
binhex4 形式の ASCII 文字列データを RLE 展開を行わないでバイナリに変換します。文字列はバイナリのバイトデータを完全に含むような長さか、または (binhex4 データの最後の部分の場合) 余白のビットがゼロになっていなければなりません。
data に対し、 binhex4 標準に従って RLE 展開を行います。このアルゴリズムでは、あるバイトの後ろに 0x90 がきた場合、そのバイトの反復を指示しており、さらにその後ろに反復カウントが続きます。カウントが 0 の場合 0x90 自体を示します。このルーチンは入力データの末端における反復指定が不完全でないかぎり解凍されたデータを返しますが、不完全な場合、例外 Incomplete が送出されます。
バージョン 3.2 で変更: 入力として bytestring または bytearray バイナリ とは オブジェクトのみを受け取ります。
binhex4 方式の RLE 圧縮を data に対して行い、その結果を返します。
バイナリを hexbin4 エンコードして ASCII 文字列に変換し、変換後の文字列を返します。引数の data はすでに RLE エンコードされていなければならず、その長さは (最後のフラグメントを除いて) バイナリ とは 3 で割り切れなければなりません。
value を CRC の初期値として data の 16 ビット CRC 値を計算し、その結果を返します。 この関数は、よく 0x1021 と表現される CRC-CCITT 多項式 バイナリ とは バイナリ とは x 16 + x 12 + x 5 バイナリ とは + 1 を使います。 この CRC は binhex4 形式で使われています。
binascii. crc32 ( data [ , value ] ) ¶
Compute CRC-32, バイナリ とは the unsigned 32-bit checksum of data, starting with バイナリ とは an initial CRC of value. The default initial CRC is zero. The algorithm is consistent with the ZIP file checksum. Since the algorithm is バイナリ とは designed for use as a checksum algorithm, it バイナリ とは is not suitable for use as a general バイナリ とは hash algorithm. Use as follows:
バージョン 3.0 で変更: The result is always unsigned. To generate バイナリ とは the same numeric value when using Python 2 or earlier, use crc32(data) & 0xffffffff .
binascii. b2a_hex ( data [ , sep [ , bytes_per_sep=1 ] ] ) ¶ binascii. hexlify ( data [ , sep [ , bytes_per_sep=1 ] ] ) ¶
バイナリ data の16進表現を返します。data の各バイトは、対応する2桁の16進表現に変換されます。したがって、返されるバイトオブジェクトは data の2倍の長さになります。
Similar functionality (but returning a text string) is also conveniently accessible using the bytes.hex() method.
If sep is specified, it must be a バイナリ とは single character str or bytes object. It will be inserted in the output after every bytes_per_sep input bytes. Separator placement is counted from the right end of the output by default, if you wish to count from the left, supply バイナリ とは a negative bytes_per_sep value.
バージョン 3.8 で変更: 引数 sep と bytes_per_sep が追加されました.
16進数表記の文字列 hexstr の表すバイナリデータを返します。この関数は バイナリ とは b2a_hex() の逆です。 hexstr は 16進数字 (大文字でも小文字でも構いません) を偶数個含んでいなければなりません。そうでない場合、例外 Error が送出されます。
Similar functionality (accepting only text string arguments, but more liberal towards whitespace) is also accessible using the bytes.fromhex() class method.
Internet Infrastructure Review(IIR)Vol.46
2020年3月26日発行
拡大する
拡大する
拡大する
逆アセンブラだけでプログラムの構造を再構成できない理由の1つは間接ジャンプ命令です。間接ジャンプ命令の行先アドレスを、静的データ解析を用いてできる限り静的に解決する解析手法を「制御フロー再構成」と呼びます。前述のとおり、関節ジャンプ命令の行先を静的に解決することは決定不能問題なので、完全解は得られません。CodeSurfer/x86 (注2) やJakstab (注3) などの先行研究では抽象解釈(Abstract interpretation)と呼ばれる解析手法を用いて行先アドレスの近似解を求めています。
しかし、制御フロー再構成にはもう1つ困難な点があります。前述のとおり、プログラムは複数の関数によって構成されています。事前に関数位置同定が行われてプログラムが関数単位に分割されている場合は、関数ごとに独立して解析を行う手続内プログラム解析(Intra-procedural program analysis)を適用できます。十分な前提知識がなく関数位置同定が正確に行えない場合は、プログラム全体を解析する全体プログラム解析(Whole program analysis)を行うことになります。事前に関数位置の同定はできていなくても実際にはプログラムはいくつかの関数に分割されています。そのため、全体プログラム解析では「文脈依存性」を考慮する必要があります。
例えば複数のプログラム位置から呼ばれる関数があった場合、制御はそれぞれの関数呼出元から関数へ処理が移り、関数の処理が終わった後、それぞれの復帰先へと処理が戻ります。復帰後の状態から関数を呼び出す前の状態を参照するためには、呼出と復帰の経路が一致している必要があります。このように経路に解析が依存することを文脈依存性と呼びます。文脈依存性を考慮しない場合、複数ある呼出元の区別がつかないため、復帰後の状態を分析するときに無関係な文脈の情報が混入し、解析の精度が著しく低下してしまいます。文脈依存性を解決するためには、スタックを用いて関数呼出/復帰の対応の整合性を保証するなどの処理が必要になります。このような解析を「手続間プログラム解析(Inter-procedural program analysis)」と呼びます。
- 逆アセンブラでは関節ジャンプ命令の行先が分からない。
- 関節ジャンプ命令の行先を既存の静的解析で決定するためには、事前にプログラムが関数に分割されている必要がある。
- バイナリプログラムから関数位置を特定するには、プログラムがどのようなコンパイラを使用したか、呼出規約に従っているかなどの前提が必要になる。
3.3 射影的単一代入形式を用いたバイナリプログラム解析
拡大する
図-4 32ビットIntel X86アーキテクチャにおける具体例
バイナリ とは
拡大する
図-5 IDA Pro で逆アセンブルした例
本研究手法では、各機械命令を単純な代入文列に変換した後、更に「静的単一代入(Static Single Assignment、SSA)」と呼ばれる表現形式に変換します。SSAはコンパイラの最適化解析で用いられる内部表現形式で、各変数の定義が一意になるように変数名の変更を行います。これにより各変数の定義と使用(def-use)関係が明確になり、情報の流れを把握することが容易になります。例えば図-6では、2ヵ所あるECXへの代入をECX1、ECX4と区別しています。
拡大する
図-7は2度目の(A)の呼び出しを終えるところまでの図です。SSAではΦ関数と呼ばれる疑似関数を導入して情報の合流を表現します。例えば、EBX8←Φ8(3:EBX2,7:EBX6)という代入文ではノード3から来たEBXレジスタの値EBX3とノード7から来た値EBX6が合流して新たにEBX8という変数に代入されています。先ほど同様にEBX_2の定義を遡ると、EBX2はΦ 関数を用いて、Φ8 (3:0x40100c,7:バイナリ とは 0x401016)として表現されます。これはすなわち、制御がノード8にノード3から来た場合のEBXレジスタの値は0x40100c、制御がノード7から来た場合は0x401016と情報の合流があることを表しています。このように特定の時点で合流した情報によって行き先アドレスが変わる場合を、本研究では「文脈依存性」として定義します。この場合、0x401017から0x401018の範囲のコードは、複数の文脈から再利用されているので、ここが「関数」であると推定することができます。
拡大する
こうして文脈依存性が検出できた場合、本手法では更にΠ関数と呼ばれる疑似関数を挿入します(図-8)。Π関数はΦ関数に対する射影関数として働きます。例えばΠ3→8(…)という式はノード8で合流した情報からノード3から来た情報を取り出す、という意味をもち、Π3→8(Φ8 (3:X,7:Y))⇒Xのように評価されます。このΠ射影関数によるSSAの拡張は「射影的単一代入(Projective Single Assignment、PSA)」と呼ぶ本研究独自の表現形式です。
拡大する
コメント