├── .gitignore ├── README.md ├── SymlinkCallback.sln └── SymlinkCallback ├── Main.cpp ├── SymlinkCallback.vcxproj └── SymlinkCallback.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /SymlinkCallback/x64/Debug 6 | /.vs/SymlinkCallback/v16 7 | /x64/Debug 8 | *.user 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SymlinkCallback 2 | 3 | Write-ups: https://windows-internals.com/dkom-now-with-symbolic-links/, https://windows-internals.com/symhooks-part-two/, https://windows-internals.com/symhooks-part-three/, https://windows-internals.com/symhooks-part-four/ 4 | 5 | This driver uses the option to set a dynamic target for a symbolic link and hooks the symlink of the C: volume. 6 | 7 | It modifies the symlink object and replaces the LinkTarget string with a callback function which will be called whenever the symlink is accessed. 8 | 9 | Then, it creates a device object and redirects the symlink target to the device object, adding a "\Foo" suffix in order to avoid direct volume open attempts (which cannot be reparsed). This allows it to intercept all file open operations on the C: volume through its IRP_MJ_CREATE handler. This handler then reparses the name back to the original C: volume target device object, removing the "\Foo" suffix that was added. 10 | 11 | Created by @aionescu (https://github.com/ionescu007/) and @yarden_shafir 12 | -------------------------------------------------------------------------------- /SymlinkCallback.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.852 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SymlinkCallback", "SymlinkCallback\SymlinkCallback.vcxproj", "{F8E9A538-16D0-4077-83CA-4C5A563D1BD7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM = Debug|ARM 11 | Debug|ARM64 = Debug|ARM64 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|ARM = Release|ARM 15 | Release|ARM64 = Release|ARM64 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|ARM.ActiveCfg = Debug|ARM 21 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|ARM.Build.0 = Debug|ARM 22 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|ARM.Deploy.0 = Debug|ARM 23 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|ARM64.ActiveCfg = Debug|ARM64 24 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|ARM64.Build.0 = Debug|ARM64 25 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|ARM64.Deploy.0 = Debug|ARM64 26 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|x64.ActiveCfg = Debug|x64 27 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|x64.Build.0 = Debug|x64 28 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|x64.Deploy.0 = Debug|x64 29 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|x86.ActiveCfg = Debug|Win32 30 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|x86.Build.0 = Debug|Win32 31 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Debug|x86.Deploy.0 = Debug|Win32 32 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|ARM.ActiveCfg = Release|ARM 33 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|ARM.Build.0 = Release|ARM 34 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|ARM.Deploy.0 = Release|ARM 35 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|ARM64.ActiveCfg = Release|ARM64 36 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|ARM64.Build.0 = Release|ARM64 37 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|ARM64.Deploy.0 = Release|ARM64 38 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|x64.ActiveCfg = Release|x64 39 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|x64.Build.0 = Release|x64 40 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|x64.Deploy.0 = Release|x64 41 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|x86.ActiveCfg = Release|Win32 42 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|x86.Build.0 = Release|Win32 43 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7}.Release|x86.Deploy.0 = Release|Win32 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {C08C45C2-5EDC-414A-BD98-B715D5CC7A81} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /SymlinkCallback/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma warning (disable: 4201) 6 | 7 | #define OBJECT_SYMBOLIC_LINK_USE_CALLBACK 0x10 8 | 9 | NTSTATUS 10 | typedef (SYMLINK_CALLBACK_FUNCTION) ( 11 | _In_ struct _OBJECT_SYMBOLIC_LINK* Symlink, 12 | _In_ PVOID SymlinkContext, 13 | _Out_ PUNICODE_STRING SymlinkPath, 14 | _Outptr_ PVOID* Object 15 | ); 16 | typedef SYMLINK_CALLBACK_FUNCTION *PSYMLINK_CALLBACK_FUNCTION; 17 | 18 | typedef struct _OBJECT_SYMBOLIC_LINK 19 | { 20 | LARGE_INTEGER CreationTime; 21 | union 22 | { 23 | UNICODE_STRING LinkTarget; 24 | struct 25 | { 26 | PSYMLINK_CALLBACK_FUNCTION Callback; 27 | PVOID CallbackContext; 28 | }; 29 | }; 30 | ULONG DosDeviceDriveIndex; 31 | ULONG Flags; 32 | ACCESS_MASK AccessMask; 33 | } OBJECT_SYMBOLIC_LINK, *POBJECT_SYMBOLIC_LINK; 34 | C_ASSERT(sizeof(OBJECT_SYMBOLIC_LINK) == 0x28); 35 | 36 | EXTERN_C_START 37 | __declspec(code_seg(".call$1")) SYMLINK_CALLBACK_FUNCTION SymLinkCallback; 38 | DRIVER_INITIALIZE DriverEntry; 39 | DRIVER_UNLOAD DriverUnload; 40 | DRIVER_DISPATCH SymHookCreate; 41 | EXTERN_C_END 42 | 43 | DECLARE_GLOBAL_CONST_UNICODE_STRING(g_TailName, L"\\Foo"); 44 | DECLARE_UNICODE_STRING_SIZE(g_DeviceName, 64); 45 | UNICODE_STRING g_LinkPath; 46 | POBJECT_SYMBOLIC_LINK g_SymLinkObject; 47 | PDEVICE_OBJECT g_DeviceObject; 48 | 49 | _Use_decl_annotations_ 50 | VOID 51 | DriverUnload ( 52 | PDRIVER_OBJECT DriverObject 53 | ) 54 | { 55 | UNREFERENCED_PARAMETER(DriverObject); 56 | 57 | // 58 | // Undo the patch, restoring the original target string 59 | // 60 | g_SymLinkObject->Flags &= ~OBJECT_SYMBOLIC_LINK_USE_CALLBACK; 61 | MemoryBarrier(); 62 | g_SymLinkObject->LinkTarget = g_LinkPath; 63 | 64 | // 65 | // Delete our device object 66 | // 67 | IoDeleteDevice(g_DeviceObject); 68 | 69 | // 70 | // Dereference the symbolic link object 71 | // 72 | ObDereferenceObject(g_SymLinkObject); 73 | } 74 | 75 | // 76 | // To avoid a race condition when modifying the symlink object 77 | // we are using a trick: 78 | // We make sure our callback function is aligned to 64k so 79 | // while the flags are not yet changed and the callback is still 80 | // treated as a unicode string, the last 2 bytes are 0000 so the 81 | // string length is 0, and the buffer is ignored. 82 | // To achieve that, we create a section that contains a buffer 83 | // sized 0xb000 and make sure our callback function is located after it, 84 | // and so it is aligned to 64k. 85 | // 86 | #pragma section(".call$0", write) 87 | __declspec(allocate(".call$0")) UCHAR _pad_[0xB000] = { 0 }; 88 | 89 | #pragma code_seg(".text") 90 | NTSTATUS 91 | SymHookCreate ( 92 | _In_ PDEVICE_OBJECT DeviceObject, 93 | _In_ PIRP Irp 94 | ) 95 | { 96 | PIO_STACK_LOCATION ioStack; 97 | PFILE_OBJECT fileObject; 98 | USHORT bufferLength; 99 | NTSTATUS status; 100 | PWCHAR buffer; 101 | UNREFERENCED_PARAMETER(DeviceObject); 102 | 103 | // 104 | // Get the FILE_OBJECT from the I/O Stack Location 105 | // 106 | ioStack = IoGetCurrentIrpStackLocation(Irp); 107 | fileObject = ioStack->FileObject; 108 | 109 | // 110 | // If this is someone directly trying to access our device object, 111 | // fail them, so that we do not crash the system (since we should 112 | // not reparse direct opens). 113 | // 114 | if (fileObject->FileName.Length < g_TailName.Length) 115 | { 116 | status = STATUS_ACCESS_DENIED; 117 | goto Exit; 118 | } 119 | 120 | // 121 | // Allocate space for the original device name, plus the size of the 122 | // file name, minus "\Foo", and adding space for the terminating NUL. 123 | // 124 | bufferLength = fileObject->FileName.Length - 125 | g_TailName.Length + 126 | g_LinkPath.Length + 127 | sizeof(UNICODE_NULL); 128 | buffer = (PWCHAR)ExAllocatePoolWithTag(PagedPool, bufferLength, 'maNF'); 129 | if (buffer == NULL) 130 | { 131 | status = STATUS_INSUFFICIENT_RESOURCES; 132 | goto Exit; 133 | } 134 | 135 | // 136 | // Append the original device name first 137 | // 138 | buffer[0] = UNICODE_NULL; 139 | NT_VERIFY(NT_SUCCESS(RtlStringCbCatNW(buffer, 140 | bufferLength, 141 | g_LinkPath.Buffer, 142 | g_LinkPath.Length))); 143 | 144 | // 145 | // Then add the name of the file name, minus "\Foo" 146 | // 147 | NT_VERIFY(NT_SUCCESS(RtlStringCbCatNW(buffer, 148 | bufferLength, 149 | fileObject->FileName.Buffer + 150 | (g_TailName.Length / sizeof(g_TailName.Buffer[0])), 151 | fileObject->FileName.Length - 152 | g_TailName.Length))); 153 | 154 | // 155 | // Ask the I/O manager to free the original file name and use ours instead 156 | // 157 | status = IoReplaceFileObjectName(fileObject, 158 | buffer, 159 | bufferLength - sizeof(UNICODE_NULL)); 160 | if (!NT_SUCCESS(status)) 161 | { 162 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 163 | DPFLTR_ERROR_LEVEL, 164 | "Failed to swap file object name: %lx\n", 165 | status); 166 | ExFreePool(buffer); 167 | goto Exit; 168 | } 169 | 170 | // 171 | // Return a reparse operation so that the I/O manager uses the new file 172 | // object name for its lookup, and starts over 173 | // 174 | Irp->IoStatus.Information = IO_REPARSE; 175 | status = STATUS_REPARSE; 176 | 177 | Exit: 178 | // 179 | // Complete the IRP with the relevant status code 180 | // 181 | Irp->IoStatus.Status = status; 182 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 183 | return status; 184 | } 185 | 186 | _Use_decl_annotations_ 187 | NTSTATUS 188 | DriverEntry ( 189 | PDRIVER_OBJECT DriverObject, 190 | PUNICODE_STRING RegistryPath 191 | ) 192 | { 193 | NTSTATUS status; 194 | HANDLE symLinkHandle; 195 | DECLARE_CONST_UNICODE_STRING(symlinkName, L"\\GLOBAL??\\c:"); 196 | OBJECT_ATTRIBUTES objAttr = RTL_CONSTANT_OBJECT_ATTRIBUTES(&symlinkName, 197 | OBJ_KERNEL_HANDLE | 198 | OBJ_CASE_INSENSITIVE); 199 | UNREFERENCED_PARAMETER(RegistryPath); 200 | 201 | // 202 | // Make sure our alignment trick worked out 203 | // 204 | if (((ULONG_PTR)SymLinkCallback & 0xFFFF) != 0) 205 | { 206 | status = STATUS_CONFLICTING_ADDRESSES; 207 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 208 | DPFLTR_ERROR_LEVEL, 209 | "Callback function not aligned correctly!\n"); 210 | goto Exit; 211 | } 212 | 213 | // 214 | // Set an unload routine so we can update during testing 215 | // 216 | DriverObject->DriverUnload = DriverUnload; 217 | 218 | // 219 | // Open a handle to the symbolic link object for C: directory, 220 | // so we can hook it 221 | // 222 | status = ZwOpenSymbolicLinkObject(&symLinkHandle, 223 | SYMBOLIC_LINK_ALL_ACCESS, 224 | &objAttr); 225 | if (!NT_SUCCESS(status)) 226 | { 227 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 228 | DPFLTR_ERROR_LEVEL, 229 | "Failed opening symbolic link with error: %lx\n", 230 | status); 231 | goto Exit; 232 | } 233 | 234 | // 235 | // Get the symbolic link object and close the handle since we 236 | // no longer need it 237 | // 238 | status = ObReferenceObjectByHandle(symLinkHandle, 239 | SYMBOLIC_LINK_ALL_ACCESS, 240 | NULL, 241 | KernelMode, 242 | (PVOID*)&g_SymLinkObject, 243 | NULL); 244 | ObCloseHandle(symLinkHandle, KernelMode); 245 | if (!NT_SUCCESS(status)) 246 | { 247 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 248 | DPFLTR_ERROR_LEVEL, 249 | "Failed referencing symbolic link with error: %lx\n", 250 | status); 251 | goto Exit; 252 | } 253 | 254 | // 255 | // Create our device object hook 256 | // 257 | RtlAppendUnicodeToString(&g_DeviceName, L"\\Device\\HarddiskVolume0"); 258 | status = IoCreateDevice(DriverObject, 259 | 0, 260 | &g_DeviceName, 261 | FILE_DEVICE_UNKNOWN, 262 | FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL, 263 | FALSE, 264 | &g_DeviceObject); 265 | if (!NT_SUCCESS(status)) 266 | { 267 | // 268 | // Fail, and drop the symlink object reference 269 | // 270 | ObDereferenceObject(g_SymLinkObject); 271 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 272 | DPFLTR_ERROR_LEVEL, 273 | "Failed create devobj with error: %lx\n", 274 | status); 275 | goto Exit; 276 | } 277 | 278 | // 279 | // Attach our create handler 280 | // 281 | DriverObject->MajorFunction[IRP_MJ_CREATE] = SymHookCreate; 282 | 283 | // 284 | // Save the original string that the symlink points to 285 | // so we can change the object back when we unload 286 | // 287 | g_LinkPath = g_SymLinkObject->LinkTarget; 288 | 289 | // 290 | // Modify the symlink to point to our callback instead of the string 291 | // and change the flags so the union will be treated as a callback. 292 | // Set CallbackContext to the original string so we can 293 | // return it from the callback and allow the system to run normally. 294 | // 295 | g_SymLinkObject->Callback = SymLinkCallback; 296 | RtlAppendUnicodeStringToString(&g_DeviceName, &g_TailName); 297 | g_SymLinkObject->CallbackContext = &g_DeviceName; 298 | MemoryBarrier(); 299 | g_SymLinkObject->Flags |= OBJECT_SYMBOLIC_LINK_USE_CALLBACK; 300 | 301 | Exit: 302 | // 303 | // Return the result back to the system 304 | // 305 | return status; 306 | } 307 | 308 | #pragma section(".call$1", execute) 309 | __declspec(code_seg(".call$1")) 310 | NTSTATUS 311 | SymLinkCallback ( 312 | _In_ POBJECT_SYMBOLIC_LINK Symlink, 313 | _In_ PVOID SymlinkContext, 314 | _Out_ PUNICODE_STRING SymlinkPath, 315 | _Outptr_ PVOID* Object 316 | ) 317 | { 318 | UNREFERENCED_PARAMETER(Symlink); 319 | 320 | // 321 | // We need to either return the right object for this symlink 322 | // or the correct target string. 323 | // It's a lot easier to get the string, so we can set Object to Null. 324 | // 325 | *Object = NULL; 326 | *SymlinkPath = *(PUNICODE_STRING)(SymlinkContext); 327 | return STATUS_SUCCESS; 328 | } 329 | -------------------------------------------------------------------------------- /SymlinkCallback/SymlinkCallback.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | Debug 22 | ARM 23 | 24 | 25 | Release 26 | ARM 27 | 28 | 29 | Debug 30 | ARM64 31 | 32 | 33 | Release 34 | ARM64 35 | 36 | 37 | 38 | {F8E9A538-16D0-4077-83CA-4C5A563D1BD7} 39 | {dd38f7fc-d7bd-488b-9242-7d8754cde80d} 40 | v4.5 41 | 12.0 42 | Debug 43 | Win32 44 | SymlinkCallback 45 | 46 | 47 | 48 | Windows10 49 | true 50 | WindowsKernelModeDriver10.0 51 | Driver 52 | WDM 53 | false 54 | 55 | 56 | Windows10 57 | false 58 | WindowsKernelModeDriver10.0 59 | Driver 60 | WDM 61 | 62 | 63 | Windows10 64 | true 65 | WindowsKernelModeDriver10.0 66 | Driver 67 | WDM 68 | false 69 | 70 | 71 | Windows10 72 | false 73 | WindowsKernelModeDriver10.0 74 | Driver 75 | WDM 76 | 77 | 78 | Windows10 79 | true 80 | WindowsKernelModeDriver10.0 81 | Driver 82 | WDM 83 | 84 | 85 | Windows10 86 | false 87 | WindowsKernelModeDriver10.0 88 | Driver 89 | WDM 90 | 91 | 92 | Windows10 93 | true 94 | WindowsKernelModeDriver10.0 95 | Driver 96 | WDM 97 | 98 | 99 | Windows10 100 | false 101 | WindowsKernelModeDriver10.0 102 | Driver 103 | WDM 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | DbgengKernelDebugger 115 | 116 | 117 | DbgengKernelDebugger 118 | 119 | 120 | DbgengKernelDebugger 121 | 122 | 123 | DbgengKernelDebugger 124 | 125 | 126 | DbgengKernelDebugger 127 | 128 | 129 | DbgengKernelDebugger 130 | 131 | 132 | DbgengKernelDebugger 133 | 134 | 135 | DbgengKernelDebugger 136 | 137 | 138 | 139 | INIT,d;.call,align=0x10000 140 | 141 | 142 | 143 | 144 | INIT,d 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /SymlinkCallback/SymlinkCallback.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {8E41214B-6785-4CFE-B992-037D68949A14} 18 | inf;inv;inx;mof;mc; 19 | 20 | 21 | 22 | 23 | Source Files 24 | 25 | 26 | --------------------------------------------------------------------------------