Enumerating USN Journal

AceInfinity

Emeritus, Contributor
Joined
Feb 21, 2012
Posts
1,728
Location
Canada
Many people probably don't know about the USN Journal, but it's essentially an optional running log of transactions on an NFTS volume that I believe is default on Windows (most modern NT versions since XP and earlier I think). This can impact performance on programs that require optimal speed for checking for file operations on demand, and of which may not have a running service or program that can keep track of filesystem changes 100% of the time (which in turn itself would probably degrade the performance of the system if it was checking all the files on your system constantly, or even calling one of the other Win32 functions that can check for file modification such as FindFirstChangeNotification or ReadDirectoryChangesW).

Here's some basic code I wrote that will give you an idea on how to query and read USN entries:

Code:
[NO-PARSE]#define __USE_MINGW_ANSI_STDIO 1

#include <Windows.h>
#include <stdio.h>
#include <assert.h>

#define PAGE_SIZE 4096

#define STRTOKEN(x) #x
#define STR(x) STRTOKEN(x)

#define HAS_FLAG(value, mask) (((value) & (mask)) == mask)

#define ERROR_EXIT(x) ret = (x); goto end

void ListUSNJournalReasons(DWORD reason)
{
    if (HAS_FLAG(reason, USN_REASON_BASIC_INFO_CHANGE))        fputs("  - USN_REASON_BASIC_INFO_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_CLOSE))                    fputs("  - USN_REASON_CLOSE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_COMPRESSION_CHANGE))       fputs("  - USN_REASON_COMPRESSION_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_DATA_EXTEND))              fputs("  - USN_REASON_DATA_EXTEND\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_DATA_OVERWRITE))           fputs("  - USN_REASON_DATA_OVERWRITE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_DATA_TRUNCATION))          fputs("  - USN_REASON_DATA_TRUNCATION\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_EA_CHANGE))                fputs("  - USN_REASON_EA_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_ENCRYPTION_CHANGE))        fputs("  - USN_REASON_ENCRYPTION_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_FILE_CREATE))              fputs("  - USN_REASON_FILE_CREATE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_FILE_DELETE))              fputs("  - USN_REASON_FILE_DELETE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_HARD_LINK_CHANGE))         fputs("  - USN_REASON_HARD_LINK_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_INDEXABLE_CHANGE))         fputs("  - USN_REASON_INDEXABLE_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_NAMED_DATA_EXTEND))        fputs("  - USN_REASON_NAMED_DATA_EXTEND\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_NAMED_DATA_OVERWRITE))     fputs("  - USN_REASON_NAMED_DATA_OVERWRITE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_NAMED_DATA_TRUNCATION))    fputs("  - USN_REASON_NAMED_DATA_TRUNCATION\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_OBJECT_ID_CHANGE))         fputs("  - USN_REASON_OBJECT_ID_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_RENAME_NEW_NAME))          fputs("  - USN_REASON_RENAME_NEW_NAME\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_RENAME_OLD_NAME))          fputs("  - USN_REASON_RENAME_OLD_NAME\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_REPARSE_POINT_CHANGE))     fputs("  - USN_REASON_REPARSE_POINT_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_SECURITY_CHANGE))          fputs("  - USN_REASON_SECURITY_CHANGE\n", stdout);
    if (HAS_FLAG(reason, USN_REASON_STREAM_CHANGE))            fputs("  - USN_REASON_STREAM_CHANGE\n", stdout);
    /*if (HAS_FLAG(reason, USN_REASON_INTEGRITY_CHANGE))       fputs("  - USN_REASON_INTEGRITY_CHANGE\n", stdout);*/
    /*if (HAS_FLAG(reason, USN_REASON_TRANSACTED_CHANGE))      fputs("  - USN_REASON_TRANSACTED_CHANGE\n", stdout);*/
}

