拾い物のコンパス

まともに書いたメモ

radare2のエイリアス機能をソースコードレベルでいじって使いやすくしてみる

 この記事はプログラムのソースコードを書き換えるものです.行う際は自己責任でお願いします.

 radare2エイリアスやマクロを使っていたが,どうにもコマンドを打ちにくいのでソースコードをいじって試行錯誤してみた.マクロ機能をいじるのは大規模な改変になりそうだったため,今回はエイリアスのみをいじることにした.結果エイリアスの識別文字を$からnにすると,比較的使いやすくなったのでそのやり方をまとめておく.
ついでに,改変後のエイリアス機能を利用した動的解析用エイリアスも紹介する.作成したエイリアスPedaを使ったGDBを意識した.

エイリアス機能

radare2エイリアス機能は以下のように定義・使用することができる.

* 定義
  $dis=`af;pdf`
* 使用
  $dis

詳しくは$?で見ることができる.

問題点

 まず基礎知識として,radare2は最初に機能を大別する文字(今回ならば$)の後に個別のコマンドが続くようになっている.
エイリアス機能は多用するのにタイプしにくい.これは識別文字に$を使っていることが原因であると考えた.
$を入力するShift+4って何回も打つのは苦痛だ,できればアルファベットのどれか,かつShiftを押さないで済ましたい.
ソースコードを読んでみると,$を定義している部分のコメントにはprefix aliases with a dashとあった.英語は苦手だが,とりあえず「dashを参考にしてエイリアスのプレフィックスを$にしました」と理解した(間違っていたらぜひ指摘してください).
恐らく,dashを起動した時に左に出てくる$のことだと思う.dashに固執する特別な意味は感じなかったので変えても問題はないだろうと判断した.
そこで,エイリアス機能の識別子である$を現在コマンドがマッピングされていないnに変更することにした.

準備

 Githubからソースコードをダウンロードする.この記事には関係はないが,Ubuntuaptなどでインストールされるradare2はバージョンが古いので,Githubからのインストールをおすすめしたい.
$ git clone https://github.com/radare/radare2 && cd radare2

編集

 次に,libr/core/cmd.cを以下のように編集する.やってることは,$nに置き換えているだけ.現在マッピングされているコマンドとかぶらなければ良いので,nでなくとも問題はない.他に現在コマンドがマッピングされていない文字はh j vがある(大文字であればもっと存在するが,Shiftを押したくないので今回は考えない).行番号はバージョンによって異なるので,適宜修正すること.

