;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Decompressor for PeX'ed programs.. 
;; All important stuff written in asm. (Everything but the interface, that is..)
;; 
;; Copyright (c) Chafe 2000

.386				        ; No advanced stuff..
.model flat,stdcall			; Win32 environment
option casemap:none			; Casesensitive

;Yeah, we need to include some stuff..

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc

;Stuff needed when linking.
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

; These are our exported functions..
public DeXOpenFile
public DeXUnpack
public FreeAllStuff

; ..and here are the ones we import from a higher level language (C)..
WinMain PROTO   :HINSTANCE,:HINSTANCE,:LPSTR,:DWORD
MessageOut PROTO :DWORD

; Maybe structurizing would make it easier to make updates, supporting new versions?
VersionInfo struct
ID              db  10 dup (?)
Checksum        dd  ?
DecryptAlgo     dd  ?
EncryptData     dd  ?
UnpackedSize    dd  ?
Sections        dd  ?
EntryPoint      dd  ?
DecompAlgo      dd  ?
ImportTable     dd  ?
ImportKey       dd  ?
ImportROL       dd  ?
VersionInfo ends

; Ehm... Only one version supported.. :/
VERSIONS    equ 1

.data
szSelect            db      'Select file to be decompressed:',0
szSelectOutput      db      'Select a name for the decompressed file:',0
szFilter            db      'Executeables',0,'*.exe',0,0
szlpstrDefExt       db      'exe',0
szCannotOpen        db      'Cannot open file.',0
szNotPE             db      'This isn',39,'t even a PE-file..',0
szInvalidChecksum   db      'Unrecognized version of PeX. (Checksum %08X)',0
szDecompNotPossible db      'Decompressing is not possible.',0
szUserAbort         db      'Aborted by user.',0
szValidChecksum     db      'Found %s, decompressing possible.',0
szSuccess           db      '%s was decompressed successfully.',0
szNewSize           db      'Unpacked filesize: %d bytes.',0
szWrittenOK         db      '%s was written successfully.',0
szFilename          db      MAX_PATH dup (0)
szFile              db      100 dup (0)
szOutput            db      'Decompressed.exe',0
szBuffer            db      100 dup (0)
ofn                 db      sizeof(OPENFILENAME) dup (0)
Temp                dd      0
Input               dd      0
Output              dd      0
Data                dd      0
PEOffset            dd      0
SectTable           dd      0
PEXSection          dd      0
OutputSize          dd      0
HeaderSize          dd      0
Version             dd      0
ImportSect          dd      0
HaveFileOpen        db      0
Versions            db      VERSIONS*sizeof(VersionInfo) dup (0)

;Important offsets when decompressing PeX v0.99
org offset Versions
db      'PeX v0.99',0
dd      3083fef2h
dd      1abh
dd      3feh
dd      214h
dd      24h
dd      397h
dd      25ch
dd      8dh
dd      19bh
dd      1c7h

.CODE
; To save a few kb, we write our own WinMain-caller..
start:
    push    SW_SHOW
    call    GetCommandLine
    push    eax
    push    NULL
    invoke  GetModuleHandle,NULL
    push    eax
    call    WinMain
    invoke  ExitProcess,eax

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Display the open-dialogbox and check for known versions of PeX.
;;

DeXOpenFile:
    push    edi				; Kernel32 feels better if edi is saved..
    cmp     HaveFileOpen,0 		; Do we have to free previously allocated memory?
    je      DontFreeStuff		
    call    FreeAllStuff
    mov     HaveFileOpen,0
DontFreeStuff:
    mov     edi,offset ofn		; Prepare the OPENFILENAME-structure
    mov     ecx,sizeof(OPENFILENAME)
    xor     eax,eax
    rep     stosb			
    mov     ofn.OPENFILENAME.lStructSize,sizeof(OPENFILENAME)
    invoke  GetModuleHandle,0
    mov     ofn.OPENFILENAME.hInstance,eax
    mov     ofn.OPENFILENAME.lpstrFilter,offset szFilter
    mov     ofn.OPENFILENAME.lpstrFile,offset szFilename
    mov     ofn.OPENFILENAME.nMaxFile,MAX_PATH
    mov     ofn.OPENFILENAME.lpstrTitle,offset szSelect
    mov     ofn.OPENFILENAME.Flags,OFN_FILEMUSTEXIST or OFN_OVERWRITEPROMPT
    mov     ofn.OPENFILENAME.lpstrDefExt,offset szlpstrDefExt
    invoke  GetOpenFileName,offset ofn	; Get compressed file..
    test    eax,eax
    jne     FileIsSelected
    mov     eax,offset szUserAbort	; Someone changed his/her mind...
    push    ebp
    push    esi
    push    ebx
    jmp     ShowError
FileIsSelected:				; Try to open the file
    invoke  CreateFile,offset szFilename,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
    cmp     eax,INVALID_HANDLE_VALUE
    jne     CanOpen
    push    ebp				; This ain't no good...
    push    esi
    push    ebx
    jmp     CannotOpen
CanOpen:
    mov     edi,eax
    mov     Input,NULL
    invoke  GetFileSize,eax,NULL	; Find out how small our compressed file really is..
    cmp     eax,40h		
    jl      NotPE			; Although it's compressed, it cannot be that small..
    push    eax
    push    0
    push    offset Temp
    push    eax
    invoke  GlobalAlloc,GMEM_FIXED or GMEM_ZEROINIT,eax	; Go get some memory!
    push    eax
    push    edi
    mov     Input,eax
    call    ReadFile			; Read the whole story..
    invoke  CloseHandle,edi		; We don't need the filehandle anymore, let's close it.
    mov     eax,Input
    add     eax,dword ptr [eax+3ch]	; Find out where the PE-signature should be
    pop     ecx
    add     ecx,Input
    sub     ecx,4
    cmp     eax,ecx			; Does eax point somewhere out of the file?
    jg      NotPE			; If so -> No PE-signature to be found..
    xor     edx,edx
    cmp     dword ptr [eax],4550h	; Is there a PE-signature?
    jne     NotPE
    mov     PEOffset,eax		; Yep! Lets save this address..
    mov     dx,word ptr [eax+14h]	; Calculate address of sectiontable..
    add     edx,18h
    add     edx,eax
    mov     SectTable,edx		; ..and store it
    mov     ebx,dword ptr [eax+28h]	; ebx = Entrypoint RVA
FindEntrySection:
    mov     ecx,dword ptr [edx+0ch]	; Find the entry-section (Where the PeX-code should be)
    cmp     ecx,ebx
    jg      TryNextSection
    add     ecx,dword ptr [edx+8]
    cmp     ecx,ebx
    jg      FoundEntrySection
TryNextSection:
    add     edx,28h
    jmp     FindEntrySection
FoundEntrySection:
    mov     ebx,dword ptr [edx+14h]	; Grab the physical offset for the section,
    add     ebx,Input			; find the address in our allocated memory,
    mov     PEXSection,ebx		; and finally, we store it.
    xor     eax,eax
    mov     ecx,40h			; Add the first 40h DWORD's to get a checksum..
CalcChecksum:
    add     eax,dword ptr [ebx]
    add     ebx,4
    loop    CalcChecksum
    mov     ecx,VERSIONS
    mov     ebx,offset Versions		; Compare the checksum to the ones of all known
CheckNextVersion:			; versions of PeX..
    mov     edx,dword ptr [ebx].VersionInfo.Checksum
    cmp     edx,eax
    je      FoundVersion
    add     ebx,sizeof (VersionInfo)
    dec     ecx
    jne     CheckNextVersion
    push    eax				; Bad luck. No matching versions to be found.
    push    offset szInvalidChecksum
    push    offset szBuffer
    call    wsprintfA
    add     esp,12
    invoke  GlobalFree,Input
    mov     Input,0
    invoke  MessageOut,offset szBuffer 	; Tell the user..
    mov     eax,offset szDecompNotPossible
    push    ebp
    push    esi
    push    ebx
    jmp     ShowError
FoundVersion:
    mov     Version,ebx			; Yes! I know how to decompress this!
    push    ebx
    push    offset szValidChecksum
    push    offset szBuffer
    call    wsprintfA   
    add     esp,12
    push    offset szBuffer
    call    MessageOut			; Got to tell the user..
    mov     HaveFileOpen,1
    push    1
    pop     eax				; Return 1 to enable the Unpack-button
    pop     edi
    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Decrypt the compressed data, decompress it, fix the sections & the header, 
;; fix the imports, guess some sectionnames, show save-dialogbox, write output..
;; And more...
;;

DeXUnpack:
    push    edi			; Save a few registers to make the caller happy
    push    ebp
    push    esi
    push    ebx
    mov     edi,PEXSection	; Since encryption-methods may be different in different
    mov     ecx,edi		; versions, I decided to place a 0C3h-byte right after the
    mov     ebx,Version		; decryption-algo in memory and then make a call to it.
    add     ecx,dword ptr [ebx].VersionInfo.DecryptAlgo
    add     edi,dword ptr [ebx].VersionInfo.EncryptData
    mov     edx,ecx        
    mov     esi,edi
PatchDecryptAlgo:
    cmp     dword ptr [ecx],49470788h	; Last bytes in the decryption-algo
    je      FoundPlaceToPatch
    inc     ecx
    jmp     PatchDecryptAlgo
FoundPlaceToPatch:
    mov     byte ptr [ecx+6],0c3h	; RET-instruction.. Now we could make a CALL to it.
    call    edx				; edi -> Data to decrypt. edx -> Decryption-algo..
    mov     eax,dword ptr [ebx].VersionInfo.UnpackedSize
    add     eax,PEXSection		; eax -> Size of unpacked data 
    invoke  GlobalAlloc,GMEM_FIXED or GMEM_ZEROINIT,dword ptr [eax]
    mov     Data,eax
    push    eax
    push    esi
    mov     eax,dword ptr [ebx].VersionInfo.DecompAlgo
    add     eax,PEXSection		; Since the decompression algorithm may be different
    call    eax				; in other versions, we just call the algo found in 
    add     esp,8			; the file.
    xor     ecx,ecx
    mov     edx,PEOffset		; Mission: Calculate the size of the unpacked file.
    xor     eax,eax			; Method: Add the size of the header (= offset to the
    mov     cx,word ptr [edx+6]		; first physical section) with the physical sizes of
    dec     eax				; all sections but the one containing the PeX-code.
    push    ecx
    mov     edx,SectTable		; Step one: Find the first physical section.
FindFirstPhysicSection:
    mov     ebp,dword ptr [edx+14h]
    test    ebp,ebp
    je      NotFirstPhysicSection	; PhysOffset == 0? Then this is a virtual section.
    cmp     eax,ebp			
    jb      NotFirstPhysicSection	
    mov     eax,ebp
NotFirstPhysicSection:
    add     edx,28h
    dec     ecx				; Go though all sections.
    jne     FindFirstPhysicSection
    mov     HeaderSize,eax		; HeaderSize = Physical offset for first section.
    pop     ecx				; Step two: Add the sizes of the sections with the 					; Headersize..
    dec     ecx				; Exclude PeX-section when adding the sizes..
    mov     edx,SectTable
CalcOutputSize:
    cmp     byte ptr [edx+22h],0	; Packed sections have their PhysSize at [edx],
    jne     NotPacked			; nonpacked have theirs at [edx+10h].
    add     eax,dword ptr [edx]
    jmp     IsPacked
NotPacked:
    add     eax,dword ptr [edx+10h]
IsPacked:
    add     edx,28h
    dec     ecx
    jne     CalcOutputSize
    mov     OutputSize,eax		; Mission successful!
    invoke  GlobalAlloc,GMEM_FIXED or GMEM_ZEROINIT,eax
    mov     Output,eax			; This is going to be the file we write
    mov     ecx,HeaderSize		; First of all, we copy the header..
    mov     esi,Input
    mov     edi,eax
    mov     eax,PEOffset
    dec     word ptr [eax+6]		; Decrease the number of sections
    rep     movsb
    mov     eax,SectTable		; eax = Input's sectiontable
    mov     ebp,eax
    sub     ebp,Input
    add     ebp,Output			; ebp = Output's sectiontable
    mov     esi,Data
    mov     ecx,dword ptr [ebx].VersionInfo.Sections
    add     esi,dword ptr [esi+ecx]	; esi -> First decompressed section
FixSection:
    cmp     byte ptr [eax+22h],0	; Is the section packed?
    jne     JustCopy			; -> No, just copy it from the input
    mov     ecx,dword ptr [eax]		; ecx = New PhysSize
    mov     dword ptr [ebp+10h],ecx
    mov     dword ptr [ebp+14h],edi	; edi == Address where data will be copied
    mov     edx,Output			; Subtract it with the address of the allocated
    sub     dword ptr [ebp+14h],edx	; memory to get the PhysOffset
    mov     dword ptr [ebp],586544h	; Set sectionname to "DeX" temporary
    mov     dword ptr [ebp+4],0
    or      dword ptr [ebp+24h],20000020h	; Make sure some flags are set.
    rep     movsb			; Copy the section from the decompressed data.
    jmp     FixNextSection
JustCopy:
    push    esi
    mov     esi,dword ptr [eax+14h]	; Section is not packed, just copy it from the input
    add     esi,Input			
    mov     ecx,dword ptr [eax+10h]
    mov     dword ptr [ebp+14h],edi	; Calculate new PhysOffset
    mov     edx,Output
    sub     dword ptr [ebp+14h],edx
    mov     dword ptr [ebp],586544h	; "DeX"
    mov     dword ptr [ebp+4],0
    or      dword ptr [ebp+24h],20000020h 	; Fix the flags..
    rep     movsb
    pop     esi
FixNextSection:
    add     eax,28h
    add     ebp,28h
    mov     ecx,PEOffset
    dec     word ptr [ecx+6]		; Make sure all sections are fixed..
    jne     FixSection
    mov     eax,dword ptr [ebp+8]	; Get VirtSize of the PeX-section
    mov     edi,PEOffset
    sub     edi,Input
    add     edi,Output
    sub     dword ptr [edi+50h],eax	; Correct ImageSize by subtracting it
    mov     eax,dword ptr [ebx].VersionInfo.EntryPoint
    add     eax,Data
    mov     eax,dword ptr [eax]		; eax = Entrypoint VA - 1
    sub     eax,dword ptr [edi+34h]	; VA - ImageBase == RVA, thus eax = Entrypoint RVA - 1 
    inc     eax				; eax = Entrypoint RVA
    mov     dword ptr [edi+28h],eax	; Update header
    xor     eax,eax
    push    edi
    mov     edi,ebp
    mov     ecx,10
    rep     stosd			; Remove the PeX-section's entry in the sectiontable
    mov     eax,dword ptr [ebx].VersionInfo.ImportTable
    add     eax,Data			; Find out where the import-table begins
    pop     edi
    mov     eax,dword ptr [eax]
    mov     dword ptr [edi+80h],eax	; Store it in the header
    call    GetOffsetFromRVA
    mov     esi,eax			; Simple decryption-algorithm for the names of all
DecryptNextLibrary:    			; imported functions.
    mov     eax,dword ptr [esi]		; Study/trace it if you really want to know how it works
    test    eax,eax
    jne     NotFinishedImports
    mov     eax,dword ptr [esi+10h]
    cmp     dword ptr [esi+4],0
    jne     NotFinishedImports
    cmp     dword ptr [esi+8],0
    jne     NotFinishedImports
    cmp     dword ptr [esi+0ch],0
    jne     NotFinishedImports
    cmp     dword ptr [esi+10h],0
    je      FinishedImports
NotFinishedImports:
    call    GetOffsetFromRVA
    mov     edi,eax
DecryptNextImport:
    mov     eax,dword ptr [edi]
    not     eax
    mov     dword ptr [edi],eax
    test    eax,eax
    je      FinishedLibrary
    test    eax,80000000h
    jne     DontTouch
    call    GetOffsetFromRVA
    mov     edx,eax
    mov     eax,dword ptr [ebx].VersionInfo.ImportKey
    mov     ecx,dword ptr [ebx].VersionInfo.ImportROL
    add     eax,Data
    add     ecx,Data
    mov     eax,dword ptr [eax]		; Get decryptionkey from decompressed data
    mov     cl,byte ptr [ecx]		; Get number of times to ROL eax too.
    push    edx
ImportDecrypt:
    mov     ch,byte ptr [edx]
    xor     ch,al
    add     ch,ah
    sub     ch,al
    rol     eax,cl
    mov     byte ptr [edx],ch
    inc     edx
    test    ch,ch
    jne     ImportDecrypt
    pop     ecx
    dec     edx
MoveNameOfImport:    
    mov     al,byte ptr [edx]		; Move name of import two bytes to make place for a hint
    mov     byte ptr [edx+2],al
    dec     edx
    cmp     edx,ecx
    jge     MoveNameOfImport
    mov     word ptr [ecx],0		; Ehm.. Hints are not needed anyway.. 
DontTouch:    
    add     edi,4
    jmp     DecryptNextImport
FinishedLibrary:
    add     esi,14h
    jmp     DecryptNextLibrary
FinishedImports:
    xor     ecx,ecx
    mov     edx,PEOffset
    sub     edx,Input
    add     edx,Output
    and     dword ptr [edx+0f4h],0fffffff7h	; Remove PeX's signature
    mov     cx,word ptr [edx+6]		; Now we're going to guess the names of the
    mov     esi,SectTable		; sections. As help, we have some values in the
    sub     esi,Input			; header. If a name can't be guessed, it'll be
    add     esi,Output			; named "DeX#", where # is a digit.
    mov     ebp,30h			; 30h == '0'	
GuessSectionNames:
    mov     eax,dword ptr [esi+0ch]
    cmp     eax,dword ptr [edx+80h]
    jne     NotImportSection
    mov     dword ptr [esi],6164692eh	; Import-section - ".idata"
    mov     dword ptr [esi+4],6174h
    jmp     GuessNextSection
NotImportSection:
    cmp     eax,dword ptr [edx+88h]
    jne     NotResourceSection
    mov     dword ptr [esi],7273722eh	; Resource-section - ".rsrc"
    mov     dword ptr [esi+4],63h
    jmp     GuessNextSection
NotResourceSection:
    cmp     eax,dword ptr [edx+0c0h]
    jne     NotTLSSection
    mov     dword ptr [esi],736c742eh	; ThreadLocalStorage - ".tls"
    jmp     GuessNextSection
NotTLSSection:
    mov     ebx,dword ptr [edx+2ch]
    cmp     eax,ebx
    jl      NotCodeSection
    add     ebx,dword ptr [edx+1ch]
    cmp     eax,ebx
    jge     NotCodeSection
    mov     dword ptr [esi],7865742eh	; Code-section - ".text"
    mov     dword ptr [esi+4],74h
    jmp     GuessNextSection
NotCodeSection:
    mov     ebx,dword ptr [edx+30h]
    cmp     eax,ebx
    jl      NotDataSection
    add     ebx,dword ptr [edx+20h]
    cmp     eax,ebx
    jge     NotDataSection
    mov     dword ptr [esi],7461642eh	; Data-section - ".data"
    mov     dword ptr [esi+4],61h
    jmp     GuessNextSection
NotDataSection:
    cmp     dword ptr [esi+10h],0
    jne     NotBSSSection
    mov     dword ptr [esi],7373622eh	; BSS-section - ".bss"
    jmp     GuessNextSection
NotBSSSection:
    mov     word ptr [esi+3],bp		; No idea - "DeX#"
    inc     ebp
GuessNextSection:
    add     esi,28h    
    dec     ecx
    jne     GuessSectionNames    
    invoke  GetFileTitle,offset szFilename,offset szFile,100
    push    offset szFile
    push    offset szSuccess
    push    offset szBuffer
    call    wsprintfA
    add     esp,12
    invoke  MessageOut,offset szBuffer 	; Tell the user how good we've been.
    push    OutputSize
    push    offset szNewSize
    push    offset szBuffer
    call    wsprintfA
    add     esp,12
    invoke  MessageOut,offset szBuffer	; Give some additional info..
    mov     byte ptr [szFilename],0
    mov     ofn.OPENFILENAME.lpstrTitle,offset szSelectOutput
    invoke  GetSaveFileName,addr ofn	; Display save-dialogbox
    invoke  CreateFile,offset szFilename,GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
    cmp     eax,INVALID_HANDLE_VALUE	; Can we finish this now?
    je      CannotOpenWrite		; -> Nope.. All our hard work is just vanished..
    mov     edi,eax
    invoke  WriteFile,eax,Output,OutputSize,offset Temp,NULL	; Write everything before ..
    invoke  CloseHandle,edi					; ..closing the filehandle.
    invoke  GetFileTitle,offset szFilename,offset szFile,100
    push    offset szFile
    push    offset szWrittenOK
    push    offset szBuffer
    call    wsprintfA
    add     esp,12
    invoke  MessageOut,offset szBuffer	; Display a nice message..
    call    FreeAllStuff		; Clean up..
    jmp     DoExit
GetOffsetFromRVA:			; Find the address in the output-buffer that matches
    mov     ecx,SectTable		; the RVA specified in eax. This is done by consulting
    sub     ecx,Input			; the sectiontable..
    add     ecx,Output
FindSection:
    mov     edx,dword ptr [ecx+0ch]
    cmp     eax,edx
    jl      WrongSection
    add     edx,dword ptr [ecx+8]
    cmp     eax,edx
    jl      FoundSection
WrongSection:
    add     ecx,28h
    jmp     FindSection
FoundSection:
    sub     eax,dword ptr [ecx+0ch]
    add     eax,dword ptr [ecx+14h]
    add     eax,Output
    ret
FreeAllStuff:				; Free all (if any) allocated buffers..
    push    esi
    mov     esi,GlobalFree
    mov     eax,Input
    test    eax,eax
    je      DontFreeInput
    push    eax
    call    esi
    mov     Input,0
DontFreeInput:
    mov     eax,Output
    test    eax,eax
    je      DontFreeOutput
    push    eax
    call    esi
    mov     Output,eax
DontFreeOutput:
    mov     eax,Data
    test    eax,eax
    je      DontFreeData
    push    eax
    call    esi
DontFreeData:
    pop     esi
    ret
NotPE:			; Various error-messages.. They all end up with a call to MessageOut
    mov     eax,Input	; and then returns..
    test    eax,eax
    je      NotPENoFree
    invoke  GlobalFree,eax
NotPENoFree:
    mov     eax,offset szNotPE
    push    ebp
    push    esi
    push    ebx
    jmp     ShowError
CannotOpenWrite:
    call    FreeAllStuff
CannotOpen:
    mov     eax,offset szCannotOpen
ShowError:
    invoke  MessageOut,eax
    xor     eax,eax
DoExit:
    pop     ebx
    pop     esi
    pop     ebp
    pop     edi
    ret
end start		; Not much more to say.. All done.
