Memakai WMI Event Di PowerShell

Pada WMI, terdapat jenis class yang disebut sebagai event class. Class seperti ini dipakai untuk menandakan bahwa sesuatu telah terjadi. Contohnya adalah class seperti Win32_DeviceChangeEvent, Win32_VolumeChangeEvent, dan sebagainya. Pada artikel ini, saya akan membuat sebuah script PowerShell yang akan mengerjakan aksi tertentu bila pengguna menghubungkan USB Flash Drive (UFD) pada komputer.

Saya dapat menggunakan event class Win32_DeviceChangeEvent untuk memeriksa apakah ada perangkat yang dihubungkan ke komputer. Bila nilai EventType dari Win32_DeviceChangeEvent adalah 2, maka ada perangkat yang dihubungkan ke USB dan bila nilai EventType adalah 3 maka ada perangkat yang dilepaskan dari port USB. Untuk mendaftarkan aksi untuk event class tertentu, saya dapat menggunakan perintah Register-WmiEvent di PowerShell. Sebagai contoh, gambar berikut ini memperlihatkan perintah PowerShell yang akan mencetak tulisan ke layar bila ada perangkat yang dihubungkan ke komputer:

Script yang mencetak tulisan bila ada perangkat yang dihubungkan ke USB

Script yang mencetak tulisan bila ada perangkat yang dihubungkan ke USB

Setelah perintah Register-WmiEvent diberikan, ia akan mendaftarkan sebuah event job yang akan terus bekerja di balik layar. Untuk menghapus atau mematikan event job tersebut, saya menggunakan perintah Remove-Job.

Sekarang, saya akan mengubah aksi pada event job menjadi sesuatu yang lebih rumit dibandingkan sekedar mencetak dengan Write-Host. Sebagai latihan, saya ingin men-copy file bernama virus.txt ke seluruh removable disk di komputer setiap kali ada perangkat USB yang dihubungkan ke komputer. Untuk memperoleh informasi mengenai removable disk di komputer, saya dapat menggunakan perintah PowerShell seperti berikut ini:

PS C:\> (gwmi Win32_LogicalDisk -Filter "DriveType=2").DeviceId

Untuk men-copy file, saya dapat menggunakan perintah Copy-Item atau aliasnya seperti cp dan copy. Sebagai contoh, perintah PowerShell berikut ini akan menghasilkan event job yang akan men-copy file virus.txt ke seluruh removable disk setiap kali ada perangkat USB yang dihubungkan ke komputer:

Script yang men-copy file ke semua removeable drive bila ada perangkat yang dihubungkan ke USB

Script yang men-copy file ke semua removeable drive bila ada perangkat yang dihubungkan ke USB

Salah satu masalah yang timbul pada perintah di atas adalah event job akan dikerjakan berkali-kali (terlihat dari output yang duplikat). Untuk mengatasinya, karena saya hanya tertarik pada penggunaan perangkat yang menyebabkan penambahan drive baru, maka saya dapat menggunakan sebuah turunan dari class Win32_DeviceChangeEvent, yaitu Win32_VolumeChangeEvent. Dengan demikian, saya dapat mengubah perintah yang saya pakai menjadi seperti berikut ini:

PS C:\> Register-WmiEvent -Query "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2" -Action `
>> {
>>   $driveBaru = $Event.SourceEventArgs.NewEvent.DriveName
>>   cp -PassThru C:\virus.txt $driveBaru\virus.txt | `
>>      %{Write-Host "Berhasil men-copy $_"}
>> }
>>

Kali ini, proses pen-copy-an file tidak akan dikerjakan berkali-kali melainkan hanya akan dikerjakan sekali saja.

Agar perintah PowerShell di atas dapat dikerjakan ulang secara mudah, saya akan membuat versi script-nya. Saya dapat menggunakan PowerShell ISE seperti yang terlihat pada gambar berikut ini:

Script yang akan men-copy file ke drive yang baru terdeteksi

Script yang akan men-copy file ke drive yang baru terdeteksi

Saya menyimpan script tersebut dengan nama copy_usb.ps1. Saya juga melakukan sedikit perubahan pada script karena Windows PowerShell ISE memiliki keterbatasan dimana Copy-Item tidak dapat mengenali drive baru (padahal ini bekerja dengan baik bila diberikan secara langsung di shell). Untuk mengatasi hal tersebut, saya mengganti pemanggilan Copy-Item dengan perintah copy dari Command Prompt. Selain itu, saya juga menambahkan loop tak terhingga agar script ini tidak pernah selesai dikerjakan selama sistem operasi belum di-shutdown atau selama tidak terjadi kesalahan. Bila saya ingin script tetap lanjut dikerjakan bila terjadi kesalahan, saya perlu menambahkan -ErrorAction SilentlyContinue pada perintah Register-WmiEvent.

Agar script tersebut dikerjakan secara otomatis setiap kali komputer dinyalakan, saya akan melakukan konfigurasi dengan menjalankan gpedit.msc. Pada tampilan Local Group Policy Editor yang muncul, saya memilih User Configuration, Windows Settings, Scripts (Logon/Logoff), dan men-double click pada Logon. Pada dialog yang muncul, saya memilih tab PowerShell Scripts dan men-klik tombol Add… untuk menambahkan file script yang telah saya buat sebelumnya. Hasilnya akan terlihat seperti pada gambar berikut ini:

Menjalankan PowerShell Scripts secara otomatis

Menjalankan PowerShell Scripts secara otomatis

Sekarang, setiap kali saya menyalakan dan login ke sistem operasi Windows, script PowerShell di atas akan dijalankan secara otomatis, seperti yang terlihat pada tampilan Process Explorer berikut ini:

Script dijalankan setiap kali komputer dinyalakan dan pengguna login

Script dijalankan setiap kali komputer dinyalakan dan pengguna login

Iklan

PowerShell Yang Lebih Bertenaga Berkat WMI

Pada artikel Apa itu Windows Management Instrumentation (WMI), saya berkenalan dengan teknologi WMI di Windows. WMI Provider menyediakan class berisi property dan method yang dapat dipanggil secara langsung di PowerShell. Memakai class dan objek WMI secara langsung memang merupakan salah satu kelebihan PowerShell yang tidak dimiliki oleh Command Prompt. PowerShell 3 di Windows 8 dilengkapi dengan perintah Get-CimClass yang dapat dipakai untuk mendapatkan informasi mengenai class WMI beserta method dan property yang tersedia di class tersebut seperti pada gambar berikut ini:

PowerShell

PowerShell

Daftar seluruh class yang ada dapat dijumpai di http://msdn.microsoft.com/en-us/library/aa394084.aspx. Secara teknis, yang lebih sering dipakai adalah objek (instance dari class). Untuk memperoleh daftar objek WMI yang ada, saya dapat menggunakan Get-WmiObject seperti pada gambar berikut ini:

PowerShell

PowerShell

Pada artikel ini, saya akan mencoba memakai beberapa objek WMI yang disediakan oleh Windows di PowerShell. Sebagai contoh, untuk melihat informasi BIOS di komputer, saya dapat memeriksa objek Win32_BIOS seperti pada gambar berikut ini:

PowerShell

PowerShell

Pada perintah di atas, saya memakai gwmi yang merupakan alias untuk Get-WmiObject.

Untuk mendapatkan informasi tanggal, saya dapat membaca objek Win32_LocalTime seperti pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai sistem operasi Windows yang sedang aktif, saya dapat membaca informasi dari instance Win32_OperatingSystem seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai komputer, saya dapat membaca property dari instance Win32_ComputerSystem seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai layar, saya dapat membaca property dari instance Win32_VideoController seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai partisi di hard drive, saya dapat membaca property dari instance Win32_DiskPartition seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi software yang ter-install di Windows, saya dapat membaca property dari instance Win32_Product. Selain itu, saya juga dapat memanggil method yang ada untuk melakukan manipulasi software tersebut. Sebagai contoh, saya dapat memanggil method Uninstall untuk menghapus software yang bersangkutan, seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai network adapter, saya dapat membaca property dari instance Win32_NetworkAdapter. Selain itu, saya dapat memanggil method Enable() atau Disable() untuk mengaktifkan dan mematikan network adapter yang bersangkutan. Sebagai contoh, saya dapat mematikan atau mengaktifkan seluruh network adapter yang ada dengan perintah seperti pada gambar berikut ini:

PowerShell

PowerShell

Membuat Script Untuk Windows PowerShell

Pada Windows Command Prompt, batch file yang berakhiran .bat adalah script yang berisi kumpulan baris perintah DOS yang akan dikerjakan sebagai satu kesatuan. Windows PowerShell juga memiliki hal serupa dalam bentuk file berakhiran .ps1. Selain itu, Windows juga sudah dilengkapi dengan Windows PowerShell Integrated Scripting Environment (ISE) yang merupakan sebuah GUI untuk menulis script PowerShell. Berikut ini adalah contoh tampilan PowerShell ISE:

Tampilan PowerShell ISE

Tampilan PowerShell ISE

Walaupun sebuah script dapat dijalankan di PowerShell ISE, belum tentu ia dapat dijalankan langsung di PowerShell. Hal ini karena secara default, Windows tidak membolehkan eksekusi script PowerShell, seperti yang terlihat pada contoh berikut ini:

PS C:\> .\latihan.ps1
.\latihan.ps1 : File C:\latihan.ps1 cannot be loaded because running scripts is disabled on this sy
stem. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ .\latihan.ps1
+ ~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

Untuk membolehkan eksekusi script PowerShell secara global, saya perlu menjalankan PowerShell sebagai Administrator dan memberikan perintah seperti:

PS C:\> Set-ExecutionPolicy Unrestricted

Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
http://go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): Y

Windows menyediakan beberapa execution policy seperti Restricted, AllSigned, RemoteSigned, Unrestricted, Bypass, dan Defined. Yang disarankan adalah AllSigned dimana yang boleh dijalankan hanya script PowerShell yang telah di-beri informasi sertifikat digital (dibagian akhir file, dalam bentuk komentar). Bypass adalah execution policy yang paling leluasa.

Selain mengubah execution policy secara global melalui Set-ExecutionPolicy, saya juga dapat menjalankan script dengan memanggil PowerShell dari Command Prompt seperti berikut ini:

C:\> PowerShell -ExecutionPolicy Unrestricted ./latihan.ps1

Seperti apa bentuk script di PowerShell?

Script pada PowerShell mendukung variabel yang selalu diawali dengan tanda dollar seperti $nama dan $proses, seperti yang terlihat pada contoh berikut ini:

$nama = "solid snake"
Write-Host -ForegroundColor Yellow $nama

Sebuah variabel dapat menampung lebih dari satu nilai yang dipisahkan dengan tanda koma. Untuk melakukan looping pada nilai tersebut (array), saya dapat menggunakan foreach seperti pada:

$nama = "solid snake"
$daftarWarna = "Yellow", "Green", "Gray", "Black", "Blue"
foreach($warna in $daftarWarna) {
    Write-Host -ForegroundColor $warna $nama
}

Script di PowerShell bisa menggunakan while, do...while, do...until, for, if, dan switch seperti pada contoh berikut ini:

[int] $i = 1

while ($i -lt 10) {
    "Nilai `$i adalah $i"
    $i++
}

# hasilnya sama dengan:

$i = 1
do {
    "Nilai `$i adalah $i"
    $i++
} until ($i -ge 10)

# hasilnya sama dengan:

foreach($i in 1..9) {
    "Nilai `$i adalah $i"
}

# hasilnya sama seperti:

for($i=1; $i -le 9; $i++) {
    "Nilai `$i adalah $i"
}

Pada ekspresi boolean di script PowerShell, saya melakukan perbandingan dengan operator seperti -eq (sama dengan), -gt (lebih dari), -ge (lebih dari sama dengan), -like, -match dan sebagainya.

Selain itu, PowerShell juga mendukung module, function dan filter. Perbedaan antara function dan filter akan terlihat pada saat melakukan piping. Pada saat melakukan piping ke function, perintah di bagian kiri akan diselesaikan terlebih dahulu baru kemudian function di sebelah kanan dikerjakan. Beda dengan function, filter tidak menunggu hingga perintah di bagian kiri selesai, melainkan dikerjakan untuk setiap iterasi. Sebagai contoh, berikut ini adalah contoh penggunaan filter dalam script untuk menghapus cache Griffon yang sudah tidak ada proyeknya lagi:

Contoh Filter Di PowerShell

Contoh Filter Di PowerShell

Memakai PowerShell Bawaan Windows