// libr/core/cmd.c
 128 static int cmd_alias(void *data, const char *input) {
 129   int i;
 130   char *def, *q, *desc, *buf;
 131   RCore *core = (RCore *)data;
       // help_msgの'$'を'n'に置き換える
 132   if (*input=='?') {
 133     const char* help_msg[] = {
 134       "Usage:", "nalias[=cmd] [args...]", "Alias commands",
 135       "n", "", "list all defined aliases",
 136       "n*", "", "same as above, but using r2 commands",
 137       "n", "dis='af;pdf'", "create command - analyze to show function",
 138       "n", "test=#!pipe node /tmp/test.js", "create command - rlangpipe script",
 139       "n", "dis=", "undefine alias",
 140       "n", "dis", "execute the previously defined alias",
 141       "n", "dis?", "show commands aliased by 'analyze'",
 142       NULL};
 143       r_core_cmd_help (core, help_msg);
 144     return 0;
 145   }
 ...
       // コマンド解析時の識別文字を変更.ここが要
 149   *buf = 'n';
 ...
       // 何も押さないでEnterした時に前回のコマンドが実行される機能を'n'で動くようにする.
2354 R_API void r_core_cmd_repeat(RCore *core, int next) {
2355   // Fix for backtickbug px`~`
2356   if (core->cmd_depth + 1 < R_CORE_CMD_DEPTH)
2357     return;
2358   if (core->lastcmd)
2359   switch (*core->lastcmd) {
2360   case 'd': // debug
2361     r_core_cmd0 (core, core->lastcmd);
2362     switch (core->lastcmd[1]) {
2363     case 's':
2364     case 'c':
2365       r_core_cmd0 (core, "sr PC;pd 1");
2366     }
2367     break;
2368   case 'p': // print
2369   case 'x':
       // ここを変更.これを忘れると,入力なしの時に前回のコマンドを実行する機能が動かない
2370   case 'n':
2371     if (next) {
2372       r_core_seek (core, core->offset + core->blocksize, 1);

インストール

セーブしたら,
$ sys/install.sh
でインストールできる.
ローカルにインストールしたいときは
$ sys/user.sh
削除は
$ make uninstall && make purge
で行うことができる.

確認

無事インストールが終わったら,反映されているかを確認する.

$ r2 /bin/ls
[0x00404840]> n?
|Usage: nalias[=cmd] [args...]Alias commands
| n                               list all defined aliases
| n*                              same as above, but using r2 commands
| ndis='af;pdf'                   create command - analyze to show function
| ntest=#!pipe node /tmp/test.js  create command - rlangpipe script
| ndis=                           undefine alias
| ndis                            execute the previously defined alias
| ndis?                           show commands aliased by 'analyze'
[0x00404840]> ndis='af;pdf'
[0x00404840]> n*
ndis=af;pdf
[0x00404840]> ndis
/ (fcn) entry0 42
|           0x00404840      31ed           xor ebp, ebp
|           0x00404842      4989d1         mov r9, rdx
|           0x00404845      5e             pop rsi
|           0x00404846      4889e2         mov rdx, rsp
|           0x00404849      4883e4f0       and rsp, 0xfffffffffffffff0
...(snip)...

これでエイリアス機能の識別文字がnになっていることが確認できた. しかし,エイリアスの定義では引数を渡す文字の@を使うことができない.
[0x00404840]> ndis='pd 10@eip
とすると,

[0x00404840]> n*
ndis='pd 10

と途切れてしまう.これはひと手間かけることで解決できる.

[0x00404840]> (test,"",pd 10@eip)
[0x00404840]> ntest=.(test)`  

マクロを定義して,そのマクロのエイリアスを設定している.これで,@問題は解決できる.

デバッグエイリアス

最後に,デバッグでよく使いそうなマクロ・エイリアスを紹介する.一部はradare2bookに載っているものを活用した.これを$HOME/.radare2rcに書いておくと起動時に毎回読み込んでくれる.

// .radare2rc for 32bit
(peda32,"",drr,pd 10@eip,pxr 40@esp)
(stepOut32,"",dso,drr,pd 10@eip,pxw 64@esp)
(stepIn32,"",ds,drr,pd 10@eip,pxw 64@esp)
(outputStack32,"",pxw 64@esp)
nn=.(stepIn32)                              ; GDBでの'si'
ni=.(stepOut32)                             ; GDBでの'ni'
nw=.(outputStack32)                         ; GDBでの'x/20xw $esp'
np=.(peda32)                                ; pedaによる出力に似た何か
ns='do;db main;dc;db- main;np'              ; GDBでの`start'.デバッグ情報が消されている場合は動作しない
nr='do;dc;np'                               ; GDBでの'run'
nc='dc;np'                                  ; GDBでの'continue'
// .radare2rc for 64bit <- この行は書かない
(peda64,"",drr,pd 10@rip,pxr 40@rsp)
(stepOut64,"",dso,drr,pd 10@rip,pxq 64@rsp)
(stepIn64,"",ds,drr,pd 10@rip,pxq 64@rsp)
(outputStack64,"",pxq 64@rsp)

nn=.(stepIn64)
ni=.(stepOut64)
nq=.(outputStack64)
np=.(peda64)
ns='do;db main;dc;db- main;np'
nr='do;dc;np'
nc='dc;np'

コマンド名は最初がnであることが条件.それ以降は自由に変えてよい.

まとめ

とりあえず,これでエイリアス機能が指をあまり動かさずに呼び出せるようになったので楽になった.しばらくはこの設定で使ってみようと思う.
書き換える箇所があれだけで済んだこととソースが読みやすかったことには感動した.機能をひとつのファイル内で完結させる部分とかは今後プログラムを書く上で意識していきたい.
しかし,もっと良い方法がデフォルトでなにかあるんじゃないかなと思いもする.Bookを読んでなにか気づいたらまた書きたい.

参考文献

Radare2Book: https://www.gitbook.com/book/radare/radare2book/details

Vimで簡単なファイルテンプレートを入力するコマンドを作成してみた

プログラムを組むとき,テンプレートを毎回手で入力することが面倒くさくなったので,Vimのショートカットにした.
編集中のファイル名を挿入する方法やレジスタについて勉強したので簡単にまとめておく.

目的

作成したいのは現在編集しているファイルに対して,自分が使っているテンプレートを書き込むだけの簡単なコマンドを実装する.本エントリではC言語向けのものを説明する.
具体的な動作は,編集中のファイルの最初と最後にそれぞれコメントを書き込むだけ.最初のコメント部にはファイル名を自動で書き込む.

Ex: filename.cを編集中の場合
/* filename.c */
<... code ...>
/* E.O.F. */

E.O.F.って手で打つと面倒くさい.

コマンド

最終的には以下を.vimrcに付け加えた.
nmap <silent> ,/ Go/* E.O.F. */<Esc>ggO/* <C-r>% */<CR>
ノーマルモードで,/と打つと実行.特に難しいものでもないから,肝となる部分だけ以下で説明する.

ファイル名の挿入

インサートモードで<C-r>+レジスタとすると,レジスタの内容を挿入することができる.今回はカレントファイル名を格納している%レジスタを使用した.

レジスタ

種類が多いから,少しだけ紹介.
 * 番号付きレジスタ: "0-9レジスタ0はコピー内容,それ以降は1から最新の削除内容が入る.
 * 名前付きレジスタ: "[a-Z]レジスタで明示的に指定することで任意に使える.
 * 消去専用レジスタ: "_レジスタ_ddとするとレジスタ内容に変化を起こさずに削除を行える.
レジスタ"<レジスタ名><コマンド>で使える.具体的にはV"ay"aレジスタに一行を保存し,"apでその内容を書き込める.
レジスタに何が入っているのかの確認は:regで行える.

まとめ

多分プラグインを探せばもっと高機能なものがあるのだろうが,そこまでの機能は求めていないからと軽い気持ちで実装したら思った以上に手間取った.レジスタについてはもっと使いこなせるようになりたい,特に_ddは多用しそうだ.

参考文献

インサートモード時に現在のファイルのフルパスを挿入するmap - YKMbPP
vimのレジスタ - Qiita

BlackArchの紹介

Gentooにはセキュリティ用にチューニングされたPentooがある.Arch Linuxにもあるんじゃないのかなと思って探したら案の定あった.
という訳で,今回はArch Linuxのセキュリティ特化ディストリBlackArchを紹介する.

BlackArchとは

ペンテスターやセキュリティ研究者向けのLinuxディストリの一つ.Arch Linuxを基盤としてしているため,使い方自体はArchと同じ.
レポジトリは非公式のものを使用している模様.
そして,現在(2016/1/5)レポジトリには1337のツールがある.ツッコミ待ちなのかと疑いたくなる数字だ・・・.

インストール

インストール方法は以下の2つがある.
* ISOからインストール
* Arch LinuxにBlackArchのレポジトリを追加する

ISOからインストール

まずは公式サイトからISOイメージを落としてくる. Download BlackArch の下の方にある日本のミラーを使うと比較的早く落とせる.
あとはVBoxなりVMwareなどで仮想マシンを作成.
もしくは
$ sudo dd bs=512M if=file.iso of=/dev/sdx
file.isoにはダウンロードしたISO,/dev/sdxにはお好みのフラッシュドライブを選択する.
USBとかを指定すると良い.USBの場合はBIOSの設定をいじってUSBからブートできるようにする.
起動できたら
User: root
Pass: blackarch
でログイン.

新しいディスクにインストールしたいときはLive CDを起動して

$ sudo pacman -S blackarch-install-scripts 
$ sudo blackarch-install 

Arch LinuxにBlackArchのレポジトリを追加する

既にインストールが終わっているArchにレポジトリを追加し,必要なだけBlackにする.

$ cd $HOME/Downloads
# スクリプトを落として確認. "86eb4efb68918dbfdd1e22862a48fda20a8145ff"と出たらOK
$ curl -O http://blackarch.org/strap.sh && sha1sum strap.sh 
$ chmod 755 ./strap.sh
# /etc/pacman.confを書き換える
$ sudo ./strap.sh
[+] installing blackarch keyring...
(snip)
[+] BlackArch Linux is ready!

/etc/pacman.confを見てみると,最後に追加されている.

[blackarch]
Server = http://www.mirrorservice.org/sites/blackarch.org/blackarch//$repo/os/$arch

以上でレポジトリの追加は終わり.これで普通にpacman -Ss <tool_name>とすると,blackmanのレポジトリからも検索されるようになる.
あとは好きなツールをインストールする.

# ツールの全リストを表示
$ pacman -Sgg | grep blackarch | cut -d' ' -f2 | sort -u 

# カテゴリを見る 
$ pacman -Sg | grep blackarch

# 指定したカテゴリのツールを全部インストール
$ sudo pacman -S blackarch-<category> 

# ツールを一個だけ入れる
$ sudo pacman -S <tool-name>

# 全ツールのインストール
$ pacman -S blackarch 

パッケージからビルドしたい人はblackmanよりインストールしていく.
時間に余裕がある人はこちらがおすすめ.

# blackmanのインストール.blackarchのレポジトリが追加されていないと見つからない.
$ sudo pacman -S blackman 

# カテゴリの表示 
$ blackman -l 

# カテゴリ内のツールを表示
$ blackman -p <category>

# ツール単体のインストール
$ sudo blackman -i <package> 

# カテゴリ単位のインストール 
$ sudo blackman -g <group> 

# 全部インストール 
$ sudo blackman -a 

カテゴリを見てみると早速ドローン関係のツール(blackarch-drone)があったりして気になる.
私の場合,端末の背景を黒にしているので,$ blackman -p blackarch-windowsとかしたときにツールの説明が表示色の関係で読みにくかった.
そこで/usr/bin/blackmanを以下のように編集した.

75行目: tput boldの追加
[-] GREEN="$(tput setaf 2)"
[+] GREEN="$(tput bold ; setaf 2)"

86行目: WHITE -> GREEN
[-] printf "%s${fmt}%s\n" "${WHITE}" "$@" "${NC}"
[+] printf "%s${fmt}%s\n" "${GREEN}" "$@" "${NC}"

94行目: GREY -> NC
[-] printf "%s${fmt}%s\n" "${GREY}" "$@" "${NC}"
[+] printf "%s${fmt}%s\n" "${NC}" "$@" "${NC}"

各自好きな色に変更するといいと思う.

感想

Kali Linuxを使ってても思うが,セキュリティ特化のディストリは知識のない身では様々なツールが入っていても,使いこなせない上にディスク容量食うわデーモンが動いていて通常動作や再起動が重くなるわで普段使いには向かない気がする.
Arch Linuxの良さはその軽さのはずだが,BlackArch全部入りしてしまうと重くなりがちである.
このような考えから,私は普通のArch Linuxを使って必要なツールのみをblackmanで適宜インストールするほうが好ましいと思う.
ちょっとマニアックなソフトウェアが簡単に見つかるから,地味に便利.
カテゴリごと入れてしまえるのは楽そうに見えはするが実際のところはどうなんだろうか.確かめていきたい.
あとguideのPDFはComming soon...を埋めて欲しい.

参考

BlackArch Linux - Penetration Testing Distribution

ペネトレーションテストにKail LinuxではなくBlackArch Linuxを使う - Qiita

セキュリティキャンプ講義「仮想化技術を用いたマルウェア解析」にチャレンジしてみた(プラグイン開発編)

前回のエントリでDECAFをコンパイル仮想マシンの用意・起動とプラグインを読み込ませるところまでやった。
今回は講義の本題であるプラグインの開発をやっていく。
私自身はQemu, DECAFや関数Hook,PEB構造体とかも全く知らず,マルウェア解析の経験もない状態で始めてみたが,何とかなったので興味があるならチャレンジしてみれば楽しいと思う.
最終的なプラグインpoppycompass/red · GitHubに置いてある.適宜参照してほしい.

プラグイン基礎

プラグインの基本をIsDebuggerPresentをHookするプラグインgeteipを使って説明する.
Hookとは,プログラムの特定の箇所にユーザが定義した処理を追加することらしい.
今回で言うと,IsDebuggerPresentが実行される前と後に任意の処理を追加する関数Hookのことを指す.
前回のエントリのコマンドを一通り試した人は<your_path>/DECAF/decaf/plugins/geteip以下にあるはずである.
まだ持っていない人は
$ git clone https://github.com/ntddk/geteip
でダウンロードすること.
このプラグインの主なファイルはplugin_cmds.hgeteip.cである.
これからそれぞれの内容を見ていきたい.
なお,解説は現在の私の理解であるため,誤り等が含まれることがある.見つけたら遠慮なく指摘していただけるとありがたい.

plugin_cmds.h

プラグインに実装した機能はqemuのコマンドとして実行する.
このコマンド名はmon_cmd_tで設定する.具体的な設定は以下の通り.

{
    .name           = "geteip",                            // コマンド名
    .args_type      = "procname:s?",                       // 引数タイプ.今回は特定のプロセス名を引数に取る
    .mhandler.cmd   = do_monitor_proc,                     // コマンド実行時に実行する関数名
    .params         = "[procname]",                        // ???用途不明???
    .help           = "tracking EIP of [procname] as block"// コメント
},

このプラグインではplugin_cmds.hで構造体を定義し,geteip.cにて

static mon_cmd_t geteip_term_cmds[] = 
{
#include "plugin_cmds.h"
    {NULL, NULL, },
};

とインクルードしている.当然のことながらgeteip.cの方に埋め込むこともできる. コマンド名は自由に変えることができるが,変更したら*.cの名前やソースの関数名も合わせて変更しないと混乱する.

geteip.c

全部説明すると長いので,Hook関連の場所だけを抜粋して説明する.

// Hook用のハンドル
static DECAF_Handle isdebuggerpresent_handle = DECAF_NULL_HANDLE;  

typedef struct {
    uint32_t call_stack[1]; //paramters and return address, 今回は引数がないのでreturn adddressが入る.
    DECAF_Handle hook_handle;
} IsDebuggerPresent_hook_context_t;  // 関数Hookには関数ごとにこの構造体を用意する.

/*
 * BOOL IsDebuggerPresent(VOID);
 */

// IsDebuggerPresent終了時に呼び出される
static void IsDebuggerPresent_ret(void *param)  
{
    IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param;
    hookapi_remove_hook(ctx->hook_handle);
    DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
    free(ctx);
}

// IsDebuggerPresent開始時に呼び出される
static void IsDebuggerPresent_call(void *opaque)  
{
    DECAF_printf("IsDebuggerPresent ");
    IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t*)malloc(sizeof(IsDebuggerPresent_hook_context_t));
    if(!ctx) return;
    DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack);
    ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], IsDebuggerPresent_ret, ctx, sizeof(*ctx));
}

