0

I always used this solution to enumerate files and folders recursively. The code works very fine showing correct file/folder names to all found. But exists a trouble related to recognition of symbolic link files, making the linked solution fail, for example:

enter image description here

Like you can see on image above, these 3 files are symbolic link files pointing to dll files in some place. Then, executing the code mentioned will prints:

  • api-ms-win-core-console-l1-1-0.dll

instead of

  • api-ms-win-core-console-l1-1-0.symlink

Another similar case with some use of IoCreateFile() function. If this receive a wrong filename or objectattributes (still referring to code of linked answer), also will fail with a ntstatus error STATUS_OBJECT_PATH_NOT_FOUND or some other related to this trouble.

Then my question is:

Is threre some solution to recognize symbolic link files where the linked code that i had used could work (and also why not any other function like ZwOpenFile etc)?

Thanks in advance by any suggestion.

Edition:

Here is a code where IoCreateFile() function fails when a simbolic link is passed as filename:

#include <ntifs.h>
#include <ntddk.h>

HANDLE
MyIoOpenFile(
        IN PCWSTR FileName,
        IN ACCESS_MASK DesiredAccess,
        IN ULONG ShareAccess)
{
        NTSTATUS ntStatus;
        UNICODE_STRING uniFileName;
        OBJECT_ATTRIBUTES objectAttributes;
        HANDLE ntFileHandle;
        IO_STATUS_BLOCK ioStatus;
        
        if (KeGetCurrentIrql() > PASSIVE_LEVEL) {
            DbgPrint("KeGetCurrentIrql() > PASSIVE_LEVEL\n");
            return 0;
        }

        RtlInitUnicodeString(&uniFileName, FileName);
        InitializeObjectAttributes(&objectAttributes, &uniFileName,
                OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);

        ntStatus = IoCreateFile(&ntFileHandle,
                DesiredAccess,
                &objectAttributes,
                &ioStatus,
                0,
                FILE_ATTRIBUTE_NORMAL,
                ShareAccess,
                FILE_OPEN,
                0,
                NULL,
                0,
                0,
                NULL,
                IO_NO_PARAMETER_CHECKING);

        if (!NT_SUCCESS(ntStatus)) {
                DbgPrint("IoCreateFile() error - 0x%X \n", ntStatus);
                return 0;
        }

        return ntFileHandle;
}

//---------------------------------------------------------------------------

HANDLE hFileHandle = MyIoOpenFile(L"\\??\\C:\\Full-Path-FileName-Here",
                FILE_READ_ATTRIBUTES,
                FILE_SHARE_READ);
               
if (hFileHandle != 0) {
    DbgPrint("hFileHandle: %08X\n", hFileHandle);
    ZwClose(hFileHandle);
}
  • 1
    code print correct file name *api-ms-win-core-console-l1-1-0.dll* and in code exist check for symlink/reparsepoint - `FILE_ATTRIBUTE_REPARSE_POINT` – RbMm Jul 18 '20 at 09:24
  • 1
    *.symlink* - this is not extension and not part of file name. this is inside *Type* column - simply description of file type. *api-ms-win-core-console-l1-1-0.symlink* - this is wrong name - not exist such file. real file name is *api-ms-win-core-console-l1-1-0.dll* which you and got (you test on system where option not show known file extensions, like *.dll* is on) – RbMm Jul 18 '20 at 11:43
  • @RbMm, then not exists solution to manipulate symbolic link (these shortcuts :D) files, like deletion for example (having your handle as reference before) - `IoCreateFile/ZwOpenFile`? already that it always will point to "*api-ms-win-core-console-l1-1-0.dll*" that not exists (`STATUS_OBJECT_PATH_NOT_FOUND`). –  Jul 18 '20 at 14:19
  • How i can delete these symbolic link files using the linked code? this is possible? remembering that `IoCreateFile()/ZwOpenFile()` fails if specify "*api-ms-win-core-console-l1-1-0.dll*" to a file that is symbolic link. –  Jul 18 '20 at 15:09
  • See the code inserted on edition about "*open symbolic link*". –  Jul 18 '20 at 15:25
  • 1
    you need use `FILE_OPEN_REPARSE_POINT` option. guess you not use it. if you want delete this file - open it with `FILE_OPEN_REPARSE_POINT|FILE_DELETE_ON_CLOSE` – RbMm Jul 18 '20 at 15:25
  • 1
    of course your code is wrong - no `FILE_OPEN_REPARSE_POINT` option – RbMm Jul 18 '20 at 15:26
  • Ohh! ok, i will try it. –  Jul 18 '20 at 15:27
  • *If the CreateOptions FILE_OPEN_REPARSE_POINT flag is not specified and ZwCreateFile attempts to open a file with a reparse point, normal reparse point processing occurs for the file. If, on the other hand, the FILE_OPEN_REPARSE_POINT flag is specified, normal reparse processing does not occur and ZwCreateFile attempts to directly open the reparse point file.* – RbMm Jul 18 '20 at 15:30
  • The vast majority of reparse-point types are not name-surrogate reparse points (i.e. the macro `IsReparseTagNameSurrogate`), i.e. they are nothing like symlinks. Their handler should not be cut out of the loop when deleting the file, unless reparsing fails as an unhandled tag (`STATUS_IO_REPARSE_TAG_NOT_HANDLED`), as `DeleteFileW` does. You need to check the reparse tag and should only bypass the handler on delete if it's not a name surrogate. The two common name-surrogate types are symlinks (`IO_REPARSE_TAG_SYMLINK`) and mount points (`IO_REPARSE_TAG_MOUNT_POINT`). – Eryk Sun Jul 18 '20 at 17:24
  • @RbMm, thank you. `FILE_OPEN_REPARSE_POINT` flag is the solution :D. +1 –  Jul 18 '20 at 18:47
  • It is not the complete solution if you're properly handling reparse points. You need to query the reparse tag to determine if it's symlink-like. Follow what the Windows API does. Map out the behavior of API functions such as `DeleteFileW` and `CopyFileExW` to see how they handle reparse points. You'll find special-cased behavior for name surrogates or specifically for `IO_REPARSE_TAG_MOUNT_POINT` and `IO_REPARSE_TAG_SYMLINK`, but for most operations on reparse points, which are not name surrogates (e.g. consider remote OneDrive files) they open the file/directory with reparsing enabled. – Eryk Sun Jul 18 '20 at 21:41
  • Once you've opened the reparse point, query the reparse tag via `ZwQueryInformationFile`: `FileAttributeTagInformation`. – Eryk Sun Jul 18 '20 at 21:43

1 Answers1

-1

From Microsoft docs:

FILE_ATTRIBUTE_REPARSE_POINT - A file or directory that has an associated reparse point, or a file that is a symbolic link.

So you can use GetFileAttributes to detect symbolic links:

LPCWSTR lpszFileName = /* get file name */;

if (GetFileAttributes(lpszFileName) & FILE_ATTRIBUTE_REPARSE_POINT)
{
    // Symbolic link
}

See comments for further information about error checks and kernel mode API

Georgy Firsov
  • 286
  • 1
  • 13
  • 2
    `(GetFileAttributes(lpszFileName) == FILE_ATTRIBUTE_REPARSE_POINT)` is wrong check. can be multiple attributes on file. must be `GetFileAttributes(lpszFileName) & FILE_ATTRIBUTE_REPARSE_POINT` (and separate check that not `INVALID_FILE_ATTRIBUTES` returned. also this api not exist in kernel. instead need call say [`ZwQueryFullAttributesFile`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwqueryfullattributesfile) if we already not have *FileAttributes* – RbMm Jul 18 '20 at 11:49
  • @RbMm I forgot, that `GetFileAttributes` returns a bit mask - fixed – Georgy Firsov Jul 18 '20 at 15:31
  • A symlink is one type of reparse point among dozens. A class of reparse point called "name surrogates" (i.e. `IsReparseTagNameSurrogate`) has symlink-ish behavior (i.e. "the file or directory represents another named entity in the system"), such as `IO_REPARSE_TAG_SYMLINK` and `IO_REPARSE_TAG_MOUNT_POINT`. But note these two have divergent behavior when traversed while reparsing relative symlinks (`IO_REPARSE_TAG_SYMLINK` reparse points with relative target paths) or when accessed in a remote path, and mountpoints should not be mindlessly handled as equivalent to symlinks. – Eryk Sun Jul 18 '20 at 17:31