Reversingとかpwnとかを解くときのメモ(かきかけ)

この記事はTSG Advent Calendar 2016 - AdventarIS17er Advent Calendar 2016 - Adventarの2日目の記事として書かれたわけではなかったのですが、記事がないので埋めなきゃいけないのと、多分このままだと書きかけのままはてなの肥やしになってしまうので、エイヤと公開したものです。僕が学習するたびに適度に更新されるはずなので、たぶん永遠に書きかけです。

Reversing,pwnとは

CTFというセキュリティについての大会でよく使われる問題の区分です。主に実行ファイルの解析や、大会サーバ上で動いている実行ファイルに対する攻撃などを行う分野です。僕は駒場祭文花帖を自動でやるやつをやっていましたが、そいういう感じのことをやります。
Webとかsteganoとかは解くのにエスパー力(ドメイン知識ともいう)が要ってつらいですが、それらに比べてエスパー問が少ない(気がする)ので僕は好きな分野です。

Reversing

実行ファイルが与えられるので、それの挙動を解析してくださいというもの。
パスワードを当てたり、ゲームの得点を改竄したり、遅いアルゴリズムを高速化したり、アンチリバーシング技術をかいくぐったりする。
アセンブリバイナリを全部読めばたいてい解けますが、時間がかかりすぎるので、知識や観察を組み合わせつつすばやく解決する技術が要ってきます。(もしくは根性)

pwn

プログラムがサーバ上で動いているので、それに対して攻撃することにより、サーバ上にあるflag.txtを頑張って読み取ってくださいというもの。
バッファオーバーフローとか、フォーマットストリングバグとか、を起こしてしまうとやばいね、という話。
実際に動いているプログラムをばりばりぶっ壊せるので楽しい。(大会でやるから許されてしまうのであって、実世界で動いているものに対してやってしまうと不正指令電磁的なんとか*1に触れてしまうのでそこんとこは注意)
攻撃方法を思いつくには、たいていまず与えられた脆弱性のある実行ファイル(たいていx86かx64のelf)を解析しないといけないです。(たまにCのコードが与えられるものもある)Reversingの知識がある程度いるので、Reversingと並列にやっていきましょう。

Reversing

いかに手短にプログラムの挙動を把握するか、という問題。知識や観察力やエスパー力を頼りに素早く問題バイナリを解析していく。

例)

  • 入出力対応から察するにROT13なのでは 
  • UUDDRLRLなのでBAとくるのでは
  • 112358とあるのでフィボナッチ数の順に呼んでやればよいのでは (これはエスパーともいう)

ツール類

  • IDA

つよい。32bitのexeとelfを解析してくれる。

動的解析ならこれ。pedaというpython拡張を入れるととても強くなるので入れましょう。

  • Hopper

最近知った心強い味方。64bitのelfを投げると解析してくれる。(ただし無償版は30分で再起動する)

  • Objdump

とりあえずかけておいてもよさそう。
コンパイル結果がだばだば出てくるので参考にするとよいです。あと.pltとか見たりする。

  • OllyDbg

Windows動的解析ならとりあえずこれかな。

  • x64dbg

64bitのときはこちらに投げたりした。まだあんまり使ったことなし。

  • z3, angr

どちらもpythonのライブラリ。z3はSAT,SMTソルバであり、n次元連立方程式を解くときとかに投げる。angrは最近でてきたもので、パスワードチェックを通るようなパスワードを探索する、みたいなことをしたいときに制約式をがりがり立てて自動的に解いてくれる。使い方がちと難しいが強力な大鉈。

  • dex2jar
  • jd-gui
  • ILSpy

それぞれ、dexの展開、jarの解析、.netの解析をやってくれる。ほかにもいろいろな形のバイナリが降ってくると思いますが、時々に応じてツールをフットワーク軽く使っていくとよいでしょう。

まず確認すべきこと

  • file,stringsコマンドをかける。
  • strace,ltraceのもとで実行する。
    • strace .. システムコールの呼び出しとか引数とかを全部出力してくれる。
    • ltrace .. ライブラリ関数の呼び出しとか引数とかを全部出力してくれる。
  • Windowsの場合、IDAやOllyDbgなどで、「全ての外部関数呼び出し」や「全ての文字列定数」を見ておく。

elfのセキュリティ機構たち

実行ファイルのセキュリティ機構[RELRO/SSP/Nxbit/ASLR/PIE] - Pwn De Ring
ここにまとまってますね...
大抵、ASRLとNXのみで、セットでPIEとかRELROがついてきたりする。

カナリー,もしくはSSP(Stack Smash Protect)

関数に入るときのebpのアドレスとか戻りアドレスとかが上書きされないようにする。
__stack_chk_fail とか言う文字列が入ってると、多分使ってる。
gs:0x14 とかからランダムな値を引っ張ってきてる。

ASLR

アドレス配置のランダム化、というやつ。ヒープ、スタック、共有ライブラリ、の位置がランダムになる。(ほかにも、.bssとかも動くらしい...?)

PIE

位置独立実行コードと呼ばれるやつ。.textが自由な位置に配置されるようになってるの。
ふつう、動的リンクするライブラリを呼び出すときには、一旦.pltみたいなやつに仲介してもらうわけですが、そうはせずに e8 fc ff ff ff みたいにしてる。
すなわち、 call rel -4 みたいになってるとこがあるので、そこをロードする際に、直接、ライブラリの関数のアドレスで上書きしていく。
ライブラリのほうはRELROとかないので、相対アドレスがちゃんとわかればちゃんと動く...(ハズ)
PIEがあるとむしろReversingのほうでだいぶ苦しむ。

RELRO

Relocation Read Only.
Partial と Fullがある。
Fullの場合、GOT(WindowsでいうIATみたいなの)を上書きできないようにする。
RELROとformat string attackによるリターンアドレス書き換え - ももいろテクノロジー
これとかですかね。

DEP,もしくはNXビット

ふつう、ヒープやスタック上にeipが飛ぶ、みたいな事案はないはずなので、ヒープやスタック上に実行コードが置かれてもそれを実行しないようにするため。.text以外にはXが立たないようになってることがあるやつ。Windowsではデフォ。最近のLinuxでもデフォ。

ascii armor

strcpyとかをするときには、\x00までしか読み込まれない。
なので、\x00を含むようなアドレスにライブラリをロードするようにすると、pwnする際に苦労する。(のだけれど、そういう場合に限ってFSBだったりgetsだったりwriteだったりを使っているのであんまり苦しんだ記憶がない)

pwnについて

今のところelfに対するpwnしか出くわしたことがないのでそれについて書きます。

確認すべきこと

Reversingのそれに加えて、

  • checksecでどんなのがかかっているかを確認する。
  • readelfで_fini_arrayがあるかを確認する。
  • vmmapでrwxな領域があるかを確認する。

使うもの

reversingで使うやつのほかに。

僕はpythonで解法を書いたりしています。(cookierubyを推している)
https://github.com/satos---jp/ctf_tools
たいてい、この中の pwnscript.py を手元に落として、それからやってます。

  • socat

手っ取り早くリモートの状況を再現したいときに叩く。僕は、コマンドを覚えていないので pwnscript.py からコピペして使えるようにしてます。
何をやっているかとかは、http://www.slideshare.net/bata_24/katagaitai-ctf-1-57598200 のP158あたりを読んでおくとよいです。

  • nasm

シェルスクリプトを生成してくれる。毎回使い方を忘れるのでさっきのリポジトリ内の
https://github.com/satos---jp/ctf_tools/blob/master/x86-execve.s
を見ながら書いてます。
シェルスクリプトを書く際の注意点として、バグったら一旦Cで書いてみて、それのシステムコールの呼び方とちゃんと比較してみるとよいです。

  • rp++

ROPをやる際にたいへん便利。

  • metasploit

alphanumeric shellcode とかを生成してくれる。

システムコール

0から作るLinuxプログラム システムコールその1 ユーザープログラムからのシステムコール呼び出し
とかの中ほどに引数の説明がある。
x86とx64で呼び方とか引数の数とかがずいぶん変わっているので注意。

x86の場合

int 0x80 (cd80)で呼ぶ。システムコール番号はeax。引数はebx,ecx,edx,esi ...

x64の場合、

syscall ()で呼ぶ。システムコール番号はrax。引数はrdi,rsi,rdx,r10 ...

知っておくとよいのは、たとえば、

  • read write (メモリの読み書き)
  • open (ファイルを開く)
  • execve (プロセスの実行)
  • pipe2 (ファイルディスクリプタをつなぎかえる。自前でsocket通信してるやつに対して使ったり。)
  • mmap (実行可能なメモリを確保する)

ですかね。

