I'm trying to learn how to unpack a simple executable which has been compressed with crinkler, let be the nasm listing here compressed with crinkler.


global start
; kernel32.lib Exports
extern _ExitProcess@4
extern _GetStdHandle@4
extern _WriteFile@20

section .text

    ; DWORD  bytes;
    mov     ebp, esp
    sub     esp, 4

    ; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
    push    -11
    call    _GetStdHandle@4
    mov     ebx, eax

    ; WriteFile( hstdOut, message, length(message), &bytes, 0);
    push    0
    lea     eax, [ebp-4]
    push    eax
    push    (message_end - message)
    push    message
    push    ebx
    call    _WriteFile@20

    ; ExitProcess(0)
    push    0
    call    _ExitProcess@4

    ; never here
    db      'Hello, World', 10

To generate the exe I'm using latest version of nasm & crinkler like this nasm -f win32 example1.asm && crinkler example1.obj kernel32.lib user32.lib opengl32.lib winmm.lib gdi32.lib legacy_stdio_definitions.lib oldnames.lib ucrt.lib /out:example1_crinkler.exe /CRINKLER /HASHTRIES:300 /COMPMODE:SLOW /ORDERTRIES:4000 /entry:start /subsystem:console

To unpack the exe I'm using OllyDbg v201 and the latest version of OllyDumpEx v1.50. Problem here is, the OllyDebugEx's website only has these sections {Overview, Features, Screenshots, Supported Debuggers, Download, Changelog}, no documentation at all, which means the plugin assumes you've already experience with the whole bunch of options/terms.

Right now I've reached the point where I've figured out how to uncompress my test executable and finding the OEP, current status below:

enter image description here

What I'd like to know now is understand the whole set of available options provided by OllyDumpEx:

  • Module {PE Base, List Section, Dump Mode, PE Source}
  • Search { Search Area, Search Mode}
  • PE {Image Base, Section Align, Entry Point}
  • Options
  • Section

Once I know how to dump it properly I'd also like to know how to fix it so I'll get the final uncompressed exe.

  • There is a tutorial available on unpacking crinkler: pouet.net/prod.php?which=53563 It creates a new dummy executable and puts the code/data from the unpacked crinkler memory in there to unpack it.
    – mrexodia
    Nov 21, 2016 at 9:56
  • @mrexodia Yeah, I was aware of that one even before of posting this question, which is completely hard to follow if you don't know some stuff. Btw, yesterday I've started to use x64dbg and I'm not missing OllyDbg at all... If I knew about that awesome debugger I'd had started this thread with the title "Trying to decompress a hello world program using x64dbg". On the other hand, the answers of this thread have referenced IDA, so... :)
    – BPL
    Nov 21, 2016 at 10:07
  • the pouet link is down, anyone can upload a copy of the zip somewhere?
    – evandrix
    Aug 19, 2019 at 1:24
  • that'd be great, few weeks ago I tried to find it on the internet and couldn't find it... no even in archive.org
    – BPL
    Aug 19, 2019 at 7:09

3 Answers 3


Just out of curiosity i downloaded crinkler compiled this code with visual c++

#include <windows.h>
int WINAPI WinMain(__in HINSTANCE,__in_opt HINSTANCE,__in LPSTR,__in int) {
    MessageBox(NULL, "Testing Crinky", "CRINKLE", MB_OK);

and linked the object file with crinkler20.exe


cl -c /nologo /W4 /O1 /Zi /analyze *.cpp

crinkler.exe /ENTRY:WinMain crinky.obj kernel32.lib user32.lib /CRINKLER /HASHTRIES:300 /COMPMODE:SLOW /ORDERTRIES:4000 /SUBSYSTEM:windows /out:crinky.exe

it created a 459 byte exe which runs fine

ls -la crinky.exe
-rwxrwxrwx  1 HP 0 459 2016-11-15 15:25 crinky.exe

enter image description here

opened it in a hexeditor to see whats inside it appears crinkler employs pe header damaging techniques (it seems to use the peheader to stuff code )

here is a hexedit view (from hxd)

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  4D 5A 32 30 50 45 00 00 4C 01 00 00 01 DB 61 7F  MZ20PE..L....Ûa.
00000010  10 D0 17 73 75 47 EB F9 08 00 02 00 0B 01 11 C9  .Ð.suGëù.......É
00000020  45 85 C0 79 1F 01 D3 50 F7 E2 90 3D 5C 00 00 00  E…Ày..ÓP÷â.=\...
00000030  F7 F3 39 C1 19 DB EB 48 00 00 40 00 04 00 00 00  ÷ó9Á.ÛëH..@.....
00000040  04 00 00 00 0F A3 2D 29 01 40 00 8D 04 00 EB CE  .....£-).@....ëÎ
00000050  00 00 00 00 EB B6 42 06 40 00 00 00 53 31 ED BB  ....ë¶[email protected]í»
00000060  02 00 00 00 90 BE 14 01 40 00 6A 01 58 BF 00 00  .....¾[email protected]¿..
00000070  42 00 B1 00 90 57 EB 12 00 00 00 00 00 00 00 00  B.±..Wë.........
00000080  5A 72 07 92 29 D1 04 00 29 D0 60 AD 01 F8 74 2C  Zr.’)Ñ..)Ð`..øt,
00000090  6A 0A 5A 89 14 54 89 54 24 10 AD 31 ED 4D 45 01  j.Z‰.T‰T$..1íME.
000000A0  C0 72 FB 74 AF 60 AC 88 C2 32 07 6B C0 6F 02 87  Àrût¯`¬ˆÂ2.kÀo.‡
000000B0  00 00 00 00 48 4F 00 D2 72 EF 75 F9 BF CE 00 42  ....HO.Òrïuù¿Î.B
000000C0  00 B9 F5 E9 1F 03 73 0C F3 66 AB 0A 06 61 8D 76  .¹õé..s.óf«..a.v
000000D0  0C 7B B7 C3 F7 F1 8D 3C 57 89 E9 31 C0 AE 74 04  .{·Ã÷ñ.<W‰é1À®t.
000000E0  00 07 75 02 41 41 0F B6 14 07 D3 E2 01 54 84 34  ..u.AA.¶..Óâ.T„4
000000F0  48 7A F3 85 DB 7F 0D D0 2C 1F 75 03 D0 14 1F F7  Hzó…Û..Ð,.u.Ð..÷
00000100  D3 FE 04 1F 61 46 EB 95 E8 6A A6 38 C6 6F 82 E9  Óþ..aFë•èj¦8Æo‚é
00000110  1B 13 09 4F 55 FF BD FF FE FF FF 9A 00 20 80 90  ...OUÿ½ÿþÿÿš. €.
00000120  32 FF BD FF FF FF FF DF 00 44 BE 63 EE 28 9E 30  2ÿ½ÿÿÿÿß.D¾cî(ž0
00000130  07 9C 4B 16 0D 6B A8 05 A4 65 86 46 50 14 FE B3  .œK..k¨.¤e†FP.þ³
00000140  42 F6 F7 88 0C 1A 94 68 7D 22 BF 3C 30 2F DD C7  Bö÷ˆ..”h}"¿<0/ÝÇ
00000150  6B F9 26 24 83 12 7D A2 3A 05 85 FE B3 D2 B1 CF  kù&$ƒ.}¢:.…þ³Ò±Ï
00000160  0F B2 80 7D 9A 49 1C 61 97 36 9F 22 F6 54 CD 3F  .²€}šI.a—6Ÿ"öTÍ?
00000170  3B 0E 4B 81 F7 A3 6D 3C 54 89 06 C3 37 51 99 E0  ;.K.÷£m<T‰.Ã7Q™à
00000180  E6 1E D8 B1 E4 7C 73 D4 9C 36 78 DA 27 8C 39 F3  æ.رä|sÔœ6xÚ'Œ9ó
00000190  BD BF 42 6B F3 1C D9 B1 E7 A5 E7 60 1F C6 5B DF  ½¿Bkó.Ù±ç¥ç`.Æ[ß
000001A0  8F A6 47 E0 B5 A7 DD 3D D6 4A CA 91 3C 35 B3 74  .¦G൧Ý=ÖJÊ‘<5³t
000001B0  CB A1 81 90 90 32 39 E4 B0 0F 4D EA E4 EA 2F 0A  Ë¡...29ä°.Mêäê/.
000001C0  6C 06 7C 8F E8 9B B1 50 6E 55 0B                 l.|.è›±PnU.

linux file utility says it is a ms dos exe

dumpbin complains no COFF header

dumpbin /nologo crinky.exe

Dump of file crinky.exe

LINK : fatal error LNK1235: corrupt or invalid COFF symbol table

file crinky.exe
crinky.exe; MS-DOS executable, MZ for MS-DOS

loaded it into windbg and looked at header appears to be completely corrupt

cdb -c ".foreach /pS 4 /ps 100 (place { lm }) { !dh place };q" crinky.exe | grep quit: -B 53

     14C machine (i386)
       0 number of sections
7F61DB01 time date stamp Mon Sep 21 09:48:09 2037

7317D010 file pointer to symbol table
F9EB4775 number of symbols
       8 size of optional header
       2 characteristics

     10B magic #
  17.201 linker version
79C08545 size of code
50D3011F size of initialized data
3D90E2F7 size of uninitialized data
      5C address of entry point
C139F3F7 base of code
         ----- new -----
00400000 image base
       4 section alignment
       4 file alignment
       2 subsystem (Windows GUI)
41743.10541 operating system version
16385.36096 image version
 4.52971 subsystem version
 642B6EB size of image
      40 size of headers
BBED3153 checksum
0114be90 size of stack reserve
016a0040 size of stack commit
0000bf58 size of heap reserve
00b10042 size of heap commit
       0  DLL characteristics
       0 [9207725A] address [size] of Export Directory
   4D129 [AD60D029] address [size] of Import Directory
2C74F801 [895A0A6A] address [size] of Resource Directory
54895414 [31AD1024] address [size] of Exception Directory
 1454DED [74FB72C0] address [size] of Security Directory
88AC60AF [6B0732C2] address [size] of Base Relocation Directory
87026FC0 [       0] address [size] of Debug Directory
D2004F48 [F975EF72] address [size] of Description Directory
4200CEBF [E9F5B900] address [size] of Special Directory
 C73031F [ AAB66F3] address [size] of Thread Storage Directory
768D6106 [C3B77B0C] address [size] of Load Configuration Directory
3C8DF1F7 [31E98957] address [size] of Bound Import Directory
 474AEC0 [ 2750700] address [size] of Import Address Table Directory
B60F4141 [E2D30714] address [size] of Delay Import Directory
34845401 [85F37A48] address [size] of COR20 Header Directory
D00D7FDB [ 3751F2C] address [size] of Reserved Directory


tested if the entry point function is a function with single exit using cdb there were no indirect calls / jmps and it contained only one exit a single return

cdb -c "g @$exentry;uf @eip;q" crinky.exe | grep -iE "call|ret"
004000d3 c3              ret

so set a bp there and executed and dumped the decryted area

cdb -c "g 4000d3;t \"dc @eip l38;q\"" crinky.exe  | grep quit: -B 17

0:000> cdb: Reading initial command 'g 4000d3;t "dc @eip l38;q"'
*** WARNING: Unable to verify timestamp for image00400000
*** ERROR: Module load completed but symbols could not be loaded for image00400000
00420000  400108bb 00abbe00 00bf0042 58004300  [email protected]
00420010  8b0c408b 008b0c40 688b008b 75ed8518  .@[email protected]
00420020  6a006a0e 006a5200 000815ff 31c30043  .j.j.Rj.....C..1
00420030  6091acc0 013c458b 78508be8 4a8bea01  ...`.E<...Px...J
00420040  20428b18 748be801 ee01fc88 c7c1ff31  ..B ...t....1...
00420050  acc03106 7d48c731 e03b3bf5 24428be4  .1..1.H}.;;...B$
00420060  8b66e801 428b480c 8be8011c 44898804  ..f..H.B.......D
00420070  01611c24 c383abe8 56b8e204 000415ff  $.a........V....
00420080  89950043 c8feacf2 c0fefb79 006a8f74  C.......y...t.j.
00420090  4200b868 00c06800 006a0042 000815ff  h..B.h..B.j.....
004200a0  006a0043 000015ff 02cc0043 72657375  C.j.....C...user
004200b0  01003233 000000ff 4e495243 00454c4b  32......CRINKLE.
004200c0  74736554 20676e69 6e697243 0000796b  Testing Crinky..
004200d0  00000000 00000000 00000000 00000000  ................

it appears the data has been decrypted but imports are resolved here

any way we can reach our code in three calls

cdb -c "g 4000d3;rm 0;pct 3"  crinky.exe

0:000> cdb: Reading initial command 'g 4000d3;rm 0;pct 3'
0042007c ff1504004300    call  dword ptr [(00430004)] ={kernel32!LoadLibraryA (7676395c)}
0042007c ff1504004300    call  dword ptr [(00430004)] ={kernel32!LoadLibraryA (7676395c)}
0042009c ff1508004300    call  dword ptr [(00430008)] ={user32!MessageBoxA (75e6ea11)}
0:000> ub @eip
00420086 fec8            dec     al
00420088 79fb            jns     image00400000+0x20085 (00420085)
0042008a fec0            inc     al
0042008c 748f            je      image00400000+0x2001d (0042001d)
0042008e 6a00            push    0
00420090 68b8004200      push    offset image00400000+0x200b8 (004200b8)
00420095 68c0004200      push    offset image00400000+0x200c0 (004200c0)
0042009a 6a00            push    0
0:000> da 4200b8
004200b8  "CRINKLE"
0:000> da 4200c0
004200c0  "Testing Crinky"

based on these the uncompressed executable may be impossible to dump without serious code ( it doesn't have any import table / nor pe header to work with )

you may have to prepend a pe header and append an import section to the raw memory

as to ollydumpex it is a tool with lots of options

pebase you can either select any module , memory range , address range list section will list the found section based on the pebase selection for example if you choose memory list sections will list the whole virtual address range from 0x10000 to 0x7fffffff (32 bit 2 gb va range ) when you rescan search pe searches for pe header in the range

dump mode rebuild tries to rebuild the imports if possible (possibly runnable exe)

raw dumps what is in memory (usefull for forensics not fro running the exe)

but you can see ollydumpex complaining corrupt header in status bar so dumping with ollydumpex is not a viable option as of now

well you can dump the contents if you start coding a special dumper fill the pe headers imports etc it might be serious work maybe

s small start

#include <windows.h>
#include <stdio.h>
int dumpdos (char * filepath){
  IMAGE_DOS_HEADER imdos = {0};
  FILE * fp = fopen(filepath,"r");
  if(fp)  { fseek(fp,0,SEEK_SET);
    fread(&imdos,sizeof(BYTE),sizeof(IMAGE_DOS_HEADER),fp); fclose(fp);
  return imdos.e_lfanew;
void dumpnt (char * filepath , int off_pehead){
  IMAGE_NT_HEADERS imnt = {0};
  FILE * fp = fopen(filepath,"r");
  if(fp)  { fseek(fp,off_pehead,SEEK_SET);
    fread(&imnt,sizeof(BYTE),sizeof(IMAGE_NT_HEADERS),fp); fclose(fp);
void main (int argc,char* argv[]){
  if(argc !=2){     printf("usage %s <path to binary>",argv[0] ); exit(-1); }
  printf ("dumping e_magic and e_lfanew from header of %s\n" , argv[1]);
  int pehead = dumpdos(argv[1]);
  printf("dumping NtSig , M/c, sect_nos, sizeopt_hdr, opthdrmagic, &EP, imgbase\n");

executing it

dumpcrink.exe ..\crinky.exe

dumping e_magic and e_lfanew from header of ..\crinky.exe
dumping NtSig , M/c, sect_nos, sizeopt_hdr, opthdrmagic, &EP, imgbase

the custom import resolver is cute

CPU Disasm
Address   Command                                  Comments
00420000  MOV     EBX, OFFSET 00400108             ; hashtable
00420005  MOV     ESI, OFFSET 004200AB             ; no of imports to resolve in k32
0042000A  MOV     EDI, OFFSET 00430000             ; destination for resolved imports
0042000F  POP     EAX                              ; peb
00420010  MOV     EAX, DWORD PTR DS:[EAX+0C]       ; Peb.Ldr
00420013  MOV     EAX, DWORD PTR DS:[EAX+0C]       ; inLoadorderModuleList
00420016  MOV     EAX, DWORD PTR DS:[EAX]          ; flink ntdll
00420018  MOV     EAX, DWORD PTR DS:[EAX]          ; flink k32
0042001A  MOV     EBP, DWORD PTR DS:[EAX+18]       ; kernel32.dllbaseAddr
0042001D  TEST    EBP, EBP                         
;  dx ((ntdll!_LDR_DATA_TABLE_ENTRY *)((int )((ntdll!_PEB *) @$peb)->Ldr->
0042001F  JNE     SHORT 0042002F                   ; if this is not k32base exit
00420021  PUSH    0
00420023  PUSH    0
00420025  PUSH    EDX
00420026  PUSH    0
00420028  CALL    NEAR DWORD PTR DS:[430008]       ; will never reach here 
;if reaches here in case of no k32 it will crash with null dereference
0042002E  RETN
0042002F  XOR     EAX, EAX
00420031  LODS    BYTE PTR DS:[ESI]                ; no of imports from k32
00420032  XCHG    EAX, ECX                         ; save no of imports from k32
00420033  PUSHAD
00420034  MOV     EAX, DWORD PTR SS:[EBP+3C]       ; k32peheaderptr (e_lfanew)
00420037  ADD     EAX, EBP                         ; k32peheaderptr PE
00420039  MOV     EDX, DWORD PTR DS:[EAX+78]       ; exporttable of k32
0042003C  ADD     EDX, EBP                         ; k32_imageexportdir
0042003E  MOV     ECX, DWORD PTR DS:[EDX+18]       ; noofname
00420041  MOV     EAX, DWORD PTR DS:[EDX+20]       ; addressofnamerva
00420044  ADD     EAX, EBP                         ; virtualaddrof export names
00420046  MOV     ESI, DWORD PTR DS:[ECX*4+EAX-4]  ; last export rva
0042004A  ADD     ESI, EBP                         ; last export addr name
0042004C  XOR     EDI, EDI                         ; 0
0042004E  ROL     EDI, 6                           ; creating hash
00420051  XOR     EAX, EAX
00420053  LODS    BYTE PTR DS:[ESI]                ; first/nth letter of export name
00420054  XOR     EDI, EAX
00420056  DEC     EAX
00420057  JGE     SHORT 0042004E                   ; null termin seek
00420059  CMP     EDI, DWORD PTR DS:[EBX]          ; comparehash with [400108]
0042005B  LOOPNZ  SHORT 00420041                   ; reloop
0042005D  MOV     EAX, DWORD PTR DS:[EDX+24]       ; matched hash end of iit
00420060  ADD     EAX, EBP
00420062  MOV     CX, WORD PTR DS:[ECX*2+EAX]
00420066  MOV     EAX, DWORD PTR DS:[EDX+1C]       ; addroffunctions
00420069  ADD     EAX, EBP
0042006B  MOV     EAX, DWORD PTR DS:[ECX*4+EAX]
0042006E  MOV     DWORD PTR SS:[ESP+1C], EAX       
; modify register eax so popad will hold the rva of k32 export
00420072  POPAD                                    ; eax stays same as addr of 
00420073  ADD     EAX, EBP                         ; rva+ base = addr of proc
00420075  STOS    DWORD PTR ES:[EDI]               ; getprocaddr achieved
00420076  ADD     EBX, 4                           ; store at 430000
00420079  LOOP    SHORT 00420033                   
  ; resolves all exports from k32 + loadlibrary
0042007B  PUSH    ESI                              ; ASCII "user32"
0042007C  CALL    NEAR DWORD PTR DS:[430004]       ; loadlibs user32 or whatever dll
00420082  XCHG    EAX, EBP
00420083  MOV     EDX, ESI                         ; ASCII "user32"
00420085  LODS    BYTE PTR DS:[ESI]                ; again null term for dll name
00420086  DEC     AL
00420088  JNS     SHORT 00420085
0042008A  INC     AL
0042008C  JE      SHORT 0042001D                   ;
; so this is standard sized custom import resolver

so i created another exe with more api calls and checked it and
set a hardware exec bp on 42008e and f9nned i landed on actual code

CPU Disasm
Address   Hex dump               Command                                                  Comments
004000BC    BF 3A014200          MOV     EDI, OFFSET 0042013A <---------
004000C1    B9 23E91F03          MOV     ECX, OFFSET 031FE923
004000C6    73 0C                JAE     SHORT 004000D4
004000C8    F3:66:AB             REP STOS WORD PTR ES:[EDI]

so 42008e is start of actual code and spans upto the hard coded address
430000 is resolved imports

CPU Dump
Address   Hex dump                                         ASCII
0042008E  53 56 8B 35|08 00 43 00|33 DB 57 53|BF E4 00 42| SV‹5 C 3ÛWS¿ä B
0042009E  00 57 68 1C|01 42 00 53|FF D6 53 57|68 0C 01 42|  WhB SÿÖSWhB
004200AE  00 53 FF D6|53 57 68 2C|01 42 00 53|FF D6 53 57|  SÿÖSWh,B SÿÖSW
004200BE  68 FC 00 42|00 53 FF D6|53 57 68 EC|00 42 00 53| hü B SÿÖSWhì B S
004200CE  FF D6 53 FF|15 00 00 43|00 5F 5E 5B|02 75 73 65| ÿÖSÿ  C _^[use
004200DE  72 33 32 00|01 FF 43 52|49 4E 4B 4C|45 00 54 65| r32 ÿCRINKLE Te
004200EE  73 74 69 6E|67 20 43 72|6F 6E 6B 79|00 00 54 65| sting Cronky  Te
004200FE  73 74 69 6E|67 20 43 72|65 6E 6B 79|00 00 54 65| sting Crenky  Te
0042010E  73 74 69 6E|67 20 43 72|61 6E 6B 79|00 00 54 65| sting Cranky  Te
0042011E  73 74 69 6E|67 20 43 72|69 6E 6B 79|00 00 54 65| sting Crinky  Te
0042012E  73 74 69 6E|67 20 43 72|75 6E 6B 79|00 00 00 00| sting Crunky

if i binary copy and dump it to a bin file i can easily analyze it with ida by rebasing it 0x40008e creating an xtern segment a, d etc

josh wrote a lengthy post below with patching instructions one doesnt have to go patching instructions all that is needed is hw bp 0x42008e -> f9 _> change addr of entry point to 2008e instead of 5c in the original pe header create backup and save backup

this mem dump can be loaded as is in idapro and it will be dandy enough like a pristine exe

ollydbg crinkledexe -> ctrl+g 42008e -> right click-> exec hw bpt -> f9 _> patch ADDROF ENTRYPOINT TO 0X2008E INSTEAD OF 0X5C ->right click_> createbackup _> right click _> save backup-> load in ida demo 6.9 bingo it is as nice as it get 
  • Btw, when you say "crinkler employs pe header damaging techniques (it seems to use the peheader to stuff code )" you're definitely right, if you're more interested on this subject you can take a look to this interesting presentation to know more about it. Right now I'm intrigued on how windows loader is able to find the entrypoint if the mSizeOfOptionalHeader is just 0x0008, what is doing windows when it reads this nonsense size of OptionalHeader?
    – BPL
    Nov 15, 2016 at 13:29
  • Also, it seems you've reached the same point I'm now as well, you've figured out how that ret instruction drives you to the uncompressed code but haven't been able yet to decompress the thing. +1 For such an elaborated answer though
    – BPL
    Nov 15, 2016 at 13:33
  • @BPL the SizeOfOptionalHeader does not have to be the actual header size; it's just a value used to calculate the section table start (see the IMAGE_FIRST_SECTION macro).
    – Igor Skochinsky
    Nov 15, 2016 at 14:03
  • @IgorSkochinsky That's interesting, so that means the windows loader will always read sizeof(IMAGE_OPTIONAL_HEADER) no matter the value of SizeOfOptionalHeader? That'd have lots of sense and it'd explain why the debugger knows where to stop (ie: AddressOfEntryPoint)
    – BPL
    Nov 15, 2016 at 15:05
  • @IgorSkochinsky Definitely knowing about that IMAGE_FIRST_SECTION clarifies quite a lot
    – BPL
    Nov 15, 2016 at 15:08

Since you have the original program and can check how the OEP looks like, maybe just try debugging the unpacker until you reach the code that looks like the OEP. Then you can try to generalize it to other executables.


If I understood the problem right, the task would have been solved in case an uncompressed (and running!) exe - having been linked with crinkler - could be produced. In the following I will show the steps how to achieve this. However, all the steps had been done in Ida and not in Olly, because I am not familiar with Olly. The procedure should be very similar. Here come the steps:

Summary: Let it run until after the "ret" of the compressor, dump to a bin file, patch the single byte explained below into a "ret" bypassing the decompressor, and finished. The nice thing with this technique is that absolutely no fiddling around with the PE header is necessary. All that magic loading with the PE header having been corrupted by crinkler remains intact. The PE header NEEDS NOT be touched in any way. Now, a more detailed description follows:

Step1: Produce an exe. I followed the line already having presented in this thread with a WinMain and a Message box. This worked without problem as expected (Win10, Compiler VS2013).

Step 2: Load it into Ida, start address was 0x40005c. Some warnings, but finally the start function shows up. It is possible to set a breakpoint at the "start" position. Notably and interestingly, the first lines of this initial code MUST NOT be changed, otherwise Ida produces strange error messages like not accepting the Win32 local debugger or asking something about a %1 object. I assume this is due to the fact that this startup code must produce NOPs for the loader as it might reside somewhere in the PE header.

Step 3: Let the decompressor run until the "ret" statement having already been discussed in this thread (in my example, at address 0x4000d3). This "ret" indicates the end of the decompressor, the code returns into the application part, but beware: The application code has not been linked with Kernel32.lib. A typical technique like the one used in shellcode programming is used. The kernel32.dll address is found similar to the usual (and undocumented) way, having been excellently written dow by skape many years ago. Once the kernel32.dll address is known, all other OS dlls and API calls can be collected dynamically. Beforehand, and obviously as a result of the crinkling procedure, the necessary names like "LoadLibraryA", "user32.dll" or "MessageBoxA" had been stored in hashed form. All windows library loading and access is done dynamically at runtime, also the generation of app defined strings. The purpose of all this is to improve the compression ratio and has (IMO!) nothing to do with the core compression algorithm. The similarity to shellcode programming is obvious: There free RAM space is a very precious resource, and therefore shellcode must be as compact as possible. Every byte counts.

Step 4: At the beginning of the application part, i.e. after the discussed "ret" statement (address 0x420000 in my example), a memory dump must be made, RVA 0x400000 becoming offset zero in the file. My dump started at 0x00400000 with a length of 0x50000. The highest address used by the uncompressor was in the example around 0x430000. The dump is trivial in Ida with a suitable small script.

Step 5: Bypassing the uncompressor. As already mentioned, this MUST NOT be done at the "start" point, as it will not succeed. But luckily, some statements later (address 0x400076) the stack has been loaded by the uncompressor with the proper application address of 0x420000. All what have to be patched is a "ret" statement at address 400076, leading the code into the uncompressed part. A single byte patch is sufficient! The start part now looks like this:

HEADER:0040005C                 public start
HEADER:0040005C start           proc near
HEADER:0040005C                 push    ebx
HEADER:0040005D                 xor     ebp, ebp
HEADER:0040005F                 mov     ebx, 2
HEADER:00400064                 nop
HEADER:00400065                 mov     esi, 400114h
HEADER:0040006A                 push    1
HEADER:0040006C                 pop     eax
HEADER:0040006D                 mov     edi, 420000h
HEADER:00400072                 mov     cl, 0
HEADER:00400074                 nop
HEADER:00400075                 push    edi
HEADER:00400076                 retn

Step 6: Thats all, what is missing is to rename the file into a something.exe and try if it works. It worked, no complaints from the windows loader, and all possibilities to investigate with static breakpoints in the debugger.

Of course, if necessary the produced uncompressed exe can be provided.

  • you can just patch the AddrofEntrypoint to 2008e instead of 5c and load the dump as is in ida pro
    – blabb
    Nov 20, 2016 at 6:26
  • @blabb: Intersting comment, however will not work neither in my solution nor in general. I selected to patch after the ret statement from the decompressor. Thus the patch does not contain the dynamically calculated addresses stored at 0x430000. These are filled in the code from address 0x420000 to 0x42008e (your suggested start point). Patching at 42008e is also not a good idea, because skipping on startup the part from RVA 20000 to 2008e assumes the OS dll addresses to be always at the same position. This would be a serious error as these addresses change e.g. with OS updates and/or versions.
    – josh
    Nov 20, 2016 at 13:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.