"Unpacker nasıl yazılır? Uygulamalı yaklaşım"
Mr_Stop 
Program İsmi: PEDimisher
Program Tipi: Packer Programı
       Kullanılan Araçlar:
 SoftICE,IDA 
Basit ()  Orta ( )  Zor ( X )  Pro ( ) 


Başlangıç

Bazı arkadaşlar unpack yapmayı yahut unpacker yazmayı gözlerinde çok büyütüyorlar.Size bu yazıda bir packerın nasıl analiz edileceğini ve iki şekilde unpacker yazmayı göstericem

Yazı

Unpacker yazmak -asprotect,telock vs gibi komplike packerlar hariç- o kadar da zor birşey değil.Asprotect telock gibi packerlarda bir kaç layer üst üste ve ayrıca yüksek bir polymorphism mevcut.Unpacker yazmayı öğrenmenizin en iyi yolu öncelikle cryptorlardan başlamak.Cryptorlarda sectionların boyutu değişmiyor.O yüzden o kadar zor olmuyor.Fakat packerlar biraz daha zor çünkü her bir section'ın alignmentını ve offset değerleri ile de uğraşmak zorundasınız.Eğer crypterlara unpacker yazmak isterseniz size basit bir örnek olarak LameCrypti önerebilirim.

Yeniden konumuza dönelim.Unpacker yazacağımız packer PeDimisher.Packerda hiç bir Antisoftice IAT yok etme vs gibi şeyler yok.Şimdi bir programı bu packer ile sıkıştırıp sonra da softice ile izleyelim.Ayrıca IDA ile de dissamble ederseniz olayı çok daha iyi anlarsınız.Evet macere başlıyor.

Programın entrpointi şöyle oluyor.

		push	ebx
		push	ecx
		push	edx
		push	esi
		push	edi
		push	ebp
		call	$+5		;delta yı al 
		pop	ebp
		mov	edx, ebp
		sub	ebp, offset unk_4030A2
		sub	edx, [ebp+403391h]
		sub	edx, 0Bh
		mov	[ebp+40339Ah], edx ;ImgBase
		cmp	byte ptr [ebp+403399h],	0 ;paketlenen section sayısı 0 mı ?
		jz	short loc_40907F ;o zaman direkt OEP e zıpla
		call	GetApis ;gerekli apilerin adreslerini bul VirtualAlloc,VirtualFree
		mov	edi, ebp

loc_409036:				; CODE XREF: start+78j
		lea	ebx, [ebp+40339Ah]
		mov	ebx, [ebx]	; ImageBase
		lea	eax, [edi+40339Eh]
		mov	eax, [eax]	; section'ın RVA sı
		add	ebx, eax
		lea	ecx, [edi+4033A2h]
		mov	ecx, [ecx]	;section'ın boyutu
		mov	ax, [ebp+40338Fh] ; Anahtar

loc_409057:				; CODE XREF: start+68j
		add	byte ptr [ebx],	10h ;burada basit bir decrypt yapılıyor.
		xor	[ebx], al
		xor	[ebx], ah
		add	byte ptr [ebx],	0AAh
		rol	ax, 3
		xchg	ah, al
		inc	ebx
		loop	loc_409057
		call	UnpackSect ;sectionı unpackle
		add	edi, 8
		dec	byte ptr [ebp+403399h] ; section sayacı
		jnz	short loc_409036
		call	FillImport  ;Import tablosunu doldur

loc_40907F:				; CODE XREF: start+2Dj
		mov	eax, [ebp+403395h] ; OEP
		mov	ebx, [ebp+40339Ah] ; ImgBase
		add	eax, ebx
		pop	ebp
		pop	edi
		pop	esi
		pop	edx
		pop	ecx
		pop	ebx
		jmp	eax		; OEP'e zıpla

GetApis		proc near		;bu fonksiyon virtualalloc ve virtualfree apilerinin adreslerini bulup saklıyor
		lea	eax, [ebp+403402h]
		push	eax
		call	dword ptr [ebp+403464h]	; GetModuleHandle
		mov	edi, eax
		lea	ebx, [ebp+40340Fh]
		push	ebx		; push offset Virtualalloc
		push	eax
		call	dword ptr [ebp+403460h]	; GetProcAddress
		mov	[ebp+4033FAh], eax ; virtualalloc'un adresi
		lea	ebx, [ebp+40341Ch]
		push	ebx		; push offset Virtualfree
		push	edi
		call	dword ptr [ebp+403460h]	; GetProcAddress
		mov	[ebp+4033FEh], eax ; virtualfree'nin adresi
		retn
GetApis		endp ; sp = -14h


UnpackSect	proc near		; CODE XREF: start+6Ap
		push	edi
		lea	ecx, [edi+4033A2h]
		mov	ecx, [ecx]
		push	PAGE_READWRITE
		push	MEM_COMMIT
		push	ecx
		push	0
		call	dword ptr [ebp+4033FAh]	; VirtualAlloc
		mov	[ebp+403267h], eax
		lea	ebx, [ebp+40339Ah]
		mov	ebx, [ebx]
		lea	eax, [edi+40339Eh]
		mov	eax, [eax]
		add	ebx, eax
		push	ebx
		push	dword ptr [ebp+403267h];output
		push	ebx ;input
		call	UnpackSection ;sectionı oluşturulan memory e kopyala
		add	esp, 8 ;stackı düzenle
		pop	edi
		mov	ecx, eax
		mov	esi, [ebp+403267h] ;unpack edilen kısmı programa kopyala
		repe movsb
		mov	eax, [ebp+403267h]
		push	MEM_RELEASE
		push	0
		push	eax
		call	dword ptr [ebp+4033FEh]	; VirtualFree
		pop	edi
		retn
UnpackSect	endp ; sp = -1Ch

FillImport	proc near		; CODE XREF: start+7Ap
		mov	edx, [ebp+40339Ah] ;IAT
		mov	esi, [ebp+4033F6h] ; ImgBase
		xor	edi, edi
		add	esi, edx
		add	edi, edx

loc_4090A7:				; CODE XREF: FillImport+93j
		mov	eax, [esi+0Ch]
		test	eax, eax
		jz	short locret_40912D
		add	eax, edx
		mov	ebx, eax
		push	eax
		call	dword ptr [ebp+403464h]	; GetModuleHandleA
		test	eax, eax
		jnz	short loc_4090C4 ; bu dll zaten	yüklü
		push	ebx
		call	dword ptr [ebp+403468h]	; LoadLibraryA

loc_4090C4:				; CODE XREF: FillImport+26j
		mov	[ebp+4031C5h], eax ; dllnin handle'ı
		mov	dword ptr [ebp+4031C9h], 0

loc_4090D4:				; CODE XREF: FillImport+88j
		mov	edx, [ebp+40339Ah]
		mov	eax, [esi]
		add	eax, edx
		add	eax, [ebp+4031C9h]
		mov	ebx, [eax]
		mov	edi, [esi+10h]
		add	edi, edx
		add	edi, [ebp+4031C9h]
		test	ebx, ebx
		jz	short loc_40911F
		test	ebx, 80000000h
		jnz	short loc_409101
		add	ebx, edx
		inc	ebx
		inc	ebx

loc_409101:				; CODE XREF: FillImport+66j
		and	ebx, 0FFFFFFFh
		push	ebx
		push	dword ptr [ebp+4031C5h]
		call	dword ptr [ebp+403460h]	; GetProcAddress
		mov	[edi], eax
		add	dword ptr [ebp+4031C9h], 4 ;diğer importa geç
		jmp	short loc_4090D4


loc_40911F:				; CODE XREF: FillImport+5Ej
		add	esi, 14h ;diğer dll ye geç
		mov	edx, [ebp+40339Ah]
		jmp	loc_4090A7


locret_40912D:				; CODE XREF: FillImport+17j
		retn
FillImport	endp

Şimdi packerı inceliyelim.Ne zaman

		push	ebp
		call	$+5		;delta yı al 
		pop	ebp

Tipinde birşey görürseniz gözleriniz felfecir okuması lazım.Bu tip kodları genelde viruslerde çok görürüz.Bu kodun yaptığı şey o andaki EIP i bulmak böylelikle bulunduğu yere göre değişken kullanabilmek.Unpacker lar da bu mantıkla çalışırlar.Unpackerın başlangıçlarında buna benzer kodlar vardır.Dikkat ederseniz IDA bunu dissamble etmede pek yardımcı olmuyor.Bunun yerine softice ile trace edip her bir satırın ne olduğunu kendiniz bulmalısınız. Program ilk olarak ImageBase ve paketlenmiş kısım sayısını buluyor.Daha sonrada kullanıcağı apilerin adreslerini GetApis fonksiyonu ile tespit ediyor.Her bir sectiona bir key vasıtası ile bir decrypt işlemi gerçekleştirip daha sonra UnpackSection fonksiyonu ile unpackleyip yeniden exeye kopyalıyor.Bütün kısımlar bittikten sonra, import tablosunu dolduruyor.Importu yaparken basit getprocaddress döngüsünü kullanıyor.Paketlenen section sayısı,IAT'ın yeri,OEP,sectionların RVA ve raw büyüklüklükleri exe de yazıyor.Bu bilgiler bize çok gerekli olduğunda onların exenin neresinde yazılı olduğunu bulmakta fayda var.SoftICE ile debug ederken açıklama yazdığım yerlerin exenin neresinde olduğunu yazmanız faydanıza olacaktır.

start+02F8H    ;değişkenlerimiz OEP ten 2f8h sonra başlıyor
key dw 370h ;decrypt ederken kullanılan key (değişiyor)
startofPedimin dd 9000h ;pediminisherın başlangıç adresi
OEP dd 1000h ; OEP
sectioncounter db 3 ; paketlenen section sayısı
ImgBase dd 0 ; ImgBase
codestart dd 1000h ; RVA
codelen dd 600h ; raw size
baseofdata dd 2000h ; RVA
datalen dd 600h ; raw size
rdatstart dd 3000h ; RVA
rdatlen dd 200h ; raw size
db 40h dup(0)
dd 20A8h ; IAT start RVA

Olay gördüğünüz gibi o kadar da zor değil.Şimdi unpacker yazımına geçiyoruz.İlk olarak dosyayı seçiyoruz ve PeDimisher ile korunup korunmadığına bakıyoruz.Bunu OEP i bulup ordaki byteları karşılaştırarak yapıyoruz.Artık pediminisher olduğunda eminiz.Buradan itibaren eside OEP var

	push [esi+35Fh];IAT'ı sakla
	push [esi+2FEh] ;OEP'i sakla
	mov al, byte ptr[esi+302h]
	mov numofpacksect,al  ;paketlenen section sayısı
	mov ax,word ptr[esi+2F8h]
	mov Key,ax
	invoke GlobalAlloc,GMEM_FIXED,ImgSize
	mov gMem,eax
	mov esi,pMapping ;file headerı oluşturulan memory e kopyala
	mov edi,gMem
	mov ecx,HeaderSize
	rep movsb
	mov unpack_start,edi ;burası diğer sectionlar için referans olucak
	mov esi,pMapping
	mov edi,gMem
	assume  edi:ptr IMAGE_DOS_HEADER
	add     edi, [edi].e_lfanew         ; + offset PE
	add     edi,sizeof IMAGE_NT_HEADERS    ; edi = section header
	assume  edi:ptr IMAGE_SECTION_HEADER
	   
@unpackloop:
	mov ebx,[edi].PointerToRawData ;sectionun yeri
	add ebx,esi
	mov ecx,[edi].SizeOfRawData
	mov ax,Key
	push ebx ;şu anki offseti sakla
@xorloop:
	add	byte ptr [ebx],	10h
	xor	[ebx], al
	xor	[ebx], ah
	add	byte ptr [ebx],	0AAh
	rol	ax, 3
	xchg	ah, al
	inc	ebx
	loop	@xorloop
	pop ebx	;offseti geriye al	
	mov eax,unpack_start		
	push eax ;destination
	push ebx ;source
	call Unpack ;eax=unpack edilen byte sayısı
	add esp,8
	mov ecx,unpack_start ;şuanki offset
	sub ecx,gMem ;- gMem = offset değeri
	mov [edi].PointerToRawData,ecx
	mov [edi].SizeOfRawData,eax
	add unpack_start,eax
	add edi,sizeof IMAGE_SECTION_HEADER ;diğer sectiona geç
	dec numofpacksect 
	jnz @unpackloop
;program da unpack edilmemiş section kaldı mı?

	.while !(dword ptr[edi]=="ret.")  ;ismi .teraphy değilse
		mov eax,[edi].PointerToRawData
		add eax,esi
		mov ebx,[edi].SizeOfRawData
		invoke MemCopy,eax,unpack_start,ebx ;sectionı kopyala
		sub edi,sizeof IMAGE_SECTION_HEADER ;bir önceki section a git
		mov eax,[edi].PointerToRawData  ;offset +size
		add eax,[edi].SizeOfRawData
		add edi,sizeof IMAGE_SECTION_HEADER ;come back
		mov [edi].PointerToRawData,eax ;yeni sectionun yeri
		add edi,sizeof IMAGE_SECTION_HEADER ;diğer sectiona geç
		add unpack_start,ebx
	.endw
		sub edi,sizeof IMAGE_SECTION_HEADER ;bir önceki sectiona dön
mov eax,[edi].Misc.VirtualSize ;son sectionın virtual addresi
mov ecx,SecAlign ;alignment değeri
invoke AlignSection,eax,ecx ;size of image i align et
add eax,[edi].VirtualAddress
mov ebx,eax ;store size of image
add edi,sizeof IMAGE_SECTION_HEADER ;teraphy sectionı sil mov ecx,sizeof IMAGE_SECTION_HEADER mov eax,0 rep stosb ;IAT+OEP+imgsize fixing mov edi, gMem assume edi:ptr IMAGE_DOS_HEADER add edi, [edi].e_lfanew ; + offset PE assume edi:ptr IMAGE_NT_HEADERS dec [edi].FileHeader.NumberOfSections ;section sayısını bir azalt mov [edi].OptionalHeader.SizeOfImage,ebx ;size of Image'i düzelt pop eax mov [edi].OptionalHeader.AddressOfEntryPoint,eax ;OEP'i düzelt pop eax ;IAT RVA yı düzelt mov [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress,eax invoke RVAToOffset,gMem,eax mov esi,eax add esi,gMem ;IAT büyüklüğünü IAT ta yürüyerek bul. assume esi:ptr IMAGE_IMPORT_DESCRIPTOR mov eax,sizeof IMAGE_IMPORT_DESCRIPTOR .while !([esi].OriginalFirstThunk==0 && [esi].TimeDateStamp==0 && \ [esi].ForwarderChain==0 && [esi].Name1==0 && [esi].FirstThunk==0) add eax,sizeof IMAGE_IMPORT_DESCRIPTOR add esi,sizeof IMAGE_IMPORT_DESCRIPTOR .endw mov [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].isize,eax

İşte olay bu kadar.Unpack edilen dosya bazen orjinal dosyadan da küçük olabiliyor.Çünkü biz section headerdan hemen sonra kodu yazıyoruz.Unpacker orjinal pediminisher ı unpack ederken hata verdi.Fazla incelemedim ama sanırım buffer yetersiz geliyor.Eğer düzeltirseniz bana sonucu yollayın J

Peki bu kadar zahmete ne gerek var? Şimdi size bu programa unpacker yazmanın daha basit bir yolunu göstericem.Size soruyorum.Eğer programı kendiniz sadece SoftICE ile dump etmek isteseydiniz ne yapardınız ? Ben olsam call FillImport olan satıra breakpoint koyardım ve orada dump ederdim daha sonra exedeki IAT ve OEP değerleri ile dosyayı düzeltirdim.FillImport fonksiyonunu geçersek IAT bozulacağından bir de IAT ile uğraşmak zorunda kalırız.

Windowsun built-in debugger desteği var.Yani API ler vasıtasıyla bir programı debug edebilirsiniz.Bizim yapacağımız, breakpoint koyarak exeyi istediğimiz yerde durdurmak ve daha sonra dump etmek.Şimdi koda geçiyoruz.

	push [esi+35Fh];IAT'ı sakla
	push [esi+2FEh] ;OEP'i sakla
	mov stinfo.dwFlags,STARTF_USESHOWWINDOW
	mov stinfo.wShowWindow,SW_HIDE ;kullanıcıyı rahatsız etme :)
	invoke     CreateProcess, 0, offset buffer, 0, 0, 0,\
                              DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, 0, 0,\            
                              OFFSET stinfo, OFFSET prinfo
.while TRUE 
	   invoke WaitForDebugEvent, addr DBEvent, INFINITE
	.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT 
					;process sonlandı
			.break 
			       
	.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
		mov ebx,DBEvent.u.CreateProcessInfo.lpStartAddress
		add ebx,07Ah ;patch addresi
		invoke WriteProcessMemory, prinfo.hProcess, ebx ,addr patch, sizeof patch, NULL ;debug edileni patchle
			.IF eax!=TRUE ;writeprocessmemory hatası
					invoke ShowErr,hWin,CTEXT("Cant Write Process Memory")
				.break ;döngüden çık
			.endif

	.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT ;exception oluştu
	
		.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT ;breakpoint mi ?
			mov cont.ContextFlags, CONTEXT_INTEGER ;eax,ebx,ecx... i okumak istiyoruz
			invoke GetThreadContext, prinfo.hThread, addr cont 
			cmp ebx,	DBEvent.u.Exception.pExceptionRecord.ExceptionAddress
		    jnz @dontcareothers  ;exception bizim breakpoint adresinde değilse ilgilenme
		invoke GlobalAlloc,GMEM_FIXED,ImgSize ;memory oluştur
		mov gMem,eax
		invoke ReadProcessMemory,prinfo.hProcess,ImgBase,gMem,ImgSize,0 ;process i memory e oku
		;RVA-> offset çevirmesine uğraşmaya gerek yok kernel bizim için onu yaptı
		mov     edi, gMem
        assume  edi:ptr IMAGE_DOS_HEADER
		add     edi, [edi].e_lfanew         ; + offset PE
        assume  edi:ptr IMAGE_NT_HEADERS
		pop eax
		mov [edi].OptionalHeader.AddressOfEntryPoint,eax ;OEP'i düzelt
		pop eax ;IAT'ı düzelt
		mov [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress,eax
		mov esi,eax
		add esi,gMem
		
;IAT büyüklüğünü IAT ta yürüyerek bul
		assume esi:ptr IMAGE_IMPORT_DESCRIPTOR
		mov eax,sizeof IMAGE_IMPORT_DESCRIPTOR
		.while !([esi].OriginalFirstThunk==0 && [esi].TimeDateStamp==0 && \
			[esi].ForwarderChain==0 && [esi].Name1==0 && [esi].FirstThunk==0)
			add eax,sizeof IMAGE_IMPORT_DESCRIPTOR 
			add esi,sizeof IMAGE_IMPORT_DESCRIPTOR 
		.endw
		mov [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].isize,eax
        add edi,sizeof IMAGE_NT_HEADERS
        assume  edi:ptr IMAGE_SECTION_HEADER
		mov edx,NumSections
		mov ecx,SecAlign			
@fixheader:  
;VS=RS VA=RA ve filealignmentı yapıyoruz
        mov eax,[edi].Misc.VirtualSize
        mov [edi].SizeOfRawData,eax
        invoke AlignSection,eax,ecx
        mov [edi].Misc.VirtualSize,eax
        mov eax,[edi].VirtualAddress
        mov [edi].PointerToRawData,eax
        add edi,sizeof IMAGE_SECTION_HEADER
        dec edx
        jnz @fixheader

Bu sefer section alignment ı da yapmak zorundayız.Çünkü bir dosya memorye yüklendiğinde fiziksel yapısı değişir.Ayrıca PeDiminsher sectionları packledikten sonra align etmiyor bu yüzden paketlenen dosyalar bazen XP ve Win2K da çalışmıyor.İşte hepsi bu kadar.Umarım size faydalı olur.İkinci yöntem ile bir sürü packer a unpacker yazabilirsiniz.Çünkü sonuçta biz bir debugger gibi çalışıyoruz.Multiple breakpointlerde koyabilirsiniz.Bundan sonrası artık sizin kabiliyet ve hayal gücünüze kalmış.Bu yazıdan faydalanırsanız, bana feedbackte bulunmanız beni sevindirir.Boşa yazmadığımı bilmek istiyorum.Ayrıca size üzerinde çalışmanız için iki unpackerın kaynak kodunu da veriyorum.Hadi kolay gelsin.

Son Notlar

Bu yazıda yazım yanlışları hatta bilgi hatası olabilir.Eğer bir yanlış bulursanız bana email atın düzeltmeye çalışırım.Bir programı eğer gerçekten kullanıyorsanız, programı satın almayı düşünün.Micro$oft programlarını - bırakın satın almayı - kullanmayı bile düşünmeyin.WinXP rulez J