diff --git a/watermark WIN10/.gitignore b/watermark WIN10/.gitignore new file mode 100644 index 0000000..259148f --- /dev/null +++ b/watermark WIN10/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/watermark WIN10/README.md b/watermark WIN10/README.md new file mode 100644 index 0000000..32ff86c --- /dev/null +++ b/watermark WIN10/README.md @@ -0,0 +1,50 @@ +

+ +## watermark_disabler +Disabling "Activate Windows" watermark made simple, the code in this repository is a PoC, and has not been tested above Windows 10 1803. + +## how does this work? +The function responsible for drawing whole desktop including the watermark is xxxDesktopPaintCallback located in win32kfull.sys. +Both of the approaches used by this project were found while analyzing functions further down in the callstack. + +### approach #1 +As you can see from the snippets below, forcing gpsi->unk874h to be zero the checks will fail and the watermark won't be drawn. +```cpp +// global tagSERVERINFO* gpsi; +// global _THREADINFO* gptiCurrent; +if ( gpsi->unk874h != 0 ) +{ + /* gptiCurrent + 0x1c0 = tagDESKTOP** */ + const auto desktop = gptiCurrent->desktops[1]; /* type: tagDESKTOP**, this is checked if it's grpdeskLogon, which is a global pointer to the lock screen */ + + HWND desktop_window = nullptr; + + /* tagDESKTOP + 0xa8 = tagWnd* */ + if ( desktop ) + desktop_window = desktop->wnd; /* type: tagWnd*, I believe this is a pointer to the lock window? */ + + should_draw_watermark = ( desktop_window == nullptr ); +} + +if ( should_draw_watermark ) + PaintWatermark(device_context, &desktop_rect); +``` + +### approach #2 +PaintWatermark calls GreExtTextOutWInternal (which is the internal function for ExtTextOutW/NtGdiExtTextOutW in wingdi.h). + +The argument passed for size (c) is a global called "gSafeModeStrLen", by setting the size (c) to 0, the string won't be rendered. The pattern for the aforementioned global inside win32kfull is 44 8B C8 44 89 0D + 7 + + +

+ Follow Me On +

+

+ + + + + + +

+

Buy Me A Coffee

