┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ PT_NOTE Disinfector │ █ │ ~ manizzle └───────────────────█ ──┘ [ Çeviri @batcain tarafından yapılmıştır ] Selam herkese. Başlangıç olarak şunu belirtmek isterim, ben bir "AVer" (AV ile uğraşan kişiler için kullanılıyor, "VXer" gibi düşünebilirsiniz.) değilim. Anti-virüsler saçmalık, hala çözülememiş bir sürü hataları var ve bu hatalardan dolayı sömürülmeye açıklar. AVler tarafından sıkça kullanılan lief ve capstone bileşenlerini fuzzlamaktan çekinmeyin. Eminim birçok çözülmemiş sıkıntıları çıkacaktır. Şimdi, zararlının etkilerini nasıl tersine çevireceğimize gelecek olursak... PT_NOTE injection tekniği epey temiz, derli toplu bir teknik; hazırlanmış hafıza alanının zararsız bir içerikle doldurulmasını sağlıyor. Ancak bilindiği üzere, neredeyse her infection tekniğinin bir de infection engelleyicisi karşılığı vardır ve bu durum yaşamın doğasının bir tezahürüdür. Bir infection tekniğinin ne kadar iyi olduğunu ölçmek için disinfection işleminin kolaylığını göz önünde bulunduruyorum. Çünkü, kullandığınız disinfection işleminde ne kadar sabit değer olursa o kadar kolay alt edilecektir. Kedi fare oyunu devam ettiği sürece yapılması gereken, her seferinde daha sinsi bir çözümle ortaya çıkmaktır. Şayet bu kedi fare oyununu kendinizle oynarsanız yazacağınız zararlı, dehşet verici ve harika bir şeye evrilebilir. Bu enfekte olan dosyayı geri çevirme tekniği için -birçok zararlının da yaptığı gibi- büyük çalıştırılabilir dosyaları yüklerken programın hafıza alanında normalde çalışan kodlarla kesişmediğinden ve üzerine yazılmadığından emin olmak için PT_NOTE segmentini olabildiğince uzak bir hafıza alanına konumlandırmak gerekiyor, sonuçta infection esnasında ayak altında dolanmak istemeyiz. PT_LOAD segmentlerini toparlayıp çalıştırılabilir olacak şekilde kullanabilmek için gruplandırılmış çalıştırılabilir segmentlerin merkezini belirlemede Kmeans algoritmasından ve Kmeans ortalamasından yararlanacağız. Genellikle infection operasyonlarında bir tane PT_NOTE segmenti zararlı kod çalıştırma için kullanılır ancak belki de daha sonra sblip komutunun size bildireceği üzere enfekte segment sayısı iki olabilir :) if (math.log(cluster_1.inertia_)/math.log(cluster_2.inertia_)) < INERTIA_RATIO: Hangi segentlerin normalden biraz daha uzak bir çalıştırılabilir dosya alanına konumlandırıldığını tespit ettikten sonra (her ne kadar PT_NOTE segmentini geçerli bir PT_LOADS adresine konumlandırıp bütün imajın yeniden konumlandırılmış adreslerini güncellesek de, yani kim bunu yapar ki? :)) enfekte olmuş alanın kodunun detaylı incelemesini gerçekleştirebiliriz. Normalde bu tarz zararlılar biraz solucan gibi takılır, birkaç dosyayı daha enfekte eder; ancak eninde sonunda asıl programın çalışma düzenine devam etmesi -bilirsiniz şüphe çekmemek için- gerekir. Yani asıl programın başlangıç noktasına atlamak için kullanılacak olan jmp komutu enfekte edilen zararlı kodun sonlarında olmalıdır. Dolayısıyla biz bu atlama komutunu aramalıyız. Bazen bu atlama işlemi direkt gerçekleşir, bazen ise atlanacak adres enfekte edilmiş kodun içerisinde bir yerlerde göreli olarak türetilir. Bu süreçte atlanacak adres daha önce işleme hafızada ayrılan temel adresin üzerine OEP eklenmesiyle bulunur. (eğer biraz daha havalı olmak isterseniz bu tarz bir operasyon için use-def chain kullanabilirsiniz, tabii sonunda zararlı da biraz daha havalı olmaya karar verirse sizi zincerleme şekilde birbirini çağıran fonksiyonları** çözmeye zorlayabilir) **(çeviri notu: orijinal terim chain cross function ) add {target}, CONST PHDR (Program başlık bilgileri) kısmına bu satırı "pop" komutuyla ekleyin vee orijinal programın çalışması temiz bir şekilde devam etsin Zararlıya bir sonraki infection işleminde iyi şanslar diliyoruz ve üç hayırla uğurluyoruz! ################################################################## #!/usr/bin/env python3 from capstone import * from collections import Counter import lief import math import numpy as np from sklearn.cluster import KMeans import sys # o anti-re'ci şerefsiz arkadaşlardan olma SUCKER_PUNCH = 3 # hem büyük hem küçük birkaç tane çalıştırılabilir dosyada test edildi # çoğu normal çalıştırılabilir dosya "1.0bir şeyler" civarında çıkıyor # inanmayacaksınız ama birkaç megabayt boyutundaki büyük dosyalar dahi # ama eminim bozulan istisna durumlar bulunuyordur INERTIA_RATIO = 1.1 def find_anomalous_load_segment(segment_ranges): segment_array = np.array(segment_ranges) cluster_2 = KMeans(n_clusters=2, random_state=0).fit(segment_array) cluster_1 = KMeans(n_clusters=1, random_state=0).fit(segment_array) if (math.log(cluster_1.inertia_)/math.log(cluster_2.inertia_)) < INERTIA_RATIO: print("No anomaly detected") return None cluster_counts = {v:k for k,v in Counter(cluster_2.labels_.tolist()).items()} if 1 not in cluster_counts: print("No singular cluster found") return None return segment_array[np.where(cluster_2.labels_ == cluster_counts[1])[0]][0] def find_oep(segment_bytes, segment_start): # şimdilik sadece x64-64 destekliyoruz ama ihtiyaç durumunda # kolayca başka mimarilere de dönüştürülebilir. # Burada başka mimarilerde de çalışsın diye # IR(Intermediate representation) kullansak epey havalı olur md = Cs(CS_ARCH_X86, CS_MODE_64) md.skipdata = True oep = None last_jump = None early_bail = 0 for r in [instr for instr in md.disasm(segment_bytes, segment_start)][::-1]: if last_jump: # eğer aşağıdaki formdaki bir komut görüyorsak # add {target}, CONST # muhtemelen OEP adresini temel adrese ekliyordur # bu durumu genelleştirip rax kaydedicisindeki değeri # okumak için use-def chain kullanabiliriz # bu durumda fonksiyonları bulmak ve göreli adrese göre # kod atlamaları için get_rip tarzı bir fonksiyon lazım olur if last_jump + ", " in r.op_str and "add" == r.mnemonic.strip(): try: oep = int(r.op_str.split(",")[1].strip(), 16) break except Exception as e: # hobi olarak devam et ama buraya düştüysek muhtemelen bulamayız # az daha dene, ama çok da deneme yani, gerek yok # anti-re yüzünden keyifler kaçmasın şimdi early_bail += 1 if early_bail == SUCKER_PUNCH: break continue if not last_jump and r.mnemonic.strip() == "jmp": target = r.op_str.strip() # bir bak jmp direkt gerçekleşiyor mu # durum böyleyse oradaki değeri OEP olarak kaydet try: oep = int(target, 16) break except Exception as e: # değilse muhtemelen kaydedicilerden(register) # aldığı değerle atlıyordur oep = None last_jump = target return oep def main(): l = lief.parse(sys.argv[1]) load_segs = [ [ll.virtual_address, ll.virtual_address + ll.virtual_size] for ll in l.segments if ll.type == lief.ELF.SEGMENT_TYPES.LOAD ] anomalous_segment_start, anomalous_segment_end = find_anomalous_load_segment(load_segs) segment_bytes = l.get_content_from_virtual_address(anomalous_segment_start, anomalous_segment_end) real_oep = find_oep(bytes(segment_bytes), anomalous_segment_start) print("found OEP: ", hex(real_oep)) l.header.entrypoint = real_oep l.write(sys.argv[1] + ".cleaned") if __name__ == "__main__": main()