int main(void)
{
    DWORD ret;
    CHAR szBuf[PAGE_SIZE];

    fputs("\nEnter Volume GUID / Drive Letter (Ex: C): ", stdout);
    scanf(" %" STR(sizeof(szBuf) - 1) "s", szBuf);

    CHAR szFilename[MAX_PATH];
    sprintf(szFilename, szBuf[1] ? "\\\\?\\Volume{%s}\\" : "\\\\.\\%s:", szBuf);
    printf("[-] Opening handle to: %s\n", szFilename);

    HANDLE hVolume = CreateFile(szFilename,
                                GENERIC_READ,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);

    if (hVolume != INVALID_HANDLE_VALUE)
    {
        puts("[-] Obtained handle to specified volume.\n");

        DWORD nBytes = 0;
        USN_JOURNAL_DATA usnJrnl = { 0 };
        ret = DeviceIoControl(hVolume,
                              FSCTL_QUERY_USN_JOURNAL,
                              NULL,
                              0,
                              (LPVOID)&usnJrnl,
                              sizeof(usnJrnl),
                              &nBytes,
                              NULL);

        if (ret)
        {
            printf("UsnJournalID    : 0x%llx\n", usnJrnl.UsnJournalID);
            printf("FirstUsn        : 0x%llx\n", usnJrnl.FirstUsn);
            printf("NextUsn         : 0x%llx\n", usnJrnl.NextUsn);
            printf("LowestValidUsn  : 0x%llx\n", usnJrnl.LowestValidUsn);
            printf("MaxUsn          : 0x%llx\n", usnJrnl.MaxUsn);
            printf("MaximumSize     : 0x%llx\n", usnJrnl.MaximumSize);
            printf("AllocationDelta : 0x%llx\n", usnJrnl.AllocationDelta);
            printf("\n");

            DWORD dwUsnReasonMask = USN_REASON_FILE_CREATE;
            
            READ_USN_JOURNAL_DATA readUsnJrnl = { 0 };
            readUsnJrnl.StartUsn = usnJrnl.FirstUsn;
            readUsnJrnl.ReasonMask = dwUsnReasonMask;
            readUsnJrnl.ReturnOnlyOnClose = FALSE;
            readUsnJrnl.Timeout = 0;
            readUsnJrnl.BytesToWaitFor = 0;
            readUsnJrnl.UsnJournalID = usnJrnl.UsnJournalID;

            DWORD dwRetBytes;
            while (readUsnJrnl.StartUsn < usnJrnl.NextUsn)
            {
                memset(szBuf, 0, PAGE_SIZE);
                ret = DeviceIoControl(hVolume,
                                      FSCTL_READ_USN_JOURNAL,
                                      &readUsnJrnl,
                                      sizeof(readUsnJrnl),
                                      &szBuf,
                                      PAGE_SIZE,
                                      &nBytes,
                                      NULL);
                if (ret)
                {
                    dwRetBytes = nBytes - sizeof(USN);
                    PUSN_RECORD record = (PUSN_RECORD)(((PUCHAR)szBuf) + sizeof(USN));
                    while (dwRetBytes > 0)
                    {
                        printf("USN: 0x%llx\n", record->Usn);
                        wprintf(L"Filename: %.*S\n", record->FileNameLength / 2, record->FileName);
                        printf("USN Reason: (0x%llx)\n", record->Usn);
                        ListUSNJournalReasons(record->Reason);
                        printf("\n");

                        dwRetBytes -= record->RecordLength;
                        record = (PUSN_RECORD)(((PCHAR)record) + record->RecordLength);
                    }

                    readUsnJrnl.StartUsn = *(USN *)&szBuf;
                }
                else
                {
                    fprintf(stderr, "Read journal failed (%lu)\n", GetLastError());
                    ERROR_EXIT(-1);
                }
            }

        }
        else
        {
            fprintf(stderr, "Query journal failed (%lx)\n", GetLastError());
            ERROR_EXIT(-1);
        }

    }
    else
    {
        fprintf(stderr, "Failed to obtain handle to speciied volume (%lx)\n", GetLastError());
        ERROR_EXIT(-1);
    }

end:
    if (hVolume != INVALID_HANDLE_VALUE) CloseHandle(hVolume);

    return ret;
}[/NO-PARSE]

There are other checks that are involved, but for the sake of simplicity I haven't gone any further than this. The cool thing about querying the USN Journal is that you can set a mask that specifies reasons behind the queried entries so that you can avoid returning potential irrelevant data based on what you're looking for. There are many other comparisons you can do too.

Example output:

Code:
[NO-PARSE]Enter Volume GUID / Drive Letter (Ex: C): [-] Opening handle to: \\.\C:
[-] Obtained handle to specified volume.

UsnJournalID    : 0x1d1798b92be7f09
FirstUsn        : 0x34a400000
NextUsn         : 0x34c6c3750
LowestValidUsn  : 0x0
MaxUsn          : 0x7fffffffffff0000
MaximumSize     : 0x2000000
AllocationDelta : 0x800000

USN: 0x34a400338
Filename: ngenlock.dat
USN Reason: (0x34a400338)
  - USN_REASON_CLOSE
  - USN_REASON_FILE_CREATE
  - USN_REASON_FILE_DELETE

USN: 0x34a400390
Filename: ngenlock.dat
USN Reason: (0x34a400390)
  - USN_REASON_FILE_CREATE

USN: 0x34a4008a0
Filename: ngenlock.dat
USN Reason: (0x34a4008a0)
  - USN_REASON_CLOSE
  - USN_REASON_FILE_CREATE
  - USN_REASON_FILE_DELETE

USN: 0x34a4008f8
Filename: ngenlock.dat
USN Reason: (0x34a4008f8)
  - USN_REASON_FILE_CREATE

USN: 0x34a400dd8
Filename: ngenlock.dat
USN Reason: (0x34a400dd8)
  - USN_REASON_CLOSE
  - USN_REASON_FILE_CREATE
  - USN_REASON_FILE_DELETE

USN: 0x34a400e30
Filename: ngenlock.dat
USN Reason: (0x34a400e30)
  - USN_REASON_FILE_CREATE

USN: 0x34a401398
Filename: ngenlock.dat
USN Reason: (0x34a401398)
  - USN_REASON_CLOSE
  - USN_REASON_FILE_CREATE
  - USN_REASON_FILE_DELETE[/NO-PARSE]

For more information on various control codes that can be used, here is a good list: Volume Management Control Codes (Windows)
 

Has Sysnative Forums helped you? Please consider donating to help us support the site!

Back
Top