// 監視対象プロセス作成時に呼び出される
static void geteip_loadmainmodule_callback(VMI_Callback_Params* params)
{
    if(strcmp(params->cp.name,targetname) == 0)
    {
        DECAF_printf("Process %s you spcecified starts \n", params->cp.name);
        target_cr3 = params->cp.cr3;
        // Hookしたい関数を登録
        isdebuggerpresent_handle = hookapi_hook_function_byname("kernel32.dll", "IsDebuggerPresent", 1, target_cr3, IsDebuggerPresent_call, NULL, 0);  
        blockbegin_handle = DECAF_register_callback(DECAF_BLOCK_BEGIN_CB, &geteip_block_begin_callback, NULL);
    }
}

プラグインをロードするとinit_pluginが呼び出される.geteip_initではVMI_register_callback関数で監視対象プロセス作成時にgeteip_loadmainmodule_callbackを呼び出すよう登録している.

実行

作成したプラグインコンパイルしてから起動した仮想マシンにロードする.

コンパイル

$ pwd
/home/<user>/DECAF/decaf/plugins/geteip
$ ./configure --decaf-path=/home/<user>/DECAF/decaf
$ make

geteip.soが作成されていれば成功.

ロード

(qemu)のプロンプトが出ている状態で

(qemu) load_plugin /home/<user>/DECAF/decaf/plugins/geteip/geteip.so
(qemu) geteip blue.exe