diff --git a/watermark WIN10/watermark_disabler.sln b/watermark WIN10/watermark_disabler.sln new file mode 100644 index 0000000..8504e2e --- /dev/null +++ b/watermark WIN10/watermark_disabler.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "watermark_disabler", "watermark_disabler\watermark_disabler.vcxproj", "{99ADE163-A416-41D2-991E-99E598476DB8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|ARM.ActiveCfg = Debug|ARM + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|ARM.Build.0 = Debug|ARM + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|ARM.Deploy.0 = Debug|ARM + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|ARM64.Build.0 = Debug|ARM64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|x64.ActiveCfg = Debug|x64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|x64.Build.0 = Debug|x64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|x64.Deploy.0 = Debug|x64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|x86.ActiveCfg = Debug|Win32 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|x86.Build.0 = Debug|Win32 + {99ADE163-A416-41D2-991E-99E598476DB8}.Debug|x86.Deploy.0 = Debug|Win32 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|ARM.ActiveCfg = Release|ARM + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|ARM.Build.0 = Release|ARM + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|ARM.Deploy.0 = Release|ARM + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|ARM64.ActiveCfg = Release|ARM64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|ARM64.Build.0 = Release|ARM64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|ARM64.Deploy.0 = Release|ARM64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|x64.ActiveCfg = Release|x64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|x64.Build.0 = Release|x64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|x64.Deploy.0 = Release|x64 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|x86.ActiveCfg = Release|Win32 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|x86.Build.0 = Release|Win32 + {99ADE163-A416-41D2-991E-99E598476DB8}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F6ECE478-6ED3-4B08-8434-8629DF60A65D} + EndGlobalSection +EndGlobal diff --git a/watermark WIN10/watermark_disabler/main.cpp b/watermark WIN10/watermark_disabler/main.cpp new file mode 100644 index 0000000..68d28eb --- /dev/null +++ b/watermark WIN10/watermark_disabler/main.cpp @@ -0,0 +1,68 @@ +#include +#include "mem_scan.hpp" +#include "mem_iter.hpp" +#include "mem_util.hpp" + + +template +__forceinline void output_to_console( const char* str, Args&&... args ) +{ + DbgPrintEx( 77, 0, str, std::forward( args )... ); +} + +__forceinline void output_appended( const char* str ) +{ + output_to_console( "[!] watermark_disabler: %s\n", str ); +} + +NTSTATUS driver_entry( ) +{ + output_appended( "loaded" ); + + /* we have to attach to csrss, or any process with win32k mapped into it, because win32k is not mapped in system modules */ + const auto csrss_process = impl::search_for_process( "csrss.exe" ); + + if ( !csrss_process ) + { + output_appended( "failed to find csrss.exe" ); + return STATUS_UNSUCCESSFUL; + } + + impl::unique_attachment csrss_attach( csrss_process ); + + output_appended( "attached to csrss" ); + + const auto win32kfull_info = impl::search_for_module( "win32kfull.sys" ); + + if ( !win32kfull_info ) + { + output_appended( "failed to find the win32kfull.sys module" ); + return STATUS_UNSUCCESSFUL; + } + + output_to_console( "[!] watermark_disabler: win32kfull.sys $ 0x%p\n", win32kfull_info->image_base ); + + const auto gpsi_instruction = impl::search_for_signature( win32kfull_info, "\x48\x8b\x0d\x00\x00\x00\x00\x48\x8b\x05\x00\x00\x00\x00\x0f\xba\x30\x0c", "xxx????xxx????xxxx" ); + + if ( !gpsi_instruction ) + { + output_appended( "failed to find gpsi, signature outdated?" ); + return STATUS_UNSUCCESSFUL; + } + + const auto gpsi = *reinterpret_cast< std::uint64_t* >( impl::resolve_mov( gpsi_instruction ) ); + + if ( !gpsi ) + { + output_appended( "gpsi is somehow nullptr" ); + return STATUS_UNSUCCESSFUL; + } + + output_to_console( "[!] watermark_disabler: gpsi $ 0x%p\n", gpsi ); + + *reinterpret_cast< std::uint32_t* >( gpsi + 0x874 ) = 0; + + output_appended( "watermark disabled" ); + + return STATUS_UNSUCCESSFUL; +} diff --git a/watermark WIN10/watermark_disabler/mem_defs.hpp b/watermark WIN10/watermark_disabler/mem_defs.hpp new file mode 100644 index 0000000..a49adbc --- /dev/null +++ b/watermark WIN10/watermark_disabler/mem_defs.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +namespace nt +{ + struct rtl_module_info + { + char pad_0[ 0x10 ]; + PVOID image_base; + ULONG image_size; + char pad_1[ 0xa ]; + USHORT file_name_offset; + UCHAR full_path[ _MAX_PATH - 4 ]; + }; + + struct rtl_modules + { + ULONG count; + rtl_module_info modules[ 1 ]; + }; + + struct image_file_header + { + USHORT machine; + USHORT number_of_sections; + }; + + struct image_section_header + { + std::uint8_t name[ 8 ]; + + union + { + std::uint32_t physical_address; + std::uint32_t virtual_size; + } misc; + + std::uint32_t virtual_address; + std::uint32_t size_of_raw_data; + std::uint32_t pointer_to_raw_data; + std::uint32_t pointer_to_relocations; + std::uint32_t pointer_to_line_numbers; + std::uint16_t number_of_relocations; + std::uint16_t number_of_line_numbers; + std::uint32_t characteristics; + }; + + struct image_nt_headers + { + std::uint32_t signature; + image_file_header file_header; + }; +} \ No newline at end of file diff --git a/watermark WIN10/watermark_disabler/mem_iter.hpp b/watermark WIN10/watermark_disabler/mem_iter.hpp new file mode 100644 index 0000000..4244a24 --- /dev/null +++ b/watermark WIN10/watermark_disabler/mem_iter.hpp @@ -0,0 +1,79 @@ +#pragma once +#include "mem_defs.hpp" +#include "util_raii.hpp" + +namespace impl +{ + extern "C" NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation( ULONG, PVOID, ULONG, PULONG ); + extern "C" NTSYSAPI PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader( PVOID ); + + nt::rtl_module_info* search_for_module( const char* module_name ) + { + /* allocate a pool with 0x2000 bytes because we don't know how big the module list is */ + auto needed_bytes = 8192ul; + impl::pool buffer_pool( ExAllocatePoolWithTag( PagedPool, needed_bytes, 'udoM' ) ); + + if ( !buffer_pool.get( ) ) + return nullptr; + + auto current_status = ZwQuerySystemInformation( 11, buffer_pool.get( ), needed_bytes, &needed_bytes ); + + /* keep allocating until the function returns STATUS_SUCCESS */ + while ( current_status == STATUS_INFO_LENGTH_MISMATCH ) + { + buffer_pool.reset( ExAllocatePoolWithTag( PagedPool, needed_bytes, 'udoM' ) ); + + if ( !buffer_pool ) + return nullptr; + + current_status = ZwQuerySystemInformation( 11, buffer_pool.get( ), needed_bytes, &needed_bytes ); + } + + if ( !NT_SUCCESS( current_status ) ) + return nullptr; + + const auto current_modules = static_cast< nt::rtl_modules* >( buffer_pool.get( ) ); + + if ( !current_modules ) + return nullptr; + + /* loop the module list, and find the needed module */ + for ( auto i = 0u; i < current_modules->count; i++ ) + { + const auto current_module = ¤t_modules->modules[ i ]; + + if ( !current_module ) + continue; + + /* file_name_offset is the offset from full_path to the actual file's name, instead of file path */ + const auto file_name = reinterpret_cast< const char* >( current_module->file_name_offset + current_module->full_path ); + + if ( std::strcmp( file_name, module_name ) != 0 ) + continue; + + return current_module; + } + + return nullptr; + } + + nt::image_section_header* search_for_section( const nt::rtl_module_info* module, const char* section_name ) + { + if ( !module ) + return nullptr; + + const auto nt_header = reinterpret_cast< nt::image_nt_headers* >( RtlImageNtHeader( module->image_base ) ); + const auto section_header = reinterpret_cast< nt::image_section_header* >( nt_header + 1 ); + + for ( auto i = 0u; i < nt_header->file_header.number_of_sections; i++ ) + { + const auto current_section = §ion_header[ i ]; + const auto current_section_name = reinterpret_cast< const char* >( current_section->name ); + + if ( !std::strcmp( current_section_name, section_name ) ) + return current_section; + } + + return nullptr; + } +} \ No newline at end of file diff --git a/watermark WIN10/watermark_disabler/mem_scan.hpp b/watermark WIN10/watermark_disabler/mem_scan.hpp new file mode 100644 index 0000000..940832a --- /dev/null +++ b/watermark WIN10/watermark_disabler/mem_scan.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "mem_util.hpp" +#include "mem_iter.hpp" +#include "util_raii.hpp" + +namespace impl +{ + inline bool search_for_signature_helper( const std::uint8_t* data, const std::uint8_t* signature, const char* mask ) + { + // check if page is correct & readable (this internally checks PTE, PDE, ...) + if ( !MmIsAddressValid( const_cast< std::uint8_t* >( data ) ) ) + return false; + + // iterate through validity of the mask (mask & signature are equal + for ( ; *mask; ++mask, ++data, ++signature ) + if ( *mask == 'x' && *data != *signature ) // if mask is 'x' (a match), and the current byte is not equal to the byte in the signature, then return false. + return false; + + return true; + } + + std::uint8_t* search_for_signature( const nt::rtl_module_info* module, const char* signature, const char* signature_mask ) + { + if ( !module ) + return nullptr; + + const auto module_start = reinterpret_cast< std::uint8_t* >( module->image_base ); + const auto module_size = module_start + module->image_size; + + /* iterate the entire module */ + for ( auto segment = module_start; segment < module_size; segment++ ) + { + if ( search_for_signature_helper( segment, reinterpret_cast< std::uint8_t* >( const_cast< char* >( signature ) ), signature_mask ) ) + return segment; + } + + return nullptr; + } + + extern "C" NTSYSAPI PCHAR NTAPI PsGetProcessImageFileName( PEPROCESS ); + + PEPROCESS search_for_process( const char* process_name ) + { + const auto kernel_module_info = search_for_module( "ntoskrnl.exe" ); + + if ( !kernel_module_info ) + return nullptr; + + /* we are scanning for a conditional jump, that jumps to a call to the unexported function that we want, so we follow the jump, then follow the call to get to the function. */ + const auto conditional_instruction = search_for_signature( kernel_module_info, "\x79\xdc\xe9", "xxx" ); + + if ( !conditional_instruction ) + return nullptr; + + const auto call_instruction = follow_conditional_jump( conditional_instruction ); + + if ( !call_instruction ) + return nullptr; + + const auto PsGetNextProcess = follow_call< PEPROCESS( __stdcall* )( PEPROCESS ) >( call_instruction ); + + if ( !PsGetNextProcess ) + return nullptr; + + PEPROCESS previous_process = PsGetNextProcess( nullptr ); + + while ( previous_process ) + { + if ( !std::strcmp( PsGetProcessImageFileName( previous_process ), process_name ) ) + return previous_process; + + previous_process = PsGetNextProcess( previous_process ); + } + + return nullptr; + } +} diff --git a/watermark WIN10/watermark_disabler/mem_util.hpp b/watermark WIN10/watermark_disabler/mem_util.hpp new file mode 100644 index 0000000..8320458 --- /dev/null +++ b/watermark WIN10/watermark_disabler/mem_util.hpp @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include "mem_defs.hpp" + +namespace impl +{ + class unique_attachment + { + private: + KAPC_STATE apc_state{}; + PEPROCESS process{}; + public: + explicit unique_attachment( PEPROCESS process ) + { + if ( !process ) + return; + + KeStackAttachProcess( process, &apc_state ); + } + + ~unique_attachment( ) + { + KeUnstackDetachProcess( &apc_state ); + ObfDereferenceObject( process ); + } + }; + + bool write_to_read_only( void* destination, void* source, std::size_t size ) + { + const std::unique_ptr mdl( IoAllocateMdl( destination, static_cast< ULONG >( size ), FALSE, FALSE, nullptr ), &IoFreeMdl ); + + if ( !mdl ) + return false; + + MmProbeAndLockPages( mdl.get( ), KernelMode, IoReadAccess ); + + const auto mapped_page = MmMapLockedPagesSpecifyCache( mdl.get( ), KernelMode, MmNonCached, nullptr, FALSE, NormalPagePriority ); + + if ( !mapped_page ) + return false; + + if ( !NT_SUCCESS( MmProtectMdlSystemAddress( mdl.get( ), PAGE_EXECUTE_READWRITE ) ) ) + return false; + + std::memcpy( mapped_page, source, size ); + + MmUnmapLockedPages( mapped_page, mdl.get( ) ); + MmUnlockPages( mdl.get( ) ); + + return true; + } + + template + __forceinline T follow_call( std::uint8_t* address ) + { + /* + 1 is the address of the calle, + 5 is the size of a call instruction */ + return ( T )( address + *reinterpret_cast< std::int32_t* >( address + 1 ) + 5 ); + } + + template + __forceinline T follow_conditional_jump( std::uint8_t* address ) + { + /* + 1 is the offset of the jump, + 2 is the size of a conditional jump */ + return ( T )( address + *reinterpret_cast< std::int8_t* >( address + 1 ) + 2 ); + } + + template + __forceinline T resolve_mov( std::uint8_t* address ) + { + /* + 3 is the address of the source, + 7 is the size of a mov instruction */ + return ( T )( address + *reinterpret_cast( address + 3 ) + 7 ); + } +} \ No newline at end of file diff --git a/watermark WIN10/watermark_disabler/util_raii.hpp b/watermark WIN10/watermark_disabler/util_raii.hpp new file mode 100644 index 0000000..6873322 --- /dev/null +++ b/watermark WIN10/watermark_disabler/util_raii.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include + +namespace impl +{ + struct unique_pool + { + void operator( )( void* pool ) + { + if ( pool ) + ExFreePoolWithTag( pool, 0 ); + } + }; + + using pool = std::unique_ptr; + + struct unique_object + { + void operator( )( void* object ) + { + if ( object ) + ObfDereferenceObject( object ); + } + }; + + template + using object = std::unique_ptr, unique_object>; +} diff --git a/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj b/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj new file mode 100644 index 0000000..5a89436 --- /dev/null +++ b/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj @@ -0,0 +1,178 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {99ADE163-A416-41D2-991E-99E598476DB8} + {1bc93793-694f-48fe-9372-81e2b05556fd} + + + 12.0 + Debug + Win32 + watermark_disabler + 10.0.18362.0 + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(IncludePath) + false + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + stdcpplatest + MaxSpeed + AnySuitable + Speed + false + true + false + false + + + driver_entry + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj.filters b/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj.filters new file mode 100644 index 0000000..55069ea --- /dev/null +++ b/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + source + + + + + header + + + header + + + header + + + header + + + header + + + \ No newline at end of file diff --git a/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj.user b/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj.user new file mode 100644 index 0000000..b2a2bcc --- /dev/null +++ b/watermark WIN10/watermark_disabler/watermark_disabler.vcxproj.user @@ -0,0 +1,6 @@ + + + + Off + + \ No newline at end of file