martedì 17 dicembre 2019

Writing a packer in Sacara

Twitter: @s4tan

GitHub project: https://github.com/enkomio/sacara

Release: https://github.com/enkomio/sacara/releases/latest

Sacara packer script: https://gist.github.com/enkomio/35b14084c1422db6740b5ed98cdb2db7

The Sacara project

It is a while that I don't write a blog post and recently I had the opportunity to work again on the Sacara project. I really like this project since it allows me to:
  • write code in x86 Assembly since all the VM code is implemented in x86 Assembly
  • to better understand how to implement a simple programming language (you can find the Sacara grammar file here)
  • how to write an assembler.
My previous post on Sacara is more than 1 year old so it is a good time to see if the project has any issues that can be resolved. After trying to write a new script, I immediately realize which was the biggest defect, writing a program was quite a pain due to the awkward syntax. It is time to improve this aspect with some syntax sugar :).

I released a new version 2.4 which adds some directives and other features that allows to easier the task of writing of a new script, leaving the VM core almost untouched.

Writing a simple packer

As done in my latest post about Sacara, even in this one I'll try to see how the AV industry will behave by analyzing a malicious program. I'll write a simple program that executes a Sacara script which purpose is to decode and run a malicious content.

This is the typical behavior of a packer, in my case I'll just decode and run the embedded content as if it was a shellcode. In general a packer will correctly maps the PE in memory and executes it by locating the Original Entry Point (OEP) but I'll leave this aspect out.

This time I'll create a C project and linking the Sacara static library, in this way we will have just one binary (and not a bunch of files as in my .NET test project created in my previous post).

For my test I wanted to use a real malware so I looked for some good stuff in VT. In the end I decided to use an unpacked Cobalt Strike payload whit SHA-1: 83a490496a7ea9562d6e3eb9a12a224fe50875e7. This is a perfect fit for my case since all Cobalt Strike modules are packed in a way that can be executed as a shellcode starting for the DOS header (the same happens with the Metasploit meterpreter_loader).

The overall design is quite simple, I'll embed the encoded malicious content as a PE resource and will use a Sacara script to decode and execute it. The Sacara script will be embedded as a PE resource too. The task done by the C code is deadly simple, just read the needed resources, allocates a memory region and run the script by providing the input buffer.

Implementation

I'll focus on the Sacara script, you can read the source code of the C code from the repository. The tasks done by the script are:
  1. Decode the password used to encrypt the malicious code
  2. RC4 decrypt the resource content
  3. Run the decrypted code
In order to easier the development and debugging of the script, I'll create various standalone scripts for each task and I'll merge them at the end. All the scripts in this post can be found in the test directory.

Decode the password used to encrypt the malicious code

As first step we want to retrieve the password used to encrypt the content. We don't want that a simple string search will reveals its value, so we will obfuscate it with a simple XOR operation by using a 1-byte key with value 0xA1.

Below you will find the relevant code with a simple test case, I think its comments are self explanatory.

// this routine will be used to stored the 
// script global data, all labels are global
proc global_data
password: 
 // encoded password
 byte 0xe0, 0xe1, 0xe2, 0xe3
endp

proc decode_password(pwd, pwd_len) 
 .mov index, 0
 
decode_pwd_loop:
 // read the byte to decode
 .mov pwd_offset, (pwd + index)
 .read.b pwd_offset 
 pop xored_char
 
 // decode the byte with hardcoded key
 .xor xored_char, 0xA1
 
 // write back the result
 pop xored_char
 .write.b pwd_offset, xored_char
 
 // check if completed
 .cmp index, pwd_len
 .inc index
 push decode_pwd_loop
 jumpifl 
 
 ret
endp

proc main
 // result must be the first variable if I want
 // to retrieve the result with SacaraRun, so set it to 0
 .mov result, 0
 
 // invoke the routine to decode the password
 .decode_password(password, 4)
 
 // read the decoded password as a double word at the specified offset
 .read.dw password
 pop result
 halt
endp
To test if it works, we will try to deobfuscate the buffer 0x41, 0x40, 0x43, 0x42. We have to first obfuscate it, so we will compute the XOR operation between the two integers 0x42434041 (little endian) and 0xA1A1A1A1, which result in 0xE3, 0xE2, 0xE1, 0xE0 (this is the same buffer that you will find at the top of the script).

