Windows Sistemde Dosya Bütünlüğü İzleme

Bir sistem yöneticisi olarak en çok korktuğun şeylerden biri, sisteminizde bir şeylerin değiştiğini ama bunu zamanında fark edemediğinizi sonradan anlamaktır. Dosya bütünlüğü izleme (File Integrity Monitoring – FIM) tam da bu sorunu çözmek için var. Windows ortamlarında bu konuya yeterince önem verilmediğini sık sık görüyorum ve bu yazıda hem teorik altyapıyı hem de sahada kullanabileceğiniz pratik araçları ve teknikleri ele alacağım.

Dosya Bütünlüğü İzleme Neden Bu Kadar Kritik?

Düşünün, bir saldırgan sisteminize sızdı. İlk yapacağı şeylerden biri kalıcılık sağlamak ve izlerini örtmektir. Bunun için ya sistem dosyalarını değiştirecek, ya arka kapı (backdoor) ekleyecek ya da konfigürasyon dosyalarını manipüle edecektir. Eğer bu değişiklikleri gerçek zamanlı veya düzenli aralıklarla takip etmiyorsanız, saldırı haftalar hatta aylar sonra fark edilebilir.

PCI DSS, HIPAA, SOX gibi uyumluluk standartları da FIM’i zorunlu kılmaktadır. Dolayısıyla bu konu hem güvenlik hem de yasal uyumluluk açısından kritiktir.

Windows üzerinde FIM için birkaç farklı yaklaşım var:

  • Yerel Windows araçları (Windows yerleşik özellikler)
  • PowerShell tabanlı özel çözümler
  • Üçüncü parti araçlar (OSSEC, Wazuh, Tripwire)
  • SIEM entegrasyonu

Bu yazıda önce temel kavramları ele alacak, ardından gerçek sahada kullanabileceğiniz kod örnekleri ile ilerliyeceğiz.

Windows Audit Policy ile Temel İzleme

Windows’un kendi içinde güçlü bir denetim mekanizması var. Öncelikle Audit Object Access politikasını etkinleştirmeniz gerekiyor.

Grup İlkesi üzerinden yapabilirsiniz ama komut satırından yapmak çok daha hızlı ve script edilebilir:

# Audit Policy'yi etkinleştir
auditpol /set /subcategory:"File System" /success:enable /failure:enable

# Mevcut durumu kontrol et
auditpol /get /subcategory:"File System"

# Object Access genel durumunu gör
auditpol /get /category:"Object Access"

Bu komutu çalıştırdıktan sonra, izlemek istediğiniz klasör veya dosyaların ACL’lerine (Access Control List) denetim girdisi eklemeniz gerekiyor. Bunu PowerShell ile şöyle yapabilirsiniz:

# Belirli bir dizine audit kuralı ekle
$acl = Get-Acl "C:CriticalFiles"
$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule(
    "Everyone",
    "Write,Delete,ChangePermissions",
    "ContainerInherit,ObjectInherit",
    "None",
    "Success,Failure"
)
$acl.AddAuditRule($auditRule)
Set-Acl "C:CriticalFiles" $acl
Write-Host "Audit rule added successfully"

Bu ayarlardan sonra Windows Event Log’da 4663 (dosyaya erişim), 4656 (dosya handle açıldı), 4670 (izinler değiştirildi) gibi event ID’leri görmeye başlarsınız.

PowerShell ile Hash Bazlı Bütünlük Kontrolü

Hash karşılaştırması, FIM’in temel taşıdır. Bir dosyanın içeriği değiştiğinde hash değeri de değişir. Bu yöntemi PowerShell ile otomatize etmek oldukça kolaydır.

Önce bir baseline (referans nokta) oluşturuyoruz:

# Kritik sistem dosyalarinin baseline hash degerlerini olustur
$targetPath = "C:WindowsSystem32"
$outputFile = "C:FIMbaseline_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$excludeExtensions = @('.log', '.tmp', '.etl')

# Klasoru olustur
New-Item -ItemType Directory -Path "C:FIM" -Force | Out-Null

$results = Get-ChildItem -Path $targetPath -File -Recurse -ErrorAction SilentlyContinue |
    Where-Object { $excludeExtensions -notcontains $_.Extension } |
    ForEach-Object {
        try {
            $hash = Get-FileHash -Path $_.FullName -Algorithm SHA256
            [PSCustomObject]@{
                FilePath     = $_.FullName
                Hash         = $hash.Hash
                LastModified = $_.LastWriteTime
                Size         = $_.Length
                CreatedDate  = $_.CreationTime
            }
        } catch {
            Write-Warning "Could not hash: $($_.FullName)"
        }
    }

