Assignment N°7 - Crypters (Delphi/ASM)
Assignment Goals (SLAE-1530)
Create a custom crypter like the one shown in the "crypters" video
Free to use any existing encryption schema
Can use any programming language
What is the purpose of a Crypter
A crypter is very close to encoders. It is a tiny application designed to encrypt a payload and decrypt the payload at runtime.
The payload is encrypted and embedded inside a host program often called a stub, when the stub is executed, it will decrypt the encrypted payload and redirect execution flow at decrypted payload address. Sometimes execution flow is not redirected but instead a new thread or a new process is created to host the payload execution.
Conversely to encoders, crypters uses complexes encryptions schema (RC4, AES, Blowfish, Camelia etc...) to keep the payload obfuscated. Each time a stub is generated, the encrypted payload will look completely different, it is a good solution to beat signature based detection systems.
Because of their complexity, crypters are often coded with higher level language such as C/C++, Delphi, .NET etc..
Creating our custom Crypter
Programming Language Used
We will use an uncommon language to create our Linux x86-32 crypter. We will use Delphi compiled with Freepascal (Lazarus IDE).
It free, open-source and cross-platform.
You can install Lazarus on Ubuntu with aptitude using the following command line:
sudo apt install lazarus
Most of the crypter code will be in Delphi except a small part in Assembly.
We will use the RC4 cipher to encrypt and decrypt our payload. This cipher strength is far enough for our needs and is sufficiently easy to implement.
RC4 specifications is well explained on Wikipedia.
We will implement RC4 specifications in a Delphi object to be more convenient.
To implement RC4 to Delphi, we need to translate two main methods:
KSA (Key-scheduling algorithm)
Quoted from Wikipedia
for i from 0 to 255 S[i] := i endfor j := 0 for i from 0 to 255 j := (j + S[i] + key[i mod keylength]) mod 256 swap values of S[i] and S[j] endfor
PRGA (Pseudo-random generation algorithm)
Quoted from Wikipedia
i := 0 j := 0 while GeneratingOutput: i := (i + 1) mod 256 j := (j + S[i]) mod 256 swap values of S[i] and S[j] K := S[(S[i] + S[j]) mod 256] output K endwhile
Before doing payload encryption, we first need to call our KSA method to initialize our RC4 cipher then we can call our PRGA method for each payload bytes. The PRGA method will return for each payload byte a new pseudo random number we will use to
xor with the current payload byte to encrypt.
In above example, the key is defined as a byte array. We could generate random keys as byte array directly but instead initial key format will be string.
The key string is called a passphrase and requires to be translated to byte array before getting used by our RC4 cipher.
We need to take care of that before calling KSA method.
Finally, in our future Delphi RC4 cipher object, we must take care of updating passphrase. This will be required by our crypter for brute-forcing key (seen later in this paper).
Encryption / Decryption
In classic RC4 cipher, both Encryption and Decryption process is using the same method.
PLAIN rc4 KEY = CIPHERED_MESSAGE rc4 KEY = PLAIN
The main issue is the lack of decryption control, how to be sure our payload was successfully decrypted? for example in the case our passphrase was wrong.
To solve this problem we will implement two distinct method:
First method will first "sign" plain text, encrypt and return content + the plain-text signature.
Second method will first decrypt cipher text, then check for decrypted content signature (plain-text candidate), if both original signature and decrypted content signature matched, then we likely have original plain text.
To sign our plain-text we will use CRC32 - Cyclic redundancy check algorithm. We will use CRC32 for it ease of implementation and speed.
CRC32 is very prone to collision so it is not the ideal solution when it comes to cryptography but it is sufficient for our crypter needs. We could have used SHA1 or SHA2 for more robustness.
Encryption / Decryption process will except a memory address (Pointer) and data size. We prefer to work directly on memory, we don't have to take care of what is the underlying type of data, we encrypt memory content on the fly.
Few words about implementing CRC32 to Delphi, we will use the following resources
The following Wikipedia page perfectly describe CRC32 principle.
We will take care of translating the following pseudo-code (quoted from Wikipedia) in Delphi.
Function CRC32 Input: data: Bytes //Array of bytes Output: crc32: UInt32 //32-bit unsigned crc-32 value //Initialize crc-32 to starting value crc32 ← 0xFFFFFFFF for each byte in data do nLookupIndex ← (crc32 xor byte) and 0xFF; crc32 ← (crc32 shr 8) xor CRCTable[nLookupIndex] //CRCTable is an array of 256 32-bit constants //Finalize the CRC-32 value by inverting all the bits crc32 ← crc32 xor 0xFFFFFFFF return crc32
Above method requires to have a CRC Table, we will grab the CRC32 Table from osdev.org and implement this array of 256 items in our Delphi unit.
Passphrase Generation Principle
At this point we know we will use both RC4 and CRC32 together.
Instead of having a static key embedded inside the stub for decryption, our stub generator will generate a random key of four bytes (or more).
This random key will be sent through our CRC32 method to generate a 8 bytes passphrase (CRC32 generate a 32bit unsigned integer, but we will output this number as an hexadecimal string) thus having a stronger key while keeping something voluntarily easy to brute-force.
Both stub generator (crypter program) and stub are not aware of the passphrase, passphrase is completely random and wiped from generator's "mind".
At run-time the stub will then brute-force the 4 bytes length passphrase until he find the correct plain-text.
Remember, we know plain-text signatures, stub will need to know that signature to verify decrypted content.
A four bytes length passphrase will take few seconds to be brute-forced, be aware that increasing key length exponentially increase the duration of payload recovery.
Embed Stub Application Template in Generator
Our stub generator will be designed to be portable (distributed as a standalone program) thus requiring the stub to be embedded inside the stub generator.
When we generate a new stub, we first extract the stub "template" from our generator application memory.
We will store the stub application as an array of byte inside a special Delphi unit. It means each time we generate a new version of our stub, we need to patch that unit.
We will write a tiny Python script to take care of that.
This python script will compile both stub and generator using the FPC compiler offered with Lazarus, then translate the raw stub program as a Delphi byte array code instruction to be placed inside a unit designed to host and extract that array.
It means that we MUST use that Python script each time we update stub code.
Payload and Payload Information storage on stub
When generator extract a new stub template, the stub is completely free of payload. We need to patch the binary stub (which is a compiled Linux application) with encrypted payload and additional information.
Multiple techniques exists to patch an already compiled binary, we will use the EOF (End Of File) technique.
Indeed, appending data at the end of an application doesn't change it execution behavior. Each application is described by what we call an header, this header describe how each section of the binary needs to be considered and mounted in memory. If we append data at the end of an executable, the header wont be aware of that data so it wont take care of it.
EOF data needs to be structured. The EOF structuring method is up to the programmer. We will structure the EOF as follows:
An EOF segment can contain any kind of data.
For our crypter, we must implement three segments:
- The encrypted payload
- The plain-text payload signature (CRC32)
- The key length, required for brute-forcing.
On stub runtime, it will retrieve above three segments for his tasks.
Payload Execution Method
When payload is decrypted at stub runtime, our final task is to redirect execution flow to payload memory address.
To do so we will use inline assembly offered by Delphi.
To execute code on memory we must be sure memory region that contains that code is set executable otherwise we will receive a seg fault.
We will use the
mmap2() syscall to create a new executable region, we will then move our payload content to that new region and finally redirect execution flow.
Initialize Registers (Delphi inline ASM)
First we initialize first four registers, mostly for debugging purpose.
xor ecx, ecx xor ebx, ebx mul ecx
Create a new executable memory region (Delphi inline ASM)
The size of that region is our payload size
mov eax, $c0 // mmap2() syscall mov ecx, [ADataSize] // payload length mov edx, $7 // PROT_READ | PROT_WRITE | PROT_EXEC mov esi, $22 // MAP_PRIVATE | MAP_ANONYMOUS mov edi, $ffffffff
Copy decrypted payload to new region (Delphi inline ASM)
mov esi, pShellcode // decrypted shellcode address mov edi, eax // eax contains new executable region address xor eax, eax @mem_copy: mov al, [esi] mov [edi], al inc si inc di loop @mem_copy
Execute shellcode (Delphi inline ASM)
sub edi, [ADataSize] // subtract shellcode length to go back to memory region start address call edi // execute shellcode
The final project is available at this Github address: https://github.com/DarkCoderSc/slae32-crypters/
git clone https://github.com/DarkCoderSc/slae32-crypters.git
It is structured as follows:
Contains the builder / stub generator source code.
Contains the stub source code.
Contains shared unit between both builder and stub
Contains standalone version of the builder (stub embedded and ready for production use)
Python script used to build a new standalone version of the builder.
It will compile new version of stub binary then embed stub binary inside builder stub unit before final compilation.
Very last step of the build is to move compiled standalone builder in dist folder and clean the workspace.
Crypter/dist/crypter <shellcode> <outputfile>
In this example we used our
cat /etc/passwd shellcode.
local@user:$ Crypter/dist/crypter "\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x73\x73\x77\x64\x68\x63\x2f\x70\x61\x68\x20\x2f\x65\x74\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe6\x50\x56\x52\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80" /tmp/encrypted_payload
Written the June 17, 2020, 11:12 a.m. by Jean-Pierre LESUEUR