Firs let's assemble it:
c:\SacaraAsm.exe test_decode_buffer.sacara

          -=[ Sacara SIL Assembler ]=-
Copyright (c) 2018-2019 Antonio Parata - @s4tan

[INFO] 2019-12-08 11:52:07 - VM code written to file: test_decode_buffer.sac
We can now test the script by running it, passing the input value 0x42434041 and expecting as result 0x42434041 (or 1111703617 in decimal notation) which is the little-endian hexadecimal notation of our buffer.
c:\SacaraRun.exe -p test_decode_buffer.sac 0x42434041
Execute file: c:\test_decode_bufferdecode_password.sac
Code execution result: 1111703617


RC4 decrypt the resource content

Our second and most complex step is the decryption of the buffer. In my previous post I used a simple XOR algorithm, for this post I decided to implement the RC4 cryptographic algorithm. If you are used to reverse malware you have probably encountered the usage of RC4 to encrypt configuration or code.

As in my previous script I'll use a lot of comments to make the code easy to understand (I also avoided some trivial optimization to avoid over complication). Since this code is quite long you can find the source code of this step in a test script, in this post I'll only show the KSA and PRGA phases.
proc ksa(password, password_length)
 .mov i, 0
 .mov j, 0

ksa_loop:
 // read the i-th byte from S array
 .read.b (S + i)
 pop S_i
 
 // read the i-th byte from password
 .read.b (password + (i % password_length))
 pop pwd_i
 
 // compute loop expression
 .mov j, ((j + S_i + pwd_i) % 256) 
 .swap(i, j)
 
 // check if I have to iterate
 .inc i 
 .cmp i, 256
 push ksa_loop
 jumpifl 
 ret
endp

proc prga(buffer, buffer_length)
 .mov i, 0
 .mov j, 0
 .mov n, 0
 
prga_loop:
 // update index i
 .mov i, ((i + 1) % 256) 
 
 // update index j
 .read.b (S + i) 
 pop S_i
 .mov j, ((j + S_i) % 256)
 
 // swap
 .swap(i, j)
 
 // read indexes
 .read.b (S + i) 
 pop S_i
 
 .read.b (S + j) 
 pop S_j
 
 // compute random
 .read.b (S + ((S_i + S_j) % 256))
 pop rnd
 
 // read n-th buffer value
 .read.b (buffer + n)
 pop buffer_n 
 
 // XOR with buffer and write back the result 
 .xor buffer_n, rnd
 pop encrypted_char
 .write.b (buffer + n), encrypted_char 

 // check if I have to iterate
 .inc n
 .cmp n, buffer_length
 push prga_loop
 jumpifl
 
 ret
endp

The assembling step is the same as before.

Run the decrypted resource

At this step we have the code in a decrypted form, we just have to run it. This can be done by the infrastructure code (the C code) or from Sacara. Since Sacara allows to invoke native code via the ncall instruction we will use this approach.

Build the code

Now we have all the pieces for our packer, once compiled the infrastructure code we can add the needed resources, that are:

  • DATA: contains the buffer that will be decrypted and executed.
  • SECRET: is the RC4 password that will beu sed to decrypt the buffer. It is XOR encoded with the 0x41 value.
  • SACARA: the Sacara code that will decrypt the buffer and execute it.


To add the resources you can use a simple utility that I wrote or any other PE explorer utility. Finally, the full source code of the Sacara packer can be found here.

Evaluation

As said my test will run a Cobalt Strike Payload encrypted with RC4 and the password: sacara_packer_password. I uploaded the file to VT and waited for the analysis, the result can be found here. We went from 52/70 to 24/69.

Not bad but to be honest I was expecting a better result. If you run the code in a real environment you will notice that the decryption of the code is quite CPU intensive and needs several seconds before to be invoked, this means that the identification of malicious content via emulation is improbable due to performance reason.

I decided to do a second test and upload the same program but without resources, this will make the program 100% safe. The result was very interesting, 12/69 AV flagged my sample as malicious. This means that they flagged the Sacara code as malicious and not real payload.

Conclusion and Future Work

This new version of Sacara improved the language in order to easier the development of a script. The next step is to provide an easy access to the Windows API in order to create more meaningful programs. Also, I want to improve the VM code in order to make it more resistant to reverse engineering.

Finally, I want to stress out that a single test case is not a valid reason to decide if an AV is good or not, so please take my result as a first step in the complex process of AV evaluation.