Practical Binary Analysisの演習用環境をDockerで作った

三行で

  1. Practical Binary Analysis はリバースエンジニアリング入門にはうってつけの本
  2. 基本的な演習環境はVMで配布されている
  3. Dockerで環境を作るときはバージョンを指定しよう

概要

Practical Binary Analysisはモダンなバイナリ解析について学ぶことができる本。 目次は下記の通り。

Chapter 1: Anatomy of a Binary
Chapter 2: The ELF Format
Chapter 3: The PE Format: A Brief Introduction
Chapter 4: Building a Binary Loader Using libbfd

Part II: Binary Analysis Fundamentals

Chapter 5: Basic Binary Analysis In Linux
Chapter 6: Disassembly and Binary Analysis Fundamentals
Chapter 7: Simple Code Injection Techniques for ELF

Part III: Advanced Binary Analysis

Chapter 8: Customizing Disassembly
Chapter 9: Binary Instrumentation
Chapter 10: Principles of Dynamic Taint Analysis
Chapter 11: Practical Dynamic Taint Analysis with libdft
Chapter 12: Principles of Symbolic Execution
Chapter 13: Practical Symbolic Execution with Triton

Part IV: Appendices

Appendix A: A Crash Course on x86 Assembly
Appendix B: Implementing PT_NOTE Overwriting Using libelf
Appendix C: List of Binary Analysis Tools
Appendix D: Further Reading

基礎的なディスアセンブラを実装したり、テイント解析やシンボリック実行をやったりと、今からリバースエンジニアリングをするなら抑えておきたい点を実装して手を動かしながら体系的に学ぶことができる。

実行環境について

そんなPractical Binary Analysisは演習用の実行環境としてVMを配布している。 別にVMでもいいのだが、単純にコードを参照したいくらいの時だと少し実行速度が遅い... なので特段VMでないといけないような処理以外はDockerで実行しようと思い至った。 少しググった所、Practical Binary Analysisの演習用環境をDockerで作っている人がいたので、その環境を利用。

https://github.com/wilvk/practical-binary

しかしこのコンテナ、RUN apt-get -y install gcc-4.9 g++-4.9を実行する際に下記エラーを出力しビルドが失敗してしまう。

Package g++-4.9 is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source

RUN add-apt-repository -y ppa:ubuntu-toolchain-r/testを実行しているのになぜ...? ググるとこれはUbuntu 18.04に起因しているっぽい https://askubuntu.com/questions/1036108/install-gcc-4-9-at-ubuntu-18-04 DockerFileを改めて見ると最初の行でUbuntuのバージョンを指定していない。

FROM ubuntu
RUN apt-get update
RUN apt-get -y install software-properties-common
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
RUN apt-get update
RUN apt-get -y install gcc-4.9 g++-4.9
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-4.9
RUN apt-get -y install build-essential
RUN apt-get -y install libc6-dbg gdb valgrind
RUN apt-get -y install binutils-dev
RUN apt-get -y install strace
RUN apt-get -y install ltrace
RUN apt-get -y install vim-common
CMD ["/bin/bash"]

そのためUbuntu 18.04をダウンロードしてきてしまいビルドが失敗してしまう。 なのでUbuntuのバージョンを16.04に指定すると、失敗せずビルドすることができる。

FROM ubuntu:16.04
RUN apt-get update
RUN apt-get -y install software-properties-common
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
RUN apt-get update
RUN apt-get -y install gcc-4.9 g++-4.9
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-4.9
RUN apt-get -y install build-essential
RUN apt-get -y install libc6-dbg gdb valgrind
RUN apt-get -y install binutils-dev
RUN apt-get -y install strace
RUN apt-get -y install ltrace
RUN apt-get -y install vim-common
CMD ["/bin/bash"]

https://github.com/famasoon/practical-binary/ にコミットしておいた。 これで、とりあえずコードを見ながら軽く動作させる環境のできあがり。

おわりに

https://practicalbinaryanalysis.com/Running Code Samples on Windows and Other Platformsの所でカーネル4.4以降だとうまく動作しないとか、そんな時のためのUbuntu 16.04 用カーネルダウングレードの仕方とかが載っているが、その時は適宜VMで演習をするなり環境を構築すればよいと思っているので問題なし。 という訳で環境を整えたのでリバースエンジニアリングをこれからやっていく(続く...?)

minikubeのダウンロードが途中で止まる

問題

タイトル通りminikubeを利用しようとしたところisoファイルのダウンロードで止まってしまう。

環境

macOS上で実行しているminikube v1.0.0

~ ❯❯❯ uname -a
Darwin DoenoMBP 18.2.0 Darwin Kernel Version 18.2.0: Fri Oct  5 19:41:49 PDT 2018; root:xnu-4903.221.2~2/RELEASE_X86_64 x86_64

~ ❯❯❯ minikube version                                                                       
minikube version: v1.0.0

~ ❯❯❯ minikube start --kubernetes-version=v1.11.3
There is a newer version of minikube available (v1.0.1).  Download it here:
https://github.com/kubernetes/minikube/releases/tag/v1.0.1

