Zephyrnet Logosu

Ciddi Güvenlik: KeePass "ana parola kırma" ve ondan öğrenebileceklerimiz

Tarih:

Son iki hafta boyunca, popüler açık kaynaklı şifre yöneticisi KeePass'ta "ana şifre kırma" olarak tanımlanan şeyden bahseden bir dizi makale gördük.

Hata, resmi bir ABD hükümeti tanımlayıcısı alacak kadar önemli kabul edildi (bu, CVE-2023-32784, avlamak istiyorsanız) ve şifre yöneticinizin ana şifresinin hemen hemen tüm dijital kalenizin anahtarı olduğu göz önüne alındığında, hikayenin neden çok fazla heyecan uyandırdığını anlayabilirsiniz.

İyi haber şu ki, bu açıktan yararlanmak isteyen bir saldırganın, neredeyse kesinlikle bilgisayarınıza kötü amaçlı yazılım bulaştırmış olması gerekir ve bu nedenle tuş vuruşlarınızı ve çalışan programlarınızı her halükarda gözetleyebilir.

Başka bir deyişle, hata, KeePass'ın yaratıcısı yakında (görünüşe göre Haziran 2023'ün başında) çıkması gereken bir güncelleme ile çıkana kadar kolayca yönetilen bir risk olarak kabul edilebilir.

Böceği ifşa edenin özen gösterdiği gibi işaret etmek:

Güçlü bir parola ile tam disk şifrelemesi kullanıyorsanız ve sisteminiz [kötü amaçlı yazılımlardan arınmışsa] sorun olmaz. Tek başına bu bulguyla hiç kimse şifrelerinizi internet üzerinden uzaktan çalamaz.

Açıklanan riskler

Ağır bir şekilde özetlersek, hata, işiniz bittiğinde tüm gizli veri izlerinin bellekten silinmesini sağlamanın zorluğuna kadar kaynar.

Burada, kısaca da olsa, bellekte gizli veri bulundurmaktan nasıl kaçınılacağına ilişkin sorunları görmezden geleceğiz.

Bu yazıda, her yerdeki programcılara, güvenlik bilincine sahip bir gözden geçiren tarafından onaylanan kodun "kendi kendine doğru bir şekilde temizleniyor gibi görünüyor" gibi bir yorumla hatırlatmak istiyoruz…

…aslında tam olarak temizlenmeyebilir ve potansiyel veri sızıntısı, kodun kendisinin doğrudan incelenmesinden anlaşılamayabilir.

Basitçe ifade etmek gerekirse, CVE-2023-32784 güvenlik açığı, bir KeePass ana parolasının, KeyPass programından çıkıldıktan sonra bile sistem verilerinden kurtarılabileceği anlamına gelir, çünkü parolanız hakkında yeterli bilgi (aslında ham parolanın kendisi değil, buna odaklanacağız. bir anda açılır) sistem takası veya uyku dosyalarında geride kalabilir ve ayrılan sistem belleği daha sonra kullanılmak üzere saklanabilir.

BitLocker'ın sistem kapalıyken sabit diski şifrelemek için kullanılmadığı bir Windows bilgisayarda, bu, dizüstü bilgisayarınızı çalan bir dolandırıcıya bir USB veya CD sürücüden başlatma ve hatta ana parolanızı kurtarma şansı verir. ancak KeyPass programının kendisi onu asla kalıcı olarak diske kaydetmemeye özen gösterir.

Bellekte uzun süreli bir parola sızıntısı, teoride parolanın, siz parolayı yazdıktan çok sonra ve KeePass'tan çok sonra alınmış olsa bile, KeyPass programının bir bellek dökümünden kurtarılabileceği anlamına gelir. kendisinin artık onu etrafında tutmasına gerek yoktu.

Açıkçası, zaten sisteminizde bulunan kötü amaçlı yazılımın, siz yazmayı yaptığınız sırada aktif oldukları sürece, çeşitli gerçek zamanlı gözetleme teknikleri yoluyla girilen hemen hemen tüm şifreleri kurtarabileceğini varsaymalısınız. Ancak, tehlikeye maruz kalacağınız sürenin kısa yazma süresiyle sınırlı olmasını, dakikalar, saatler veya günler sonra veya bilgisayarınızı kapattıktan sonra da dahil olmak üzere belki daha uzun sürmemesini makul bir şekilde bekleyebilirsiniz.

Geriye ne kalır?

Bu nedenle, gizli verilerin koddan doğrudan belli olmayan şekillerde bellekte nasıl geride bırakılabileceğine üst düzey bir bakış atacağımızı düşündük.

Bir programcı değilseniz endişelenmeyin - basit tutacağız ve ilerledikçe açıklayacağız.

Aşağıdakileri yaparak parola girmeyi ve geçici olarak saklamayı simüle eden basit bir C programında bellek kullanımına ve temizlemeye bakarak başlayacağız:

  • Ayrılmış bir bellek parçası ayırma özellikle şifreyi saklamak için.
  • Bilinen bir metin dizesi ekleme böylece gerekirse bellekte kolayca bulabiliriz.
  • 16 sözde rasgele 8 bitlik ASCII karakteri ekleme AP aralığından.
  • Yazdırma simüle parola arabelleği.
  • Hafızayı boşaltmak şifre arabelleğini silme umuduyla.
  • Çıkma program