のように使う.今回であればロード時にHello, World!のメッセージがでるはず.

注意点

Anti-Qemu trick回避

IsDebuggerPresent Hook

IsDebuggerPresentデバッグ
* されている : 0以外
* されていない: 0
を返す関数.
今回だと,デバッグしていることを隠したいからIsDebuggerPresent終了時に戻り値を0に書き換えてやるとblue.exeデバッグされていないと勘違いさせることができる.
geteipプラグインは関数Hookまで書いてあるから,戻り値を書き換えるだけで良い.
関数終了時の処理を少しだけいじる.戻り値はqemuEAXに入っている.

// IsDebuggerPresent終了時に呼び出される
static void IsDebuggerPresent_ret(void *param)  
{
    IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param;
    hookapi_remove_hook(ctx->hook_handle);
    cpu_single_env->regs[R_EAX] = 0; // 追加
    DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
    free(ctx);
}

これでIsDebuggerPresentの戻り値は常に0となる. ここからようやく課題スタート.

課題プログラム

一つ一つのLevelが回避できることを確認していきたい人は,これからは下に書いたコードをgeteip.cに書き加えていけば各Levelを回避していけるはず.
未検証なので,回避できた・ここが足りなかったなどを教えてもらえると嬉しい.
前回のエントリのコマンドを一通りやっていれば/home/<user>DECAF/VMsblueというディレクトリがあるはず.
やっていない人は
$ git clone https://github.com/ntddk/blue.git
でダウンロードする.
blue/Release/blue.exeがプログラム本体で,blue/blue/blue.cppソースコード
構造は極めて簡単で,

if (IsDebuggerPresent() == FALSE && Blue() == 0 && Charlie() == 0 && Delta() == 0 && Echo() == 0)
    Flag();

の通り,各関数の戻り値を0になるようにする.一つでも回避できないと即停止.

Level1: Blue

static inline int Blue()
{
    printf("\n[Level 1] Blue ...");
    Sleep(360000);
    DWORD time1 = GetTickCount();
    Sleep(500);
    if ((GetTickCount() - time1) < 450)   Detected();
    else return 0;
}

仕組みはまずSleepで6分潜伏したあと2回GetTickCountを呼び出して,Sleepがちゃんと実行されているかを確認しているような感じ. 毎回6分も待つのは辛いので真面目に回避することにする.
やるべきはSleepの引数を1にして,2回のGetTickCountの戻り値の差を450以上にすること.
これらは以下のコードで実現できる.

// 関数Hook用の構造体を定義
typedef struct {
    uint32_t call_stack[2]; // return address and parameters ([0]: ret addr, [1]: time)
    DECAF_Handle hook_handle;
} Sleep_hook_context_t;

typedef struct {
    uint32_t call_stack[1]; // return address only -> VOID
    DECAF_Handle hook_handle;
} GetTickCount_hook_context_t;
// 構造体定義終わり


/*
 * BOOL Sleep(DWORD dwMilliseconds);
 */
static void Sleep_ret(void *param)
{
    // DECAF_printf("Sleep exit\n");
    Sleep_hook_context_t *ctx = (Sleep_hook_context_t *)param;
    hookapi_remove_hook(ctx->hook_handle);
    free(ctx);
}

static void Sleep_call(void *opaque)
{
    // DECAF_printf("Sleep entry\n");
    Sleep_hook_context_t *ctx = (Sleep_hook_context_t*)malloc(sizeof(Sleep_hook_context_t));
    if(!ctx) return;

    // bypass Sleep
    // Sleepの引数を ctx->call_stackに読み込み(call_stack[0]: ret addr, call_stack[1]: dwMilliseconds)
    DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 2*4, ctx->call_stack);
    // 引数の書き換え.1ミリ秒だけ待機するようにした.
    ctx->call_stack[1] = 1;
    // call_stackをメモリに書き込む.これで変更が反映される.
    DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 2*4, ctx->call_stack);

    ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], Sleep_ret, ctx, sizeof(*ctx));
}
/* Sleep end */

/*
 * DWORD GetTickCount(VOID)
 */

static void GetTickCount_ret(void *param)
{
    // GetTickCountは二回呼び出される.最初の呼び出しの時だけ戻り値を変更したい.
    // 静的変数 flag を使うことで実装した.
    static int flag = 0;
    // DECAF_printf("GetTickCount exit\n");
    GetTickCount_hook_context_t *ctx = (GetTickCount_hook_context_t *)param;
    hookapi_remove_hook(ctx->hook_handle);

    // 最初の呼び出しでは戻り値は0,二回目は正しい値が返る.
    // 呼び出し間の差が450以上になれば良いので,最初(450), 2回目(900)とかやって遊ぶのも面白い.オーバフローには注意
    if (!flag) {                                // ture is only first call
        cpu_single_env->regs[R_EAX] = 0;    // return 0
        flag = 1
    } else {
        flag = 0;
    }
    // DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
    free(ctx);
}

static void GetTickCount_call(void *opaque)
{
    // DECAF_printf("GetTickCount entry\n");
    GetTickCount_hook_context_t *ctx = (GetTickCount_hook_context_t*)malloc(sizeof(GetTickCount_hook_context_t));
    if(!ctx) return;
    DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack);
    ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], GetTickCount_ret, ctx, sizeof(*ctx));
}
/* GetTickCount end */

// 監視対象の関数を登録する.'[+]'の行を付け加えるだけ.
static void red_loadmainmodule_callback(VMI_Callback_Params* params)
{
        [+] sleep_handle = hookapi_hook_function_byname("kernel32.dll", "Sleep", 1, target_cr3, Sleep_call, NULL, 0);
        [+] gettickcount_handle = hookapi_hook_function_byname("kernel32.dll", "GetTickCount", 1, target_cr3, GetTickCount_call, NULL, 0);
}

ctx->callstackには関数呼び出し時は[ret_addr, arg0, arg1, ...]となっていて,関数終了時にはret_addrなどは消える.
編集が終わったら,

$ pwd
/home/<user>/DECAF/decaf/plugins/geteip
$ make

で再度コンパイルする.その後仮想マシンを起動した状態で

(qemu) unload_plugins # 既にプラグインを挿入している場合
(qemu) load_plugin /home/<user>/DECAF/decaf/plugins/geteip/geteip.so
(qemu) geteip blue.exe

ここまでやったあと,Windows上でblue.exeを動かしてみる.
Level2で検知されたらOK.

Level2: Charlie

コードは以下の通り.

static inline int Charlie()
{
    printf("\n[Level 2] Charlie ...");
    SYSTEM_INFO siSysInfo;
    GetSystemInfo(&siSysInfo);
    if (siSysInfo.dwNumberOfProcessors < 2) Detected();
    else return 0;
}

GetSystemInfo関数は引数の_SYSTEM_INFO構造体に各種値を入れる.構造体は9の変数を持っている.

