4

私はいくつかのセキュリティ関連のことを研究していて、今は自分のスタックで遊んでいます。私がやっていることは非常に些細なはずです。スタックを実行しようとさえしていません。単に、64 ビット システムで命令ポインターを制御できることを示すためです。知っているすべての保護メカニズムをオフにして、それで遊ぶことができるようにしました(NXビット、ASLR、-fno-stack-protector -z execstackでコンパイルも)。私は 64 ビット アセンブリの経験があまりなく、自分自身を検索して実験するのに時間を費やした後、私が経験している問題に誰かが光を当てることができるかどうか疑問に思っています。

境界チェックなしで文字列をスタック常駐バッファーに単純にコピーするプログラム (以下のソース コード) があります。ただし、一連の 0x41 で上書きすると、RIP が 0x4141414141414141 に設定されることが予想されますが、代わりに RBP がこの値に設定されることがわかります。セグメンテーション違反が発生しますが、RSP が有効な値に設定されていても、RET 命令の実行時に RIP がこの (不正な) 値に更新されません。RET命令の直前にRSPに一連の0x41を含む読み取り可能なメモリがあることをGDBで確認しました。

私は LEAVE 命令がしたという印象を受けました:

MOV (E)SP、(E)BP

POP (E)BP

ただし、64ビットでは、「LEAVEQ」命令が実行されるようです(同様):

MOV RBP、QWORD PTR [RSP]

この命令の実行前後にすべてのレジスタの内容を観察するだけで、これを行うと考えています。ただし、LEAVEQ は RET 命令のコンテキスト依存の名前のようですが (GDB の逆アセンブラーがそれを指定します)、それはまだ 0xC9 のままです。

そして、RET 命令は RBP レジスタで何かを行っているようで、おそらくそれを逆参照していますか? 私は RET がしたという印象を受けました (同様に):

MOV RIP、QWORD PTR [RSP]

ただし、前述のように、RBP を逆参照しているようです。これは、他のレジスタに不正な値が含まれていないように見えるときにセグメンテーション違反が発生するためだと考えています。

プログラムのソース コード:

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

int vuln_function(int argc,char *argv[])
{
    char buffer[512];

    for(int i = 0; i < 512; i++) {
        buffer[i] = 0x42;
    }

    printf("The buffer is at %p\n",buffer);

    if(argc > 1) {
        strcpy(buffer,argv[1]);
    }

    return 0;
}    

int main(int argc,char *argv[])
{
    vuln_function(argc,argv);

    return 0;
}

for ループは、バッファーの正当な部分を 0x42 で埋めるためだけにあるため、オーバーフローの前に、デバッガーでどこにあるかを簡単に確認できます。

デバッグ セッションの抜粋は次のとおりです。

(gdb) disas vulnerable
Dump of assembler code for function vulnerable:
   0x000000000040056c <+0>:     push   rbp
   0x000000000040056d <+1>:     mov    rbp,rsp
   0x0000000000400570 <+4>:     sub    rsp,0x220
   0x0000000000400577 <+11>:    mov    DWORD PTR [rbp-0x214],edi
   0x000000000040057d <+17>:    mov    QWORD PTR [rbp-0x220],rsi
   0x0000000000400584 <+24>:    mov    DWORD PTR [rbp-0x4],0x0
   0x000000000040058b <+31>:    jmp    0x40059e <vulnerable+50>
   0x000000000040058d <+33>:    mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000400590 <+36>:    cdqe   
   0x0000000000400592 <+38>:    mov    BYTE PTR [rbp+rax*1-0x210],0x42
   0x000000000040059a <+46>:    add    DWORD PTR [rbp-0x4],0x1
   0x000000000040059e <+50>:    cmp    DWORD PTR [rbp-0x4],0x1ff
   0x00000000004005a5 <+57>:    jle    0x40058d <vulnerable+33>
   0x00000000004005a7 <+59>:    lea    rax,[rbp-0x210]
   0x00000000004005ae <+66>:    mov    rsi,rax
   0x00000000004005b1 <+69>:    mov    edi,0x40070c
   0x00000000004005b6 <+74>:    mov    eax,0x0
   0x00000000004005bb <+79>:    call   0x4003d8 <printf@plt>
   0x00000000004005c0 <+84>:    cmp    DWORD PTR [rbp-0x214],0x1
   0x00000000004005c7 <+91>:    jle    0x4005e9 <vulnerable+125>
   0x00000000004005c9 <+93>:    mov    rax,QWORD PTR [rbp-0x220]
   0x00000000004005d0 <+100>:   add    rax,0x8
   0x00000000004005d4 <+104>:   mov    rdx,QWORD PTR [rax]
   0x00000000004005d7 <+107>:   lea    rax,[rbp-0x210]
   0x00000000004005de <+114>:   mov    rsi,rdx
   0x00000000004005e1 <+117>:   mov    rdi,rax
   0x00000000004005e4 <+120>:   call   0x4003f8 <strcpy@plt>
   0x00000000004005e9 <+125>:   mov    eax,0x0
   0x00000000004005ee <+130>:   leave  
   0x00000000004005ef <+131>:   ret    