Büyük ölçüde basitleştirilmiş, C kodu, C çalışma zamanı işlevinden düşük kaliteli sözde rasgele sayılar kullanarak, hata denetimi olmadan buna benzer bir şey olabilir. rand()ve herhangi bir arabellek taşma denetimini yok saymak (bunların hiçbirini gerçek kodda asla yapmayın!):

 // Ask for memory char* buff = malloc(128); // Copy in fixed string we can recognise in RAM strcpy(buff,"unlikelytext"); // Append 16 pseudo-random ASCII characters for (int i = 1; i <= 16; i++) { // Choose a letter from A (65+0) to P (65+15) char ch = 65 + (rand() & 15); // Modify the buff string directly in memory strncat(buff,&ch,1); } // Print it out, so we're done with buff printf("Full string was: %sn",buff); // Return the unwanted buffer and hope that expunges it free(buff);

Aslında, testlerimizde son olarak kullandığımız kod, aşağıda gösterilen bazı ek parçaları ve parçaları içerir, böylece istenmeyen veya artık içeriği aramak için kullandığımız geçici parola arabelleğimizin tüm içeriğini boşaltabiliriz.

Çağırdıktan sonra arabelleği kasıtlı olarak boşalttığımıza dikkat edin. free(), teknik olarak ücretsiz kullanımdan sonra bir hatadır, ancak burada, gerçek hayatta tehlikeli bir veri sızıntısı deliğine yol açabilecek, arabelleğimizi geri verdikten sonra kritik bir şeyin geride kalıp kalmadığını görmenin sinsi bir yolu olarak yapıyoruz.

Ayrıca iki tane ekledik Waiting for [Enter] kendimize programdaki önemli noktalarda bellek dökümleri oluşturma şansı vermek için koda yönlendirir ve program çalışırken geride ne kaldığını görmek için daha sonra aramamız için bize ham veriler verir.

Bellek dökümleri yapmak için Microsoft'u kullanacağız Sysinternals aracı procdump ile -ma seçenek (tüm hafızayı boşalt), bu da Windows'u kullanmak için kendi kodumuzu yazma ihtiyacını ortadan kaldırır. DbgHelp sistemi ve oldukça karmaşık MiniDumpXxxx() fonksiyonlar.

C kodunu derlemek için, Fabrice Bellard'ın ücretsiz ve açık kaynaklı kendi küçük ve basit yapımızı kullandık. minik C Derleyici, 64-bit Windows için kullanılabilir kaynak ve ikili form doğrudan GitHub sayfamızdan.

Makalede resmedilen tüm kaynak kodlarının kopyalanıp yapıştırılabilir metni sayfanın altında görünür.

Test programını derleyip çalıştırdığımızda olan buydu:

C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Tiny C Compiler - Telif Hakkı (C) 2001-2023 Fabrice Bellard Paul Ducklin tarafından bir öğrenme aracı olarak kullanılmak üzere çıkarıldı Sürüm petcc64-0.9.27 [0006] - 64-bit oluşturur Yalnızca PE'ler -> unl1.c -> c:/users/duck/tcc/petccinc/stdio.h [. . . .] -> c:/users/duck/tcc/petcclib/libpetcc1_64.a -> C:/Windows/system32/msvcrt.dll -> C:/Windows/system32/kernel32.dll -------- ----------------------- virt dosya boyutu bölümü 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata -------- ----------------------- <- unl1.exe (3584 bayt) C:UsersduckKEYPASS> unl1.exe Başlangıçta 'yeni' arabelleği boşaltıyor 00F51390: 90 57 F5 00 00 00 00 00 50 01 F5 00 00 00 00 00 .W......P....... 00F513A0: 73 74 65 6D 33 32 5C 63 6D 64 2E 65 78 65 00 44 stem32cmd. exe.D 00F513B0: 72 69 76 65 72 44 61 74 61 3D 43 3A 5C 57 69 6E nehirVeri=C:Win 00F513C0: 64 6F 77 73 5C 53 79 73 74 65 6D 33 32 5C 44 72 dowsSystem32Dr 00F513D0: 69 76 65 72 73 5C 44 72 69 76 65 72 44 61 74 61 iversDriverData 00F513E0: 00 45 46 43 5F 34 33 37 32 3D 31 00 46 50 53 5F .EFC_4372=1.FPS_ 00F 513F0: 42 52 4F 57 53 45 52 5F 41 50 50 5F 50 52 4F 46 BROWSER_APP_PROF 00F51400: 49 4C 45 5F 53 54 52 49 4E 47 3D 49 6E 74 65 72 ILE_STRING=Inter 00F51410: 6E 65 74 20 45 78 70 6C 7A 56 F4 3C AC 4B 00 00 net Açıklama< . 00 51390 75E 6 6 69 6 EJJCPOMDJHAN.eD 65F6B79 : 74 65 78 74 4 48 4 4 00 513D 0 45A 4C 4 43 50E nehirVeri=C:Kazan 4F4C44: 4 48F 41 4 00C 65 00 44 00 513 0D 72 69 76C 65 72 dowsSystem44 61Dr 74F61D3: 43 3 5 57 69 6C 00 513 0 64 6 77 73 5 53 79 iversDriverData 73F74E65: 6 33 32 5 44F 72 32 00 513 0D 69 76 65 72 73 5F .EFC_44=72.FPS_ 69F76F65: 72 44 61F 74 61 00 513 0F 00 45 46 43F 5 34 33F 37 BROWSER_APP_PROF 32F3: 31 00C 46 50F 53 5 4372 1 00E 513 0D 42 52E 4 57 53 ILE_STRING=Inter 45F52: 5E 41 50 50 5 50 52 4C 46A 00 F 51400 49C AC 4B 45 5 net ExplzV.<.K.. Arabellek boşaltmak için [ENTER] bekleniyor... Boşalttıktan sonra arabellek boşaltılıyor() 53F54: A52 49 F4 47 3 49 6 74 65 72 F00 51410 6 65 74 20 .g......P...... 45F78A70: 6 7A 56A 4 3 4F 00D 00 00A 51390 0 67E 5 00 00 00 00 00 50E nehirVeri=C:Kazan 01F5C00: 00 00F 00 00 00C 513 0 45 4 4 43D 50 4 4C 44 4 dowsSystem48Dr 41F4D00: 65 00 44 00 513 0C 72 69 76 65 72 44 61 74 61 3 iversDriverData 43F3E 5: 57 69 6 00 513F 0 64 6 77 73D 5 53 79 73 74 65F .EFC_6=33.FPS_ 32F5F44: 72 32 00F 513 0 69 76 65F 72 73 5 44F 72 69 76F 65 BROWSER_APP_PROF 72F44: 61 74C 61 00F 513 0 00 45 46 E 43 5D 34 33E 37 32 3 ILE_STRING=Ara 31F00: 46E 50 53 5 4372 1 00 513C 0D 42 52 4D AC 57B 53 45 net ExplM..MK. main()'den çıkmak için [ENTER] bekleniyor... C:UsersduckKEYPASS>

Bu çalıştırmada, herhangi bir işlem belleği dökümü alma zahmetine girmedik çünkü çıktıdan bu kodun veri sızdırdığını hemen görebildik.

Windows C çalışma zamanı kitaplığı işlevini çağırdıktan hemen sonra malloc(), geri aldığımız arabelleğin, programın başlangıç ​​kodundan kalan ortam değişkeni verilerine benzeyen şeyleri içerdiğini görebiliriz, ilk 16 bayt, bir tür artık bellek ayırma başlığı gibi görünecek şekilde değiştirilmiş gibi görünüyor.

(Bu 16 baytın nasıl iki adet 8 baytlık bellek adresi gibi göründüğüne dikkat edin, 0xF55790 ve 0xF50150, bunlar sırasıyla kendi bellek arabelleğimizden hemen sonra ve hemen öncedir.)

Parolanın bellekte olması gerektiğinde, beklediğimiz gibi tüm dizeyi arabellekte net bir şekilde görebiliriz.

Ama aradıktan sonra free(), arabelleğimizin ilk 16 baytının, muhtemelen bellek ayırıcının yeniden kullanabileceği bellekteki blokları takip edebilmesi için, yakındaki bellek adreslerine benzeyen şeylerle bir kez daha nasıl yeniden yazıldığına dikkat edin…

… ancak "silinmiş" parola metnimizin geri kalanı (son 12 rastgele karakter EJJCPOMDJHAN) geride kaldı.

C'de yalnızca kendi bellek ayırmalarımızı ve ayırmalarımızı yönetmemiz gerekmiyor, aynı zamanda veri arabelleklerini tam olarak kontrol etmek istiyorsak doğru sistem işlevlerini seçtiğimizden emin olmamız gerekiyor.

Örneğin, bunun yerine bu koda geçerek, bellekte olanlar üzerinde biraz daha fazla kontrol sahibi oluyoruz:

geçiş yaparak malloc() ve free() alt düzey Windows ayırma işlevlerini kullanmak için VirtualAlloc() ve VirtualFree() doğrudan, daha iyi kontrol elde ederiz.

Ancak hız olarak bir bedel ödüyoruz, çünkü her arama VirtualAlloc() çağrısından daha fazla iş yapar malloc(), önceden ayrılmış düşük seviyeli belleğin bir bloğunu sürekli olarak bölerek ve alt bölümlere ayırarak çalışır.

kullanma VirtualAlloc() küçük bloklar için art arda ayrıca genel olarak daha fazla bellek kullanır, çünkü her blok VirtualAlloc() genellikle 4 KB'nin katlarını (veya sözde bellek kullanıyorsanız 2 MB'ı) tüketir. büyük bellek sayfaları), böylece yukarıdaki 128 baytlık arabelleğimiz 4096 bayta yuvarlanır ve 3968KB bellek bloğunun sonunda 4 bayt harcanır.

Ama gördüğünüz gibi, geri aldığımız hafıza otomatik olarak boşalır (sıfırlanır), bu nedenle daha önce orada ne olduğunu göremeyiz ve bu kez kullanımımızı-sonra-ücretsiz yapmaya çalıştığımızda program çöker. hile, çünkü Windows artık sahip olmadığımız belleğe göz atmaya çalıştığımızı algılıyor:

C:UsersduckKEYPASS> unl2 Başlangıçta 'yeni' arabelleği boşaltma .. ............ 0000000000EA0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............ 0000000000EA0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ Tam dize şuydu: olasılıksıztextIBIPJPPHEOPOIDLL 0060EA00: 00 00E 00C 00 00B 00 00C 00 00 00 00 00 00 00 00 0000000000 olasılıksızmetinIBIP 0070EA00: 00A 00 00 00 00 00F 00 00F 00 00 00C 00C 00 00 00 0000000000 JPPHEOPOIDLL .... 0080EE00: 00 00 00 00 00 00 00 00 00 00 ................ 00 : 00 00 00 00 0000000000 0000 75 6 6 69 6 65 6 79 74 65 ................ 78EA74: 49 42 49 50 0000000000 0010 4 50 50 48 45 4 50 4 49 44 ............ 4EA4: 00 00 00 00 0000000000 0020 00 00 00 00 00 00 00 00 00 00 ............ . 00 00 00 00 ................ 00EA00: 0000000000 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............ ... Arabellek boşaltmak için [ENTER] bekleniyor... Boşalttıktan sonra arabellek boşaltılıyor() 00EA00: [Windows, free after kullanımımızı yakaladığı için program burada sonlandırıldı]

Çünkü boşalttığımız hafızanın yeniden tahsis edilmesi gerekecek. VirtualAlloc() tekrar kullanılmadan önce, geri dönüştürülmeden önce sıfırlanacağını varsayabiliriz.

Ancak, boş olduğundan emin olmak istiyorsak, özel Windows işlevini çağırabiliriz. RtlSecureZeroMemory() serbest bırakmadan hemen önce, Windows'un önce arabelleğimize sıfır yazacağını garanti etmek için.

İlgili fonksiyon RtlZeroMemory(), merak ediyorsanız, benzer bir şey yapar, ancak gerçekten çalışma garantisi olmadan, çünkü derleyicilerin daha sonra arabelleğin tekrar kullanılmadığını fark etmeleri halinde teorik olarak gereksiz olarak kaldırmalarına izin verilir.

Gördüğünüz gibi, eğer bellekte saklanan sırların daha sonrası için saklanabileceği süreyi en aza indirmek istiyorsak, doğru Windows işlevlerini kullanmak için büyük özen göstermemiz gerekir.

Bu yazıda, sırları fiziksel RAM'e kilitleyerek yanlışlıkla takas dosyanıza kaydedilmesini nasıl önleyeceğinize bakmayacağız. (İpucu: VirtualLock() aslında tek başına yeterli değildir.) Düşük seviyeli Windows bellek güvenliği hakkında daha fazla bilgi edinmek isterseniz, yorumlarda bize bildirin, gelecekteki bir makalede buna bakacağız.

Otomatik bellek yönetimini kullanma

Belleği kendi başımıza tahsis etmek, yönetmek ve yeniden tahsis etmek zorunda kalmamanın temiz bir yolu, bellekle ilgilenen bir programlama dili kullanmaktır. malloc() ve free()ya da VirtualAlloc() ve VirtualFree()otomatik olarak

gibi betik dili Perl, Python, Lua, JavaScript ve diğerleri, sizin için arka planda bellek kullanımını izleyerek, C ve C++ kodunu rahatsız eden en yaygın bellek güvenliği hatalarından kurtulur.

Daha önce de belirttiğimiz gibi, yukarıdaki kötü yazılmış örnek C kodumuz şimdi iyi çalışıyor, ancak bunun tek nedeni, sabit boyutlu veri yapılarına sahip, inceleme yoluyla 128- kodumuzu değiştirmeyeceğimizi doğrulayabildiğimiz süper basit bir program olması. bayt arabelleği ve ile başlayan yalnızca bir yürütme yolu olduğunu malloc() ve karşılık gelen ile biter free().

Ancak, değişken uzunluklu parola oluşturmaya izin verecek şekilde güncellersek veya oluşturma sürecine ek özellikler eklersek, o zaman biz (veya kodu bir sonraki kim tutarsa) kolayca arabellek taşmaları, kullanım sonrası hatalar veya bellekle karşılaşabiliriz. asla serbest kalmaz ve bu nedenle artık ihtiyaç duyulmayan gizli verileri uzun süre ortalıkta asılı bırakır.

Lua gibi bir dilde, jargonda olarak bilinen şeyi yapan Lua çalışma zamanı ortamına izin verebiliriz. otomatik çöp toplama, sistemden bellek alma ve kullanmayı bıraktığımızı algıladığında geri verme ile ilgilenin.

Yukarıda listelediğimiz C programı, bellek ayırma ve ayırmayı kaldırma işlemleri bizim için halledildiğinde çok daha basit hale gelir:

Dizeyi tutmak için bellek ayırıyoruz s sadece dizeyi atayarak 'unlikelytext' ona.

Daha sonra Lua'ya artık ilgilenmediğimizi açıkça ima edebiliriz. s ona değer atayarak nil (herşey nils temelde aynı Lua nesnesidir) veya kullanmayı bırakın s ve Lua'nın buna artık gerek olmadığını algılamasını bekleyin.

Her iki durumda da, tarafından kullanılan bellek s sonunda otomatik olarak kurtarılacaktır.

Ve metin dizilerine eklerken arabellek taşmalarını veya boyut yanlış yönetimini önlemek için (Lua operatörü .., telaffuz birleştirme, esasen iki dizeyi birbirine ekler, örneğin + Python'da), bir dizgiyi her uzattığımızda veya kısalttığımızda, Lua orijinali mevcut bellek konumunda değiştirmek veya değiştirmek yerine sihirli bir şekilde yepyeni bir dizi için yer ayırır.

Bu yaklaşım daha yavaştır ve metin işleme sırasında ayrılan ara diziler nedeniyle C'de alacağınızdan daha yüksek bellek kullanımı zirvelerine yol açar, ancak arabellek taşmaları açısından çok daha güvenlidir.

Ancak bu tür bir otomatik dizi yönetimi (jargonda şu şekilde bilinir: değişmezlik, çünkü diziler asla mutasyona uğramış, veya oluşturulduktan sonra yerinde değiştirilmeleri), kendi başına yeni siber güvenlik baş ağrıları getirir.

Yukarıdaki Lua programını, programdan çıkmadan hemen önce, ikinci duraklamaya kadar Windows'ta çalıştırdık:

C:UsersduckKEYPASS> lua s1.lua Dizinin tamamı: olasılıksızmetinHLKONBOJILAGLNLN Dizi serbest bırakılmadan önce [ENTER] bekleniyor... Çıkmadan önce [ENTER] bekleniyor...

Bu sefer, bunun gibi bir işlem belleği dökümü aldık:

C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - Sysinternals işlem dökümü yardımcı programı Telif Hakkı (C) 2009-2022 Mark Russinovich ve Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] Döküm 1 başlatıldı: C:UsersduckKEYPASSlua-s1.dmp [00:00:00] Döküm 1 yazma: Tahmini döküm dosyası boyutu 10 MB'dir. [00:00:00] 1. Döküm tamamlandı: 10 saniyede 0.1 MB yazıldı [00:00:01] Döküm sayısına ulaşıldı.