$results | Export-Csv -Path $outputFile -NoTypeInformation -Encoding UTF8
Write-Host "Baseline created: $outputFile with $($results.Count) files"

Şimdi bu baseline ile güncel durumu karşılaştıran bir kontrol scripti yazalım:

# Baseline ile mevcut durumu karsilastir
param(
    [string]$BaselineFile = "C:FIMbaseline.csv",
    [string]$ScanPath = "C:WindowsSystem32",
    [string]$ReportPath = "C:FIMReports"
)

New-Item -ItemType Directory -Path $ReportPath -Force | Out-Null
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$reportFile = "$ReportPathfim_report_$timestamp.txt"

# Baseline yukle
$baseline = Import-Csv -Path $BaselineFile
$baselineHash = @{}
foreach ($entry in $baseline) {
    $baselineHash[$entry.FilePath] = $entry
}

$changes = @()
$newFiles = @()
$deletedFiles = @()

# Mevcut dosyalari tara
$currentFiles = Get-ChildItem -Path $ScanPath -File -Recurse -ErrorAction SilentlyContinue

foreach ($file in $currentFiles) {
    $currentHash = (Get-FileHash -Path $file.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash
    
    if ($baselineHash.ContainsKey($file.FullName)) {
        if ($baselineHash[$file.FullName].Hash -ne $currentHash) {
            $changes += [PSCustomObject]@{
                Type     = "MODIFIED"
                FilePath = $file.FullName
                OldHash  = $baselineHash[$file.FullName].Hash
                NewHash  = $currentHash
                Time     = Get-Date
            }
        }
    } else {
        $newFiles += $file.FullName
    }
}

# Silinen dosyalari bul
foreach ($key in $baselineHash.Keys) {
    if (-not (Test-Path $key)) {
        $deletedFiles += $key
    }
}

# Rapor olustur
$report = @"
FIM Report - $timestamp
=======================
Modified Files: $($changes.Count)
New Files: $($newFiles.Count)
Deleted Files: $($deletedFiles.Count)

MODIFIED FILES:
$($changes | ForEach-Object { "- $($_.FilePath)`n  Old: $($_.OldHash)`n  New: $($_.NewHash)" } | Out-String)

NEW FILES:
$($newFiles | ForEach-Object { "- $_" } | Out-String)

DELETED FILES:
$($deletedFiles | ForEach-Object { "- $_" } | Out-String)
"@

$report | Out-File -FilePath $reportFile -Encoding UTF8
Write-Host "Report saved: $reportFile"

# Degisiklik varsa Event Log'a yaz
if ($changes.Count -gt 0 -or $newFiles.Count -gt 0 -or $deletedFiles.Count -gt 0) {
    Write-EventLog -LogName Application -Source "FIM-Monitor" -EventId 9001 -EntryType Warning -Message "FIM Alert: Changes detected! Modified:$($changes.Count) New:$($newFiles.Count) Deleted:$($deletedFiles.Count)"
}

Windows Event Log İzleme ile Gerçek Zamanlı Tespit

Hash bazlı kontrol periyodik olarak çalışır. Ama gerçek zamanlı izleme için Windows Event Log’u takip etmek daha etkilidir. Aşağıdaki script, belirli event ID’lerini dinleyerek anlık bildirim yapabilir:

# Gercek zamanli Event Log izleme
$watchEvents = @(4663, 4670, 4656, 4657, 4659)
$criticalPaths = @("C:WindowsSystem32", "C:WindowsSysWOW64", "C:Program Files")

# Event filtresi olustur
$filterXML = @"
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[System[(EventID=4663 or EventID=4670 or EventID=4656)]]
      and
      *[EventData[Data[@Name='ObjectType']='File']]
    </Select>
  </Query>
</QueryList>
"@

Write-Host "Starting real-time FIM monitoring... Press Ctrl+C to stop"
Write-Host "Watching for events: $($watchEvents -join ', ')"

# Surekli izleme dongusu
while ($true) {
    $events = Get-WinEvent -FilterXml $filterXML -MaxEvents 10 -ErrorAction SilentlyContinue
    
    foreach ($event in $events) {
        $eventXML = [xml]$event.ToXml()
        $objectName = ($eventXML.Event.EventData.Data | Where-Object { $_.Name -eq 'ObjectName' }).'#text'
        
        foreach ($critPath in $criticalPaths) {
            if ($objectName -like "$critPath*") {
                $logEntry = "[ALERT] $(Get-Date) | EventID: $($event.Id) | User: $($event.UserId) | File: $objectName"
                Write-Host $logEntry -ForegroundColor Red
                Add-Content -Path "C:FIMrealtime_alerts.log" -Value $logEntry
            }
        }
    }
    
    Start-Sleep -Seconds 30
}

Wazuh ile Kurumsal Seviyede FIM

Sahada büyük ortamlar için açık kaynaklı Wazuh platformunu şiddetle tavsiye ediyorum. OSSEC’in üzerine geliştirilmiş, SIEM özellikli, merkezi yönetilebilir bir çözüm. Windows agent kurulumu sonrasında ossec.conf dosyasına FIM kuralları ekleyebilirsiniz.

Wazuh agent üzerinde FIM konfigürasyonu şu şekilde yapılır:

# Wazuh ossec.conf icindeki FIM bolumu ornegi
# C:Program Files (x86)ossec-agentossec.conf dosyasina eklenecek

<syscheck>
  <!-- FIM etkinlestir -->
  <disabled>no</disabled>
  
  <!-- Tarama araligi (saniye) -->
  <frequency>3600</frequency>
  
  <!-- Baslangicta tarama yap -->
  <scan_on_start>yes</scan_on_start>
  
  <!-- Gercek zamanli izleme -->
  <directories realtime="yes" report_changes="yes" check_all="yes">
    C:WindowsSystem32
  </directories>
  
  <directories realtime="yes" check_all="yes">
    C:WindowsSysWOW64
  </directories>
  
  <directories realtime="yes" check_all="yes">
    C:Program Files
  </directories>
  
  <!-- Hizli degisen dosyalari hariç tut -->
  <ignore>C:WindowsSystem32winevt</ignore>
  <ignore>C:WindowsSystem32wbemLogs</ignore>
  <ignore type="sregex">.log$|.tmp$|.etl$</ignore>
  
  <!-- Registry izleme -->
  <windows_registry>HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRun</windows_registry>
  <windows_registry>HKEY_LOCAL_MACHINESystemCurrentControlSetServices</windows_registry>
</syscheck>

Wazuh agent’i yeniden başlatmak için:

# Wazuh agent servisini yeniden baslat
net stop WazuhSvc
net start WazuhSvc

# Veya PowerShell ile
Restart-Service -Name WazuhSvc -Force
Get-Service WazuhSvc | Select-Object Name, Status, StartType

Registry Bütünlük İzleme

Windows sistemlerde sadece dosyaları değil, registry’yi de izlemek kritik. Özellikle startup anahtarları, servis kayıtları ve güvenlik politikası ayarları değişirse alarm vermelisiniz.

# Kritik registry anahtarlarini izle ve baseline al
$criticalKeys = @(
    "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionRun",
    "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionRunOnce",
    "HKLM:SYSTEMCurrentControlSetServices",
    "HKLM:SOFTWAREMicrosoftWindows NTCurrentVersionWinlogon",
    "HKCU:SOFTWAREMicrosoftWindowsCurrentVersionRun"
)

$registryBaseline = @()

foreach ($key in $criticalKeys) {
    try {
        $values = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue
        if ($values) {
            $values.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object {
                $registryBaseline += [PSCustomObject]@{
                    KeyPath   = $key
                    ValueName = $_.Name
                    ValueData = $_.Value
                    CheckTime = Get-Date
                    Hash      = (($key + $_.Name + $_.Value) | Get-Hash -Algorithm SHA256 -AsString)
                }
            }
        }
    } catch {
        Write-Warning "Cannot read: $key"
    }
}

# Sonuclari kaydet
$registryBaseline | Export-Csv -Path "C:FIMregistry_baseline.csv" -NoTypeInformation
Write-Host "Registry baseline saved: $($registryBaseline.Count) entries"

# Degisiklikleri kontrol et
$currentRegistry = @()
foreach ($key in $criticalKeys) {
    $values = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue
    if ($values) {
        $values.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object {
            $currentRegistry += [PSCustomObject]@{
                KeyPath   = $key
                ValueName = $_.Name
                ValueData = $_.Value
            }
        }
    }
}

# Yeni veya degismis registry girisleri bul
$baseline = Import-Csv "C:FIMregistry_baseline.csv"
$baselineKeys = $baseline | Group-Object -Property KeyPath, ValueName

foreach ($current in $currentRegistry) {
    $found = $baseline | Where-Object { $_.KeyPath -eq $current.KeyPath -and $_.ValueName -eq $current.ValueName }
    if ($null -eq $found) {
        Write-Host "[NEW REGISTRY ENTRY] $($current.KeyPath)$($current.ValueName) = $($current.ValueData)" -ForegroundColor Yellow
    } elseif ($found.ValueData -ne $current.ValueData) {
        Write-Host "[MODIFIED REGISTRY] $($current.KeyPath)$($current.ValueName)" -ForegroundColor Red
        Write-Host "  Old: $($found.ValueData)"
        Write-Host "  New: $($current.ValueData)"
    }
}

Scheduled Task ile FIM Otomasyonu

Tüm bu scriptleri el ile çalıştırmak pratik değil. Windows Task Scheduler ile düzenli çalıştırılacak şekilde ayarlamalısınız:

# FIM scriptini Scheduled Task olarak kaydet
$taskName = "FIM-DailyCheck"
$scriptPath = "C:FIMScriptsfim_check.ps1"
$logPath = "C:FIMLogs"

New-Item -ItemType Directory -Path $logPath -Force | Out-Null

# Task Action olustur
$action = New-ScheduledTaskAction `
    -Execute "PowerShell.exe" `
    -Argument "-NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`" >> `"$logPathfim_$(Get-Date -Format 'yyyyMMdd').log`" 2>&1"

# Her gun saat 03:00'da calistir
$trigger = New-ScheduledTaskTrigger -Daily -At "03:00"

# SYSTEM hesabi ile calistir
$principal = New-ScheduledTaskPrincipal `
    -UserId "SYSTEM" `
    -LogonType ServiceAccount `
    -RunLevel Highest

# Task ayarlari
$settings = New-ScheduledTaskSettingsSet `
    -ExecutionTimeLimit (New-TimeSpan -Hours 2) `
    -RestartCount 3 `
    -RestartInterval (New-TimeSpan -Minutes 5) `
    -StartWhenAvailable

# Task'i kaydet
Register-ScheduledTask `
    -TaskName $taskName `
    -TaskPath "Security" `
    -Action $action `
    -Trigger $trigger `
    -Principal $principal `
    -Settings $settings `
    -Description "Daily File Integrity Monitoring Check" `
    -Force

Write-Host "Scheduled task created: Security$taskName"
Get-ScheduledTask -TaskName $taskName | Select-Object TaskName, State, TaskPath

Gerçek Dünya Senaryosu: Web Sunucusunda FIM

Birkaç yıl önce bir müşterimde tam bu senaryoyu yaşadık. IIS tabanlı bir web sunucusunda haftalarca süren, fark edilemeyen bir webshell yerleşimi vardı. O günden bu yana, tüm web sunucularımda şu şekilde özel bir FIM kuralı çalıştırıyorum:

Web kök dizinlerini izlemek için kritik path listesi:

  • C:inetpubwwwroot: Ana web içeriği
  • C:inetpubwwwroot.aspx, .php, *.asp: Script dosyaları
  • C:WindowsSystem32inetsrv: IIS binary dosyaları
  • C:WindowsSystem32inetsrvconfig: IIS konfigürasyonları

Bu senaryo için özelleştirilmiş bir monitoring scripti:

# Web sunucusu FIM - Webshell tespiti odakli
param(
    [string]$WebRoot = "C:inetpubwwwroot",
    [string]$BaselinePath = "C:FIMweb_baseline.csv",
    [string]$AlertEmail = "[email protected]"
)

# Tehlikeli uzantilar
$dangerousExtensions = @('.asp', '.aspx', '.php', '.php5', '.phtml', '.shtml', '.cfm', '.cgi', '.pl')
$suspiciousPatterns = @('cmd.exe', 'powershell', 'eval(', 'base64_decode', 'shell_exec', 'passthru')

$alerts = @()

# Yeni eklenen script dosyalarini bul
if (Test-Path $BaselinePath) {
    $baseline = Import-Csv $BaselinePath
    $baselineFiles = $baseline | Select-Object -ExpandProperty FilePath
    
    $currentScripts = Get-ChildItem -Path $WebRoot -Recurse -File |
        Where-Object { $dangerousExtensions -contains $_.Extension.ToLower() }
    
    foreach ($script in $currentScripts) {
        # Baseline'da olmayan yeni script dosyasi
        if ($script.FullName -notin $baselineFiles) {
            $alerts += "[CRITICAL] New script file detected: $($script.FullName)"
            Write-Host "[CRITICAL] New script file: $($script.FullName)" -ForegroundColor Red
        }
        
        # Dosya icerigi suphelik pattern iceriyor mu?
        try {
            $content = Get-Content -Path $script.FullName -Raw -ErrorAction SilentlyContinue
            foreach ($pattern in $suspiciousPatterns) {
                if ($content -match [regex]::Escape($pattern)) {
                    $alerts += "[SUSPICIOUS] Pattern '$pattern' found in: $($script.FullName)"
                    Write-Host "[SUSPICIOUS] $pattern found in $($script.FullName)" -ForegroundColor Magenta
                }
            }
        } catch { }
        
        # Son 24 saatte degistirilen dosyalar
        if ($script.LastWriteTime -gt (Get-Date).AddHours(-24)) {
            $hashCurrent = (Get-FileHash $script.FullName -Algorithm SHA256).Hash
            $baselineEntry = $baseline | Where-Object { $_.FilePath -eq $script.FullName }
            
            if ($baselineEntry -and $baselineEntry.Hash -ne $hashCurrent) {
                $alerts += "[WARNING] Modified in last 24h: $($script.FullName)"
            }
        }
    }
}

# Alert ozeti
if ($alerts.Count -gt 0) {
    $alertMessage = "FIM Web Server Alert - $(Get-Date)`n`n" + ($alerts -join "`n")
    Write-Host "`n$alertMessage" -ForegroundColor Red
    
    # Event Log'a yaz
    Write-EventLog -LogName Application -Source "FIM-WebMonitor" -EventId 9002 `
        -EntryType Error -Message $alertMessage
    
    # Log dosyasina kaydet
    Add-Content -Path "C:FIMweb_alerts.log" -Value $alertMessage
    
    Write-Host "`nTotal alerts: $($alerts.Count)"
} else {
    Write-Host "No issues detected. Web root is clean." -ForegroundColor Green
}

