1 /*
2  * PROJECT:     shimdbg
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Test tool for SHIM engine caching
5  * COPYRIGHT:   Copyright 2016-2017 Mark Jansen (mark.jansen@reactos.org)
6  */
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <ctype.h>
11 #include <ntndk.h>
12 
13 NTSYSAPI ULONG NTAPI vDbgPrintEx(_In_ ULONG ComponentId, _In_ ULONG Level, _In_z_ PCCH Format, _In_ va_list ap);
14 #define DPFLTR_ERROR_LEVEL 0
15 
xprintf(const char * fmt,...)16 void xprintf(const char *fmt, ...)
17 {
18     va_list ap;
19 
20     va_start(ap, fmt);
21     vprintf(fmt, ap);
22     vDbgPrintEx(-1, DPFLTR_ERROR_LEVEL, fmt, ap);
23     va_end(ap);
24 }
25 
26 
CallApphelp(APPHELPCACHESERVICECLASS Service,PAPPHELP_CACHE_SERVICE_LOOKUP CacheEntry)27 void CallApphelp(APPHELPCACHESERVICECLASS Service,
28                 PAPPHELP_CACHE_SERVICE_LOOKUP CacheEntry)
29 {
30     NTSTATUS Status = NtApphelpCacheControl(Service, CacheEntry);
31     xprintf("NtApphelpCacheControl returned 0x%x\n", (unsigned int)Status);
32 }
33 
MapFile(char * filename,UNICODE_STRING * PathName,int MapIt)34 HANDLE MapFile(char* filename, UNICODE_STRING* PathName, int MapIt)
35 {
36     OBJECT_ATTRIBUTES LocalObjectAttributes;
37     IO_STATUS_BLOCK IoStatusBlock;
38     NTSTATUS Status;
39     HANDLE FileHandle = NULL;
40     RtlCreateUnicodeStringFromAsciiz(PathName, filename);
41     if (MapIt)
42     {
43         InitializeObjectAttributes(&LocalObjectAttributes, PathName,
44             OBJ_CASE_INSENSITIVE, NULL, NULL);
45         Status = NtOpenFile(&FileHandle,
46                     SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_DATA | FILE_EXECUTE,
47                     &LocalObjectAttributes, &IoStatusBlock,
48                     FILE_SHARE_READ | FILE_SHARE_DELETE,
49                     FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
50         if (!NT_SUCCESS(Status))
51         {
52             xprintf("Failed opening the file, using a NULL handle\n");
53             FileHandle = NULL;
54         }
55     }
56     return FileHandle;
57 }
58 
CallApphelpWithImage(char * filename,int MapIt,APPHELPCACHESERVICECLASS Service,char * ServiceName)59 void CallApphelpWithImage(char* filename, int MapIt,
60                         APPHELPCACHESERVICECLASS Service, char* ServiceName)
61 {
62     UNICODE_STRING PathName = {0};
63     APPHELP_CACHE_SERVICE_LOOKUP CacheEntry;
64 
65     HANDLE FileHandle = MapFile(filename, &PathName, MapIt);
66 
67     xprintf("Calling %s %s mapping\n", ServiceName, (MapIt ? "with" : "without"));
68 
69     RtlInitUnicodeString(&CacheEntry.ImageName, PathName.Buffer);
70     CacheEntry.ImageHandle = FileHandle ? FileHandle : (HANDLE)-1;
71     CallApphelp(Service, &CacheEntry);
72     // we piggy-back on the PathName, so let the Cleanup take care of the string
73     //RtlFreeUnicodeString(&CacheEntry.ImageName);
74 
75     if (FileHandle)
76         NtClose(FileHandle);
77     RtlFreeUnicodeString(&PathName);
78 }
79 
IsOpt(char * argv,const char * check)80 int IsOpt(char* argv, const char* check)
81 {
82     if( argv && (argv[0] == '-' || argv[0] == '/') ) {
83         return !_strnicmp(argv + 1, check, strlen(check));
84     }
85     return 0;
86 }
87 
HandleImageArg(int argc,char * argv[],int * pn,char MapItChar,APPHELPCACHESERVICECLASS Service,char * ServiceName)88 int HandleImageArg(int argc, char* argv[], int* pn, char MapItChar,
89                     APPHELPCACHESERVICECLASS Service, char* ServiceName)
90 {
91     int n = *pn;
92     if (n+1 < argc)
93     {
94         int MapIt = argv[n][1] == MapItChar;
95         CallApphelpWithImage(argv[n+1], MapIt, Service, ServiceName);
96         (*pn) += 1;
97         return 0;
98     }
99     xprintf("Error: no image name specified\n");
100     return 1;
101 }
102 
103 typedef WORD TAG;
104 typedef UINT64 QWORD;
105 
106 #define TAG_TYPE_MASK 0xF000
107 #define TAG_TYPE_DWORD 0x4000
108 #define TAG_TYPE_QWORD 0x5000
109 #define TAG_TYPE_STRINGREF 0x6000
110 
111 #define ATTRIBUTE_AVAILABLE 0x1
112 #define ATTRIBUTE_FAILED 0x2
113 
114 typedef struct tagATTRINFO
115 {
116     TAG   type;
117     DWORD flags;  /* ATTRIBUTE_AVAILABLE, ATTRIBUTE_FAILED */
118     union
119     {
120         QWORD qwattr;
121         DWORD dwattr;
122         WCHAR *lpattr;
123     };
124 } ATTRINFO, *PATTRINFO;
125 
126 static PVOID hdll;
127 static LPCWSTR (WINAPI *pSdbTagToString)(TAG);
128 static BOOL (WINAPI *pSdbGetFileAttributes)(LPCWSTR, PATTRINFO *, LPDWORD);
129 static BOOL (WINAPI *pSdbFreeFileAttributes)(PATTRINFO);
130 
InitApphelp()131 static BOOL InitApphelp()
132 {
133     if (!hdll)
134     {
135         static UNICODE_STRING DllName = RTL_CONSTANT_STRING(L"apphelp.dll");
136         static ANSI_STRING SdbTagToString = RTL_CONSTANT_STRING("SdbTagToString");
137         static ANSI_STRING SdbGetFileAttributes = RTL_CONSTANT_STRING("SdbGetFileAttributes");
138         static ANSI_STRING SdbFreeFileAttributes = RTL_CONSTANT_STRING("SdbFreeFileAttributes");
139         if (!NT_SUCCESS(LdrLoadDll(NULL, NULL, &DllName, &hdll)))
140         {
141             xprintf("Unable to load apphelp.dll\n");
142             return FALSE;
143         }
144         if (!NT_SUCCESS(LdrGetProcedureAddress(hdll, &SdbTagToString, 0, (PVOID)&pSdbTagToString)) ||
145             !NT_SUCCESS(LdrGetProcedureAddress(hdll, &SdbGetFileAttributes, 0, (PVOID)&pSdbGetFileAttributes)) ||
146             !NT_SUCCESS(LdrGetProcedureAddress(hdll, &SdbFreeFileAttributes, 0, (PVOID)&pSdbFreeFileAttributes)))
147         {
148             LdrUnloadDll(hdll);
149             hdll = NULL;
150             xprintf("Unable to resolve functions\n");
151             return FALSE;
152         }
153     }
154     return TRUE;
155 }
156 
157 
HandleDumpAttributes(int argc,char * argv[],int * pn,const char * opt)158 int HandleDumpAttributes(int argc, char* argv[], int* pn, const char* opt)
159 {
160     UNICODE_STRING FileName;
161     PATTRINFO attr;
162     DWORD num_attr, n;
163     int argn = *pn;
164     const char* arg;
165 
166     if (!InitApphelp())
167         return 1;
168 
169     if (strlen(argv[argn]) > (strlen(opt)+1))
170     {
171         arg = argv[argn] + strlen(opt);
172     }
173     else if (argn+1 >= argc)
174     {
175         xprintf("Error: no image name specified\n");
176         return 1;
177     }
178     else
179     {
180         arg = argv[argn+1];
181         (*pn) += 1;
182     }
183 
184     RtlCreateUnicodeStringFromAsciiz(&FileName, arg);
185 
186     if (pSdbGetFileAttributes(FileName.Buffer, &attr, &num_attr))
187     {
188         xprintf("Dumping attributes for %s\n", arg);
189         for (n = 0; n < num_attr; ++n)
190         {
191             TAG tagType;
192             LPCWSTR tagName;
193             if (attr[n].flags != ATTRIBUTE_AVAILABLE)
194                 continue;
195 
196             tagName = pSdbTagToString(attr[n].type);
197 
198             tagType = attr[n].type & TAG_TYPE_MASK;
199             switch (tagType)
200             {
201             case TAG_TYPE_DWORD:
202                 xprintf("<%ls>0x%lx</%ls>\n", tagName, attr[n].dwattr, tagName);
203                 break;
204             case TAG_TYPE_STRINGREF:
205                 xprintf("<%ls>%ls</%ls>\n", tagName, attr[n].lpattr, tagName);
206                 break;
207             case TAG_TYPE_QWORD:
208                 xprintf("<%ls>0x%I64x</%ls>\n", tagName, attr[n].qwattr, tagName);
209                 break;
210             default:
211                 xprintf("<!-- Unknown tag type: 0x%x (from 0x%x)\n", tagType, attr[n].type);
212                 break;
213             }
214         }
215         xprintf("Done\n");
216     }
217     else
218     {
219         xprintf("Unable to get attributes from %s\n", arg);
220     }
221 
222 
223     RtlFreeUnicodeString(&FileName);
224     return 0;
225 }
226 
227 UNICODE_STRING AppCompatCacheKey = RTL_CONSTANT_STRING(L"\\Registry\\MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\AppCompatCache");
228 OBJECT_ATTRIBUTES AppCompatKeyAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES(&AppCompatCacheKey, OBJ_CASE_INSENSITIVE);
229 UNICODE_STRING AppCompatCacheValue = RTL_CONSTANT_STRING(L"AppCompatCache");
230 #define REG_BINARY                  ( 3 )   // Free form binary
231 
232 
233 /* produce a hex dump, stolen from rdesktop.c */
hexdump(unsigned char * p,unsigned int len)234 void hexdump(unsigned char *p, unsigned int len)
235 {
236     unsigned char *line = p;
237     unsigned int i, thisline, offset = 0;
238 
239     while (offset < len)
240     {
241         xprintf("%04x ", offset);
242         thisline = len - offset;
243         if (thisline > 16)
244             thisline = 16;
245 
246         for (i = 0; i < thisline; i++)
247             xprintf("%02x ", line[i]);
248 
249         for (; i < 16; i++)
250             xprintf("   ");
251 
252         for (i = 0; i < thisline; i++)
253             xprintf("%c", (line[i] >= 0x20 && line[i] < 0x7f) ? line[i] : '.');
254 
255         xprintf("\n");
256         offset += thisline;
257         line += thisline;
258     }
259 }
260 
DumpRegistryData(int IncludeDump)261 void DumpRegistryData(int IncludeDump)
262 {
263     HANDLE KeyHandle;
264     NTSTATUS Status;
265     KEY_VALUE_PARTIAL_INFORMATION KeyValueObject;
266     PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = &KeyValueObject;
267     ULONG KeyInfoSize, ResultSize;
268 
269     xprintf("Dumping AppCompatCache registry key\n");
270 
271     Status = NtOpenKey(&KeyHandle, KEY_QUERY_VALUE, &AppCompatKeyAttributes);
272 
273     Status = NtQueryValueKey(KeyHandle, &AppCompatCacheValue,
274                 KeyValuePartialInformation, KeyValueInformation,
275                 sizeof(KeyValueObject), &ResultSize);
276 
277     if (Status == STATUS_BUFFER_OVERFLOW)
278     {
279         KeyInfoSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + KeyValueInformation->DataLength;
280         KeyValueInformation = malloc(KeyInfoSize);
281         if (KeyValueInformation != NULL)
282         {
283             Status = NtQueryValueKey(KeyHandle, &AppCompatCacheValue,
284                         KeyValuePartialInformation, KeyValueInformation,
285                         KeyInfoSize, &ResultSize);
286         }
287     }
288 
289     if (NT_SUCCESS(Status) && KeyValueInformation->Type == REG_BINARY)
290     {
291         ULONG crc;
292         if (IncludeDump)
293             hexdump(KeyValueInformation->Data, KeyValueInformation->DataLength);
294         crc = RtlComputeCrc32(0, KeyValueInformation->Data, KeyValueInformation->DataLength);
295         xprintf("Len: %lu, Crc: 0x%lx\n", KeyValueInformation->DataLength, crc);
296     }
297     else
298     {
299         xprintf("Failed reading AppCompatCache from registry (0x%lx)\n", Status);
300     }
301 
302     if (KeyValueInformation != &KeyValueObject)
303         free(KeyValueInformation);
304 
305     if (KeyHandle)
306         NtClose(KeyHandle);
307 }
308 
309 int _getch();
310 
main(int argc,char * argv[])311 int main(int argc, char* argv[])
312 {
313     int n, unhandled = 0, keepopen = 0;
314 
315     for (n = 1; n < argc; ++n)
316     {
317         char* arg = argv[n];
318         if (IsOpt(arg, "d"))
319         {
320             xprintf("Calling ApphelpCacheServiceDump\n");
321             CallApphelp(ApphelpCacheServiceDump, NULL);
322             unhandled = 0;
323         }
324         else if (IsOpt(arg, "h"))
325         {
326             DumpRegistryData(arg[1] == 'h');
327             unhandled = 0;
328         }
329         else if (IsOpt(arg, "f"))
330         {
331             xprintf("Calling ApphelpCacheServiceFlush\n");
332             CallApphelp(ApphelpCacheServiceFlush, NULL);
333             unhandled = 0;
334         }
335         else if (IsOpt(arg, "z"))
336         {
337             xprintf("Calling ApphelpDBGReadRegistry\n");
338             CallApphelp(ApphelpDBGReadRegistry, NULL);
339             unhandled = 0;
340         }
341         else if (IsOpt(arg, "x"))
342         {
343             xprintf("Calling ApphelpDBGWriteRegistry\n");
344             CallApphelp(ApphelpDBGWriteRegistry, NULL);
345             unhandled = 0;
346         }
347         else if (IsOpt(arg, "l"))
348         {
349             unhandled |= HandleImageArg(argc, argv, &n, 'l',
350                         ApphelpCacheServiceLookup, "ApphelpCacheServiceLookup");
351         }
352         else if (IsOpt(arg, "u"))
353         {
354             unhandled |= HandleImageArg(argc, argv, &n, 'u',
355                         ApphelpCacheServiceUpdate, "ApphelpCacheServiceUpdate");
356         }
357         else if (IsOpt(arg, "r"))
358         {
359             unhandled |= HandleImageArg(argc, argv, &n, 'r',
360                         ApphelpCacheServiceRemove, "ApphelpCacheServiceRemove");
361         }
362         else if (IsOpt(arg, "a"))
363         {
364             unhandled |= HandleDumpAttributes(argc, argv, &n, "a");
365         }
366         else if (IsOpt(arg, "k"))
367         {
368             keepopen = 1;
369         }
370         else
371         {
372             unhandled = 1;
373         }
374     }
375     if (unhandled || argc == 1)
376     {
377         xprintf("Usage: %s [-d|-z|-x|-h|-H|-f|-[l|L] <image>|-[u|U] <image>|-[r|R] <image>|-k]\n", argv[0]);
378         xprintf("           -d: Dump shim cache over debug output\n");
379         xprintf("           -z: DEBUG Read shim cache from registry\n");
380         xprintf("           -x: DEBUG Write shim cache to registry\n");
381         xprintf("           -h: Hexdump shim registry key\n");
382         xprintf("           -H: Crc + Length from shim registry key only\n");
383         xprintf("           -f: Flush (clear) the shim cache\n");
384         xprintf("           -l: Lookup <image> in the shim cache\n");
385         xprintf("           -L: Lookup <image> in the shim cache without mapping it\n");
386         xprintf("           -u: Update (insert) <image> in the shim cache\n");
387         xprintf("           -U: Update (insert) <image> in the shim cache without mapping it\n");
388         xprintf("           -r: Remove <image> from the shim cache\n");
389         xprintf("           -R: Remove <image> from the shim cache without mapping it\n");
390         xprintf("           -a: Dump file attributes as used in the appcompat database\n");
391         xprintf("           -k: Keep the console open\n");
392     }
393     if (keepopen)
394     {
395         _getch();
396     }
397     return unhandled;
398 }
399