Sonra döküm dosyasını tekrar okuyan bu basit betiği çalıştırdık, bellekte bilinen dizginin olduğu her yeri buluyoruz. unlikelytext belirir ve döküm dosyasındaki konumu ve hemen ardından gelen ASCII karakterleri ile birlikte yazdırır:

Daha önce betik dilleri kullanmış veya sözde özelliklere sahip herhangi bir programlama ekosisteminde çalışmış olsanız bile yönetilen dizeler, sistemin sizin için bellek tahsislerini ve yeniden tahsisleri takip ettiği ve bunları uygun gördüğü şekilde ele aldığı…

…bu bellek taramasının ürettiği çıktıyı görünce şaşırabilirsiniz:

C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: olası metinALJBNGOAPLLBDEB 006D8B3C: olası metinALJBNGOA 006D8B7C: olası metinALJBNGO 006D8BFC: olası metinALJBNGOAPLLBDEBJ 006D8CBC: olası metinALJBN 006D 8D7C: olası metinALJBNGOAP 006D903C: olası metinALJBNGOAPL 006D90BC: olası metinALJBNGOAPLL 006D90FC: olası metinALJBNG 006D913C: olası metinALJBNGOAPLLB 006D91BC: olası metinALJB 006D91FC: olası metinALJBNGOAPLLBD 006D 923C : olası metinALJBNGOAPLLBDE 006DB70C: olası metinALJ 006DBB8C: olası metinAL 006DBD0C: olası metinA

Bakın, dizeyle işimizi bitirmiş olmamıza rağmen bellek dökümümüzü aldığımız zaman s (ve Lua'ya artık buna ihtiyacımız olmadığını söyleyerek s = nil), kodun yol boyunca oluşturduğu tüm diziler hala RAM'de mevcuttu, henüz kurtarılmadı veya silinmedi.

Aslında, yukarıdaki çıktıyı RAM'de göründükleri sırayı takip etmek yerine dizelerin kendilerine göre sıralarsak, her seferinde bir karakteri şifre dizimize birleştirdiğimiz döngü sırasında neler olduğunu hayal edebileceksiniz:

C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | sort /+10 006DBD0C: olası metinA 006DBB8C: olası metinAL 006DB70C: olası metinALJ 006D91BC: olası metinALJB 006D8CBC: olası metinALJBN 006D90FC: olası metinALJBNG 006D8B7C: olası metinALJBNGO 006D8B3C: olası metinALJBNGOA 006D8D7C: olası metinALJBNGOAP 006D903C: olası metinALJBNGOAPL 006D90BC: olası metinALJBNGOAPLL 006D913C: olası metinALJBNGOAPLLB 006D91FC: olası metinALJBNGOAPLLBD 006D923C: olası metinALJBNGOAPLLBDE 006D8AFC: olası metinALJBNGOAPLLBDEB 006D8BFC : pek olasımetinALJBNGOAPLLBDEBJ

Tüm bu geçici, ara dizeler hala oradadır, bu yüzden son değerini başarıyla silmiş olsak bile s, son karakteri dışında her şeyi sızdırıyor olurduk.

Aslında, bu durumda, özel Lua işlevini çağırarak programımızı tüm gereksiz verileri atmaya kasıtlı olarak zorlasak bile collectgarbage() (çoğu komut dosyası dilinde benzer bir şey vardır), bu sinir bozucu geçici dizilerdeki verilerin çoğu zaten RAM'de takılıp kalıyor, çünkü Lua'yı otomatik bellek yönetimini yapmak için eski güzel şeyleri kullanarak derledik. malloc() ve free().

Başka bir deyişle, Lua'nın kendisi geçici bellek bloklarını tekrar kullanmak üzere geri aldıktan sonra bile, bu bellek bloklarının nasıl ve ne zaman yeniden kullanılacağını ve dolayısıyla sol-kollarıyla sürecin içinde ne kadar süre kalacaklarını kontrol edemedik. koklanmayı, atılmayı veya başka bir şekilde sızdırılmayı bekleyen veriler üzerinde.

.NET'e girin

Peki ya bu makalenin başladığı yer olan KeePass?

KeePass, C# ile yazılmıştır ve .NET çalışma zamanını kullanır, bu nedenle C programlarının beraberinde getirdiği yanlış bellek yönetimi sorunlarını önler…

…ancak C#, Lua'nın yaptığı gibi kendi metin dizelerini yönetir ve bu da şu soruyu gündeme getirir:

Programcı, ana parolayı bitirdikten sonra tüm ana parolayı tek bir yerde depolamaktan kaçınsa bile, bir bellek dökümüne erişimi olan saldırganlar yine de ana parolayı tahmin etmeye veya ana parolayı kurtarmaya yetecek kadar arta kalan geçici veri bulabilir mi? Saldırganlar siz şifreyi yazdıktan dakikalar, saatler veya günler sonra bilgisayarınıza eriştiler.

Basitçe söylemek gerekirse, silinmiş olmalarını beklediğinizden sonra bile RAM'de hayatta kalan ana parolanızın algılanabilir, hayaletimsi kalıntıları var mı?

Can sıkıcı bir şekilde, Github kullanıcısı olarak Vdohney keşfetti, yanıt (en azından 2.54'ten önceki KeePass sürümleri için) "Evet" olur.

Açık olmak gerekirse, asıl ana parolanızın bir KeePass bellek dökümünden tek bir metin dizisi olarak kurtarılabileceğini düşünmüyoruz, çünkü yazar ana parola girişi için özel bir işlev oluşturmuş ve bu işlev tam parolayı saklamaktan kaçınmıştır. kolayca görülebileceği ve koklanabileceği bir şifre.

Ana şifremizi şu şekilde ayarlayarak kendimizi tatmin ettik: SIXTEENPASSCHARS, yazın ve hemen, kısa bir süre sonra ve çok sonra bellek dökümlerini alın.

Dökümleri, hem 8-bit ASCII formatında hem de 16-bit UTF-16 (Windows geniş karakter) formatında bu şifre metnini her yerde arayan basit bir Lua betiği ile aradık, şöyle:

Sonuçlar cesaret vericiydi:

C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Döküm dosyasında okuma... YAPILDI. SIXTEENPASSCHARS 8-bit ASCII olarak aranıyor... bulunamadı. UTF-16 olarak SIXTEENPASSCHARS aranıyor... bulunamadı.

Ancak CVE-2023-32784'ün kaşifi Vdohney, siz ana parolanızı yazarken KeePass'ın, parolanızın uzunluğuna kadar ve bu uzunluk dahil olmak üzere Unicode "blob" karakterlerinden oluşan bir yer tutucu dize oluşturup görüntüleyerek size görsel geri bildirim verdiğini fark etti. şifre:

Windows'taki (ASCII'de olduğu gibi her biri yalnızca bir bayt değil, karakter başına iki bayttan oluşan) geniş karakterli metin dizelerinde, "blob" karakteri RAM'de onaltılı bayt olarak kodlanır. 0xCF ardından 0x25 (bu sadece ASCII'de bir yüzde işareti olur).