İyi Pratikler ve Dikkat Edilmesi Gerekenler

Windows FIM’i doğru kurmak kadar doğru işletmek de önemli. Sahada gördüğüm en sık hatalar şunlar:

  • Fazla geniş kapsam: Her dosyayı izlemeye çalışmak, hem performansı düşürür hem de alarm yorgunluğuna yol açar. Kritik path’leri önceliklendirin.
  • Baseline’ı koruyamamak: Meşru değişikliklerden sonra baseline’ı güncellemezseniz, sürekli yanlış alarm alırsınız. Patch Tuesday’den sonra mutlaka baseline’ı yenileyin.
  • Log’ları saklamama: FIM log’larını en az 90 gün, PCI uyumluluğu için 1 yıl saklamanız gerekir. Log rotasyonu ve arşivleme planı yapın.
  • Sadece dosya sistemi izlemek: Registry, servisler, zamanlanmış görevler ve ağ paylaşımları da izlemeniz gereken kritik noktalardır.
  • Test etmemek: FIM sisteminizin gerçekten çalışıp çalışmadığını düzenli aralıklarla simüle edilmiş değişikliklerle test edin.
  • Alert yorgunluğu: Çok fazla alarm üretirseniz, gerçek olaylar gürültüde kaybolur. Alert eşiklerini ve önceliklendirmeyi iyi yapın.
  • Şifreli raporlama: FIM raporları hassas sistem bilgisi içerir. Bunları şifreli kanallar üzerinden iletip saklamalısınız.

Sonuç

Windows sistemlerde dosya bütünlüğü izleme, savunma katmanlarının en temellerinden biri. Küçük ortamlar için PowerShell tabanlı özel scriptler yeterli olabilir, ancak büyük ve kurumsal yapılar için Wazuh, OSSEC veya ticari bir çözüm tercih etmenizi öneririm. Önemli olan, hangi aracı kullandığınızdan çok ne izlediğinizi ve alarmları nasıl değerlendirdiğinizi bilmektir.

Başlangıç için şu adımları öneririm: Audit Policy’yi etkinleştir, kritik dizinler için baseline oluştur, periyodik kontrol scriptini Task Scheduler’a ekle ve alarm mekanizmasını kur. Bu dört adım bile sizi çoğu saldırıya karşı önemli ölçüde daha görünür kılacak.

FIM bir kez kurulup unutulacak bir şey değil. Düzenli olarak gözden geçirilmesi, baseline’larının güncellenmesi ve alarm eşiklerinin iyileştirilmesi gereken, canlı bir güvenlik sürecidir. Sisteminizde bir şeyler değiştiğinde, bunu saldırgan değil siz fark etmelisiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir