Önceki yazıda SQL PFX parolası biçimindeki örnek verileri yalnızca kullanım anında çözmenin neden yeterli olmadığını görmüştük. Uygulama kendi tamponunu temizliyordu ama CryptUnprotectData çağrısının dönüşünde aldığımız kayıt kalıyordu. kez aynı noktaya uygulama tarafından bakacağız: çözme fonksiyonuna müdahale edildiğini fark edip çağrıyı başlatmayabilir miyiz?
Önceki incelemelerde kullandığımız SafiyeMonitor, çalışma zamanı analiz araçlarına örnek. yazıdaki amacım, tür araçlarla yapılan incelemeleri belli seviyede zorlaştırmak. Bunun için geliştirdiğim Thick App Integrity Guard kütüphanesine çalışma zamanı kontrolü ekledim. Korumasız korumalı örneğe aynı analiz betiğini yükleyip verinin hangi durumda yakalanabildiğini karşılaştırdım.
yazıdaki DLL laboratuvar kodları, yaklaşımı çalışan örnek üzerinden göstermek için hazırlandı. Daha kapsamlı dayanıklı uygulamalar yazılabilir; mevcut kod izlenen fonksiyonlar, tespit yöntemleri, hata yönetimi performans açısından geliştirilebilir. Kendi ürününe eklemek isteyen geliştiricinin, örneği uygulamasının mimarisine uyarlayıp gerçek çalışma ortamında yeniden test etmesi gerekir.
yazıdaki örnekler v0.4.0 sürümüne. kod İngilizce entegrasyon rehberleri GitHub deposunda, Windows x64 DLL, yerel NuGet paketi örnek uygulama ise sürüm sayfasında bulunuyor.
kontrolde çalışma zamanı analiz aracının sürece bağlanmasını engellemiyoruz. Bağlantı kuruluyor. Uygulama, izlediği fonksiyonların giriş kodu değişmişse hassas işlemi başlatmıyor. Kazanımı sınırını baştan ayırmak gerekiyor: belirli hook’u fark etmek, uygulamayı incelenemez hâle getirmekle aynı şey değil.
Çalışma zamanı analiz araçları kullanım anındaki veriyi nasıl görüyor?Çalışma zamanı analiz araçları, çalışan sürece bağlanarak veya uygulamayı gözlem altında başlatarak fonksiyon çağrılarını izleyebilir. laboratuvarda kullandığım analiz betiği, izlenecek native fonksiyonlara Interceptor.attach gözlem noktaları ekliyor. Farklı araçlar aynı gözlem için farklı yöntemler kullanabilir.
DPAPI örneğinde iki farklı an önemli:
- CryptProtectData çağrısına girerken uygulamanın şifrelenecek açık verisi.
- CryptUnprotectData başarılı döndüğünde uygulamanın kullanacağı çözülmüş veri.
Analiz betiği, ikinci çağrının çıkışındaki DATA_BLOB yapısından baytları alıp crypto_event kaydı olarak gönderiyor. aşama işletim sisteminin şifrelemeyi kırması değil. Uygulama kendi yetkisiyle veriyi çözüyor; gözlemci ortaya çıktığı noktadan kopya alıyor.
Şekil 1: onLeave içindeki kırmızı kutuda, başarılı dönüşten sonra açık verinin okunması kayıt olarak gönderilmesi görülüyor.testte kullanılan altyapı Frida 16.6.4. Fonksiyon giriş çıkışlarını izleyen Interceptor arayüzünün kapsamı API belgesinde açıklanıyor. Sonuçlar burada denenen sürüm yönteme ait; başka çalışma zamanı analiz araçları veya değiştirilmiş ajanlar için aynı sonucu kendiliğinden varsaymıyorum.
Dosya bütünlüğü neden sorunu çözmüyor?Diskteki DLL değişmemiş olabilir. İzlediğimiz müdahale, sürecin belleğine yüklenmiş fonksiyon üzerinde gerçekleşiyor. yüzden dosyanın SHA-256 özetini kontrol edip doğru bulmak, fonksiyonun bellekteki giriş kodunun aynı kaldığını göstermiyor.
Kütüphanedeki imzalı manifest kontrolünü korudum. Dağıtılan dosyaların beklenen sürümle eşleşmesi ayrı ihtiyaç. Fakat çalışma zamanı tespiti için ayrı RuntimeIntegritySession ekledim. IntegritySession dosyaları, yeni session ise belirlenen native fonksiyon girişlerini debugger durumunu kontrol ediyor.
KontrolBaktığı yerBu laboratuvardaki göreviİmzalı manifest dosya özetiDiskteki dağıtım dosyalarıDosyanın beklenen imzalı sürümle eşleşmesiNative fonksiyon girişiSürecin belleğindeki seçili baytlarYaygın inline hook değişikliğini fark etmekDebugger durumuGeçerli sürecin debugger bilgisiDebugger altında çalışmayı ayrıca değerlendirmekİşlem öncesi uygulama kararıHassas çağrının önündeki kodBaşarısız kontrolde işlemi başlatmamakDLL’yi projeye referans vermek kararları kendiliğinden uygulamıyor. Ana uygulamanın kontrol sonucunu kullanması başarısızsa ilgili işleme devam etmemesi gerekiyor.
Fonksiyonun değiştiğini nasıl anlıyoruz?Yalnızca ilk baytın atlama komutu olup olmadığına bakmak istemedim. Meşru fonksiyon atlamayla başlayabilir. Uygulama açılırken bellekte gördüğümüz baytları “temiz kopya” diye kaydetmek yeterli değil; gözlemci daha uygulama başlamadan müdahale etmiş olabilir.
nedenle karşılaştırma için fonksiyonun diskteki sistem DLL’sinde bulunan baytlarını kullanıyorum. Akış şöyle:
Export’un sahibi önemli. DLL’nin dışa aktardığı isim başka DLL’ye yönlendirilebilir. Böyle durumda ilk DLL’nin dosya ofsetini kullanmak yanlış baytları karşılaştırır. çözülen adresin sahibi olan gerçek modülü buluyor PE içindeki konumu buna göre hesaplıyor.
Her fark saldırı demek değil. Windows’un yükleme sırasında relocation uyguladığı alanı ham disk baytlarıyla karşılaştırırsak meşru fark üretebiliriz. sürüm, karşılaştırılan 32 baytla relocation kaydı kesiştiğinde alanı sessizce atlamıyor; desteklenmeyen karşılaştırma olarak başarısız dönüyor. Okunamayan, çözülemeyen veya desteklenmeyen hedef başarı sayılmıyor.
Şekil 2: Kırmızı kutuda diskteki referansın okunması, bellekteki girişin alınması karşılaştırma sonucu. Fark bulunduğunda RuntimeNativeEntryChanged dönüyor.Varsayılan politika dört fonksiyonu izliyor:
FonksiyonNeden kapsamda?crypt32!CryptProtectDataŞifrelenecek açık verinin girişte gözlemlenmesicrypt32!CryptUnprotectDataÇözülen verinin dönüşte gözlemlenmesibcrypt!BCryptFinishHashÖzet sonucunun üretildiği native sınırncrypt!NCryptVerifySignatureİmza doğrulama sonucunun üretildiği native sınırliste Windows’taki bütün kriptografi yollarını kapsamıyor. Kullanılan sağlayıcı, uygulamanın dili hangi API’leri çağırdığı ayrıca incelenmeli. Örneğin OpenSSL kullanan uygulamada aynı dört ismi izlemek tek başına uygulamanın TLS çağrılarını kontrol etmiş olmuyor.
Debugger kontrolü bütün çalışma zamanı analizlerini gösterir ?IsDebuggerPresent, CheckRemoteDebuggerPresent.NET debugger bilgisi ayrı kontrol katmanı. Windows API’sinin bildirdiği debugger durumunu değerlendiriyoruz. CheckRemoteDebuggerPresent adındaki “remote”, başka bilgisayarı değil ayrı süreçteki debugger’ı anlatıyor; ayrıntı Microsoft belgesinde.
Sonucun olumsuz olması, süreçte hiçbir gözlem aracı bulunmadığını göstermiyor. karşılaştırmada asıl tespit debugger bayrağından değil, izlenen fonksiyonların giriş baytlarındaki değişiklikten geldi.
Kod, çalışan süreçler arasında belirli analiz aracının adını arayıp karar vermiyor. aracın adının değiştirilmesiyle ortadan kalkabilecek kontrolü ana koruma olarak kullanmadım. Burada baktığımız şey, hassas çağrının girişinin diskteki karşılığıyla eşleşip eşleşmediği.
Laboratuvarın düzeniÖrnek uygulama RuntimeProbe.exe. Aynı uygulamanın korumalı --unguarded açılan korumasız modu. Üretilen veri LAB_RUNTIME_ işaretiyle başlayan, her çalıştırmada yeniden oluşturulan 60 baytlık sentetik tampon. Gerçek SQL hesabı, PFX dosyası veya dış sunucu.
Korumalı örnek önce gerekli DLL’leri yüklüyor başlangıç kontrolünü yapıyor. Kontrol başarılıysa sahte veriyi oluşturup DPAPI koruyor. sıra önemli: veriyi önce şifreleyip kontrolü sonra yapsaydık, başlangıçta takılan hook CryptProtectData girişinde veriyi zaten görebilirdi.
Uygulama hazır olduğunda check, use exit komutlarını kabul ediyor. use komutu veriyi çözüyor, örnek iş için alıyor sahip olduğu tamponları temizliyor. Çıktıya açık veriyi yazmıyor; yalnızca bayt sayısını SHA-256 özetini ekliyor.
Testte analiz aracının hook betiğini değiştirmeden kullandım. Süreçleri komutlarını kontrol ettiğim karşılaştırmayı otomatik testle tekrarladım; ilk korumalı gözlemi REA üzerinden doğruladım. seçilen betiğin kurduğu hook’ları kapsıyor. Çalışma zamanı analiz araçlarının bütün özelliklerinin sınandığı anlamına gelmiyor.
Korumasız örnek: Veri yine yakalanıyorİlk uygulamayı --unguarded açtım. Başlangıç yanıtı hazır olduktan sonra analiz aracını aynı sürece bağlayıp hook betiğini yükledim. aşamada kriptografi DLL’leri zaten yüklüydü.
Ardından use komutunu gönderdim. Uygulama DPAPI verisini çözdü başarılı yanıt verdi. Analiz aracında CryptUnprotectData için crypto_event oluştu.
Kaydın yalnızca aynı uzunlukta veriye ait olmadığını kontrol etmek için, yakalanan baytların SHA-256 özetini uygulamanın çözdüğü verinin özetiyle karşılaştırdım. İkisi eşleşti. Böylece önceki yazıdaki davranışı aynı laboratuvarda tekrar gördük: tampon daha sonra temizlense kullanım anındaki kopya alınmıştı.
sonuç yalnızca sentetik veriye. Test raporuna açık veriyi eklemedim. Olay sayısı, bayt sayısı eşleşmeyi gösteren özetler yeterliydi.
Korumalı örnek: Çözme çağrısından önce durmakİkinci çalıştırmada aynı uygulamayı korumalı modda açtım. Başlangıç kontrolü RuntimeVerified verdi. Sonra yine çalışan sürece bağlandım aynı analiz betiğini yükledim.
use yanıtının karar alanları kez şöyleydi:
{ "operation": "use", "guarded": true, "allowed": false, "code": "RuntimeNativeEntryChanged" }Yanıttaki bulgular, CryptProtectData CryptUnprotectData girişlerinin beklenen baytlarla eşleşmediğini gösteriyordu. Uygulama başarısız kontrolün ardından Dpapi.Unprotect çağrısına gitmedi. işlem için analiz aracında CryptUnprotectData verisi oluşmadı.
Şekil 3: BeforeOperation kontrolü başarısızsa continue komutun işlenmesi bırakılıyor. Dpapi.Unprotect çağrısı kararın arkasında.Burada yalnızca “kayıt gelmedi” sonucuna dayanmıyoruz. Aynı betik korumasız örnekte veriyi yakalayabiliyor. Korumalı örnekte ise hem uygulamanın ret yanıtı var hem kodda çözme çağrısı kararın arkasında. Test iki davranışı birlikte kontrol ediyor.
Analiz aracının süreçteki başka işlemleri görmesi devam edebiliyor. Örneğin REA üzerinden yaptığım gözlemde, doğrulayıcının sistem DLL’lerini açması dosya izleme kayıtlarında görünüyordu. Koruma, seçili hassas işlemi durduruyor; süreçteki bütün görünürlüğü kaldırmıyor.
Hook kaldırılınca ne oluyor?Hook betiğini kaldırıp bağlantıyı kapattıktan sonra aynı uygulamada tekrar use komutu gönderdim. Sonuç RuntimeSessionBlocked. İlk hatadan sonra session engellenmişti; hook’un kaldırılması onu açmadı.
Yeni gözlem yapılmayan süreç başlattığımda ise başlangıç kontrolü geçti işlem çalıştı. iki sonucu ayrı değerlendirmek gerekiyor: mevcut session hatayı koruyor, temiz yeni session baştan ölçüm yapıyor.
Şekil 4: Üstteki kutu mevcut engelde RuntimeSessionBlocked dönüşünü, alttaki kutu ise başarısız kontrolde blocked değerinin ayarlanmasını gösteriyor.Karşılaştırmayı NET örneğinde gerçek NativeAOT DLL’yi çağıran ayrı Python sürecinde tekrarladım. Her iki tarafta 6 senaryo geçti. Bunlara analiz aracını yalnızca bağlayıp girişlere hook takmamak dahil: durumda işlem çalıştı. BCryptFinishHash NCryptVerifySignature girişlerine eklenen gözlem hook’ları ise ayrı testlerde tespit edildi. ölçümler çalışan laboratuvara sonradan bağlanma akışına ait; bütün erken enjeksiyon veya tespit atlatma yollarının sınandığı anlamına gelmiyor.
DLL’yi uygulamaya nasıl ekliyoruz?.NET uygulamasında tek runtime session oluşturup ilgili özelliği açmadan her hassas işlemden önce kontrol edebiliriz:
using IntegrityGuard; // Uygulama oturumu boyunca saklanır. var runtime = new RuntimeIntegritySession(); var startup = runtime.Check(IntegrityCheckpoint.Startup); if (!startup.Success) return; // Daha sonra, hassas çağrıdan hemen önce aynı session: var beforeUse = runtime.Check(IntegrityCheckpoint.BeforeOperation); if (!beforeUse.Success) return; // Ancak şimdi hassas çağrıyı başlatın.Her işlemde yeni session oluşturmak önceki hatanın bilgisini bırakır. timer arka plan işleri aynı korunan iş akışı içinde ele alınmalı. Kütüphane kendiliğinden işlem sonlandırmıyor veya başka uygulamaları kapatmıyor. Hangi özelliğin engelleneceğine ana uygulama karar veriyor.
NativeAOT DLL’deki ig_runtime_check dışa aktarımı varsayılan politikayı çağırıyor:
if (ig_runtime_check(IG_BEFORE_OPERATION) != IG_OK) { return; /* Hassas işlemi başlatma. */ }Native kontrol süreç ömrü boyunca aynı runtime session’ı tutuyor; dosya oturumu açıp kapatmak engeli temizlemiyor. Mevcut ig_check ise imzalı dosyaları kontrol etmeye devam ediyor. İki çağrının görevleri farklı. C başlığı sonuç kodları runtime rehberinde native API rehberinde açıklanıyor.
Çalıştırılabilir NET laboratuvarını hazırlamak için:
dotnet build samples/RuntimeProbe -c Release - artifacts/runtime-probe \artifacts\runtime-probe\RuntimeProbe.exe # Korumasız karşılaştırma: \artifacts\runtime-probe\RuntimeProbe.exe --unguardedGerçek analiz betiğiyle süreç karşılaştırması tests/runtime_instrumentation_tests.py içinde. Betik yalnızca kendisinin başlattığı laboratuvar sürecine bağlanıyor. Gerçek müşteri uygulamasına veya başka çalışan sürece kendiliğinden bağlanmıyor.
Hangi incelemeyi zorlaştırıyoruz, hangisi kapsam dışında? DurumBu sürümün davranışı veya sınırıİzlenen girişe yerleşen yaygın inline hookBayt farkını görebilir işlem öncesinde ret verebilirDesteklenen debugger bilgisi olumluRuntime kontrolü başarısız olurHook kaldırıldıktan sonra aynı sessionEngellenmiş kalırDosyanın diskte değiştirilmesiAyrı imzalı manifest kontrolü gerekirStatik strings veya decompilationBu kontrol bunları engellemezAnaliz aracının yalnızca sürece bağlanmasıBağlantının reddedilmesi sağlanmazİzlenmeyen fonksiyon veya girişin dışındaki değişiklikBu ölçümün kapsamına girmeyebilirKontrolden sonra, çağrıdan önce müdahaleYarış penceresi devam ederBelleği başka yoldan okumakBütün bellek okumaları engellenmezKontrolün veya karar noktasının değiştirilmesiAynı süreçteki katman hedef alınabilir32 baytlık giriş karşılaştırması bütün fonksiyonun veya uygulamanın tamamının bütünlük ölçümü değil. Debugger kontrolünü, modül adresini çözmeyi veya dosyayı okumayı sağlayan fonksiyonlar süreç içinde çalışıyor. mekanizmaları değiştiren, ölçümü yanıltan veya uygulamadaki ret dalını kaldıran müdahale ayrıca değerlendirilmeli.
Benzer şekilde hook’un yalnızca araç adıyla ilişkilendirilmesi doğru olmaz. Antivirüs, EDR başka meşru araçlar native kodda değişiklik oluşturabilir. Windows güncellemesi sırasında diskteki dosya süreçteki görüntü farklı kalabilir. Kontrolün bildirdiği şey, politikanın izlediği girişin beklenen dosya baytlarıyla eşleşmemesidir. Hedef Windows sürümünde uyumluluk yanlış alarm testleri gerekir.
Tespit sonrası nasıl davranmalı?Laboratuvarda hassas işlemi reddettim session’ı engelledim. Üretimde kullanıcıya neden işlemin açılamadığını anlatan mesaj, güvenilen yeniden başlatma veya destek yolu gerekir. Kaydedilmemiş işi kaybettiren ani kapanışlar yerine ilgili özelliği durdurmak daha uygun olabilir.
Kontrol sonucunu sunucuya gönderiyorsak sonucu tek başına güven kanıtı saymamalıyız. RuntimeVerified yazabilen istemci metni sunucuya gönderebilir. Kullanıcı, işlem yetkisi sunucuda ayrıca doğrulanmalı. Ortak SQL veya PFX sırrını istemciden kaldırmakla ilgili önceki yazıdaki mimari tercih geçerliliğini koruyor.
karşılaştırmada elde ettiğimiz sonuç daha sınırlı ama ölçülebilir: aynı analiz hook’u korumasız örneğin çözdüğü veriyi yakaladı; korumalı örnek, izlenen girişlerdeki değişikliği fark edip çözme çağrısını başlatmadı. Amaç katmanda çalışma zamanı analiz araçlarının kullandığı belirli yöntemleri fark etmek, hassas işlem için tepki vermek gözlemcinin işini zorlaştırmak. Bundan sonra bakılması gereken, hangi müdahalelerin ölçümün dışında kaldığı gerçek uygulamada aynı tepkinin hangi işlem noktalarına bağlanacağı.