Membaca Change Journal File Di NTFS Dengan Visual C++


Kode program untuk artikel ini dapat dijumpai di https://gist.github.com/JockiHendry/9263890.

Pada volume yang memakai file system NTFS, Windows 7 akan mencatat setiap aktifitas perubahan pada file dan directory. Dengan demikian, pengguna bisa tahu apa saja file dimodifikasi di volume/partisi tersebut. Windows akan menyimpan informasi ini di sebuah file bernama \$Extend:$J (file bernama \$Extend di alternate data stream bernama $J). File ini tidak akan penuh karena isinya akan terus ditimpa. Lalu bagaimana cara melihat file ini? Ini adalah salah satu file internal yang dipakai oleh NTFS dan tidak dapat dilihat oleh pengguna (sama seperti file lain seperti $MFT, $MFTMirr, $Boot dan sebagainya). Cara yang paling akurat adalah dengan memeriksa isinya (membaca dari sector). Sebagai alternatifnya, Windows juga menyediakan control code FSCTL_READ_USN_JOURNAL di API DeviceIOControl(). Cara ini lebih mudah karena saya tidak perlu membaca sector secara langsung. Tapi cara ini tidak selalu efektif dalam setiap kondisi karena ia hanya bisa diterapkan di partisi yang sedang di-mount (memiliki drive letter seperti C atau D).

Pada artikel ini saya akan memakai Visual C++ karena API Windows ditulis dan dipublikasikan dengan bahasa C. Memakai bahasa tingkat tinggi seperti .NET dan Java hanya akan menambah repotnya mengurus interoperability. Mahasiswa yang baru belajar sering kali menganggap C dan C++ sudah ‘punah’ digantikan bahasa tingkat tinggi (terutama bila melihat dari kronologi perkembangan bahasa pemograman). Tapi faktanya tidak demikian, developer tetap butuh bahasa yang dekat dengan mesin agar bisa mengendalikan mesin. Mesin komputer tidak bisa membaca HTML dan tidak mengerti OOP; mesin komputer hanya mengerti bit dan byte. Bahasa C tetap merupakan bahasa pilihan saya dalam pemograman low-level hingga saat ini karena ia memang lebih dekat dengan mesin (misalnya memiliki pointer untuk membaca alamat memori secara langsung).

Saya akan mulai dengan membuat sebuah proyek Win32 Console Application di Visual Studio. Karena operasi yang dilakukan oleh program ini membutuhkan hak akses Administrator, maka perlu ada dialog UAC untuk menjalankan program sebagai Administrator (bila belum). Untuk itu saya men-klik kanan nama proyek, memilih Properties. Pada dialog yang muncul, saya memilih Configuration Properties, Linker, Manifest File. Setelah itu, saya mengubah nilai UAC Execution Level menjadi requireAdministrator (/level=’requireAdministrator’).

Langkah pertama yang saya lakukan adalah membuka volume NTFS yang akan dibaca berdasarkan kode hurufnya dengan memanggil Win32 API CreateFile(), seperti yang terlihat pada kode program berikut ini:

#include "stdafx.h"
#include <Windows.h>
#include <WinIoCtl.h>

int _tmain(int argc, _TCHAR* argv[])
{

    // Membaca volume C:
    HANDLE h = CreateFile(L"\.c:", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, OPEN_EXISTING, 0, NULL);
    if (h == INVALID_HANDLE_VALUE) {
        printf("Gagal membaca drive C. Kode Kesalahan: %dn", GetLastError());
        return;
    }

    // Lanjut disini...

    return 0;
}

Setelah itu, saya akan memanggil DeviceIOControl() dengan code berupa FSCTL_QUERY_USN_JOURNAL untuk mendapatkan nilai USN pertama. Saya membutuhkan nilai ini sebagai parameter untuk FSCTL_READ_USN_JOURNAL nanti. Untuk itu, saya menambahkan kode program seperti berikut ini:

...
// Membaca informasi Change Journal dengan FSCTL_QUERY_USN_JOURNAL
USN_JOURNAL_DATA journal;
DWORD jumlahByte;
if (!DeviceIoControl(h, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &journal, sizeof(journal), &jumlahByte, NULL)) {
    printf("Gagal membaca Change Journal.  Kode kesalahan: %dn", GetLastError());
    return -1;
}
printf("USN_JOURNAL_DATA.UsnJournalID    = %#020llxn", journal.UsnJournalID);
printf("USN_JOURNAL_DATA.FirstUsn        = %#020llxn", journal.FirstUsn);
printf("USN_JOURNAL_DATA.NextUsn         = %#020llxn", journal.NextUsn);
printf("USN_JOURNAL_DATA.LowestValidUsn  = %#020llxn", journal.LowestValidUsn);
printf("USN_JOURNAL_DATA.MaxUsn          = %#020llxn", journal.MaxUsn);
printf("USN_JOURNAL_DATA.MaximumSize     = %#020llxn", journal.MaximumSize);
printf("USN_JOURNAL_DATA.AllocationDelta = %#020llxn", journal.AllocationDelta);
...

Pada kode program diatas, saya memakai %llx pada printf() untuk mencetak versi heksadesimal dari nilai __int64 yang juga dikenal sebagai long long (tipe USN adalah alias dari __int64). Bila saya menjalankan kode program di atas, saya akan memperoleh hasil yang sama dengan perintah fsutil usn readdata c:.

Nilai dari journal.FirstUsn adalah record pertama yang dapat dibaca di Change Journal. Oleh sebab itu, saya akan mulai membaca mulai dari record tersebut dengan menggunakan FSCTL_READ_USN_JOURNAL seperti yang terlihat pada kode program berikut ini:

...
// Membaca isi Change Journal
READ_USN_JOURNAL_DATA cariUSN;
CHAR hasil[4096];

cariUSN.StartUsn = journal.FirstUsn;
cariUSN.UsnJournalID = journal.UsnJournalID;
memset(hasil, 0, 4096);
if (!DeviceIoControl(h, FSCTL_READ_USN_JOURNAL, &cariUSN, sizeof(cariUSN), &hasil, 4096, &jumlahByte, NULL)) {
    printf("Gagal membaca record di Change Journal.  Kode kesalahan: %dn", GetLastError());
    return -1;
}

printf("nDaftar Record di Change Journal:nn");

PUSN_RECORD record = (PUSN_RECORD)(((PUCHAR)hasil) + sizeof(USN));
printf("USN        : %#020llxn", record->Usn);
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : %#lxnn", record->Reason);

record = (PUSN_RECORD)(((PCHAR) record) + record->RecordLength);
printf("USN        : %#020llxn", record->Usn); 
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : %#lxnn", record->Reason);
...

Pada kode program di atas, saya menampilkan dua record pertama. Hasil kembalian dari FSCTL_READ_USN_JOURNAL ditampung dalam variabel hasil. Delapan (8) byte pertama dari hasil adalah nilai USN berikutnya setelah USN yang terakhir kali dikembalikan. Ukuran hasil hanya 4096 bytes sehingga ada kemungkinan besar tidak seluruh record tertampung sehingga saya perlu melakukan perulangan dengan kembali memanggil FSCTL_READ_USN_JOURNAL. Selain itu, saya juga harus memperhatikan nilai variabel jumlahByte berisi jumlah byte yang terpakai dari 4096 bytes yang saya sediakan.

Kode program di atas hanya menampilkan dua record saja. Ini tidak begitu berguna! Oleh sebab itu, saya akan menghapus dan menggantinya menjadi sebuah perulangan yang menampilkan seluruh record di Change Journal seperti yang terlihat berikut ini:

...
// Membaca isi Change Journal
printf("nDaftar Record di Change Journal:nn");

READ_USN_JOURNAL_DATA cariUSN;
PUSN_RECORD record;
CHAR hasil[4096];
USN nextUSN;

cariUSN.StartUsn = journal.FirstUsn;
cariUSN.ReasonMask = 0xFFFFFFFF;
cariUSN.ReturnOnlyOnClose = 0;  
cariUSN.BytesToWaitFor = 0;
cariUSN.UsnJournalID = journal.UsnJournalID;