書き換えるとこ

大抵のpwn問では、まず制御をどうにかして奪える状況にしてやる必要があります。(eipを奪うとかいう)
で、code部分はたいてい書き換え不能なので他の部分をどうにかしてやる必要があります。

スタック

おなじみ。ReturnAddressがあるので書き替えるとよい。ebpを書き換えてスタックをずらし、その後ReturnAddressをどうこうするのもある。

GOT

WindowsにおけるIATみたいなもの。外部ライブラリの参照をするのでここを書き換えた状態でlibcの関数を呼ぶとeipが奪える。

Heap

C++で仮想関数とか使ってる場合はそこを書き換えると奪える。他にもmallocの管理部をぶっ壊せばいろいろなとこが書き換えられたりする。

_fini_array

GOT的な場所が他にもあって、exit関数内で呼び出される関数テーブルというのがあり、そこを書き換えるとよい。

文字入出力について

入出力を溢れさせてたいてい書き換えるので、入出力関数の特性を知っておくとよい。
入力では、scanf, gets, fgets, read 、 出力では、printf, puts, fputs, write 、他に strcpy,strncpy とかですかね。

関数 書き込みが止まるもの
scanf 水平タブ(0x9),改行(0xa),垂直タブ(0xb),改ページ(0xc),キャリッジリターン(0xd),スペース(0x20)
gets 0xa
read なし
printf 0x0
puts 0x0
write なし
strcpy 0x0

(手元のUbuntuで試した結果です)
入力は、どれもNULL文字と0xffはそのまま気にせず読み込むそうです。
ほかにも、s[10]; のとき、fgets(stdin,s,10); は問題ないが、 scanf("%10",s); や strncpy(s,t,10); ではoff_by_one エラーが起こるとか。

eipを奪った後

flagを表示してくれる関数、もしくはsystem("/bin/sh");みたいなのがあるとき

そこに飛ばせばよい。

NXビットが立ってないとき

自分のシェルコードを適当なとこに流し込んで、そこに飛ばすようにする。
シェルコード内では、system関数があればそれを呼ぶもよし、ないならexecveシステムコールを叩いて自前でやる。
自分のシェルコードがどこにあるか分からない(たとえばスタックのアドレスが不明)なら、まずebpとかをリークさせる。
また、流し込めるシェルコードの量が短いときは、一旦readとかを呼んで、後から追加でシェルコードを実行させる。(stagerという)

NXビットが立っているとき

自分の実行可能コードが流し込めないので、元からあるコードをパッチワーク的につなぐことによってどうにかする。『returnなんとか』とか『ほげほげoriented programming』とか呼ばれる技法はたいていこのあたりの話。

returnなんとか

return to libc

libcの関数を順々に呼んでいこう、というもの。
ふつう、関数が呼び出された直後のスタックというのは、

return addr
arg0
arg1
...

となってるわけです。さて、関数からretする直前には、

return addr
...

となって、pop eip されることにより return addrに飛ぶわけですが、オーバーフローさせることにより、たとえば

gotにあるsystemのアドレス
piyo
arg1
arg2
...

としてやると、system関数の先頭で、

piyo
arg1
arg2
...

となっているので、あたかもアドレスpiyoから関数が呼ばれたかのように見えます。

return oriented programming

ROPとよばれるやつ。
先ほどの状況では関数は1つしか呼べないので、たとえばfopenしたあとwriteみたいなことはできません。そこで、ROPガジェットというやつを利用します。
たいてい、関数がretする直前ではいろいろスタックに退避していた値をレジスタにpopします。すなわち

0x8048060 pop ebx
0x8048061 pop ebp
0x8048062 ret

みたいなことになっているアドレスがあったりするので、例えば、

gotにあるputsのアドレス
0x8048061
putsで表示したいアドレス
gotにある(ry
...

としてやると、
puts(putsで表示したいアドレス);
が実行された後、putsからretするときに

0x8048061
putsで表示したいアドレス
gotにある(ry
...

となり、pop ebp後に

gotにある(ry
...

となるので、関数呼び出しが連鎖していきます。こういうのをROPといいます。


以降かきかけ。
大嘘を書いてる可能性があるので気付いたらマサカリを投げてください。

参考

みなさんご存知といった感じ。

韓国の。むずい。

最近主に埋めてる。pwn問がたくさんあるのでありがたい。

なんか似たような本紹介をこないだも部誌でやった気がしますね...