Assignment N°7 - Crypters (Delphi/ASM)

Assignment Goals

SLAE32

This paper is part of the certification process following the SLAE32 course (x86 Assembly Language and Shellcoding on Linux) intended to prepare me to become a future certified OSCE.

If you are willing to pass the certification I really suggest you to wait until you finished your own certification process before reading that paper.

Why? the goal of that certification is to practice and learn how to solve each assignment by yourself. If you read this paper you will get spoiled and seriously oriented to my personal solution and take the risk to abuse of some shortcuts.

Student ID: 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:

local@user:$ sudo apt install lazarus

Most of the crypter code will be in Delphi except a small part in Assembly.

Encryption Schema

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.

Key Format

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: Encrypt() and Decrypt():

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.

Process

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.

CRC32 Implementation

Few words about implementing CRC32 to Delphi, we will use the following resources

Wikipedia

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:

EOF_SEGMENT_N|SIZE_OF_SEGMENT_N|...|EOF_SEGMENT2|SIZE_OF_SEGMENT2|EOF_SEGMENT1|SIZE_OF_SEGMENT1

An EOF segment can contain any kind of data.

For our crypter, we must implement three segments:

  1. The encrypted payload
  2. The plain-text payload signature (CRC32)
  3. 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

Final Project

The final project is available at this Github address: https://github.com/DarkCoderSc/slae32-crypters/

local@user:$ git clone https://github.com/DarkCoderSc/slae32-crypters.git

It is structured as follows:

crypter Folder

Contains the builder / stub generator source code.

stub Folder

Contains the stub source code.

shared Folder

Contains shared unit between both builder and stub

dist Folder

Contains standalone version of the builder (stub embedded and ready for production use)

build.py

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.

Build Usage

local@user:$ python3 build.py

Crypter Usage

local@user:$ Crypter/dist/crypter <shellcode> <outputfile>

Example

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

Command Result

local@user:$ /tmp/encrypted_payload

Command Result

comments powered by Disqus