Shell berbasis CLI yang sangat terkenal di Windows adalah Command Prompt. Shell yang dulunya dikenal sebagai MS-DOS Prompt ini dapat dipakai untuk mengerjakan perintah DOS seperti dir, cd, del dan sebagainya. DOS sudah lama punah. Oleh sebab itu, Windows juga dilengkapi dengan shell yang lebih canggih yang disebut sebagai Windows PowerShell. Windows 7 sudah dilengkapi dengan PowerShell 2, Windows 8 dilengkapi PowerShell 3 dan Windows 8.1 dilengkapi PowerShell 4. Walaupun demikian, tampaknya popularitas Command Prompt tidak akan dapat digeser oleh PowerShell.

Pada kesempatan ini, saya akan mencoba memakai beberapa perintah PowerShell. Untuk membuka shell tersebut, saya men-klik Start Menu, memilih All Programs, Accessories, Windows PowerShell dan men-klik icon Windows PowerShell. Saya kemudian dapat memberikan perintah PowerShell seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Terlihat bahwa saya dapat memberikan dir dan cd seperti pada Command Prompt. Bedanya, pada PowerShell, segala sesuatunya adalah object (memiliki method dan properties). Perintah yang paling umum yang diberikan di PowerShell berada dalam bentuk cmdlet. Berbeda dengan perintah Command Prompt yang bersifat statis, cmdlet di-implementasi-kan dalam bentuk class .NET yang dapat ditambahkan oleh pengguna atau pihak ketiga.

Perintah dir dan cd yang saya pakai sebenarnya hanya sebuah alias ke cmdlet Get-ChildItem dan Set-Location. Untuk membuktikannya, saya dapat memberikan perintah Get-Alias seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Jadi, perintah dir yang saya berikan sama saja dengan memanggil Cmdlet Get-ChildItem.

Pada Command Prompt, saya dapat men-format output dari dir dengan memberikan argumen seperti dir/w, dir/p dan sebagainya. Lalu bagaimana dengan PowerShell? Saya dapat menggunakan pipe operator yang merujuk ke Cmdlet dengan verb Format seperti Format-Wide seperti yang terlihat pada contoh berikut ini:

Memakai PowerShell

Memakai PowerShell

Sebagai contoh lainnya, saya dapat menggunakan Get-ChildItem untuk menampilkan seluruh file JAR yang ada di direktori dan subdirectori saat ini seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Contoh lainnya, saya dapat menampilkan file JAR yang diurutkan berdasarkan ukuran file dari yang terbesar hingga terkecil, seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Salah satu hal menarik yang saya suka dari PowerShell adalah Cmdlet Out-GridView yang dapat menampilkan object dalam bentuk tabel GUI. Sebagai contoh, saya dapat menampilkan hasil Get-ChildItem dalam bentuk GUI dengan menggunakan perintah seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Selain itu, saya juga dapat mengisi argumen untuk sebuah Cmdlet melalui GUI dengan menggunakan Show-Command seperti pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Segala sesuatu yang diproses dalam PowerShell adalah objek. Untuk melihat property dan method yang dihasilkan, saya dapat menggunakan Get-Member seperti:

PS C:\simple-jpa-demo-inventory> Get-ChildItem | Get-Member


   TypeName: System.IO.DirectoryInfo

Name                      MemberType     Definition
----                      ----------     ----------
Mode                      CodeProperty   System.String Mode{get=Mode;}
Create                    Method         void Create(), void Create(System.Security.AccessControl.DirectorySecurity ...
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreateSubdirectory        Method         System.IO.DirectoryInfo CreateSubdirectory(string path), System.IO.Director...
Delete                    Method         void Delete(), void Delete(bool recursive)
EnumerateDirectories      Method         System.Collections.Generic.IEnumerable[System.IO.DirectoryInfo] EnumerateDi...
EnumerateFiles            Method         System.Collections.Generic.IEnumerable[System.IO.FileInfo] EnumerateFiles()...
EnumerateFileSystemInfos  Method         System.Collections.Generic.IEnumerable[System.IO.FileSystemInfo] EnumerateF...
Equals                    Method         bool Equals(System.Object obj)
GetAccessControl          Method         System.Security.AccessControl.DirectorySecurity GetAccessControl(), System....
GetDirectories            Method         System.IO.DirectoryInfo[] GetDirectories(), System.IO.DirectoryInfo[] GetDi...
GetFiles                  Method         System.IO.FileInfo[] GetFiles(string searchPattern), System.IO.FileInfo[] G...
GetFileSystemInfos        Method         System.IO.FileSystemInfo[] GetFileSystemInfos(string searchPattern), System...
GetHashCode               Method         int GetHashCode()
GetLifetimeService        Method         System.Object GetLifetimeService()
GetObjectData             Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, Sys...
GetType                   Method         type GetType()
...

   TypeName: System.IO.FileInfo

Name                      MemberType     Definition
----                      ----------     ----------
Mode                      CodeProperty   System.String Mode{get=Mode;}
AppendText                Method         System.IO.StreamWriter AppendText()
CopyTo                    Method         System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(s...
Create                    Method         System.IO.FileStream Create()
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreateText                Method         System.IO.StreamWriter CreateText()
Decrypt                   Method         void Decrypt()
Delete                    Method         void Delete()
...

Cmdlet Get-ChildItem pada perintah di atas akan mengembalikan array yang berisi System.IO.DirectoryInfo dan/atau System.IO.FileInfo.

Get-ChildItem tidak hanya dipakai untuk mengakses file system, tapi juga layanan yang ditawarkan oleh provider lainnya dalam bentuk analogi drive dan file. Untuk melihat apa saja provider yang tersedia, saya dapat menggunakan Get-PSProvider seperti pada contoh berikut ini:

PS C:\> Get-PSProvider

Name                 Capabilities                                      Drives
----                 ------------                                      ------
Alias                ShouldProcess                                     {Alias}
Environment          ShouldProcess                                     {Env}
FileSystem           Filter, ShouldProcess, Credentials                {C}
Function             ShouldProcess                                     {Function}
Registry             ShouldProcess, Transactions                       {HKLM, HKCU}
Variable             ShouldProcess                                     {Variable}

Sampai disini, yang saya akses adalah file dan direktori yang disediakan oleh FileSystem provider. Provider yang berbeda akan menawarkan drive yang berbeda. Untuk melihat apa saja yang dapat dipakai sebagai drive, saya dapat menggunakan Get-PSDrive seperti pada contoh berikut ini:

PS C:\simple-jpa-demo-inventory> Get-PSDrive

Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
Alias                                  Alias
C                1165,11        917,46 FileSystem    C:\                                      simple-jpa-demo-inventory
Cert                                   Certificate   \
D                                      FileSystem    D:\
Env                                    Environment
Function                               Function
HKCU                                   Registry      HKEY_CURRENT_USER
HKLM                                   Registry      HKEY_LOCAL_MACHINE
Variable                               Variable
WSMan                                  WSMan

Sebagai contoh, saya dapat membaca isi registry dengan perintah seperti pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

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

Isi File Yang Tak Terlihat Di NTFS Alternative Data Stream

Secara fisik, sebuah media penyimpanan seperti hard disk drive (HDD) menyimpan data dalam bentuk byte per byte. Satuan terkecil yang dapat dialokasikan adalah sebuah sector. Pada kebanyakan HDD, 1 sector terdiri atas 512 byte. HDD dengan fitur Advanced Format (AF) memungkinkan 1 sector terdiri atas 4K byte guna meningkatkan efisiensi pada ukuran yang besar. Tapi pengguna tidak menulis dan membaca sector secara langsung. Sistem operasi menawarkan sebuah lapisan abstraksi yang disebut sebagai file. Sebuah file dapat menempati satu atau lebih sector di harddisk. Kenapa tidak langsung menyimpan data ke sector? Karena pengguna awam tidak ingin menghafal nomor sector ๐Ÿ™‚ File lebih mudah dipakai karena ia memiliki konsep direktori dan metadata seperti nama dan ukuran.

Tata cara pengelolaan file oleh sistem operasi disebut sebagai file system. Contoh file system yang menjadi legenda di Windows adalah File Allocation Table (FAT). FAT sudah ada sejak Microsoft lahir dimana ia dirancang oleh seorang karyawan pertama di Microsoft. Tidak ada perubahan signifikan pada file system ini selain mengubah pengenal cluster menjadi 32-bit pada FAT32. Walaupun demikian, FAT32 masih populer hingga sekarang, padahal di Windows 7, Microsoft telah menciptakan penerus FAT yang disebut sebagai exFAT (Extended File Allocation Table). exFAT lebih efisien untuk flash drive dan memory card. Tapi penggunaan FAT32 masih bisa dijumpai dengan mudah di media penyimpanan kamera digital, MP3 player murah, dan sebagainya. Lisensi menjadi masalah besar karena perangkat yang memakai exFAT tanpa perjanjian kerja sama dengan Microsoft dapat dituntut ๐Ÿ˜‰ Ini adalah pukulan keras bagi dunia open-source karena exFAT adalah file system ‘rahasia’ dimana orang-orang tidak bisa tahu apa yang sesungguhnya disimpan.

Bila exFAT ditujuan untuk flash drive, maka NTFS (New Technology File System) adalah penerus FAT untuk media penyimpanan permanen seperti HDD. Beruntungnya, NTFS tidak dilindungi oleh paten. Selain itu, hampir semua informasi NTFS sudah di-reverse engineer dan hasilnya telah dipublikasikan secara umum. Ini sebabnya Linux bisa mendukung NTFS secara resmi dalam bentuk driver NTFS-3g. Linux sendiri memiliki file system andalannya yang disebut sebagai ext4.

Jadi, sebuah file adalah kumpulan byte yang mewakili sebuah data yang tersimpan, bukan? Ini adalah pandangan yang normal di UNIX dan turunannya dimana segala sesuatunya adalah file. Tapi pada beberapa sistem operasi lain seperti Windows dan Mac OS, sebuah file dapat terdiri atas satu atau lebih data stream (disebut juga fork). Pada artikel ini, saya akan melihat contoh file yang memiliki lebih dari satu stream di NTFS.

Saya mulai dengan membuat sebuah file bernama latihan.txt secara biasa seperti pada gambar berikut ini:

Membuat file biasa

Membuat file biasa

Sekarang, saya akan menulis ke file yang sama tetapi pada stream yang berbeda seperti yang terlihat pada gambar berikut ini:

Menambahkan isi  file pada alternate data stream

Menambahkan isi file pada alternate data stream

Terlihat bahwa sekarang file latihan.txt memiliki dua isi yang berbeda.

Isi pertama adalah isi di main data stream yang terlihat di perintah dir dan Explorer. Bila saya melihat di Explorer, tetap hanya ada sebuah file dengan ukuran 25 bytes. Bila saya men-double click file tersebut, maka notepad akan muncul dengan isi sesuai pada main data stream. Hal ini menunjukkan bahwa pengguna selalu bekerja pada main data stream pada aktifitas hariannya.

Isi yang kedua adalah isi yang tersimpan di alternate data stream bernama rahasia. Banyak pengguna yang tidak menyadari adanya ‘isi sampingan’ dari sebuah file. Hal ini karena perintah dir, Explorer, dan aplikasi seperti Notepad dan Word selalu bekerja pada main data stream.

Untuk melihat apakah sebuah file memiliki alternate data stream atau tidak, saya dapat menggunakan perintah dir/r seperti yang terlihat pada gambar berikut ini:

Melihat file dengan alternate data stream

Melihat file dengan alternate data stream

Lalu apa gunanya alternate data stream? Salah satu fitur Windows yang aktif menggunakannya adalah Attachment Execution Service. Fitur ini akan menampilkan peringatan keamanan pada saat pengguna menjalankan sebuah program yang di-download dari Internet. Untuk mengenali apakah sebuah file berasal dari Internet, Windows menambahkan sebuah stream bernama Zone.Identifier pada executable file yang di-download.

Saya akan mencoba mensimulasikan perilaku tersebut pada sebuah file exe biasa dengan menambahkan stream Zone.Identifier seperti yang terlihat pada gambar berikut ini:

Menambahkan alternate data stream Zone.Identifier pada file exe

Menambahkan alternate data stream Zone.Identifier pada file exe

Sekarang, bila saya menjalankan file latihan.exe, saya akan memperoleh pesan peringatan keamanan seperti berikut ini:

Windows memakai alternate data stream untuk mengenali file yang di-download dari internet

Windows memakai alternate data stream untuk mengenali file yang di-download dari internet

Sebuah contoh lainnya adalah penggunaan ‘iseng’ adalah dengan menyertakan informasi pengenal pada file exe yang saya sebarkan. Anggap saja saya memiliki file latihan.exe yang hanya saya berikan kepada Perusahaan A, Perusahaan B, dan PerusahaanC. Saya dapat menambahkan informasi lisensi dengan membuat stream lisensi.txt yang berisi nama perusahaan setiap kali mendistribusikan sebuah file latihan.exe seperti yang terlihat pada gambar berikut ini:

Menambah data teks pada file exe dengan alternate data stream

Menambah data teks pada file exe dengan alternate data stream

Bila seseorang men-copy file tersebut ke pihak lain, maka isi dari stream lisensi.txt akan turut di-copy (selama masih memakai NTFS). Dengan demikian, bila terdapat kebocoran, saya dapat memeriksa isi stream lisensi.txt untuk mencari tahu siapa yang membocorkan seperti yang terlihat pada gambar berikut ini:

Membaca data teks pada file exe

Membaca data teks pada file exe

Tentu saja cara ini hanya cara ‘iseng’ yang tidak untuk keperluan serius. Isi dari alternate data stream hanya akan dipertahankan bila file di-copy ke sesama file system NTFS. Bila di-copy ke file system FAT32, maka isi alternate data stream akan hilang. Selain itu, pengguna mahir bisa dengan mudah melihat isi file secara fisik per sector tanpa abstraksi file system.

Membuat Program C# Yang Membaca Data UserAssist

Sistem operasi Windows 7 adalah sebuah sistem operasi yang ‘pintar’. Contohnya, Windows memiliki fitur SuperFetch yang akan menganalisa pola penggunaan program berdasarkan hari dan jam sehingga Windows dapat menebak file-file apa saja yang mungkin akan dipakai pengguna. Berdasarkan informasi tersebut, Windows akan mengisi file ke memori sebelum dipakai sehingga mengurangi kemungkinan page fault. Selain itu, Windows juga terkadang sibuk mengurusi log NTFS di balik layar. Tergantung pada penggunanya, perilaku ‘pintar’ tidak selalu positif. Beberapa pengguna yang ingin punya kendali penuh bisa saja tidak senang dengan Windows yang suka sibuk sendiri tanpa disuruh.

Contoh ‘kepintaran’ Windows 7 yang bisa menuju ke arah berbahaya adalah ia selalu mencatat jumlah eksekusi sebuah program serta kapan sebuah program terakhir kali dijalankan ke lokasi registry HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist. Walaupun program dihapus, catatan ini tetap akan ada selama-lamanya ๐Ÿ™‚ Dengan fitur ini, Windows 7 dapat menampilkan program populer yang sering dipakai di menu Start. Masalahnya adalah banyak yang tidak tahu bahwa Windows 7 menyimpan catatan seperti ini. Sepertinya Microsoft berusaha menyembunyikan isi registry tersebut dari tangan jahil karena ia menyamarkan isi registry UserAssist tersebut dengan enkripsi ROT13. Ini adalah jenis enkripsi tidak aman yang sangat mudah dipecahkan; ROT13 hanya melakukan subtitusi huruf seperti A menjadi N, B menjadi O, C menjadi P, dan seterusnya. Walaupun mudah dipecahkan, penggunakan enkripsi ROT13 dan nilai dalam bentuk binary membuat saya sulit mencerna isi dari registry UserAssist dengan mudah.

Oleh sebab itu, saya akan mencoba membuat sebuah program C# yang akan membaca isi registry UserAssist tersebut dan menampilkan informasi dalam bentuk tabel yang lebih mudah dicerna. Program ini adalah sebuah aplikasi WPF yang saya ujikan pada Windows 7 32-bit dan 64-bit. Kode program secara lengkap dapat ditemukan di http://github.com/JockiHendry/ProgramExecutionCounter.

Kode program yang saya buat untuk membaca dari registry terlihat seperti berikut ini:

...
private void OnSearch()
{            
   countEntries.Clear();

   RegistryKey reg = Registry.CurrentUser.OpenSubKey(SelectedSourceType.Key);
   foreach (string valueName in reg.GetValueNames())
   {                
      CountEntry entry = new CountEntry();
      entry.Name = valueName;

      // filter by name
      if (!string.IsNullOrEmpty(NameFilter))
      {
          if (!entry.DecodedName.ToUpper().Contains(NameFilter.ToUpper())) continue;
      }

      entry.Value = (byte[]) reg.GetValue(valueName);
      entry.RegKey = reg.ToString();
      ...
      countEntries.Add(entry);
   }
}  
...      

Pada kode program di atas, saya memakai class Registry dari namespace Microsoft.Win32 untuk membaca isi registry. Saya kemudian melakukan konversi masing-masing value yang saya temukan menjadi sebuah object CountEntry.

Pada class CountEntry, saya menerjemahkan nama yang dienkripsi dengan algoritma ROT13 dengan menggunakan method dictionary lookup. Saya memilih cara ini karena lebih mudah dan jumlah kombinasi yang ada sangat sedikit (A-Z, a-z). Selain itu, nama di registry UserAssist juga mengandung GUID yang mewakili folder spesial di Windows (seperti MyComputer, Program Files, dan sebagainya). Saya bisa memperoleh informasi GUID untuk folder spesial di Windows dengan melihat isi header KnownFolders.h yang terdapat di folder Include di Windows SDK. Setelah membuat variabel static yang berisi dictionary lookup untuk ROT13 dan daftar terjemahan GUID folder, saya kemudian memulai proses dekripsi dengan kode program seperti berikut ini:

...
public String DecodedName
{
    get
    {
        if (string.IsNullOrEmpty(Name))
        {
            return "";
        }
        else
        {
            string result = new string(Name.ToCharArray().Select(c =>
            {
                return lookupTable.ContainsKey(c) ? lookupTable[c] : c;
            }).ToArray());
            foreach (var f in folderGUID)
            {
                if (result.Contains(f.Key)) result = result.Replace(f.Key, f.Value);
            }
            return result;
        }
    }
}
...

Nilai dari setiap key di registry UserAssist adalah deretan byte sebesar 72 byte. Tidak ada yang tahu persis apa saja informasi yang tersimpan, selain Microsoft selaku pencipta Windows (rasa waspada ini tidak perlu ada bila memakai sistem operasi open-source ๐Ÿ˜‰ ). Berdasarkan informasi yang diperoleh dari hasil pencarian Google, saya hanya akan mengambil 4 byte mulai dari posisi ke-4 yang mewakili jumlah eksekusi program dalam bilangan integer 32-bit. Untuk mengubah deretan byte menjadi sebuah int, saya memakai class BitConverter seperti yang terlihat pada kode program berikut ini:

...
public byte[] Value
{
    get
    {
        return this.value;
    }

    set
    {
        this.value = value;
        executionCount = BitConverter.ToInt32(value, 4);
    }
}
...

Tool sederhana ini juga memungkinkan pengguna untuk mengubah jumlah eksekusi secara langsung dari tabel. Untuk menerjemahkan bilangan int yang dimasukkan oleh pengguna menjadi byte array, saya kembali menggunakan class BitConverter dengan memanggil method GetBytes() seperti yang terlihat pada kode program berikut ini:

...
if (e.PropertyName == "ExecutionCount")
{
    try
    {                            
        byte[] newCount = BitConverter.GetBytes(countEntry.ExecutionCount);
        newCount.CopyTo(countEntry.Value, 4);
        Registry.SetValue(entry.RegKey, entry.Name, entry.Value);
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error Updating Registry: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}
...

Pada kode program diatas, saya memanggil method SetValue() dari class Registry untuk menulis perubahan ke registry.

Selain mengubah, tool ini juga memungkinkan pengguna untuk menghapus entry yang dipilihnya. Untuk itu saya perlu memanggil method DeleteValue() dari sebuah RegistryKey untuk menghapus nilai yang dipilih oleh pengguna, seperti yang terlihat pada kode program berikut ini:

public void OnDelete()
{
    if (MessageBox.Show("Do you really want delete this entry?", "Delete Confirmation",
        MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
    {
        RegistryKey key = Registry.CurrentUser.OpenSubKey(RegKey.Replace(@"HKEY_CURRENT_USER", ""), true);
        if (key != null)
        {
            try
            {
                key.DeleteValue(Name);
                PropertyChanged(this, new PropertyChangedEventArgs("DeleteCommand"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error Deleting Registry: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

    }
}

Sekarang, saya akan mencoba menjalankan program. Pada saat saya men-klik tombol Search, saya akan memperoleh hasil seperti pada gambar berikut ini:

Menampilkan seluruh entry yang ada

Menampilkan seluruh entry yang ada

Saya dapat langsung mengubah nilai jumlah eksekusi dengan men-double klik pada kolom angka, mengisi angka baru dan menekan tombol Enter, seperti yang terlihat pada gambar berikut ini:

Mengubah nilai registry untuk jumlah eksekusi

Mengubah nilai registry untuk jumlah eksekusi

Selain itu, saya dapat langsung menghapus sebuah entry dengan men-klik icon dengan tanda silang di baris yang bersesuaian, seperti yang terlihat pada gambar berikut ini:

Menghapus nilai dari registry

Menghapus nilai dari registry

Saya juga dapat menampilkan informasi detail untuk sebuah baris dengan men-klik icon kaca pembesar, seperti yang terlihat pada gambar berikut ini:

Menampilkan informasi detail untuk sebuah entry

Menampilkan informasi detail untuk sebuah entry

Lalu, apa manfaat dari tool sendiri ini? Karena registry UserAssist ini akan tetap menyimpan informasi program yang dijalankan walaupun program sudah dihapus atau di-uninstall, maka saya dapat menggunakannya untuk memeriksa apakah pengguna pernah menjalankan sebuah program atau tidak. Fungsi lainnya, misalnya saya dapat memeriksa apa saja program yang pernah dijalankan langsung melalui flash disk (dengan men-klik di Explorer) dengan fasilitas pencarian seperti pada gambar berikut ini:

Melakukan pencarian

Melakukan pencarian

Apakah kali ini ‘kepintaran’ Windows dirasa membuat pengguna khawatir dengan privasi mereka? Beruntungnya, Windows menyediakan fitur untuk mematikan fasilitas ini. Saya dapat menghilangkan pencatatan ini dengan men-klik kanan pada task bar yang kosong, memilih menu Properties. Pada dialog yang muncul, saya memilih tab Start Menu dan menghilangkan tanda centang pada Store and display recently opened programs in the Start menu seperti yang terlihat pada gambar berikut ini:

Menghilangkan pencatatan pada registry UserAssist

Menghilangkan pencatatan pada registry UserAssist

Memahami Manajemen Memori Fisik Di Windows 7

Pada artikel sebelumnya, Memahami Address Translation Di Windows, saya menunjukkan bahwa masing-masing proses di Windows mengakses virtual address yang dipetakan ke physical address. Pemetaan ini berlaku unik untuk setiap proses yang ada karena masing-masing proses memiliki page table-nya sendiri. Dengan demikian, walaupun Notepad.exe mengakses alamat virtual X dan Paint.exe mengakses alamat virtual X yang sama, lokasi physical address yang dituju bisa saja berbeda. Untuk melihat mapping dari physical address ke virtual address secara lebih mudah, saya dapat menggunakan tool RAMMap dari SysInternals Suite seperti yang terlihat pada gambar berikut ini:

Melihat pemetaan physical address ke virtual address dengan RAMMap

Melihat pemetaan physical address ke virtual address dengan RAMMap

Pada artikel ini, saya akan mencoba mempelajari bagaimana Windows mengelola main memory secara fisik (bukan berdasarkan apa yang dilihat oleh program). Windows membagi wilayah main memory (RAM) ke dalam bagian-bagian yang disebut sebagai page. Ukuran default untuk sebuah page adalah 4K (0x1000). Windows mencatat alokasi page untuk masing-masing proses kedalam lokasi yang disebut sebagai Page Frame Number (PFN) database. Saya dapat melihat isi PFN database dengan Windows Debugger seperti yang terlihat pada gambar berikut ini:

Melihat Isi PFN

Melihat Isi PFN

Pada eksekusi di atas, setelah memperoleh alamat PFN database, saya menampilkan isinya dimana setiap PFN terdiri atas 24 byte. Windows Debugger memiliki perintah !pfn yang bisa dipakai untuk menampilkan informasi PFN secara lebih mudah dimengerti. Karena setiap page berukuran 4K (0x1000), maka PFN 1 mulai dari physical address 0x1000, PFN 2 mulai dari physical address 0x2000, dan seterusnya.

PFN mengandung informasi state dari page di memori fisik yang bisa berupa salah dari active, transition, standby, modified, modified no-write, free, zeroed, rom, dan bad. Saya dapat menampilkan alokasi memori fisik berdasarkan statusnya dengan perintah !memusage seperti pada berikut ini:

kd> !memusage 8

loading PFN database
loading (100% complete)
Compiling memory usage data (99% Complete).
             Zeroed: 262475 (1049900 kb)
               Free:      5 (    20 kb)
            Standby:  25173 (100692 kb)
           Modified:   1632 (  6528 kb)
    ModifiedNoWrite:      0 (     0 kb)
       Active/Valid:  98211 (392844 kb)
         Transition:      2 (     8 kb)
                Bad:    484 (  1936 kb)
            Unknown:      0 (     0 kb)
              TOTAL: 387498 (1549992 kb)

Selain itu, saya juga dapat menampilkan informasi yang dalam bentuk grafis dengan tool RAMMap seperti yang terlihat pada gambar berikut ini:

Isi physical memory berdasarkan status page

Isi physical memory berdasarkan status page

Satu fakta menarik tentang Windows 7 versi 32-bit adalah walaupun saya memiliki RAM 4 GB, saya tidak akan pernah bisa mengakses seluruh memori fisik tersebut secara penuh. Mengapa demikian? Hal ini karena sistem operasi 32-bit hanya memungkinkan pengalamatan hingga maksimal 2^32 = 4 GB. Masalahnya, alamat tersebut tidak sepenuhnya dipakai untuk memori fisik (RAM), tetapi juga dibutuhkan untuk hardware lainnya seperti video card dan audio card.

Untuk membuktikannya, saya akan membuka Device Manager, lalu memilih menu View, Resource By Connection. Pada bagian Memory, saya menemukan hasil seperti berikut ini (khusus untuk hardware yang sedang saya pakai):

Pemetaan physical address

Pemetaan physical address

Pada gambar di atas, terlihat bahwa alamat 0xA0000 sampai 0xBFFFF telah dipetakan untuk keperluah chipset. Total dari wilayah ini adalah sebesar 0x20000 bytes (atau 131.072 bytes). Bukan hanya itu saja, juga ada alokasi alamat dari wilayah 0xD0000 sampai 0xDFFFF sebesar 0x10000 bytes (atau 65.536 bytes) dan alokasi dari wilayah 0xBDE00000 sampai 0xFFFFFFFF sebesar 0x42200000 bytes (atau 1 GB).

Dengan demikian, total wilayah yang telah dipakai untuk keperluan selain memori fisik mencapai lebih dari 1 GB. Hal ini menyebabkan sisa wilayah yang dapat dipakai untuk pengalamatan memori fisik menjadi sekitar 3 GB. Windows menampilkan informasi ini di System Properties seperti yang terlihat pada gambar berikut ini:

Windows 32-bit tidak dapat mengakses seluruh wilayah 4 GB memori

Windows 32-bit tidak dapat mengakses seluruh wilayah 4 GB memori

Masalah ini tidak terjadi pada sistem operasi 64-bit karena secara teoritis, pengalamatan dapat dilakukan hingga mencapai 2^64 = 16 EB (Exa Byte). Ini jauh lebih dari cukup untuk kebutuhan hardware saat ini. Pada kenyataannya, karena alasan efisiensi, prosesor modern saat ini hanya dapat mengakses hingga maksimal 4 PB. Dukungan ini akan terus ditingkatkan seiring waktu berjalan. Sistem operasi Windows 7 sendiri memiliki lisensi yang membatasi penggunaan memori. Sebagai contoh, lisensi Windows 7 Ultimate 64-bit hanya membolehkan penggunaan memori RAM hingga maksimal 192 GB.

Memahami Address Translation Di Windows

Pada sebuah PC, pengguna menyimpan program secara permanen pada media seperti hard disk drive (HDD). Ukuran hard disk yang tersedia saat ini mulai dari 20 GB hingga 2 TB. Sudah bisa ditebak, pasti ada banyak program yang dapat disimpan di hard disk. Lalu, bagaimana dengan fakta bahwa untuk menjalankan sebuah program, ia harus di-load ke main memory (RAM) terlebih dahulu? Ukuran sebuah keping tunggal main memory saat ini tersedia mulai dari 1 GB hingga 64 GB. Sebuah motherboard PC biasanya menyediakan maksimal 8 slot kosong. Walaupun demikian, ukuran main memory (RAM) tidak akan pernah menyamai ukuran hard disk drive (HDD)!

Pertanyaannya: seandainya saya men-install sebuah program berukuran 4 GB ke harddisk, kenapa saya bisa menjalankan program tersebut pada RAM yang hanya sebesar 1 GB? Hal ini karena sistem operasi menggunakan teknik paging untuk menciptakan sebuah ilusi bahwa PC memiliki main memory yang tidak terbatas. Ketika memori menjadi penuh, sistem operasi menyimpan page lama yang jarang diakses ke hard disk. Ketika program perlu mengakses page yang tidak ada di main memory, akan terjadi page fault. Sistem operasi memberikan respon dengan membaca page yang sebelumnya telah disimpan ke hard disk dan mengisinya di main memory. Semakin kecil ukuran main memory, maka kemungkinan page fault terjadi akan semakin besar. Hal ini terlihat dari lampu indikator hard disk yang semakin sering berkedip dimana efeknya adalah program berjalan lebih lambat.

Hal yang erat kaitannya dengan paging adalah virtual memory. Dengan virtual memory, program menganggap bahwa dirinya mengakses lokasi memori yang sama dan berurut. Padahal, lokasi memori ini adalah virtual address dan bukan lokasi memori yang sesungguhnya! Sistem operasi nantinya akan menerjemahkan dari virtual address ke lokasi memori fisik yang sesungguhnya. Dua program berbeda boleh saja merasa dirinya mengakses lokasi memori yang sama (secara kode program), tapi pada saat dijalankan, sistem operasi bisa saja memetakannya ke lokasi fisik yang berbeda di main memory. Dengan demikian, pembuat program tidak perlu khawatir dengan bentrokan alamat memori bila terdapat lebih dari satu program yang dijalankan secara bersamaan (multitasking).

Sebagai contoh, saya membuka Notepad dan mengetik “thesolidsnake”. Saya menemukan bahwa string tersebut disimpan di lokasi virtual address 0x0029E0F0. Bagaimana caranya Windows menerjemahkan virtual address ke physical address di main memory?

Eksperimen dengan Windows Debugger menunjukkan bahwa physical address untuk 0x0029E0F0 (pada proses notepad.exe) adalah 0x4b3140f0 seperti yang terlihat pada gambar berikut ini:

Mencari physical address dari virtual address

Mencari physical address dari virtual address

Pada gambar di atas, saya menggunakan perintah !vtop untuk melakukan translasi dari virtual address menjadi physical address. Kemudian, saya menggunakan perintah !dc yang menerima parameter berupa physical address untuk menampilkan isi memori pada alamat tertentu.

Bagaimana proses perhitungannya secara manual? Dengan anggapan bahwa sistem tidak mendukung PAE, saya akan mengubah nilai 0x0029E0F0 ke dalam bilangan binary dan membaginya menjadi seperti:

0000 0000 00 | 10 1001 1110  | 0000 1111 0000
|            |               |
|            |               |- Byte index
|            |
|            |- Page table index
|
|- Page directory index

Terlihat bahwa nilai dari page directory index adalah 0, nilai dari page table index adalah 0x29E, dan nilai dari byte index adalah 0xF0. Saya dapat memperoleh lokasi page table entry (PTE) dengan menggunakan rumus berikut ini:

PTE address = PTE_BASE + (page directory index) * PAGE_SIZE + (page table index) * sizeof(MMPTE)
            = 0xC0000000 + (0 * 0x1000) + (0x29E * 4)
            = 0xC0000000 + 0 + 0xA78
            = 0xC0000A78

Setelah menemukan lokasi PTE, saya dapat melihat isinya dengan perintah seperti pada gambar berikut ini:

Melihat PTE untuk proses Notepad.exe

Melihat PTE untuk proses Notepad.exe

Saya berpindah ke context untuk proses Notepad.exe agar perintah dd dapat menampilkan lokasi yang tepat. Hal ini karena setiap proses memiliki page table-nya masing-masing dan saya sedang mencari page table entry (PTE) untuk proses Notepad.exe.

Pada gambar di atas, terlihat bahwa saya menemukan nilai PTE berupa 0x4b314867. Nilai ini terdiri atas nilai page frame number berupa 0x4b314 dan status flag berupa 0x867. Dengan demikian, lokasi physical address adalah:

physical address = page frame number  * 0x1000 + byte index
                 = 0x4B314 * 0x1000 + 0xF0
                 = 0x4B3140F0

Terlihat bahwa hasil perhitungan manual ini sama seperti hasil perhitungan yang diperoleh oleh !vtop.

Apa Itu Data Execution Prevention (DEP) Di Windows 7?

Salah satu jenis serangan yang sering dimanfaatkan oleh hacker adalah mencari cara untuk meng-eksekusi apa yang seharusnya hanya sebuah data. Sebagai contoh, pada aplikasi web, sebuah nilai yang seharusnya adalah nama atau alamat dapat disisipi dengan JavaScript yang secara tidak sengaja di-eksekusi saat ditampilkan. Hal serupa juga terjadi pada tingkat sistem operasi. Hacker bisa saja melakukan serangan stack overflow atau heap overflow dengan mengisi data secara berlebihan pada sebuah aplikasi yang tidak aman. Bila data yang berlebihan ini mengandung program (dalam bentuk instruksi mesin), hacker bisa saja menyebabkan instruksi mesin yang ada di stack atau heap tersebut dikerjakan.

Untuk mengatasi permasalahan tersebut, prosesor modern memperkenalkan apa yang disebut sebagai NX bit (No-eXecute bit). Pada prosesor AMD, fitur ini disebut sebagai Enhanced Virus Protection (EVP). Pada prosesor Intel, fitur ini disebut sebagai XD bit (eXecute-Disable bit). Partisipasi prosesor sendirian tidak cukup; manajer memori pada sistem operasi perlu memberikan dukungannya dengan memberi tahu bagian memori mana yang tidak boleh di-eksekusi. Sistem operasi Windows mengimplementasikan dukungan ini dalam bentuk fitur yang diberi nama Data Execution Prevention (DEP). Sedikit membingungkan, bukan? Untuk merujuk ke teknologi yang sama, terdapat beberapa nama berbeda tergantung pada perusahaan besar dibaliknya ๐Ÿ˜‰

Untuk mengatur fitur Data Execution Prevention (DEP) di Windows 7, saya akan mengetik View advanced system settings di menu Start dan memilih item yang muncul. Pada tab Advanced di dialog System Properties yang muncul, saya men-klik tombol Settings… di bagian Performance. Pada dialog yang muncul, saya memilih tab Data Execution Prevention seperti yang terlihat pada gambar berikut ini:

Mengatur fitur DEP di Windows

Mengatur fitur DEP di Windows

Saya dapat melihat program apa saja yang terlindungi oleh DEP melalui Task Manager. Untuk itu, saya perlu memilih menu View, Select Columns… dan memberi tanda centang pada Data Execution Prevention (DEP). Setelah itu, saya dapat melihat dukungan DEP pada kolom Data Execution Prevention di Task Manager seperti pada gambar berikut ini:

Memeriksa status DEP pada aplikasi

Memeriksa status DEP pada aplikasi

Untuk prosesor yang tidak mendukung NX bit, Windows 7 juga menyediakan fasilitas Safe Structured Exception Handling (SEH). Untuk mendukung fasilitas ini, program harus di-compile dengan flag /SAFESEH di Visual C++. Fasilitas ini akan bekerja pada saat terjadi exception dimana ia akan memeriksa apakah exception handler adalah salah satu dari yang sudah terdaftar di dalam program (dan bukan yang barusan di-inject oleh hacker).

Seberapa efektif perlindungan yang diberikan DEP tergantung pada aplikasi yang dijalankan. Aplikasi yang menghasilkan data untuk di-eksekusi sebagai program (misalnya Just-In-Time compiler) tidak dapat memanfaatkan perlindungan DEP. Dengan demikian, walaupun dijalankan pada prosesor dan sistem operasi yang mendukungan DEP, aplikasi tetap rentan terhadap serangan JIT spraying (seperti pada exploit teknologi Adobe PDF Reader dan Adobe Flash).