typedef struct _SYSTEM_INFO { // sinf 
   call_stack[0]:    union {
   DWORD  dwOemId; 
   struct { 
   WORD wProcessorArchitecture; 
   WORD wReserved; 
   }; 
   }; 
   call_stack[1]:   DWORD  dwPageSize; 
   call_stack[2]:   LPVOID lpMinimumApplicationAddress; 
   call_stack[3]:   LPVOID lpMaximumApplicationAddress; 
   call_stack[4]:   DWORD  dwActiveProcessorMask; 
   call_stack[5]:   DWORD  dwNumberOfProcessors;        // プロセッサ数
   call_stack[6]:   DWORD  dwProcessorType; 
   call_stack[7]:   DWORD  dwAllocationGranularity; 
   call_stack[8]_low:    WORD  wProcessorLevel; 
   call_stack[8]_high:   WORD  wProcessorRevision; 
   } SYSTEM_INFO;

_SYSTEM_INFO構造体は6番目の要素にdwNumberOfProcessorsというプロセッサ数を格納する変数があり,これが1だと仮想環境と判断し,停止する.
回避方法はシンプルで,こいつを2以上に書き換えてやれば良い.
実装は以下の通り.使う関数などはLevel1と同じ.

static DECAF_Handle getsysteminfo_handle     = DECAF_NULL_HANDLE;

typedef struct {
    uint32_t call_stack[10];  // return address and parameters
    DECAF_Handle hook_handle;
} GetSystemInfo_hook_context_t;

/*
 * VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);
 */

static void GetSystemInfo_ret(void *param)
{
    // DECAF_printf("GetSystemInfo exit\n");
    GetSystemInfo_hook_context_t *ctx = (GetSystemInfo_hook_context_t *)param;
    hookapi_remove_hook(ctx->hook_handle);

    // 構造体を読みだす.
    DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 10*4, ctx->call_stack);
    // 変数の書き換え
    ctx->call_stack[5] = 0x2;  // SYSTEM_INFO.dwNumberOfProcessors is 0x2
    // 反映
    DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 10*4, ctx->call_stack);  // write back to memory
    // DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
}

static void GetSystemInfo_call(void *opaque)
{
    // DECAF_printf("GetSystemInfo entry\n");
    GetSystemInfo_hook_context_t *ctx = (GetSystemInfo_hook_context_t*)malloc(sizeof(GetSystemInfo_hook_context_t));
    if(!ctx) return;
    DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack);
    ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], GetSystemInfo_ret, ctx, sizeof(*ctx));
}

/* GetSystemInfo end */
static void red_loadmainmodule_callback(VMI_Callback_Params* params)
{
   [+] getsysteminfo_handle = hookapi_hook_function_byname("kernel32.dll", "GetSystemInfo", 1, target_cr3, GetSystemInfo_call, NULL, 0);
}

編集が終わったら,コンパイルする.

(qemu) unload_plugins # 既にプラグインを挿入している場合
(qemu) load_plugin /home/<user>/DECAF/decaf/plugins/geteip/geteip.so
(qemu) geteip blue.exe

Level2が回避できたことを確認したらLevel3へ.

Level3: Delta

さて,ここまでは引数や戻り値をいじってやればよかったので,簡単だった.
Level3からはWinodwsについての知識が必要になってくるので複雑になってくる.
問題のコードは以下の通り.

static inline int Delta()
{
    printf("\n[Level 3] Delta ...");
    unsigned long NumberOfProcessors = 0;
    __asm
    {
        mov eax, fs:[0x30]
        mov eax, [eax + 0x64]
        mov NumberOfProcessors, eax
    }
    if (NumberOfProcessors & 0x1) Detected();
    else return 0;
}

やっていることはLevel2と同じくNumberOfProcessorsを確認している.
実はIsDebuggerPresentGetSystemInfoもPEB(Process Environment Block)構造体というものの要素を参照する関数である.
だから問題としては関数がアセンブラになったと考えるとわかりやすいだろうか.
PEB構造体は各プロセスごとに作成される構造体で,ヒープやファイル,ロードしたDLLの内容などが入っている.
PEB構造体はTEB(Thread Environment Block)構造体という,スレッドごとに作成される構造体の先頭から0x30バイト先にその先頭アドレスが入っている. 絵で書くとこんな感じか

           TEB構造体
    |         0x0      |
    |         ...      |
    |         ...      |
    |         ...      |          PEB構造体
    |    0x30(PEB)     | -> |       0x0       |
                            |       ...       |
                            |       ...       |
                            |       0x64      | -> value of NumberOfProcessors

決してプラグインに上のアセンブラを混ぜ返してやればいいなんて考えないように.エミュレータ上のレジスタプラグインに書いたレジスタは別物だと思う.やる人はあまりいないと思うが,やって時間を浪費したアホがここにいるので一応書いておく.
このトリックの対処としてはPEB構造体の値そのものを書き換えてやればよい.実装は以下の通り.

// bypass level3, 引数はデバッグ用
static void level3(int line_num)
{
    uint32_t NumberOfProcessors = 0;
    uint32_t base = 0, peb_addr = 0, peb = 0;
    // TEB構造体のアドレスを取得.FSレジスタに格納されている
    base = cpu_single_env->segs[R_FS].base;
    // PEB構造体の入っているアドレスを計算
    peb_addr = base + 0x30;
    // PEB構造体のアドレスを読み込む
    DECAF_read_mem(NULL, peb_addr, 4, &peb);
    // NumberOfProcessorsを読み込む
    DECAF_read_mem(NULL, peb+0x64, 4, &NumberOfProcessors);
    //DECAF_printf("FS(TEB): 0x%08x, peb_addr: 0x%08x, peb: 0x%08x\n", base, peb_addr, peb);

    // 変数を変更して反映
    NumberOfProcessors = 0x2;
    DECAF_write_mem(NULL, peb+0x64, 4, &NumberOfProcessors);  // write back to memory
}
// level3 end
static void red_loadmainmodule_callback(VMI_Callback_Params* params)
{
   [+] level3(__LINE__);   // replace NumberOfProcessors
}

コンパイルして回避できたことを確認したら終了.
PEBやらTEB構造体を初めて知ったが,奥が深い.
PEB構造体は探せば中身が出てくるが,実際はUndocumentedらしい.

Level4: Echo

最後の課題.
多分今できているプラグインを読み込ませて何回かblue.exeを実行するとLevel4が回避できる時と回避できない時があるはず.もしちゃんと検知してくれない時は何回か実行すると良い.
問題のコードは以下の通り.

static inline int Echo()
{
    printf("\n[Level 4] Echo ...");
    __try{__asm{cmpxchg8b fs:[0x1000]}}
    __except (1){Detected();}
    return 0;
}

cmpxchg8b fs:[0x1000]

   if ([EDX:EAX] == fs:[0x1000]) fs:[0x1000] = [ECX:EBX];
   else [EDX:EAX] = fs:[0x1000];
// [EDX:EAX]はEDXが上位32ビットでEAXが下位32ビットとする64ビット値

といった動作をする.この命令自体F00Fバグと呼ばれるバグの原因となった厄介な機械語らしい.
これがどうしてQemu検知できるかというと,read-onlyのページに書き込みした場合,本物のCPUならばPage Faultが起こる.Qemuの場合はPage Faultが起こらないらしい.どうやらQemuの実装上の欠陥をついた方法のようだ.ソースは https://www.symantec.com/avcenter/reference/Virtual_Machine_Threats.pdf
正直未だにこの検知の仕組みがよくわかっていない.整理できていない思考を書いておく.面倒な人は読み飛ばし推奨.真面目に読んだ人は意見が欲しい.

よくわからん!

  • ソースの文章によると,メモリに書き込んでみて,Page Faultが起きなければ良いということなのだろう.しかし,Qemuだと例外が起こらないのだから,try文で検知できるのだろうか・・・?
  • [EDX:EAX]は特に書き換えられていない.つまり[EDX:EAX]fs:[0x1000]と関係がないから,[EDX:EAX] = fs:[0x1000]の代入が起こるはず.これだとメモリがread-onlyだとかは関係がないんじゃなかろうか.
  • 加えて,このLevelは検知される時と検知されない時の原因はPEB構造体がfs:[0x1000]に配置されるか否かであることがわかった.
    条件としては
     配置される : 検知されない
     配置されない: 検知される
    Olly DebugのQ&Aを見てみると,WindowsXPのSP1以前はfs:[0x1000]にPEB構造体が配置されることが約束されていたようだ.これがなにか関係があるのかとも思ったが,Qemu検知とは全く関係がない・・・はず.

Level4復帰

散々混乱した末に以下の結論に至った.
よくわからんが,PEB構造体は書き込み可である.fs:[0x1000]にPEB構造体が配置されていない時はread-onlyなのだろう(未検証・予想・適当).
だから,fs:[0x1000]に格納されているアドレスを書き込み可能なアドレスにしてしまえばいいと考えた.
Windowsのプログラムで書き込み可能フラグが立っているアドレスの調べ方がわからなかったので,手っ取り早くスタックの先頭アドレス(ESP)に書き換えることにした.これでうまく行ってしまったので,これが答えの一つなのだろう.他にもっと冴えたやり方あるのかな.
cmpxchg8bが実行される時だけを検知して処理を行わないとプログラムがクラッシュする可能性がある.また,実行後はちゃんと元の値に戻さなければならない.
このあたりの検知は機械語を一つ実行するごとにHookしてcmpxchg8bかどうかを判定した.これにはDECAF_INSN_BEGIN_CBを使うことで実装することができる.
当然ながらこのやり方はとてつもなく重い.間違ってもこのHook中の処理にprintfとか挟んではいけない.やらかした奴が言うんだから間違いない.
説明が長くなったが,実装は以下の通り.

// level4
static uint32_t save_peb = 0;
static uint32_t save_val[2] = {0, 0};
static uint32_t save_base = 0;
static void red_insn_begin_callback(DECAF_Callback_Params* params)
{
    // detect cmpxchg8b and replace fs:[0x1000]
    uint32_t cur_insn = 0,
             cmpxchg8b = 0x00c70f64, // opcode of 'cmpxchg8b: 0x640fc7 ->(fix endian) 0x00c70f64 
             base, peb_addr, tmp_addr;
    // 3Byte読み込んで,比較
    DECAF_read_mem(NULL, cpu_single_env->eip, 3, &cur_insn);
    if (cur_insn == cmpxchg8b) {    // catch cmpxchg8b
        base = cpu_single_env->segs[R_FS].base;
        DECAF_read_mem(NULL, base+0x30, 4, &save_peb);  // get peb
        if (base > save_peb) { // peb is rear base
            DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 8, save_val);
            /*                  DECAF_printf("FS(TEB): 0x%08x, peb: 0x%08x\n", base, save_peb); */
            /*                  DECAF_printf("edx:eax %08x:%08x, ecx:ebx %08x:%08x\n", cpu_single_env->regs[R_EDX], cpu_single_env->regs[R_EAX], cpu_single_env->regs[R_ECX], cpu_single_env->regs[R_EBX]); */
            save_base = base;
            cpu_single_env->segs[R_FS].base = cpu_single_env->regs[R_ESP]-0x1000;  // fs = ESP - 0x1000
        }
    }
}

static void red_insn_end_callback(DECAF_Callback_Params* params)
{
    return;
}
// level4 end
static void red_loadmainmodule_callback(VMI_Callback_Params* params)
{
    [+] insnbegin_handle = DECAF_register_callback(DECAF_INSN_BEGIN_CB, &red_insn_begin_callback, NULL);    // level4
    [+] insnend_handle = DECAF_register_callback(DECAF_INSN_END_CB, &red_insn_end_callback, NULL);  // level4
}
static void red_cleanup(void)
{

    [+] if (insnbegin_handle != DECAF_NULL_HANDLE)
    [+] {
    [+]     DECAF_unregister_callback(DECAF_INSN_BEGIN_CB, insnbegin_handle);
    [+]     insnbegin_handle = DECAF_NULL_HANDLE;
    [+] }
    [+] if (insnend_handle != DECAF_NULL_HANDLE)
    [+] {
    [+]     DECAF_unregister_callback(DECAF_INSN_END_CB, insnend_handle);
    [+]     insnend_handle = DECAF_NULL_HANDLE;
    [+] }
}

上で偉そうなことを行ったが,実はこのコード,アドレスを書き換えたあと戻していなかったりする.戻さなくてもクラッシュしないプログラムだったので,面倒くさいからそのままにしておいた.良い子のみんなはちゃんと戻そう.
これで全てのトリックを回避できるはず.
回避するとフラグの文字列が出力される.洋書のタイトルのようだ.

最後に

説明したいことが多くて,必要以上に長くなってしまった.ここまで読み進めた人はお疲れ様でした.
全てのヒントはスライドの中に紛れ込んでいたりする.探してみるのもおもしろい.
スライドの情報のみだと説明が足りないので,DECAFの公式サイトやソースコードを読むのが一番役に立った.大規模なプログラムのソースを読み散らかすのは楽しい経験だったと思う.
マルウェアの仮想化回避はLevel2とLevel3のように同じやり方でも少し変えるだけで全く別のものになってしまう.いたちごっこな雰囲気を強く感じる演習だった.これはセキュリティベンダが苦労するわけだと思う.
セキュリティキャンプ全国ではこれを数時間でやらせることを考えると結構鬼畜。出たかったな・・・。
最後に、黒米さんと愛甲さん,楽しい時間をありがとうございました.

裏ワザ

  • 仮想マシン起動時に-smp 2,maxcpus=2をつけて割り当てるプロセッサを2つにするとLevel2,3は無条件でクリアできる.Level4は不安定だから場合によってはLevel1を回避しただけでフラグまでたどり着ける.
  • Level4とか面倒くさいから,該当するバイナリをNOPに書き換えるのもあり?(未検証)