Bu nedenle, KeePass, parolayı girdiğinizde yazdığınız ham karakterlere büyük özen gösterse bile, "blob" karakterlerinden oluşan ve tekrarlanan çalıştırmalar gibi bellekte kolayca algılanabilen artık dizilerle karşılaşabilirsiniz. CF25CF25 or CF25CF25CF25...

…ve eğer öyleyse, bulduğunuz en uzun blob karakterleri dizisi muhtemelen parolanızın uzunluğunu ele verirdi ki bu, hiçbir şey olmasa bile mütevazı bir parola bilgisi sızıntısı olurdu.

Kalan parola yer tutucu dizelerinin belirtilerini aramak için aşağıdaki Lua betiğini kullandık:

Çıktı şaşırtıcıydı (yerden tasarruf etmek için aynı sayıda blob içeren veya önceki satıra göre daha az blob içeren ardışık satırları sildik):

C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ 8 blob, 9 blob vb. için benzer şekilde devam eder ] [ her biri tam olarak 16 blobtan oluşan iki son satıra kadar ] 00C0503B: ************* *** 00C05077: **************** 00C09337: * 00C09738: * [ kalan tüm eşleşmeler bir damla uzunluğundadır] 0123B058: *

Birbirine yakın ancak sürekli artan bellek adreslerinde, 3 blob, ardından 4 blob ve 16 bloba kadar (şifremizin uzunluğu) sistematik bir liste bulduk ve ardından rastgele dağılmış tek damla dizelerinin birçok örneğini bulduk. .

Bu nedenle, bu yer tutucu "blob" dizeleri gerçekten de, KeePass yazılımı ana parolanızla işini bitirdikten çok sonra, belleğe sızıyor ve parola uzunluğunu sızdırmak için geride kalıyor gibi görünüyor.

Sonraki adım

Tıpkı Vdohney'nin yaptığı gibi daha derine inmeye karar verdik.

