GuLoader'ın Gizleme Konusunda Benzersiz Yaklaşımı: Yığın Manipülasyonunu Anlamak
Tersine mühendislik kabuk kodundan veya genel olarak gizlenmiş kötü amaçlı yazılımlardan hoşlananlar için, GuLoader adında ilginç bir "kötü amaçlı yazılım" ailesiyle karşılaşmış olabilirsiniz; bu kötü amaçlı yazılım, geçmişte CloudEyE adlı bir yazılım paketleyicisi satan bir şirketle bağlantılı olduğu için alıntılarda yer almaktadır (bkz.:Kontrol Noktası Araştırması), ancak kullanımının çoğunun AgentTesla, Formbook, Remcos ve daha fazlası gibi başka kötü amaçlı yazılımları yüklemek olduğu görülüyor.
Son zamanlarda, kötü amaçlı yazılım işlevselliğine gelişimsel bir bakış açısıyla, özellikle de gizleme açısından giderek daha fazla bakıyorum ve GuLoader'ın çekirdeği yalnızca bir kabuk kodu bloğu olduğundan - yani konu gizleme söz konusu olduğunda çok fazla esneklik var - daha derin bir dalış yapmaya karar verdim. Odaklanmayı seçtiğim ana unsurlardan biri, veri bloklarının şifresini çözerken kullanılan, çekirdek veri yükünde bulunan yığın manipülasyonunu içeriyordu.
Dolayısıyla, bu yazıda, GuLoader'ın bu veri bloklarının şifresini çözmek için yığını nasıl manipüle ettiğine kısa bir göz atacağız ve ardından kabuk kodunun geliştirilmesi zor olduğundan, bir yürütülebilir dosya içindeki dizelerin şifresini çözmek için bu işlevselliği kendimiz uygulamaya çalışacağız!
GuLoader'da Yığın Manipülasyonunu Analiz Etme
Örnek SHA-256: d42e8d8eaddfc72e19cb0256ca22c6d502e90c2fb1bdc2cdad092e906df0b886
Gönderinin odak noktası olmadığından kabuk kodunun paketten çıkarılması sürecine değinmeyeceğim, ancak bu spesifik örneği paketinden çıkarmak için aşağıdaki adımları uygulayabilirsiniz:
- Set BP on VirtualAlloc
- Set a Hardware-Execute BP on first byte of allocated memory
- Once hit, setup Trace-Into to break on the condition dis.iscond(cip) == 1
- You should be at a conditional jump; set a BP on the address after the jump
- This will be the "entry-point" of the shellcode
- Dump out the shellcode at this point, and open in IDA
- In IDA, go to the entry-point found earlier, and define as code
- The function jumps around a bit, but go to the end and find the call
- This call is going to be where our analysis starts off at
Bu adımları izlediyseniz fonksiyon aşağıdaki gibi görünmelidir:

Koşullu atlama sınır noktasını ayarlamamızın nedeni, ek bir XOR şifreleme katmanının bulunmasıdır, dolayısıyla bunu atlamak istiyoruz. Ek olarak, oldukça karmaşıktır, dolayısıyla koşullu atlamayı manuel olarak bulmak daha uzun zaman alacaktır.
"Giriş noktası" işlevinde göze çarpan özel bir çağrı vardır; kabuk kodu içindeki, henüz işlev olarak tanımlanmamış bir adrese yapılan çağrı, işlevin sonunda olası bir geri dönüş talimatı eksikliğine işaret eder.

Fonksiyonu incelediğimizde, başka bir alt rutine çağrı öncesinde bazı okunaklı montaj talimatlarının olduğunu ve ardından montaj dışı talimatların geldiğini görebiliriz. Bu oldukça iyi bir göstergedirsub_251C2muhtemelen bazı yığın manipülasyonları vardır; aksi takdirde işlem koduna geri dönerdi0xD9bu bir istisna yaratacaktır. Diğer olasılık da şusub_251C2baytların şifresini doğrudan kendisinden sonra çözerek yürütmenin devam etmesine izin verir; bu, montaj kodu parçacıkları yerine tüm işlevlerin şifresini çözen SmokeLoader'a biraz benzer bir tekniktir.

Bir göz atıyorumsub_251C2ilk talimatın bir olmasıyla birlikte, bazı açık yığın manipülasyonlarının meydana geldiğini görebiliriz.$+5'ı ara, esasencallbir sonraki adres, bir sonraki adresi yığına itiyor. Bundan hemen sonra yığının en üstündeki değer açılır.ebxyani şimdiebxadresini içerecektirpop ebxOperasyon.
Daha sonra, içindeki adresten ofsetleri çıkararak birkaç çıkarma işleminin gerçekleştiğini görüyoruz.ebx. Bu sonuçta daha önce bulunan bir bayta işaret etmesiyle sonuçlanır.sub_251C2, özellikle içindecmp ecx, 0x27B1B28A–0x1Bbayt.

Kaynak koda dönüştürme bunu pek iyi göstermiyor, ancak iki XOR döngüsünü gösteriyor, önce verilerin şifresini çözüyor, sonra artık gerekli olmadığında verileri yeniden şifreliyor.

İşlev birkaç farklı işlem gerçekleştirir, ancak bunun özü - ya da en azından en çok ilgilendiğimiz kısmı - verilerin şifresinin çözülmesini engellemeye yardımcı olan yığın manipülasyonudur.
Kendimizi Uygulamak
Şimdi işin eğlenceli kısmı geliyor; özel veri yükümüzdeki dizelerin şifresini çözmek için benzer yığın manipülasyonunu kullanmaya çalışıyoruz. Öncelikle bu nasıl yapılandırılacak?
Muhtemelen değerleri sabit kodlayacağız; bu, bir derleyici tarafından çalışma zamanında hesaplanmayacaktır ve özel derleme adımları eklemiyoruz. Olası seçeneklerden biri, bir dizeyi kabul etmek için bir Python betiği oluşturmak ve ardından VS projemize eklenebilecek verileri dağıtmaktır, ancak yalnızca bir dizenin şifresini çözeceğiz, bu yüzden her şeyi manuel olarak yapacağız.
Aşağıda görebileceğiniz programın yapısına ilişkin oldukça basit bir örnek hazırladım. İkili dosya içindeki ana çağrı işlevi yürütür (muhtemelen__declspec(çıplak)kongre)w_string_decrypt(), bu da doğrudan aramaya yol açarstring_decrypt()(ayrıca kullanarak__declspec(çıplak)). Bu doğrudan çağrı,$geçerli_adres + 5geri dönülecek adres olacak yığına. Ancak daha fazla kod kullanmak yerine şifrelenmiş dizemizi ekleyeceğiz.

Yani şimdi eğerstring_decrypt()işlevi yığının en üstündeki değeri basitçe içine yerleştirmektiebx, şifreli dizeye yönelik, şifreyi çözmeye hazır bir işaretçiye sahip olacaktır! Tahmin edebileceğiniz gibi, yığın ve yazmaçlarla baş etmenin en kolay (tek olmasa da) yolu olduğu için bunun bazı kısımlarında satır içi derlemeyi kullanacağız. Bu, Visual Studio'yu kullanarak 64 bitlik bir örnekle bunu yapmayı umuyorsanız işe yaramayacağı anlamına gelir.
Artık program yapısı hakkında oldukça iyi bir fikrimiz var, geliştirmeye başlayabiliriz - ancak bunu yapmadan önce sadece bir sorumluluk reddi beyanı: Ben bir yazılım/kötü amaçlı yazılım geliştiricisi değilim ve Python her zaman kötü amaçlı RE işleri için benim tercihim olmuştur, bu nedenle kod en iyi uygulamalarla aynı çizgide olmayabilir. İşe yararsa kutumda bir onay işareti var, o yüzden bunu nasıl istersen öyle al!
Başlangıç olarak çok basit bir örnek hazırladım:
#include <stdio.h>
#include <windows.h>
int main() {
LPCSTR target_string = "https://www.0ffset.net/beginner/"
MessageBoxA(NULL, target_string, "Greetings!", MB_ABORTRETRYIGNORE);
return 0;
}
Kodun yaptığı tek şey, sağlanan metni içeren bir mesaj kutusu görüntülemektir; çok basit şeyler. Plan şifrelemekhedef_stringve bunun çağrıya aktarılmasını sağlayınMesajKutusuA. Oluşturulacak bir sonraki öğe, saf satır içi derlemede geliştirilecek olan dize şifre çözme işlevinin kendisidir. Bu çok basit olacak, dize üzerinde yinelemeler yapılacak, her bayt 1 baytlık bir anahtarla XORlanacak (bununla devam edeceğiz)0xC3) ve şifresi çözülmüş baytı farklı bir bellek bölgesinde depolamak.
__declspec(naked) void string_decrypt() {
__asm {
xor eax, eax ; use as byte storage
xor edx, edx ; use as counter
mov ecx, 0xc3 ; key
mov ebx, target_string_length ; length of string
mov edi, offset target_string ; string to be decrypted
mov esi, offset decrypted_str ; where to store the string
xor_loop:
mov al, [edi + edx] ; mov encrypted byte into al
xor eax, edx ; decrypt byte
mov [esi + edx], al ; move decrypted byte to mem
inc edx ; inc counter
cmp ebx, edx
jne xor_loop
mov eax, esi ; store as usual return value
ret
}
}
Yukarıdaki kod mükemmel olmasa da, nihai sonuç için sadece bir taslaktır; hedef dizeye uzaklıklar aktarmayacağız ve şifresi çözülmüş dizenin nerede saklanacağına ilişkin değişkenler tanımlamayacağız.
ilestring_decrypt()işlev tanımlandığında, işlev için bir sarıcı tanımlamak istiyoruz. Bu oldukça basit olacak; yalnızca dize şifre çözme işlevine bir çağrı ve ardından aşağıda göreceğiniz gibi şifrelenmiş dize gelecek. nasıl kullandığımıza dikkat edin__declspec(çıplak)özniteliğin amacı, derleyici tarafından uygulanan herhangi bir yığın işlemeyi kaldırmaktır.
__declspec(naked) void w_string_decrypt() {
__asm {
call string_decrypt
encrypted_string
}
}
Ne olduğunu merak ediyor olabilirsinşifrelenmiş_dizeyukarıdaki kodda olması veya en azından derleyici tarafından nasıl kabul edildiği varsayılır. Peki, kullanılarak bir makro olarak tanımlanacaktır.#tanımlamakdirektif. Bu makronun değeri bizim şifrelenmiş dizimiz olacak ama bunu tam olarak nasıl yapacağız?
Koda bir bayt eklemek için genellikle derlemede DB'yi kullansanız da, Visual Studio satır içi derlemede bu işlevselliği sunmaz, bu nedenle__asm_emitsözde talimat, örneğin ...
⚠️ Bu konu 0ffset.net botu tarafindan otomatik olarak ice aktarilmistir.
🔗 Kaynak Baglantisi: https://www.0ffset.net/reverse-engineering/guloaders-stack-manipulation/