なんだこれ

QemuのありえないHz.CPUが溶けないなら欲しい性能. f:id:poppycompass:20151219001811p:plain

参考

DECAF - decaf-platform - DECAF Binary Analysis Platform - "Taking the jitters out of dynamic binary analysis" - Google Project Hosting

Win32 Thread Information Block - Wikipedia, the free encyclopedia

https://www.symantec.com/avcenter/reference/Virtual_Machine_Threats.pdf

OllyDbg Q&A (Digital Travesia)

セキュリティキャンプ講義「仮想化技術を用いたマルウェア解析」にチャレンジしてみた(準備編)

今年の八月中旬に行われたセキュリティキャンプ全国大会2015の解析トラック15・16-Dでの講義「仮想化技術を用いたマルウェア解析」にチャレンジして、一通りできた。
発表者の黒米さんの許可が戴けたのでその流れを書き残す。
発表で使われたスライドは黒米さんの技術ブログ" 一生あとで読んでろ "に置いてある。

構成

プラグインを書くことよりも環境の準備に相当手間取ったので、準備編とプラグイン開発編の二段階で書いていく。
内容は以下の通り。
* 準備編
DECAFの導入と操作方法、注意点。
* プラグイン開発編
DECAF上で動かしているWindows仮想マシンで、仮想化検知を施されたプログラムblue.exeの検知を回避するプラグインを開発していく。

DECAFの準備

DECAFとは

DECAFは"Dynamic Executable Code Analysis Framework)の略で、QEMUにテイント解析機能を追加したTEMUの後継として作成されたバイナリ解析フレームワークのこと。詳しくはBitBlazeプロジェクトを検索。

インストール

インストールした環境は以下の通り。
* Ubuntu 14.04 LTS 64bit
他にはUbuntu 14.04 LTS 32bitArch Linux 64bitでインストールを試みたが、コンパイルが上手くいかず断念した。

まず、適当なディレクトリに移動してから以下のコマンドを実行していく。

$ sudo apt-get update
$ git clone https://github.com/sycurelab/DECAF
$ cd DECAF/decaf
$ pwd
/home/<user>/DECAF/decaf
$ sudo apt-get install qemu
$ sudo apt-get install build-dep qemu
$ sudo apt-get install binutils-dev
$ sudo apt-get install libboost-all-dev
$ ./configure # スライドの方にはテイント解析などをオンにするオプションが載っているが、今回はなくても問題なかった。  
$ make

もしもpython関係のエラーが出たら、コマンドのpythonが3系にリンクが張られていないかを確認すること。DECAFのスクリプトは2系で書かれていて、例外処理関係でエラーが出る。
具体的にはconfig-host.makPYTHON=pythonPYTHON=python2.7とかに書き換える。
これ以外のエラーが出たら・・・頑張れ!
DECAF自体のインストールはmakeが上手くいけばおしまい。
次はWindows仮想マシンを用意する。

仮想マシンの準備

Developer Resources : Microsoft Edge Devで30日だけ動作するWindows仮想マシンを配布しているので、ダウンロードする。
トップページ下にある"Virtual Machine"をクリック。
Windowsタブで
* Virtual Machine => IE8 on XP
* Select Platform => VMWare
と設定し、"Download .zip"からダウンロード。

$ pwd
/home/<user>/DECAF/decaf
$ mkdir ../VMs
$ mv $HOME/Downloads/IE8.XP.For.Windows.VMware.zip $HOME/DECAF/VMs
$ cd ../VMs
$ unzip IE8.XP.For.Windows.VMware.zip

VMWare用の仮想マシンからqemu用の仮想マシンを作成する.
作成には少々時間がかかる.容量も7GBほど食われた覚えがあるから注意.

$ qemu-img convert <name of VM>.vmdk -O qcow2 pre_ie8.qcow2
$ qemu-img convert -p -f qcow2 -O qcow2 -o compat=0.10 pre_ie8.qcow2 ie8.qcow2

qcowに変換しても動作するが,スナップショットが使えない.
作成が終わったら,下のコマンドで起動してみる.
$ $HOME/DECAF/decaf/i386-softmmu/qemu-system-i386 $HOME/DECAF/decaf/VMs/ie8.qcow2 -m 2048 -monitor stdio

これでWindowsXPが立ち上がることを確認する。-mオプションは割り当てるメモリの量を指定する.1GBでは動作が遅い。
また,今回は1つしかプロセッサを与えていないため,しばらくは処理が重いが時間を置くと落ち着く.
画面が引き延ばされるのを修正したいならCtrl+Alt+uを押す。
マウスが暴走するようなら
$ export SDL_VIDEO_X11_DGAMOUSE=0
Windowsが立ち上がることを確認したらシャットダウンする。
次に問題のプログラムをWindnowsにコピーする。

$ git clone https://github.com/ntddk/blue.git
$ sudo modprobe nbd
$ sudo qemu-nbd -c /dev/nbd0 ie8.qcow2
$ sudo /sbin/fdisk -l /dev/nbd0
$ sudo mount -o loop,offset=$((63*512)) /dev/nbd0 /mnt
$ sudo cp -r blue /mnt/Documents and Settings/All Users/Desktop
$ sudo umount /mnt
$ sudo qemu-nbd -d /dev/nbd0
$ sudo rmmod nbd

これで起動したときのデスクトップにblueのフォルダが出るようになる。
また,プラグインコンパイルしておく.

$ pwd
/home/<user>/DECAF/VMs
$ cd ../decaf/plugins
$ git clone https://github.com/ntddk/geteip
$ cd geteip
$ ./configure --decaf-path=/home/<user>/DECAF/decaf
$ make

このプラグインはIsDebuggerPresentをフックする.関数フックのテンプレートとして勉強になる.
今後の起動はDECAFの公式HPやスライドに載っている自動化スクリプトを使ってもできる.
下のスクリプトも同じ動作をするのでHaskell好きな人はどうぞ.

-- decaf_auto.hs
import System.IO
import System.Process         -- createProcess
import System.Posix.Directory -- changeWorkingDirectory
import Control.Concurrent (forkIO, threadDelay)

inputCmd p_in p_out cmd = do
    hPutStrLn p_in cmd
    threadDelay (1000 * 1000)
    -- Nonblocking
    forkIO $ putStrLn =<< hGetContents p_out

rawInput msg = do
    putStr msg                                             
    getLine

main :: IO ()
main = do
    hSetBuffering stdout NoBuffering
    decaf_path <- rawInput "*** Enter the root directory of DECAF (i386-softmmu/qemu-system-i386 should be there): "
    image_path <- rawInput "*** Enter the image path: "
    changeWorkingDirectory decaf_path
    (Just p_stdin, Just p_stdout, _, _) <- 
        createProcess (shell $ "i386-softmmu/qemu-system-i386 " ++ image_path ++ " -m 2048 -monitor stdio") { std_in = CreatePipe, std_out = CreatePipe }
    hSetBuffering p_stdin NoBuffering
    hSetBuffering p_stdout NoBuffering
    threadDelay (3 * 1000 * 1000)
    inputCmd p_stdin p_stdout "ps"
    inputCmd p_stdin p_stdout "help"
    return ()
