@@ -151,7 +151,7 @@ static HRESULT Hook_ITSPropertySet_GetStringProperty(ITSPropertySet* This, const
151
151
152
152
if (SUCCEEDED (hr)) {
153
153
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 : " " );
155
155
delete[] propValueA;
156
156
}
157
157
else {
@@ -233,6 +233,175 @@ static bool TSPropertySet_Hook(ITSPropertySet* pTSPropertySet, ITSPropertySetVtb
233
233
return true ;
234
234
}
235
235
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
+
236
405
class CMsRdpPropertySet : public IMsRdpExtendedSettings
237
406
{
238
407
public:
@@ -244,20 +413,19 @@ class CMsRdpPropertySet : public IMsRdpExtendedSettings
244
413
245
414
if (m_pTSPropertySet)
246
415
{
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 ;
256
421
}
257
- else
258
- {
422
+ else if (vtblVersion == 30 ) {
423
+ m_vtbl32 = NULL ;
259
424
m_vtbl30 = (ITSPropertySetVtbl30*)m_pTSPropertySet->vtbl ;
260
425
}
426
+ else {
427
+ MsRdpEx_LogPrint (ERROR, " Unknown TSPropertySet vtable version: %d" , vtblVersion);
428
+ }
261
429
262
430
if (!g_TSPropertySet_Hooked) {
263
431
TSPropertySet_Hook (m_pTSPropertySet, m_vtbl30, m_vtbl32);
@@ -423,7 +591,6 @@ class CMsRdpPropertySet : public IMsRdpExtendedSettings
423
591
424
592
HRESULT __stdcall GetBStrProperty (const char * propName, BSTR* propValue) {
425
593
HRESULT hr = E_FAIL;
426
- BSTR bstrVal = NULL ;
427
594
WCHAR* wstrVal = NULL ;
428
595
429
596
if (m_vtbl32) {
@@ -916,8 +1083,6 @@ HRESULT __stdcall CMsRdpExtendedSettings::SetRecordingPipeName(const char* recor
916
1083
917
1084
HRESULT CMsRdpExtendedSettings::AttachRdpClient (IMsTscAx* pMsTscAx)
918
1085
{
919
- HRESULT hr;
920
-
921
1086
m_pMsTscAx = pMsTscAx;
922
1087
923
1088
ITSObjectBase* pTSWin32CoreApi = NULL ;
0 commit comments