To disable this notification, run the following:
minikube config set WantUpdateNotification false
😄  minikube v1.0.0 on darwin (amd64)
💥  Kubernetes downgrade is not supported, will continue to use v1.14.0
🤹  Downloading Kubernetes v1.14.0 images in the background ...
🔥  Creating virtualbox VM (CPUs=2, Memory=2048MB, Disk=20000MB) ...
💿  Downloading Minikube ISO ...
 1.97 MB / 142.88 MB [>-----------------------------------------]   1.38% 28m14s

毎回2%くらいダウンロードしたあたりでハングしてしまう。

対策

GitHubのissueをみる限り既知の問題のようでワークアラウンドが出ている。 Hung downloading ISO (network contention with image pull) #4035 minikube start時に--cache-images=falseオプションをつけて問題を回避する。

minikube start --cache-images=false

自分の環境だとこれで正常にisoがダウンロードできるようになった。 上記GitHubのissueを見る限り、このバグに対応するコードをv1.0.1に盛り込む予定っぽいので今後のバージョンアップに期待。

インタプリタを作っている

Go言語でつくるインタプリタを読んでいる。 前々から何かプログラミング言語を作ってみたかったので買ってみた。 まだハッシュマップとかマクロとか実装していないけど一旦紹介。

本の目次

内容

本書ではmonkeyというC言語風の言語を実装していく。 実装の仕方はmonkeyのインタプリタ実装に必要な物を 1. テストを書く 2. テストをする 3. 最小限必要な単位で実装する 4. テストをする 5. 実用的な範囲まで拡張して実装する

といったテスト駆動開発の手法を用いて進めていく。 テスト内容もGolangのスライスで管理しているため、簡単にテストケースを増やせる。 そのため新しく機能を実装しようと思った時でも簡単にテストと実装を繰り返すことができる。

字句解析

この章では入力されたソースコードトークンへ変換する。 主に入力されたソースコードをどこまで読んだか、どのトークンへ変換するか機能を実装する。

構文解析

前章で作成した字句解析器を利用しASTを作れるよう実装していく。

評価

ASTを解釈、評価していく。 このあたりで計算や関数の作成ができるようになる。

インタプリタの拡張

真偽値や数値以外に文字列や配列、ハッシュマップを実装する。 今はここまでやった。

終わりに

まだもう少しだけ実装する内容が残っているので早く読み進める。

LeetCode TwoSum をテストしながら解く

LeetCodeという競技プログラミングサービスがある。 まだ始めたばかりなのだがとっつきやすい作りになっていて良い。 そんなLeetCodeの問題 TwoSum を解いてみた。 ただ解いてみたというのも面白くないので今回はテストコードも書きながら解答してみる。

TwoSum

問題はへのリンク https://leetcode.com/problems/two-sum/。 問いは下記の通り。

数値の入った配列と整数値targetが与えられるので、配列の中から足すとtargetになる値の組み合わせを見つけ、その要素がどこにあるかを答えよ。 必ず答えは存在するものとする。 また別々の位置の値を必ず使用する事

以下に例を書く

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].

テストコードを書く

コードを弄ってブラウザ上でテストしても問題はないが遅い。 なので今回は手元ですぐに動作するテストコードを書いた。

package twosum

import (
    "reflect"
    "testing"
)

type testCase struct {
    array  []int
    target int
}

func TestTwoSum(t *testing.T) {
    tests := []struct {
        input  testCase
        output []int
    }{
        {testCase{[]int{2, 7, 11, 15}, 9}, []int{0, 1}},
        {testCase{[]int{2, 3, 4, 11, 15}, 6}, []int{0, 2}},
    }

    for i, tt := range tests {
        sum := twoSum(tt.input.array, tt.input.target)
        if !reflect.DeepEqual(sum, tt.output) {
            t.Errorf("tests[%d] failed - input: %+v - answer: %+v output: %d¥n", i, tt.input.array, tt.output, sum)
        }
    }
}

$GOPATH/src/leetcode/twosum/か何か適当なディレクトリでこれを書きgo test ./twosums/と入力すればお手軽テスト環境の完成。

あとはこのテストを通るような解答を考える。

解答

いくつか解法がある。 2つループを書いて総当たりする方法なんかは簡単だが、渡されるスライスが大きくなればなるほど時間計算量が増える。 今回は一旦ハッシュテーブルを作成してその中から値の組み合わせを見つける解法で解いた。

package twosum

func twoSum(nums []int, target int) []int {
    var res []int
    res = make([]int, 2)

    numMap := make(map[int]int)
    for i, num := range nums {
        numMap[num] = i
    }

    for j, firstNum := range nums {
        comp := target - firstNum
        value, ok := numMap[comp]
        if !ok {
            continue
        }
        if value == j {
            continue
        }

        res[0] = j
        res[1] = value

        return res
    }

    return res
}
  1. 数値とその位置をハッシュテーブル化
  2. targetからスライスから取り出した数値を引き、足してtargetになる数値を取り出す
  3. ハッシュテーブルを確認し、足してtargetになる値があればその位置を返す
  4. 見つけるまで1-3を繰り返す

終わりに

Goのtest機能、楽に使えて良いですね。 まだLeetCode始めたばかりなのでもっと問題を解いていこうと思います。