-- E.O.F.

やっていることはqemu-system-i386のプロセスを作成し,そのプロセスの標準入力・標準出力に対しての操作を行っている.
しかしこのスクリプト(Python版も含めて)は使わないほうがいいと感じた.
これは実行すると書いてある動作以外をせず,qemuのプロンプトが出ないで入力・出力が閉じてしまう.
使うなら下のコマンドで普通に実行したほうがいい.

$ pwd
/home/<user>/DECAF/VMs
$ ../decaf/i386-softmmu/qemu-system-i386 ie8.qcow2 -m 2048 -monitor stdio

出力が一段落ついたあたりで一回Enterを押すと,(qemu)のプロンプトが出る.
プロンプトを表示させてプラグインを読みこませる.

(qemu) load_plugin ../decaf/plugins/geteip/geteip.so
(qemu) geteip blue.exe

これでblue.exeを実行した時にgeteip.cの内容が実行される.blue.exeblue/Release/blue.exeにある.
何もいじっていなければIsDebuggerPresentの実行時と実行後にそれぞれフックしたメッセージが出るだけのはず.
この辺はどうにも不安定で,いじっていなくてもIsDebuggerPresentを回避できたりした.
詳しいことはプラグイン開発編で書く.

最後に

ここまでがうまく行けば,準備は終わり.
誤字やうまく行かないところがあれば気軽に言ってくれると嬉しい.
ここまで長い記事を書いたことがないので,どこか間違いがありそうな気がする.
次は,blue.exeのソースを参考にプラグインを書いていく.

アセンブリ言語をコンパイル・実行してみる

GASとNASMで64bitプログラミングの基本をやってみた。
実行するのは基本中の基本である"hello, world"を出力するだけのプログラム。

環境

  • gcc: version 5.2.0 (GCC)
  • nasm: NASM version 2.11.08 compiled on Mar 24 2015
  • Arch Linux x86_64: 4.2.5-1-ARCH

64bitアセンブラ基本

64bitのアセンブラにおいてはレジスタは以下のように使われる。
rax: システムコール番号
rdi: 第一引数
rsi: 第二引数
rdx: 第三引数
システムコール番号は/usr/include/asm/unistd_64.hに書いてある。

これを今回のwriteの場合に当てはめると
rax: 0x10 (write)
rdi: 1 (stdout)
rsi: pointer of strings ("hello, world!!!")
rdx: 16 (文字列長)

GAS

GASはGNU ASのことであり、GCCコンパイルすることができる。デフォルトではAT&T記法なので今回はIntel記法に変更してからプログラミングする。 ソースは以下の通り。

/* hello.s 64bit */

.intel_syntax noprefix
.globl _start

_start:
// write(1, "hello, world!!!\n", 16)
   xor rax, rax
   xor rdi, rdi
   xor rdx, rdx

   inc rax
   inc rdi

   mov rsi, 0x0a212121646c726f
   push rsi
   mov rsi, 0x77202c6f6c6c6568
   push rsi
   mov rsi, rsp

   push 0x10
   pop rdx
   syscall

// exit(1)
   push 0x3c
   pop rax
   syscall
$ gcc -nostdlib hello.s -o hello_gas  
$ ./hello_gas
hello, world!!!

NASM

NASMはNetwide Assemblerの略であり、nasmというコンパイラを使ってコンパイルする。 NASM自体は公式サイトからダウンロードするか各ディストリのレポジトリからインストールできる。Arch Linuxでは
$ pacman -S nasm
でインストールできる。

; hello.asm 64bit
section .text
global _start

_start:
   xor rax, rax
   xor rdi, rdi
   xor rdx, rdx

   inc rax     ; write
   inc rdi     ; stdout

   mov rsi, 0x0a212121646c726f
   push rsi
   mov rsi, 0x77202c6f6c6c6568
   push rsi
   mov rsi, rsp

   push 0x10
   pop rdx
   syscall

; exit(1)
   push 0x3c
   pop rax
   syscall
   
$ nasm -f elf64 hello.asm && ld hello.o -o hello_nasm
$ ./hello_nasm`  
hello, world!!!

両方ともこんな風にコンパイルされている。

   xor rax, rax
   xor rdi, rdi
   xor rdx, rdx
   inc rax
   inc rdi
   movabs rsi, 0x0a212121646c726f
   push rsi
   movabs rsi, 0x77202c6f6c6c6568
   push rsi
   push rsi
   mov rsi, rsp
   push 0x10
   pop rdx
   syscall
   push 0x3c
   pop rax
   syscall

プログラムは一応NULLバイトをなくしてある。
syscallをsysenterと書いてデバッグしたのはいい思い出である。64bitではint 0x80とsysenterはサポートされていないようだ。
それぞれコメントアウトの仕方が違うのでそこは注意が必要である。

比較

GASの方はコメントの仕方がCライクであったり、pushの時に自動でBYTE変換してくれる、GCCで使えるなど、芸が細かい。
NASMの方はオブジェクトファイルが作りやすいから、CTFとかで使いやすい。
どちらを使うかは好みの問題だと思う。

LightDMの壁紙が変わらないときの対処

Arch Linuxをインストールする際に最初はDM(デスクトップマネージャ)にGDMを使っていたが、認証成功時に一瞬CUIっぽいのが出ていた。これが微妙だったからUbuntuOpenSUSEで利用されている軽量DMのLightDMに変えた。
このとき認証画面の背景を変更するのに割と手間取ったためメモを残しておく。

環境

問題

ArchWikiには背景を変えたいときは/etc/lightdm/lightdm-gtk-greeter.confbackgroundを編集すればよいと書いてある。
具体的には、
・単色にしたいとき
background=#000000
・画像を使いたいとき

background=/usr/share/pixmaps/<wallpaper>.jpg
user-background = true  

これではうまくいかなかった。調べてみると、OpenSUSEのフォーラムに答えがあった。

対処

画像があるディレクトリをroot所有にして、画像自体はパーミッション644にすると上手くいった。
$ chown root <pict_dir>
$ chown root <wallpaper>.jpg
$ chmod 644 <wallpaper>.jpg
やっていること自体は画像を置くことが推奨されている/usr/share/pixmapsに近づけているだけだったりする。要はこれに合わせるか、このディレクトリに入れろってことのようだ。
もしこれでうまくいかないときはlightdm-gtk-greeter-settingsを使うと良い。
GUI画面で異常が起こっているかどうかを確認しながら作業することができる。
これは
$ pacman -S lightdm-gtk-greeter-settings
でインストールできる。
起動したら、Backgroundの右にあるmultihead setupを選び、そこでディスプレイと画像を指定する。
異常がある場合黄色い三角が出てくる。

参考

Problem changing 'lightdm' theme