Format String Bug(弊研究室の某課題について考える8日目)

はじめに

この記事は弊研究室の某課題について考えるの8日目の記事です

今回ですが超優秀な記事があるのでそこを自分なりにわかりやすく説明していきます。

また攻撃方法とかはよくあるので仕組みについて重点的にやります。

お品書き

  • Format String Bug(FSB)
  • Format String Attack(どういうことができるかだけ)

Format String Bug

Format String(書式指定子)を扱える関数においてユーザが自由に書式指定子を配置できるバグです。書式指定子とはprintf等の関数を使うときにprintf("%d",1);のようこの%dが書式指定子です。書式指定子の例は以下の表に..

指定子 引数 用途
%c char 1文字出力
%s char * 文字列出力
%d int, short 整数を10進で出力
%x int, short, unsigned int, unsigned short 整数を16進で出力
%n 整数へのポインタ 文字数を指定した変数に格納
%p 変数 変数のポインタの値を16進で出力

さてこのバグが存在するプログラムを例に示します。

/*fsb.c*/
#include<stdio.h>

int main(int argc, char *argv[]){
        printf(argv[1]);
        return 0;
}

これをコンパイルして実行すると下のようになります。

$ ./a.out AAAA
AAAA$

ここで書式指定子を入れた入力をしてみます。

$ ./a.out AAAA%pBBBB
AAAA0x7ffedde001a8BBBB$

なにか変な値が出力されています。これがFSBです。ちなみに出力されているのはスタックの値です。

FSBの理屈

まずは普通のprintf関数のスタック状況を見てみましょう。

printf("%d %d %d", 1, 2, 3)

f:id:kataware8136:20171208223209p:plain

まぁこうなっています。こうなっているものと思っておいて問題ないです。2日めの記事を読み込んだ人なら厳密には違うとわかるでしょうがこの節の最後に捕捉します。

さてではFSBバグのあるさっきのものだとどうなっているか。

f:id:kataware8136:20171208225026p:plain

こうなっています。なのでAAAABBBBの間に変な値が出力されていたのですね。これがFSBです。次からprintf関数の捕捉していくのでどうでもいい人は飛ばして...

printf関数の捕捉

C言語の関数ですが、2日目にも合った通り引数はレジスタ積まれます。なのでprintf("%d %d %d", 1, 2, 3)ではrsirdxレジスタ1,2等の変数は格納されています。ではもしレジスタに引数が格納されていなかったら?その時は引数はレジスタに積まれているとして足りない引数をレジスタから引っ張ります。さらに足りなければスタックから引っ張ります。

引数の積まれ方はOSによって変わるのでそこらへんは注意してください

/*printf_sample.c*/
#include<stdio.h>

int main(){
        printf("%d %p",1);
        return 0;
}

出力はこんな感じ

$ ./a.out
1 0x7fffffffe598

gdb-pedaで見てみます。

[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x7fffffffe598 --> 0x7fffffffe7df ("XDG_SESSION_ID=73")
RSI: 0x1
RDI: 0x4005d4 --> 0x7025206425 ('%d %p')
RBP: 0x7fffffffe4a0 --> 0x400550 (<__libc_csu_init>:    push   r15)
RSP: 0x7fffffffe4a0 --> 0x400550 (<__libc_csu_init>:    push   r15)
RIP: 0x400539 (<main+19>:       call   0x400400 <printf@plt>)
R8 : 0x4005c0 (<__libc_csu_fini>:       repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>:        push   rbp)
R10: 0x846
R11: 0x7ffff7a2d740 (<__libc_start_main>:       push   r14)
R12: 0x400430 (<_start>:        xor    ebp,ebp)
R13: 0x7fffffffe580 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40052a <main+4>:   mov    esi,0x1
   0x40052f <main+9>:   mov    edi,0x4005d4
   0x400534 <main+14>:  mov    eax,0x0
=> 0x400539 <main+19>:  call   0x400400 <printf@plt>
   0x40053e <main+24>:  mov    eax,0x0
   0x400543 <main+29>:  pop    rbp
   0x400544 <main+30>:  ret
   0x400545:    nop    WORD PTR cs:[rax+rax*1+0x0]
Guessed arguments:
arg[0]: 0x4005d4 --> 0x7025206425 ('%d %p')
arg[1]: 0x1
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe4a0 --> 0x400550 (<__libc_csu_init>:   push   r15)
0008| 0x7fffffffe4a8 --> 0x7ffff7a2d830 (<__libc_start_main+240>:       mov    edi,eax)
0016| 0x7fffffffe4b0 --> 0x0
0024| 0x7fffffffe4b8 --> 0x7fffffffe588 --> 0x7fffffffe7bb ("/home/rize/Documents/adventor/a.out")
0032| 0x7fffffffe4c0 --> 0x1f7ffcca0
0040| 0x7fffffffe4c8 --> 0x400526 (<main>:      push   rbp)
0048| 0x7fffffffe4d0 --> 0x0
0056| 0x7fffffffe4d8 --> 0x4d2a215a3833886d
[------------------------------------------------------------------------------]

RSIとRDXの値が出力されていますね。

引数を増やしていくとスタックの値が出力されるので最初の説明はあながち間違ってもいないのです。

Format String Attack

おまけです。弊研究室の某課題について考える番外編とかやるならその時にしっかり記事作ります

ではFSBを使った攻撃について説明します。書式指定子の%xでスタックを表示することが%nで書き込むことができます。

%sで指定されたアドレスが指す文字列を出力することができます。

例えば

./a.out AAAA.%x.%x.%x.%x.--snip--.%x
AAAA---snip--.41414141.--snip--

41414141が出力されるのを[x]番目とした際に

AAAAの部分に読み出したいアドレスをセットし%[x]$sを用いることでメモリ内容を出力することが可能です。

終わりに

正直この内容一日でまとめるのきついのじゃが

そして7日目の記事、画像を使いまわしたらリターンアドレス消し忘れました。ごめんね。

参考資料