strcpy() の呼び出しの直前に中断しますが、バッファーが 0x42 でいっぱいになった後です。

(gdb) break *0x00000000004005e1

プログラムは引数として 650 個の 0x41 を使用して実行されます。これは、スタック上の戻りアドレスを上書きするのに十分なはずです。

(gdb) run `perl -e 'print "A"x650'`

戻りアドレス 0x00400610 のメモリを検索します (これは main の逆アセンブリを見て見つけたものです)。

(gdb) find $rsp, +1024, 0x00400610
0x7fffffffda98
1 pattern found.

x/200x でメモリを調べると、そのサイズのためにここでは省略した概要が得られますが、バッファの正当なサイズを示す 0x42 と戻りアドレスがはっきりとわかります。

0x7fffffffda90: 0xffffdab0      0x00007fff      0x00400610      0x00000000

strcpy() の直後の新しいブレークポイント:

(gdb) break *0x00000000004005e9
(gdb) set disassemble-next-line on
(gdb) si
19 }
=> 0x00000000004005ee <vulnerable+130>:  c9     leave  
   0x00000000004005ef <vulnerable+131>:  c3     ret    
(gdb) i r
rax            0x0      0
rbx            0x0      0
rcx            0x4141414141414141       4702111234474983745
rdx            0x414141 4276545
rsi            0x7fffffffe17a   140737488347514
rdi            0x7fffffffdb00   140737488345856
rbp            0x7fffffffda90   0x7fffffffda90
rsp            0x7fffffffd870   0x7fffffffd870
r8             0x1      1
r9             0x270    624
r10            0x6      6
r11            0x7ffff7b9fff0   140737349550064
r12            0x400410 4195344
r13            0x7fffffffdb90   140737488346000
r14            0x0      0
r15            0x0      0
rip            0x4005ee 0x4005ee <vulnerable+130>

   0x00000000004005ee <vulnerable+130>:  c9     leave  
=> 0x00000000004005ef <vulnerable+131>:  c3     ret    
(gdb) i r
rax            0x0      0
rbx            0x0      0
rcx            0x4141414141414141       4702111234474983745
rdx            0x414141 4276545
rsi            0x7fffffffe17a   140737488347514
rdi            0x7fffffffdb00   140737488345856
rbp            0x4141414141414141       0x4141414141414141
rsp            0x7fffffffda98   0x7fffffffda98
r8             0x1      1
r9             0x270    624
r10            0x6      6
r11            0x7ffff7b9fff0   140737349550064
r12            0x400410 4195344
r13            0x7fffffffdb90   140737488346000
r14            0x0      0
r15            0x0      0
rip            0x4005ef 0x4005ef <vulnerable+131>
(gdb) si

Program received signal SIGSEGV, Segmentation fault.
   0x00000000004005ee <vulnerable+130>:  c9     leave  
=> 0x00000000004005ef <vulnerable+131>:  c3     ret    
(gdb) i r
rax            0x0      0
rbx            0x0      0
rcx            0x4141414141414141       4702111234474983745
rdx            0x414141 4276545
rsi            0x7fffffffe17a   140737488347514
rdi            0x7fffffffdb00   140737488345856
rbp            0x4141414141414141       0x4141414141414141
rsp            0x7fffffffda98   0x7fffffffda98
r8             0x1      1
r9             0x270    624
r10            0x6      6
r11            0x7ffff7b9fff0   140737349550064
r12            0x400410 4195344
r13            0x7fffffffdb90   140737488346000
r14            0x0      0
r15            0x0      0
rip            0x4005ef 0x4005ef <vulnerable+131>

リターン アドレスが上書きされていることを確認しました。RIP が次のアドレスに設定されることを期待する必要がありました。

(gdb) x/4x 0x7fffffffda90
0x7fffffffda90: 0x41414141      0x41414141      0x41414141      0x41414141
(gdb) x/4x $rsp          
0x7fffffffda98: 0x41414141      0x41414141      0x41414141      0x41414141

それでもRIPは明らかに:

rip            0x4005ef 0x4005ef <vulnerable+131>

Why has not RIP gotten updated as I'm expecting? What does LEAVEQ and RETQ really do on 64-bit? In short, what am I missing here? I have tried to omit the compiler arguments when compiling just to see if it makes any difference, it doesn't seem to make any difference.

4

3 に答える 3

6

これらの 2 つの命令は、期待どおりの動作をしています。前のスタック フレームを で上書きしたので、 を押すと、0x41次のようになりますleaveq

mov rsp, rbp
pop rpb

今は前に行っrspた場所を指しrbpます。ただし、メモリのその領域を上書きしたため、 を実行するpop rbpと、ハードウェアは基本的にこれを実行します。

mov rbp, [rsp]
add rsp,1

しかし、[rsp]今は があり0x41ます。これがrbp、その値で満たされるのを見ている理由です。

rip期待どおりに設定されない理由については、 toをret設定してから、命令フェッチで例外 (ページ フォールト) を生成しているためです。この場合、正しいものを表示するために GDB に依存しません。プログラムのテキスト セグメント内の有効なアドレスで戻り値を上書きしてみてください。おそらく、この奇妙な動作は見られないでしょう。rip0x41

于 2012-07-24T22:08:41.640 に答える
2

x32 で EIP 0x41414141 のクラッシュが発生する理由は、プログラムが以前に保存された EIP 値をスタックからポップして EIP に戻すときに、CPU がメモリ アドレス 0x41414141 で命令を実行しようとするため、セグメンテーション違反が発生するためです。(もちろん、実行前にページを取得する必要があります)

ここで、x64 の実行中に、プログラムが以前に保存された RIP 値を RIP レジスタに戻すときに、カーネルはメモリ アドレス 0x4141414141414141 で命令を実行しようとします。第 1 に、正規形式のアドレス指定により、任意の仮想アドレスのビット 48 から 63 はビット 47 のコピーでなければなりません (符号拡張に似た方法で)。さもないと、プロセッサは例外を発生させます。それが問題ではない場合、最大ユーザー空間アドレスが 0x00007FFFFFFFFFF であるため、カーネルはページ フォールト ハンドラーを呼び出す前に追加のチェックを行います。

要約すると、x32 アーキテクチャでは、アドレスは「検証」なしでページ フォールト ハンドラーに渡されます。ページ フォールト ハンドラーは、カーネルがプログラム segfault を送信するようにトリガーするページをロードしようとしますが、x64 はここまで到達しません。

それをテストし、RIP を 0x0000414141414141 で上書きすると、カーネル パスによる事前チェックが行われ、ページ フォールト ハンドラが x32 の場合のように呼び出されるため、期待値が RIP に配置されることがわかります (もちろん、プログラムはクラッシュ)。

于 2013-05-15T04:36:46.223 に答える