while (1) {
    memset(hasil, 0, 4096);
    if (!DeviceIoControl(h, FSCTL_READ_USN_JOURNAL, &cariUSN, sizeof(cariUSN), &hasil, 4096, &jumlahByte, NULL)) {
        printf("Gagal membaca record di Change Journal.  Kode kesalahan: %dn", GetLastError());
        return -1;
    }           

    record = (PUSN_RECORD)(((PUCHAR)hasil) + sizeof(USN));
    jumlahByte -= sizeof(USN);      

    while (jumlahByte > 0) {
        printf("USN        : %#020llxn", record->Usn);
        printf("Nama File  : %Sn", record->FileName);
        printf("Reason     : %#lxnn", record->Reason);

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

    nextUSN = *(USN*) &hasil;       
    if (nextUSN==cariUSN.StartUsn) break;       
    cariUSN.StartUsn = nextUSN;
}
...

Pada kode program di atas, saya selalu memakai sebuah variabel yang sama untuk menampung entry yang dibaca yaitu hasil (sebuah array 4096 bytes). Setiap kali akan membaca di perulangan berikutnya, saya mengosongkan nilai hasil dengan memset(). Dengan demikian, saya dapat membaca Change Journal yang memiliki ukuran besar (dalam satuan GB) tanpa harus takut kehabisan memori. Untuk menentukan kapan harus berhenti, saya memeriksa apakah USN berikutnya yang dikembalikan oleh FSCTL_READ_USN_JOURNAL sama dengan nilai cariUSN.StartUsn yang saya berikan diawal. Bila sama, ini berarti tidak ada lagi yang dapat dibaca dan FSCTL_READ_USN_JOURNAL tidak boleh dipanggil lagi.

Bila saya menjalankan program, saya akan memperoleh banyak output. Alangkah baiknya bila program berhenti setiap kali mencapai batas layar. Oleh sebab itu, saya kemudian menambahkan kode program seperti berikut ini:

...
int _tmain(int argc, _TCHAR* argv[])
{
    // Mengatur console
    CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTitle(L"Change Journal Viewer by TheSolidSnake");   
    ...
    while (1) {
        ...

        while (jumlahByte > 0) {

            ...

            GetConsoleScreenBufferInfo(hStdOut, &screenBufferInfo);                 
            if (screenBufferInfo.dwCursorPosition.Y + 4 > screenBufferInfo.srWindow.Bottom) {                                
                printf("Tekan sembarang tombol untuk melanjutkan...");
                getchar();
                system("cls");
            }
        }

        ...
    }
    return 0;
}
...

Sekarang, hasil dari program sudah mulai bisa dimengerti. Tapi informasi yang ditampilkan rasanya terlalu banyak. Oleh sebab itu, saya akan membatasi hanya menampilkan Reason yang berupa USN_REASON_FILE_CREATE, USN_REASON_FILE_DELETE dan USN_REASON_RENAME_NEW_NAME. Selain itu, saya akan menampilkan Reason yang saat ini dalam bentuk bilangan heksadesimal menjadi tulisan yang lebih mudah dimengerti. Untuk itu, saya melakukan perubahan kode program seperti berikut ini:

...
cariUSN.ReasonMask = USN_REASON_FILE_CREATE | USN_REASON_FILE_DELETE | USN_REASON_RENAME_NEW_NAME;
...
printf("USN        : %#020llxn", record->Usn);
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : ");
if (record->Reason & USN_REASON_CLOSE) {
    printf(" CLOSE");
}
if (record->Reason & USN_REASON_FILE_CREATE) {
    printf(" FILE_CREATE");
}
if (record->Reason & USN_REASON_FILE_DELETE) {
    printf(" FILE_DELETE");
}
if (record->Reason & USN_REASON_RENAME_NEW_NAME) {
    printf(" RENAME_NEW_NAME");
}
printf("nn");
...

Sekarang, bila saya menjalankan program, saya akan memperoleh hasil berupa riwayat file yang dibuat atau dihapus pada volume C: seperti yang terlihat pada gambar berikut ini:

Tampilan program saat dijalankan

Tampilan program saat dijalankan

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: