📜 ⬆️ ⬇️

Using Crinkler with Delphi

Introduction


There are many ways to reduce the size of the executable file. When the castration of system RTL units has already been completed, and the size still cuts the scope for the imagination of the demostsener working in the direction of 4k intro or 64k demo, compressors take over. One of the most famous among them is UPX , because exists under the mass of platforms and is indifferent to the contents of the executable file. But there are linker compressors, which, due to their specificity, are capable of producing more subtle frauds with the executable file at the assembly stage.
In this article, we will talk about the well-known Crinkler linker in the circle of demosceners and the problems that I had to solve in order to adapt this miracle to build the Delphi project.

How it works?


The first step is to explain the principle of operation. The result of the compiler operation are object files that already have compiled machine code. Behind the linker is the task of correctly collecting all these files and associating the functions being called (distribute addresses). For Windows, the file consists of PE sections, the main ones are the sections of the code itself and the import table of functions of external libraries. One of the undocumented features of the PE executable file is the ability to reduce the size of the header by almost 10 times (in more detail ). Also present is one of the nice features of import tables in PE - they can be indexed! This means that it is not at all necessary to save the name for each function imported from the dll, but rather only an ordinal index. This is certainly not safe if the indexing of functions in the dll changes from version to version, but with respect to the Windows system libraries, it will “roll”.

Compressors of executable files are capable of compressing the code and import table by inserting their unpacking code into the executable file. Crinkler literally collects all the knowledge about PE compression accumulated by mankind (pathos) and gives you a cut exe.

Preparation and Compilation


So, we figured out the theory, now get down to business and immediately come across a rake associated with the generation of object files. The fact is that modern versions of Delphi, starting with 5, use the OMF format of object managers, while all C ++ compilers (for which Crinkler is designed) generate COFF format files. Refusing some "syntactic sugar", you can safely roll back to Delphi 3 that knew how to create COFF. But this problem will not disappear, because even the obtained COFFs do not exactly coincide with the necessary ones. In order not to go into details of the differences in formats, I’ll just say that there is a standard linker from Microsoft that can understand and most importantly fix, compiled in Delphi 3 COFF for use in Crinkler .
As a result, the compilation and correction will look something like this:
dcc32.exe myunit.pas -jP link.exe -edit myunit.obj 

So, let's say, you have learned how to compile the object files of the required format. Only 2 problems remain: cropping system modules and the difference in the naming of the entry point.
')
System modules in Delphi are presented in the form of System.pas and SysInit.pas , they contain almost all the fundamental functionality used in the program (strings, dynamic arrays, classes, interfaces, and other unnecessary nonsense to the demoscenter). It is there that is the entry point of the program. The compiler itself is highly dependent on their contents, so their presence is mandatory for successful compilation. It should be noted that the Delphi compiler implicitly for each unit keeps the initialization / finalization code of the sections, even if they are completely empty. Therefore, in order to save the size of the executable file, the demostsense-pascal language should think hard before creating another unit in the project.
Through numerous experiments, I managed to reduce System.pas to such joy:
unit System;
interface
type
TGUID = Byte;
var
_HandleFinally : Byte;
implementation
end .


For reference, I will say that _HandleFinally in the original is a function and is used in the bowels of the compiler, but since we do not want our linker to swear at functions unknown to him, doing such a hack with a variable.

In SysInit.pas, I would recommend to include the code of your demoscene in order not to produce extra unit initialization code. Better yet, include your code directly in the initialization section.
unit SysInit;
interface
var
_HandleFinally : Byte;
procedure ExitProcess(uExitCode: Cardinal); stdcall; external 'kernel32.dll' name '_ExitProcess@4';
implementation
initialization
//... ...
ExitProcess(0);
end .


Surely the keen eye of the reader noticed the features of naming external dll functions. If the meaning is not clear, simply add "_" to the beginning and "@ size_parameters" to the end, or else you can open the necessary lib file with a notebook and find the definition of the function. Also, we see our fake variable again.
This business is compiled by a simple bat'nik:
 rm system.dcu rm system.obj rm sysinit.dcu rm sysinit.obj echo -------------- dcc32.exe system.pas sysinit.pas -jP link.exe -edit sysinit.obj crinkler.exe kernel32.lib sysinit.obj /OUT:test.exe /ENTRY:initialization$qqrv /PRINT:IMPORTS /PRINT:LABELS /SUBSYSTEM:WINDOWS /COMPMODE:SLOW /UNSAFEIMPORT /HASHSIZE:256 /HASHTRIES:1000 /ORDERTRIES:10000 /TRUNCATEFLOATS:8 pause 

If you wish, you can play around with the parameters and get some profit from this for a specific situation.
After compiling we get a clean file size of 572 bytes . If it doesn’t seem like a great achievement on a dummy file, then as the code grows, the compressor with all its advantages will go into action.

It should be borne in mind that the Crinkler linking process takes a decent amount of time, so for debugging, it will be very useful to adapt the link for building:
 link.exe kernel32.lib user32.lib gdi32.lib opengl32.lib sysinit.obj /OUT:test_orig.exe /ENTRY:initialization$qqrv /MERGE:.rdata=.text /MERGE:_INIT_=.text /FILEALIGN:512 /SECTION:.text,ERWX /IGNORE:4078 /IGNORE:4108 /IGNORE:4089 /NODEFAULTLIB /SUBSYSTEM:WINDOWS 

In this case, instantly get our application-dummy size 1.5kb.

Full ammunition with an example of a demo

Source: https://habr.com/ru/post/113477/


All Articles