Extracting the code from perl2exe packaged files

Target: perl2exe
URL: http://www.indigostar.com/perl2exe.htm

 

Introduction

Perl2exe is a program that is used to run Perl scripts natively on Windows, without needing to install a Perl interpreter.

This might seem like magic to some people but we know better don’t we ;)

 

How it works

Perl2exe works in a similar fashion to py2exe (python version – different author), that is it enumerates all the dependencies of the main script, including any custom libraries. It then packages both the “main” script and all dependencies into the main exe (dropper here on in). Also stored inside the main exe is the perl interpreter, stored as a windows dll (you didn’t really think it was magic did you?).

The dropper drops the perl interpreter to the current windows Temp directory (on 2k/XP: \Documents and Settings\<username>\Local Settings\Temp) followed by a random directory starting with p, with the name p2x<version>.dll, i.e. p2x05043.dll.

Once it drops it, it resolves (via GetProcAddress) the Perl interpreter’s main export, RunPerl(). It passes to this function a pointer to some shared memory which contains the encrypted files and scripts.

 

Dumping the decrypted files

The dropper doesn’t really do much of consequence, except call RunPerl(), so break on the GetProcAddress and trace until the “call eax”:

.text:004011E3 push offset LibFileName ; "p2x588.dll"
.text:004011E8
.text:004011E8 loc_4011E8: ; CODE XREF:_main+2Fj
.text:004011E8 call ds:LoadLibraryA
.text:004011EE mov esi, eax
.text:004011F0 test esi, esi
.text:004011F2 jnz short loc_401204
.text:004011F4 push offset Format ; "ERROR:LoadLibrary p2x588.dll failed\n"
.text:004011F9 call ds:printf
.text:004011FF pop ecx
.text:00401200 push 1
.text:00401202 jmp short loc_401227
.text:00401204 ;
---------------------------------------------------------------------------
.text:00401204
.text:00401204 loc_401204: ; CODE XREF:_main+40j
.text:00401204 push offset ProcName ; "RunPerl"
.text:00401209 push esi ; hModule
.text:0040120A call ds:GetProcAddress
.text:00401210 test eax, eax
.text:00401212 mov RunPerl, eax
.text:00401217 jnz short loc_40122A
.text:00401219 push offset aErrorGetprocad ; "ERROR:GetProcAddress p2x588.dll failed"...
.text:0040121E call ds:printf
.text:00401224 pop ecx
.text:00401225 push 2
.text:00401227
.text:00401227 loc_401227: ; CODE XREF:_main+50j
.text:00401227 pop eax
.text:00401228 jmp short loc_401263
.text:0040122A ;
---------------------------------------------------------------------------
.text:0040122A
.text:0040122A loc_40122A: ; CODE XREF:_main+65j
.text:0040122A push edi
.text:0040122B push [ebp+envp]
.text:0040122E push [ebp+argv]
.text:00401231 push [ebp+argc]
.text:00401234 call eax
.text:00401236 add esp, 0Ch
.text:00401239 mov edi, eax
.text:0040123B push esi ; hLibModule
.text:0040123C call ds:FreeLibrary

Once there, step in.

At this point, you should go to the Temp directory and find the folder and dropped p2x<version>.dll (here on in p2x.dll).

Load this up in IDA.

There is a routine in the p2x.dll which will loop over the decrypted TOC, the TOC contains a list of filenames, sizes and a flag indicating whether it is encrypted or not.

As its enumerating the list (at startup) it will compare the filename to a list of file extensions:

Searching for these strings, it should be easy to locate the function in question:

.text:28091BC8 push offset aIsext_initFile ;"ISEXT_Init: filename = %s\n"
.text:28091BCD call ds:printf
.text:28091BD3 pop ecx
.text:28091BD4 pop ecx
.text:28091BD5
.text:28091BD5 loc_28091BD5: ; CODE XREF:sub_280917BF+406j
.text:28091BD5 push offset aP2x ; "p2x"
.text:28091BDA push esi ; Str
.text:28091BDB call edi ; strstr
.text:28091BDD pop ecx
.text:28091BDE cmp eax, esi
.text:28091BE0 pop ecx
.text:28091BE1 jnz short loc_28091C01
.text:28091BE3 push esi ; SubStr
.text:28091BE4 call strlen
.text:28091BE9 mov [esp+230h+var_230], offset a_dll ;".dll"
.text:28091BF0 push esi ; Str
.text:28091BF1 lea ebx, [eax+esi-4]
.text:28091BF5 call edi ; strstr
.text:28091BF7 pop ecx
.text:28091BF8 cmp eax, ebx
.text:28091BFA pop ecx
.text:28091BFB jz loc_28091DBB
.text:28091C01
.text:28091C01 loc_28091C01: ; CODE XREF:sub_280917BF+422j
.text:28091C01 push offset a_dll ; ".dll"
.text:28091C06 push esi ; Str
.text:28091C07 call edi ; strstr
.text:28091C09 pop ecx
.text:28091C0A test eax, eax
.text:28091C0C pop ecx
.text:28091C0D jnz drop_file
.text:28091C13 push offset a_pll ; ".pll"
.text:28091C18 push esi ; Str
.text:28091C19 call edi ; strstr
.text:28091C1B pop ecx
.text:28091C1C test eax, eax
.text:28091C1E pop ecx
.text:28091C1F jnz drop_file
.text:28091C25 push offset a_so ; ".so"
.text:28091C2A push esi ; Str
.text:28091C2B call edi ; strstr

You may notice I have renamed one of the jump locations as “drop_file”, this is because if one of the files stored in memory is a dll, or a jpg or any one of the file types mentioned above, it will deliberately drop it to disk in the same Temp folder that the p2x.dll resides.

Since at this point the files are decrypted, forcing the jmp at: 28091BE1 and the jmp at: 28091BE1, will enable you to drop the “_main.pl” file as well as the pre and post configuration scripts automatically, by either running or stepping over the instructions for a few iterations of the loop.

That’s all there is to it, no need to try and find decryption keys, just let the program do its work and decrypt it and drop it for us!

Note: This *would* work for all files in the package, however some are contained within a virtual directory structure which, since it doesnt exist on disk means the CreateFileA will fail. A way around this is to mirror the directory structure in the temp folder or perhaps patch in a lil code to do a mkdir() before the CreateFileA. This is really only important if you need one of the libraries, since the _main.pl should be one of the very first files dropped and in the root directory.

Note: Adding -p2x_debug to the command line when running the target will leave a handy debug log behind ;)