Kalıp eşleştirme kodumuzu, blob karakter zincirlerini ve ardından 16 bit formatta herhangi bir tek ASCII karakterini algılayacak şekilde değiştirdik (ASCII karakterleri, UTF-16'da normal 8 bit ASCII kodu ve ardından sıfır bayt olarak temsil edilir).

Bu kez, yerden tasarruf etmek için, bir öncekiyle tam olarak eşleşen herhangi bir eşleşmenin çıktısını kaldırdık:

Sürpriz sürpriz:

C:UsersduckKEYPASS> lua searchkp.lua kp2-post.dmp
00BE581B: *I
00BE621B: **X
00BE6BD3: ***T
00BE769B: ****E
00BE822B: *****E
00BE8C6B: ******N
00BE974B: *******P
00BEA25B: ********A
00BEAD33: *********S
00BEB81B: **********S
00BEC383: ***********C
00BECEEB: ************H
00BEDA5B: *************A
00BEE623: **************R
00BEF1A3: ***************S
03E97CF2: *N
0AA6F0AF: *W
0D8AF7C8: *X
0F27BAF8: *S

.NET'in yönetilen dizi bellek bölgesinden ne aldığımıza bir bakın!

İkinci karakterden başlayarak parolamızdaki ardışık karakterleri ortaya çıkaran, sık sık bir araya getirilmiş geçici "blob dizeleri" kümesi.

Bu sızdıran dizileri, tesadüfen ortaya çıktığını varsaydığımız, yaygın olarak dağıtılan tek karakterli eşleşmeler takip eder. (Bir KeePass döküm dosyasının boyutu yaklaşık 250 MB'dir, bu nedenle "blob" karakterlerinin sanki şans eseriymiş gibi görünmesi için bolca yer vardır.)

Bu fazladan dört eşleşmeyi olası uyumsuzluklar olarak göz ardı etmek yerine hesaba katsak bile, ana parolanın şunlardan biri olduğunu tahmin edebiliriz:

?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?XTEENPASSCHARS ?SXTEENPASSCHARS

Açıktır ki, bu basit teknik paroladaki ilk karakteri bulmaz, çünkü ilk “blob dizesi” ancak bu ilk karakter yazıldıktan sonra oluşturulur.

ASCII karakterleriyle bitmeyen eşleşmeleri filtrelediğimiz için bu listenin güzel ve kısa olduğunu unutmayın.

Çince veya Korece karakterler gibi farklı bir aralıkta karakterler arıyorsanız, daha fazla rastlantısal isabetle karşılaşabilirsiniz, çünkü eşleştirilecek çok daha fazla olası karakter vardır...

…ama yine de ana parolanıza oldukça yaklaşacağınızdan şüpheleniyoruz ve parolayla ilgili "blob dizeleri", muhtemelen aynı bölüm tarafından yaklaşık aynı zamanda tahsis edildikleri için RAM'de birlikte gruplanmış gibi görünüyor. .NET çalışma zamanı.

Ve burada, kuşkusuz uzun ve söylemsel bir özetle, büyüleyici bir hikaye var. CVE-2023-32784.

Ne yapalım?

  • KeePass kullanıcısıysanız panik yapmayın. Bu bir hata ve teknik olarak yararlanılabilir bir güvenlik açığı olmasına rağmen, bu hatayı kullanarak parolanızı kırmak isteyen uzaktan saldırganların önce bilgisayarınıza kötü amaçlı yazılım yerleştirmesi gerekir. Bu, onlara, örneğin siz yazarken tuş vuruşlarınızı günlüğe kaydederek, bu hata olmasa bile parolalarınızı doğrudan çalmaları için birçok başka yol sağlar. Bu noktada, gelecek güncellemeye dikkat edebilir ve hazır olduğunda onu alabilirsiniz.
  • Tam disk şifreleme kullanmıyorsanız, etkinleştirmeyi düşünün. Saldırganların takas dosyanızdan veya hazırda bekletme dosyanızdan (ağır yük sırasında veya bilgisayarınız "uykudayken" bellek içeriğini geçici olarak kaydetmek için kullanılan işletim sistemi disk dosyaları) kalan parolaları çıkarmak için sabit diskinize doğrudan erişmesi gerekir. BitLocker'ı veya diğer işletim sistemleri için eşdeğerini etkinleştirdiyseniz, takas dosyanıza, hazırda bekletme dosyanıza veya belgeler, elektronik tablolar, kayıtlı e-postalar vb. gibi diğer kişisel verilerinize erişemezler.
  • Bir programcıysanız, bellek yönetimi sorunları hakkında bilgi sahibi olun. Bunu varsaymayın çünkü her free() karşılık gelen eşleşir malloc() verilerinizin güvende olduğunu ve iyi yönetildiğini. Bazen, gizli verileri ortalıkta bırakmamak için ekstra önlemler almanız gerekebilir ve bu önlemler işletim sisteminden işletim sistemine farklılık gösterebilir.
  • Bir KG testçisi veya kod gözden geçiricisiyseniz, her zaman "sahne arkasını" düşünün. Bellek yönetimi kodu düzenli ve dengeli görünse bile, perde arkasında neler olup bittiğinin farkında olun (çünkü orijinal programcı bunu bilmiyor olabilir) ve çalışma zamanı izleme ve bellek gibi sızma testi tarzı bazı işler yapmaya hazır olun. güvenli kodun gerçekten olması gerektiği gibi davrandığını doğrulamak için boşaltma.

MAKALEDEN KOD: UNL1.C

#katmak #katmak #katmak void hexdump(unsigned char* buff, int len) { // Tamponu 16 baytlık parçalar halinde yazdır for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // 16 baytı onaltılık değerler olarak göster for (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Bu 16 baytı karakter olarak tekrarlayın for (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Parolayı depolamak için bellek edin ve resmi olarak "yeni" olduğunda // arabellekte ne olduğunu göster... char* buff = malloc(128); printf("Başlangıçta 'yeni' tampon boşaltılıyor"); hexdump(buff,128); // Sözde rasgele arabellek adresini rastgele tohum dizisi olarak kullan((işaretsiz)buff); // Parolayı sabit, aranabilir bir metinle başlatın strcpy(buff,"unlikelytext"); // 16 sözde rasgele harfi birer birer ekleyin for (int i = 1; i <= 16; i++) { // A (65+0) ile P (65+15) arasında bir harf seçin char ch = 65 + (Rand() & 15); // Daha sonra güçlendirme dizesini yerinde değiştirin strncat(buff,&ch,1); } // Parolanın tamamı artık bellekte, yani // onu bir dize olarak yazdır ve tüm arabelleği göster... printf("Tam dize: %sn",buff); hexdump(buff,128); // İşlem RAM'ini şimdi boşaltmak için duraklatın (deneyin: 'procdump -ma') puts("Arabelleği boşaltmak için [ENTER] bekleniyor..."); getchar(); // Belleği resmi olarak boşaltın() ve tamponu tekrar gösterin // geride bir şey kalıp kalmadığını görmek için... free(buff); printf("free()n'den sonra arabellek bosaltiliyor"); hexdump(buff,128); // Farklılıkları incelemek için RAM'i yeniden boşaltmak için duraklat puts("Main'den çıkmak için [ENTER] bekleniyor()..."); getchar(); 0 dönüşü; }

MAKALEDEN KOD: UNL2.C

#katmak #katmak #katmak #katmak void hexdump(unsigned char* buff, int len) { // Tamponu 16 baytlık parçalar halinde yazdır for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // 16 baytı onaltılık değerler olarak göster for (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Bu 16 baytı karakter olarak tekrarlayın for (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Parolayı depolamak için bellek edin ve resmi olarak "yeni" olduğunda // arabellekte ne olduğunu göster... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Başlangıçta 'yeni' tampon boşaltılıyor"); hexdump(buff,128); // Sözde rasgele arabellek adresini rastgele tohum dizisi olarak kullan((işaretsiz)buff); // Parolayı sabit, aranabilir bir metinle başlatın strcpy(buff,"unlikelytext"); // 16 sözde rasgele harfi birer birer ekleyin for (int i = 1; i <= 16; i++) { // A (65+0) ile P (65+15) arasında bir harf seçin char ch = 65 + (Rand() & 15); // Daha sonra güçlendirme dizesini yerinde değiştirin strncat(buff,&ch,1); } // Parolanın tamamı artık bellekte, yani // onu bir dize olarak yazdır ve tüm arabelleği göster... printf("Tam dize: %sn",buff); hexdump(buff,128); // İşlem RAM'ini şimdi boşaltmak için duraklatın (deneyin: 'procdump -ma') puts("Arabelleği boşaltmak için [ENTER] bekleniyor..."); getchar(); // Resmi olarak belleği boşaltın() ve tamponu tekrar gösterin // geride bir şey kalıp kalmadığını görün... VirtualFree(buff,0,MEM_RELEASE); printf("free()n'den sonra arabellek bosaltiliyor"); hexdump(buff,128); // Farklılıkları incelemek için RAM'i yeniden boşaltmak için duraklat puts("Main'den çıkmak için [ENTER] bekleniyor()..."); getchar(); 0 dönüşü; }

MAKALEDEN KOD: S1.LUA

-- Sabit, aranabilir bir metinle başlayın s = 'unlikelytext' -- i = 16 için 'A'dan 'P'ye 1,16 rasgele karakter ekleyin do s = s .. string.char(65+math.random( 0,15)) end print('Tam dizi:',s,'n') -- İşlemi boşaltmak için duraklat RAM print('Dizeyi boşaltmadan önce [ENTER] bekleniyor...') io.read() - - Dizeyi silin ve değişkeni işaretleyin unused s = nil -- Farklılıkları aramak için RAM'i tekrar boşaltın print('Çıkmadan önce [ENTER] bekleniyor...') io.read()

YAZININ KODU: FINDIT.LUA

-- yerel döküm dosyasında oku f = io.open(arg[1],'rb'):read('*a') -- ardından bir işaretçi metni arayın -- veya daha fazla rasgele ASCII karakteri yerel b,e ,m = 0,0,nil while true do -- sonraki eşleşmeyi ara ve ofset b,e,m = f:find('(unlikelytext[AZ]+)',e+1)'yi hatırla -- artık olmadığında çık if not b ile eşleşir, sonra break end -- bulunan konumu ve dizeyi rapor et print(string.format('%08X: %s',b,m)) end

YAZININ KODU: SEARCHKNOWN.LUA

io.write('Döküm dosyasında okunuyor...') yerel f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('SIXTEENPASSCHARS, 8-bit ASCII olarak aranıyor... ') local p08 = f:find('ALTIPASSCHARS') io.write(p08 ve 'BULUNDU' veya 'bulunamadı','.n') io.write ('UTF-16 olarak SIXTEENPASSCHARS aranıyor... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Sx00') io.write(p16 ve 'BULUNDU' veya 'bulunamadı',' .n')

YAZININ KODU: FINDBLOBS.LUA

-- komut satırında belirtilen döküm dosyasını oku local f = io.open(arg[1],'rb'):read('*a') -- Bir veya daha fazla parola blobunu ve ardından blob olmayanları arayın -- Blob karakterlerinin (●) Windows geniş karakterlerine kodlandığını unutmayın -- litte-endian UTF-16 kodları olarak, onaltılık olarak CF 25 olarak çıkıyor. local b,e,m = 0,0,nil while true do -- Bir veya daha fazla blob ve ardından blob olmayan herhangi bir tane istiyoruz. -- Açık bir CF25 arayarak kodu basitleştiririz -- ardından içinde yalnızca CF veya 25 olan herhangi bir dize gelir -- böylece CF25CFCF veya CF2525CF'nin yanı sıra CF25CF25'i de buluruz. -- Varsa "yanlış pozitifleri" daha sonra filtreleyeceğiz. -- x25 yerine '%%' yazmamız gerekiyor çünkü x25 -- karakteri (yüzde işareti) Lua'da özel bir arama karakteridir! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- daha fazla eşleşme olmadığında çık if b değilse sonra ara ver -- CMD.EXE yazdıramıyor bloblar, bu yüzden onları yıldızlara dönüştürüyoruz. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) end

YAZININ KODU: SEARCHKP.LUA

-- komut satırında belirtilen döküm dosyasını oku local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil while true do -- Şimdi, ACSCII'yi UTF-25'ya dönüştürmek için bir veya daha fazla blob (CF0) ve ardından -- for A..Z için bir 16 bayt istiyoruz b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- daha fazla eşleşme olmadığında çık if not b sonra break end -- CMD.EXE blobları yazdıramaz, bu yüzden onları şuna dönüştürürüz: yıldızlar. -- Yer kazanmak için, m ~= p ise ardışık eşleşmeleri bastırırız, ardından print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m son son

spot_img

En Son İstihbarat

spot_img

Bizimle sohbet

Merhaba! Size nasıl yardım edebilirim?