@@ -151,7 +151,7 @@ static HRESULT Hook_ITSPropertySet_GetStringProperty(ITSPropertySet* This, const
151151
152152 if (SUCCEEDED (hr)) {
153153 char * propValueA = _com_util::ConvertBSTRToString ((BSTR)*propValue);
154- MsRdpEx_LogPrint (TRACE, " ITSPropertySet::GetStringProperty(%s, \" %s\" )" , propName, propValueA);
154+ MsRdpEx_LogPrint (TRACE, " ITSPropertySet::GetStringProperty(%s, \" %s\" )" , propName, propValueA ? propValueA : " " );
155155 delete[] propValueA;
156156 }
157157 else {
@@ -233,6 +233,175 @@ static bool TSPropertySet_Hook(ITSPropertySet* pTSPropertySet, ITSPropertySetVtb
233233 return true ;
234234}
235235
236+ static bool IsLikelyVoidMethodNoArgs (void * fn)
237+ {
238+ if (!fn)
239+ return false ;
240+
241+ MEMORY_BASIC_INFORMATION mbi = { 0 };
242+ if (!VirtualQuery (fn, &mbi, sizeof (mbi)))
243+ return false ;
244+
245+ DWORD prot = mbi.Protect ;
246+
247+ bool isExecutable =
248+ (prot & PAGE_EXECUTE) ||
249+ (prot & PAGE_EXECUTE_READ) ||
250+ (prot & PAGE_EXECUTE_READWRITE) ||
251+ (prot & PAGE_EXECUTE_WRITECOPY);
252+
253+ if (!isExecutable || mbi.State != MEM_COMMIT || (prot & PAGE_GUARD))
254+ return false ;
255+
256+ #if defined(_M_AMD64) || defined(__x86_64__)
257+ uint8_t * code = (uint8_t *)fn;
258+
259+ size_t available = mbi.RegionSize - ((uintptr_t )fn - (uintptr_t )mbi.BaseAddress );
260+ if (available < 8 )
261+ return false ;
262+
263+ // Pattern 1: sub rsp, 0x28
264+ if (code[0 ] == 0x48 && code[1 ] == 0x83 && code[2 ] == 0xEC )
265+ return true ;
266+
267+ // Pattern 2: push rbp; mov rbp, rsp
268+ if (code[0 ] == 0x55 && code[1 ] == 0x48 && code[2 ] == 0x89 && code[3 ] == 0xE5 )
269+ return true ;
270+
271+ // Pattern 3: add rcx, 0x50; jmp rel32 lock method stubs
272+ if (code[0 ] == 0x48 && code[1 ] == 0x83 && code[2 ] == 0xC1 && code[3 ] == 0x50 &&
273+ code[4 ] == 0xE9 )
274+ return true ;
275+
276+ #elif defined(_M_IX86) || defined(__i386__)
277+ uint8_t * code = (uint8_t *)fn;
278+
279+ if ((mbi.RegionSize - ((uintptr_t )fn - (uintptr_t )mbi.BaseAddress )) < 3 )
280+ return false ;
281+
282+ // Typical prologue: push ebp; mov ebp, esp
283+ if (code[0 ] == 0x55 && code[1 ] == 0x8B && code[2 ] == 0xEC )
284+ return true ;
285+
286+ // Alternate: sub esp, imm8
287+ if (code[0 ] == 0x83 && code[1 ] == 0xEC )
288+ return true ;
289+
290+ #elif defined(_M_ARM64) || defined(__aarch64__)
291+ uint32_t * ins = (uint32_t *)fn;
292+
293+ size_t available = mbi.RegionSize - ((uintptr_t )fn - (uintptr_t )mbi.BaseAddress );
294+ if (available < 8 )
295+ return false ;
296+
297+ uint32_t instr0 = ins[0 ];
298+ uint32_t instr1 = ins[1 ];
299+
300+ // Pattern 1: stp x29, x30, [sp, #-16]! ; mov x29, sp
301+ if (instr0 == 0xA9BF7BF0 && instr1 == 0x910003FD )
302+ return true ;
303+
304+ // Pattern 2: ret
305+ if (instr0 == 0xD65F03C0 )
306+ return true ;
307+
308+ // Pattern 3: add x0, x0, #imm ; b target
309+ if ((instr0 & 0xFFC003FF ) == 0x91000000 && // ADD x0, x0, #imm
310+ (instr1 & 0xFC000000 ) == 0x14000000 ) // B <imm>
311+ return true ;
312+ #endif
313+
314+ return false ;
315+ }
316+
317+ static int TSPropertySet_FindLockFunctionsInVtbl (void ** vtbl)
318+ {
319+ const int blockSize = 4 ;
320+ const int maxEntries = 30 ;
321+
322+ for (int i = 0 ; i <= maxEntries - blockSize; i++)
323+ {
324+ bool allMatch = true ;
325+
326+ for (int j = 0 ; j < blockSize; j++) {
327+ void * fn = vtbl[i + j];
328+
329+ if (!IsLikelyVoidMethodNoArgs (fn)) {
330+ allMatch = false ;
331+ break ;
332+ }
333+ }
334+
335+ if (allMatch)
336+ return i; // found the start of a 4-function block
337+ }
338+
339+ return -1 ; // not found
340+ }
341+
342+ static int TSPropertySet_DetectVtblVersion (void ** vtbl)
343+ {
344+ int vtblVersion = -1 ;
345+ DWORD ctlVersion = 0 ;
346+
347+ if (MsRdpEx_IsAddressInRdclientAxModule (vtbl))
348+ {
349+ ctlVersion = g_rdclientax.tscCtlVer ;
350+
351+ if (ctlVersion >= 5326 ) {
352+ vtblVersion = 32 ;
353+ }
354+ else {
355+ vtblVersion = 30 ;
356+ }
357+ }
358+ else
359+ {
360+ ctlVersion = g_mstscax.tscCtlVer ;
361+
362+ if (ctlVersion >= 27842 ) {
363+ // First seen in Windows 11 Insider Preview Build 27842
364+ vtblVersion = 32 ;
365+ }
366+ else {
367+ vtblVersion = 30 ;
368+ }
369+ }
370+
371+ /* *
372+ * The vtable contains a block of 4 functions that take void and return void.
373+ * Detect common assembly pattern matching that simple function prototype,
374+ * and leverage this to find the offset where those functions begin:
375+ *
376+ * CTSPropertySet::EnterReadLock(void)
377+ * CTSPropertySet::LeaveReadLock(void)
378+ * CTSPropertySet::EnterWriteLock(void)
379+ * CTSPropertySet::LeaveWriteLock(void)
380+ *
381+ * Once we know the offset, use it to detect which vtable we're dealing with
382+ */
383+
384+ int lockFunctionsOffset = TSPropertySet_FindLockFunctionsInVtbl (vtbl);
385+
386+ if (lockFunctionsOffset > 0 ) {
387+ MsRdpEx_LogPrint (DEBUG, " Found TSPropertySet lock functions at vtable offset %d" , lockFunctionsOffset);
388+
389+ if (lockFunctionsOffset == 20 ) {
390+ vtblVersion = 32 ;
391+ }
392+ else if (lockFunctionsOffset == 18 ) {
393+ vtblVersion = 30 ;
394+ }
395+ else {
396+ MsRdpEx_LogPrint (WARN, " Unknown TSPropertySet lock functions vtable offset: %d" , lockFunctionsOffset);
397+ }
398+ }
399+
400+ MsRdpEx_LogPrint (DEBUG, " TSPropertySet ctlVersion: %d vtblVersion: %d" , ctlVersion, vtblVersion);
401+
402+ return vtblVersion;
403+ }
404+
236405class CMsRdpPropertySet : public IMsRdpExtendedSettings
237406{
238407public:
@@ -244,20 +413,19 @@ class CMsRdpPropertySet : public IMsRdpExtendedSettings
244413
245414 if (m_pTSPropertySet)
246415 {
247- if (MsRdpEx_IsAddressInRdclientAxModule (m_pTSPropertySet->vtbl ))
248- {
249- DWORD version = g_rdclientax.tscCtlVer ;
250-
251- if (version >= 5326 ) {
252- m_vtbl32 = (ITSPropertySetVtbl32*)m_pTSPropertySet->vtbl ;
253- } else {
254- m_vtbl30 = (ITSPropertySetVtbl30*)m_pTSPropertySet->vtbl ;
255- }
416+ int vtblVersion = TSPropertySet_DetectVtblVersion (((void **)m_pTSPropertySet->vtbl ));
417+
418+ if (vtblVersion == 32 ) {
419+ m_vtbl32 = (ITSPropertySetVtbl32*)m_pTSPropertySet->vtbl ;
420+ m_vtbl30 = NULL ;
256421 }
257- else
258- {
422+ else if (vtblVersion == 30 ) {
423+ m_vtbl32 = NULL ;
259424 m_vtbl30 = (ITSPropertySetVtbl30*)m_pTSPropertySet->vtbl ;
260425 }
426+ else {
427+ MsRdpEx_LogPrint (ERROR, " Unknown TSPropertySet vtable version: %d" , vtblVersion);
428+ }
261429
262430 if (!g_TSPropertySet_Hooked) {
263431 TSPropertySet_Hook (m_pTSPropertySet, m_vtbl30, m_vtbl32);
@@ -423,7 +591,6 @@ class CMsRdpPropertySet : public IMsRdpExtendedSettings
423591
424592 HRESULT __stdcall GetBStrProperty (const char * propName, BSTR* propValue) {
425593 HRESULT hr = E_FAIL;
426- BSTR bstrVal = NULL ;
427594 WCHAR* wstrVal = NULL ;
428595
429596 if (m_vtbl32) {
@@ -916,8 +1083,6 @@ HRESULT __stdcall CMsRdpExtendedSettings::SetRecordingPipeName(const char* recor
9161083
9171084HRESULT CMsRdpExtendedSettings::AttachRdpClient (IMsTscAx* pMsTscAx)
9181085{
919- HRESULT hr;
920-
9211086 m_pMsTscAx = pMsTscAx;
9221087
9231088 ITSObjectBase* pTSWin32CoreApi = NULL ;
0 commit comments