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.

giovedì 6 giugno 2019

hm0x14 CTF: reversing a (not so simple) crackme

Twitter: @s4tan

Writeup GitHub project: https://github.com/enkomio/Misc/tree/master/Hm0x14Writeup

I'm not used to participate in CTF competition but in this case I personally know the author of this challenge and I consider her to be very smart, so I decided to give it a try. As I hope to show in this writeup, the challenge is very interesting and not the typical reverse engineering challenge.

Introduction

The challenge file is:

hm0x14.exe
SHA-256: 7cad36c64df33e30673d98e24be4d60c38ba433aa72f8d2bec14f69db4dbf173

It is a C++ application. As first step I run the application to see what it looks like. I have to admit that the author put a lot of effort in making the challenge appealing from a UI point of view. Below an image of the run of the challenge:



Analysis

Before I continue, I have to say that this challenge remained unsolved since its creation. For this reason the author decided to have a talk on how to solve it. This write up is not based on her presentation.

When you open the file in IDA you can immediately see that the main function is quite big. Taking a look at the decompiled source code we can see that the program initializes a DES provider and then read the resource Segreto from 4DES which content is displayed in the image below:



Proceeding with the debugging, it is clear that most of the code is in charge for the UI animation. After stepping a bit with the debugger, it will block on the function that reads the input password. After this function, it is easy to trace the program and see which is the function that accepts as first parameter the input password (which is, in this case, the string "1234567890").


00402E97 | 8D8D 50FEFFFF | lea ecx,dword ptr ss:[ebp-1B0] |
00402E9D | 50            | push eax                       | eax:&L"1234567890"
00402E9E | E8 08F2FFFF   | call hm0x14.4020AB             |
00402EA3 | 59            | pop ecx                        | ecx:L"xe"
00402EA4 | 33C0          | xor eax,eax                    | eax:&L"1234567890"



By decompiling the code we can see that the main goal of this function is to invoke another function that I called hash_chars and then generates 4 symmetric keys. Since in the video there is a 4DES banner I suspect that this function creates 4 keys that will be used in this new crypto algorithm :)


  memset(&v25, 0, 0x20u);
  if ( v7 )
    v8 = password;
  else
    v8 = *password;
  hash_chars(v8, &v25, v6);            ; generate 4DES key buffer (8 byte = 64 bit, a typical DES key length)
  if ( *(password + 20) < 8u )
    v9 = password;
  else
    v9 = *password;
  hash_chars(v9 + 2 * v6, &v26, v6);   ; generate 4DES key buffer (8 byte = 64 bit, a typical DES key length)
  if ( *(password + 20) < 8u )
    v10 = password;
  else
    v10 = *password;
  hash_chars(v10 + 4 * v6, &v27, v6);  ; generate 4DES key buffer (8 byte = 64 bit, a typical DES key length)
  if ( *(password + 20) >= 8u )
    v3 = *password;
  hash_chars(v3 + 6 * v6, &v28, hKey); ; generate 4DES key buffer (8 byte = 64 bit, a typical DES key length)
  v11 = v19;
  v12 = bcrypt_generate_symmetric_key(v19, &hKey, &v25);
  v13 = bcrypt_generate_symmetric_key(v11, &v22, &v26);
  v14 = bcrypt_generate_symmetric_key(v11, &v23, &v27);
  v15 = bcrypt_generate_symmetric_key(v19, &v21, &v27);      ; <----- ?!? (1)



At this point we are not in good luck, since breaking this algorithm seems to be not so easy (just consider that 3DES is still considered a strong algorithm). Let's take a look at the function in charge for creating the key from our password, in the image below I have highlighted the main points:

The loop is executed a number of times that depends on the password length. The meaning of the various circles is:

* blue circle: read the ith character of the input password

* orange circle: this is the main code. It just multiplies the current key value for 0x1F and save only the low DWORD result value (remember this fact).

* green circle: the value of the blue circle is added to the result

* read circle: before to return the result in the ESI register, the value is shifted left by 5 (other point to remember)

The code to generate the key from a password can be represented by the following F# code:


let hashChunk(password: String, offset: Int32, rounds: Int32) =
 let mutable result = 1UL
 for i=0 to rounds-1 do
  result <- (result * 0x1FUL) + uint64 password.[i + offset]
 (result <<< 5) &&& 0x00000000FFFFFFFFUL

let generateKey(password: String) =
 let keys = [
  let size = password.Length >>> 2
  for i=0 to 3 do
   let value = hashChunk(password, i * size, size)
   yield BitConverter.GetBytes(value)
 ]     

 (keys.[1], keys.[0]) // I'll exaplin later why I only return these two keys



Finally, the decryption of the resource content is done by executing the following code:



Which can be summarized as:

P = D(E(D(E(C, k1), k2), k3), k4)



If during the decryption the application identify an error, the image of the skull is displayed (if you are wondering which skull, watch the first video till the end ^^).

Implementation Errors

Let's take a break to do a recap of the info that we have. Despite the fact that each key is 8 bytes long, only the first 4 bytes are used, so here we have the first error. However, breaking such a keyspace is still not feasible with my laptop.

One of the most important aspect that will help us is pointed out in the decompiled code above with reference (1). I'll rewrite the code below for easy reference:


v12 = bcrypt_generate_symmetric_key(v19, &hKey, &v25);
v13 = bcrypt_generate_symmetric_key(v11, &v22, &v26);
v14 = bcrypt_generate_symmetric_key(v11, &v23, &v27);
v15 = bcrypt_generate_symmetric_key(v19, &v21, &v27);



Do you see it? The last two operations reference the same exact value! By debugging the application we can notice this fact since the first two operations use the same key, invalidating the result. So the effective decryption process is:

P = D(E(D(E(C, k1), k1), k2), k3) = D(E(C, k2), k3)



So we downgraded the algorithm to a 2DES and if you have ever followed a cryptographic course, you know that there is a reason if we jumped from DES to 3DES by skipping 2DES.

Meet In the Middle

The reason why 2DES is considered not secure is for this specific attack. By quoting wikipedia:

When trying to improve the security of a block cipher, a tempting idea is to encrypt the data several times using multiple keys. One might think this doubles or even n-tuples the security of the multiple-encryption scheme, depending on the number of times the data is encrypted, because an exhaustive search on all possible combination of keys (simple brute-force) would take 2^(n-k) attempts if the data is encrypted with k-bit keys n times.

The MITM is a generic attack which weakens the security benefits of using multiple encryptions by storing intermediate values from the encryptions or decryptions and using those to improve the time required to brute force the decryption keys. This makes a Meet-in-the-Middle attack (MITM) a generic space–time tradeoff cryptographic attack.

The MITM attack attempts to find the keys by using both the range (ciphertext) and domain (plaintext) of the composition of several functions (or block ciphers) such that the forward mapping through the first functions is the same as the backward mapping (inverse image) through the last functions, quite literally meeting in the middle of the composed function. For example, although Double DES encrypts the data with two different 56-bit keys, Double DES can be broken with 2^57 encryption and decryption operations.


Since we know that our key is 32 bit long we can break this encryption with 2^37 operations. Nice... in theory. I don't know about you, but my laptop is still not so powerful to break such a keyspace. There must be some other way to downsize the key.

Indeed, there is! If you take another look at the function that generates the key from a password you will notice that the final result is left shifted 5 times, this means that the least 5 important bits are always zero! With this information we can downgrade the key from 32 bit to 27 bit!

At this point I started to implement the algorithm, but 27 bit are still too much for my laptop. I have to confess that I was stuck at this point. Talking with the author, she told me that there is still a way to downgrade the key size from 27 to 24 bits.

I struggled a bit on this part, until I realized, the parity bit! It is pretty know that DES uses a key of 64 bits but the effective size is 56 bits. This is due to the fact that the last bit is used as parity bit and it is not consider in the encryption process. Since we have 3 full bytes (the last one is shifted by 5 so doesn't count), by removing 1 bit from each byte we reach the final size of 24 bits.

Finding the plaintext

At this point we have in place the theory and the feasibility of the attack but we miss one last piece, the plaintext to encrypt. Unfortunately the program doesn't seem to give any hints on the format of the plaintext, so I decided to take another look at the program. As every experienced reverse engineering in the world would do, I run the most sophisticated analysis, I run strings on the binary. I discovered some interesting strings like:

"Scrivi il messaggio e premi INVIO, control Z, INVIO... "
"Inserisci la password SEGRETA e premi INVIO!! "
"Stai per creare un messaggio segreto                    con"


those strings were effectively referenced in the binary. By taking a look at the referencing code I discovered that if the binary doesn't found the encrypted resource, it enters in another state and allows to create a secret message. So I removed the resource and started the program again. The image below show how to create a secret message.



Since I created a new protected message I'm finally able to see which is the screen displayed to the user when a message is correctly decrypted. From this screen I can see that the first 8 bytes are always the same and their value is: "Oggetto:". Finally we have all the missing pieces of our puzzle.

Break the rule!

We reached the end of the writeup, let's do a quick recap of the attack:

P = D(E(C, k2), k3) => E(P, k3) = E(C, k4) =>
E("Oggetto:", k3) = E("\xA8\xEC\xE8\x6E\x9D\xB5\xE1\xB7", k4)



I have to compute the two parts for each k3 and k4 until I found two keys that generates the same value.

So, the implementation of the Meet In The Middle attack is composed of two steps, in the first part I encrypt the plaintex with all keys from the 24 bit keyspace and save the result and the key. Then I proceed to encrypt the ciphertext with each possible key and try to find a match with the first step. If a match is found I broke the encryption.

On my laptop it took a while to complete. In the following result you can see the execution of the first step of the attack:


-=[ Start encrypt plaintext: 6/6/2019 2:54:27 PM ]=-
Start iteration 0 of 7 at 6/6/2019 2:54:27 PM
Start iteration 1 of 7 at 6/6/2019 3:07:01 PM
Start iteration 2 of 7 at 6/6/2019 3:18:45 PM
Start iteration 3 of 7 at 6/6/2019 3:32:16 PM
Start iteration 4 of 7 at 6/6/2019 3:45:15 PM
Start iteration 5 of 7 at 6/6/2019 3:58:26 PM
Start iteration 6 of 7 at 6/6/2019 4:10:37 PM
Start iteration 7 of 7 at 6/6/2019 4:22:27 PM
-=[ End encrypt plaintext: 6/6/2019 4:34:14 PM ]=-


And here is the second part of the attack where you can see that the password was successfully bruteforced:


-=[ Populate storage from pre-built table: 6/6/2019 5:56:37 PM ]=-
-=[ Start identify key: 6/6/2019 5:58:03 PM ]=-
Start iteration 0 of 7 at 6/6/2019 5:58:03 PM
Start iteration 1 of 7 at 6/6/2019 6:01:58 PM
Encrypt Password found: 20-8A-34-40-00-00-00-00
Decrypt Password found: 80-A0-DE-1C-00-00-00-00
-=[ End identify key: 6/6/2019 6:08:43 PM ]=-
-=[ Secret message: 6/6/2019 6:08:47 PM ]=-
Oggetto: Finché la barca va.

Quell'augel d'ebano, allora, così tronfio e pettoruto
tentò fino ad un sorriso il mio spirito abbattuto:
«Sebben spiumato e torvo, - dissi, - un vile non sei tu
certo, o vecchio spettral corvo della tenebra di Pluto?
Quale nome a te gli araldi dànno a corte di Re Pluto?»
Disse il corvo allor: «HM{2005d05af414ac92a3ffc5beecbd94f4}!».

PS: questo «4DES» non mi sembra molto sicuro. ho dei seri dubbi sull'algo-
ritmo di hashing della password, e comunque quando si implementa un algorit-
mo non standard si rischia sempre di fare degli erroi grossolani. anche ba-
nali errori di copia-incolla possono essere fatali per la sicurezza. E poi
il logo è così lento a disegnarsi! Troviamo un'alternativa?



Once that I retrieve the keys I'm able to decrypt the text and visualize the FLAG (which is HM{2005d05af414ac92a3ffc5beecbd94f4}), as can be seen from the following video:



Conclusion

This challenge was very entertaining, not because of the reversing part (that was pretty easy to be honest) but because was built with the idea to show how difficult is to implement a new cryptographic algorithm by demonstrating how a real world attack works.

Side Note

The challenge is full of funny comments having joke of hackers. The author told me that she created this challenge by considering a middle-aged developer.

The final message is an excerpt from the poem "The Raven" where the quote "Nevermore" was replaced with the MD5 of "Barbra Streisand" (the actual CTF flag value). This is a tribute to a meme that was popular at that time (actually I didn't realize this thing, it was the author that told me to search for that MD5).

Source Code

The full source code is on my Github account, I report here just the most important parts to break the encryption (for an updated version please visit the Github website).

namespace Hm0x14Writeup

open System
open System.Text
open System.IO
open System.Collections.Generic
open System.Reflection

module Program =    

 let mangleKey(k0: Int32, k1: Int32, k2: Int32, k3: Int32) = [|
  byte k0 <<< 5
  byte k1 <<< 1
  byte k2 <<< 1
  byte k3 <<< 1
  0uy
  0uy
  0uy
  0uy
 |]

 let getKeys() = seq {
  for i0=0 to 0x7 do
   Console.WriteLine("Start iteration {0} of 7 at {1} ", i0, DateTime.Now)
   for i1=0 to 0x7F do
    for i2=0 to 0x7F do
     for i3=0 to 0x7F do
      yield (mangleKey(i0, i1, i2, i3))
 }

 let buildEncryptedTextTable(plainText: Byte array, storage: Dictionary<String, Byte array>) =
  Console.WriteLine("-=[ Start encrypt plaintext: {0} ]=-", DateTime.Now)

  Utility.getKeys()
  |> Seq.iter(fun key ->
   try
    let encryptedBuffer = Encryption.encrypt(plainText, key)
    storage.[BitConverter.ToString(encryptedBuffer)] <- key
   with _ -> ()
  )

  Console.WriteLine()
  Console.WriteLine("-=[ End encrypt plaintext: {0} ]=-", DateTime.Now)
  Console.WriteLine()

 let populateStorage(storage: Dictionary<String, Byte array>) =
  if not <| Storage.storageExists() then
   let plainText = Encoding.UTF8.GetBytes("Oggetto:")
   buildEncryptedTextTable(plainText, storage)
   Storage.saveEncryptedText(storage) 
  Storage.loadEncryptedText(storage)

 let findKey(encryptedText: Byte array, storage: Dictionary<String, Byte array>) =
  Console.WriteLine("-=[ Start identify key: {0} ]=-", DateTime.Now)
  let mutable (encKey, decKey) = (Array.empty<Byte>, Array.empty<Byte>)

  Utility.getKeys()
  |> Seq.iter(fun key ->
   try
    let encryptedBuffer = Encryption.encrypt(encryptedText, key) |> BitConverter.ToString
    if storage.ContainsKey(encryptedBuffer) then
     encKey <- storage.[encryptedBuffer]
     decKey <- key
     Console.WriteLine("Encrypt Password found: " + BitConverter.ToString(storage.[encryptedBuffer]))
     Console.WriteLine("Decrypt Password found: " + BitConverter.ToString(key))                
     Console.ReadLine() |> ignore
   with _ -> ()
  )

  Console.WriteLine()
  Console.WriteLine("-=[ End identify key: {0} ]=-", DateTime.Now)
  Console.WriteLine()

  (encKey, decKey)

 let getCipherText() =
  let curDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)
  File.ReadAllBytes(Path.Combine(curDir, "4DES_SEGRETO"))

 [<EntryPoint>]
 let main argv = 
  let storage = new Dictionary<String, Byte array>()
  populateStorage(storage)

  // decrypt the cipher text
  let ciphertext = getCipherText()     
  let (encKey, decKey) = findKey(ciphertext.[0..7], storage)

  // print the cipherText
  let secretMessage = Encryption.twoDesDecrypt(encKey, decKey, ciphertext)
  Console.WriteLine(secretMessage)
  0

domenica 19 maggio 2019

Sojobo - Yet another binary analysis framework

Twitter: @s4tan

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

Sojobo is a new binary analysis framework written in .NET and based on B2R2. I created this project for learning purpose and to make my work easier during malware analysis.

B2R2

A couple of months ago a new binary analysis framework named B2R2 was released ([01, 02]), which also won the "BAR 2019 Best Paper Award" ([03]). It immediately attracted my attention since it is fully developed in F# in .NET Core and doesn't need any external libraries. This was a big plus for me since I love F# and I always had issues with the most common binary analysis frameworks (like the needs of a specific library version or the python binding is not working with the latest version or they are supposed to run only on Linux).

B2R2 is a framework with an academic origin (this is a very rare case, since academic are reluctant to release working source code) and the developer is very responsive (and kind) on GitHub. It supports various CPU architectures and implements a new IR (LowUIR) which is very simple to understand. All sound very promising :)

Unfortunately, as the B2R2 main developer wrote ([04]), it is a frontend framework and at the moment no implementation is provided as backend. Also, they are considering running a business on the implementation of a backend framework and at the moment they are unsure when they will release it.

In the meantime that such code will be released I decided to write a backend on my own :)

Using Sojobo

Sojobo allows to emulate PE binary (32 bit) and to interact with the emulation. It implements a Sandbox class that can be used to emulate a given binary. In the following paragraph we will see how to write a simple generic unpacker.

Implementing a generic unpacker

As first example I tried to write a tool that dumps a dynamically allocated memory region which is then executed. My purpose was to write a generic unpacker (as a POC of course) by following the principles described in the paper "Automatic Static Unpacking of Malware Binaries" ([05]). This kind of tools are pretty common among malware analysts, recently a new one was released([06]).

You can find the source code of this sample in the GitHub repository, I'll paste it here for convenience:


#include <stdint.h>
#include <Windows.h>

void copy_code(void *buffer)
{
 __asm 
 {
  jmp start
 code:
  push ebp
  mov ebp, esp
  xor eax, eax
  mov edx, 1
  mov ecx, DWORD PTR [ebp+8]
 l: 
  xadd eax, edx
  loop l
  mov esp, ebp
  pop ebp
  ret
 start:
  mov esi, code;
  mov edi, buffer;
  mov ecx, start;
  sub ecx, code;
  rep movsb
 }
}


int main()
{
 uint32_t ret_val = 0;
 void *fibonacci = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
 copy_code(fibonacci);
 ret_val = ((uint32_t (*)(uint32_t))fibonacci)(6);
 VirtualFree(fibonacci, 0x1000, MEM_RELEASE);
 return 0;
}


As you can see the code allocates a new memory region, invokes a function to copy some code and executes it. I tried to mimic a malware that unpacks the real payload in memory and then executes it. My goal is to dump such code.

To do that I'll follow a simple principle (described in the referred paper): if a memory region that was previously written to is executed, then I'll dump it to disk. By using Sojobo I subscribed to an event handler that is invoked each time that a memory is accessed. I can now step trough the process and monitor if a region that was previously written is now executed.

One of the first issue was to emulate invocation of external function (like VirtualAlloc). With Sojobo you can easily emulate such call by following a given coding convention (I'm a fan of convention over configuration paradigm [07]) but don't worry, Sojobo already implements emulation for some functions and I plan to support many more functions.

Saying that, the solution to our problem is the following one (the code is also in GitHub):

namespace ES.EndToEndTests

open System
open System.IO
open System.Collections.Generic
open B2R2
open ES.Sojobo.Model
open ES.Sojobo

module DumpDynamicMemory =
    let private _memoryRegions = new List<MemoryRegion>()
    let mutable private _memoryDumped = false

    let private memoryAccessedHandler(operation: MemoryAccessOperation) =
        match operation with
        | Read address -> ()
        | Write(address, value) -> ()
        | Allocate memRegion -> _memoryRegions.Add(memRegion)
        | Free memRegion -> ()

    let private writeDisassembly(activeProcess: IProcessContainer) =
        let text = Utility.formatCurrentInstruction(activeProcess)
        Console.WriteLine(text)

    let private identifyUnpackedCode(activeProcess: IProcessContainer) =
        if not _memoryDumped then
            let pc = activeProcess.GetProgramCounter().Value |> BitVector.toUInt32
            _memoryRegions
            |> Seq.tryFind(fun memRegion -> 
                pc >= uint32 memRegion.BaseAddress &&
                pc < uint32 memRegion.BaseAddress + uint32 memRegion.Content.Length
            )
            |> Option.iter(fun memRegion ->
                // a previously allocated region now is being executed, maybe unpacked code!            
                let filename = String.Format("mem_{0}.bin", memRegion.BaseAddress)
                File.WriteAllBytes(filename, memRegion.Content)
                Console.WriteLine("[+] Dynamic code dumped to: {0}!", filename)
                _memoryDumped <- true
            )

    let private step(activeProcess: IProcessContainer) =
        writeDisassembly(activeProcess)
        identifyUnpackedCode(activeProcess)

    let private getTestFile() =
        ["Release"; "Debug"]
        |> Seq.map(fun dir -> Path.Combine("..", "..", "..", dir, "RunShellcodeWithVirtualAlloc.exe"))
        |> Seq.tryFind(File.Exists)

    let ``dump dynamically executed memory``() =
        let sandbox = new Win32Sandbox() 
        let exe = 
            match getTestFile() with
            | Some exe -> exe
            | None ->
                Console.WriteLine("RunShellcodeWithVirtualAlloc.exe not found, please compile it first!")
                Environment.Exit(1)
                String.Empty

        sandbox.Load(exe)

        // setup handlers
        let proc = sandbox.GetRunningProcess()
        proc.Memory.MemoryAccess.Add(memoryAccessedHandler)
        proc.Step.Add(step)
        
        // print imported function
        proc.GetImportedFunctions()
        |> Seq.iter(fun symbol ->
            Console.WriteLine(
                "Import: [0x{0}] {1} ({2}) from {3}", 
                symbol.Address.ToString("X"), 
                symbol.Name, 
                symbol.Kind, 
                symbol.LibraryName
            )            
        )
        
        // run the sample
        sandbox.Run()


The code is quite simple, each time that a memory region is allocated I add it to a list. For each executed instruction I monitor if EIP is in the range of one of the previously allocated memory and if so I dump the region content to disk. If we execute the code a new file is written to disk which contains the following disassembled code:


L_00000000:   push ebp
L_00000001:   mov ebp, esp
L_00000003:   xor eax, eax
L_00000005:   mov edx, 0x1
L_0000000A:   mov ecx, [ebp+0x8]
L_0000000D:   xadd eax, edx
L_00000010:   loop 0xd
L_00000012:   pop ebp
L_00000013:   ret 


A real world sample: emulates KPOT v2.0 and dumps the deobfuscated strings

Let's try to use Sojobo with a real world case. Recently, Proofpoint published a new article about a new KPOT version ([08]). We will consider the sample with SHA256: 67f8302a2fd28d15f62d6d20d748bfe350334e5353cbdef112bd1f8231b5599d.

In the GitHub repository I included the KPOT sample too, I took precaution to be sure that it is not executed by mistake (it is XORed, base64 encoded and with a corrupt PE header).

Our goal is to dump the strings once that they are decrypted. The function in charge for the decryption is at address 0x0040C8F5 and once that it returns in EAX is stored the length of the string and the EDI register points to the decrypted buffer. We can then read the memory content and print it.

Sojobo tries to emulate the most common functions and in particular it emulates GetLastError by returning 0 (success). If we take a look at the KPOT code we spot the following one:


.text:004103BB                 call    ds:LoadUserProfileW
.text:004103C1                 test    eax, eax
.text:004103C3                 jnz     short loc_4103D0
.text:004103C5                 call    ds:GetLastError
.text:004103CB                 cmp     eax, 57h ; 'W'
.text:004103CE                 jz      short loc_4103D5
.text:004103D0                 jmp     near ptr loc_4103D0+1 ; Jump to garbage


Basically, if the GetLastError code is different than 0x57 the process crash (jump to garbage data). So we have to override the GetLastError default function definition in order to force to return 0x57. This is done by creating a class with name Kernel32 and a function with name GetLastError that accepts as first parameter a ISandbox object. Take a look at this file for the implementation details. Then, we add our assembly to the Sandbox in order to consider our function implementation, finally as done before we setup a process step handler, which contains the following code:


private static void ProcessStep(Object sender, IProcessContainer process)
{
 var ip = process.GetProgramCounter().ToInt32();
 if (ip == _retAddresDecryptString)
 {
  // read registers value
  var decryptedBufferAddress = process.GetRegister("EDI").ToUInt64();
  var bufferLength = process.GetRegister("EAX").ToInt32();
  
  // read decrypted string
  var decryptedBuffer = process.Memory.ReadMemory(decryptedBufferAddress, bufferLength);
  var decryptedString = Encoding.UTF8.GetString(decryptedBuffer);
  Console.WriteLine("[+] {0}", decryptedString);
 }
}


By reversing the sample we know that the decrypt function end at address 0x0040C928, so when this point is reached we can dump the decrypted string by reading the EAX and EDI register values and also by reading the process memory. Find below an example of execution:


-=[ Start Emulation ]=-
[+] wininet.dll
[+] winhttp.dll
[+] ws2_32.dll
[+] user32.dll
[+] shell32.dll
[+] advapi32.dll
[+] dnsapi.dll
[+] netapi32.dll
[+] gdi32.dll
[+] gdiplus.dll
[+] oleaut32.dll
[+] ole32.dll
[+] shlwapi.dll
[+] userenv.dll
[+] urlmon.dll
[+] crypt32.dll
[+] mpr.dll
-=[ Emulation Completed ]=-


Of course that list is by no means exhaustive. We will see in the next paragraphs why of this.

It is really so simple and smooth?

I would love to say yes, but there are still some limitations (that I already planned to solve). The output above is taken by emulating the KPOT function that is in charge for loading the real used DLLs. Before that code we have the following one:


.text:00406966 64 A1 30 00 00 00             mov     eax, large fs:30h ; read PEB
.text:0040696C 8B 40 18                      mov     eax, [eax+18h]    ; read Heap
.text:0040696F C3                            retn


Basically, it reads the Heap base address from PEB. A solution to this would be to place some fake values but it is not a good solution in the long term (KPOT resolves function addresses by walking the EAT). So I defined a PEB and TEB structures and written them to the process memory (I also correctly initialized the FS register). I have also implemented a serialization algorithm that will allows us to "read" object type from memory (instead that just a bunch of raw bytes). This will be very handy if we want to customize some complex structure (like PEB in this case). In the next paragraph we will take advantage of this feature.

The second problem is that KPOT tries to resolve function addresses by walking the Ldr field. It also use the Ldr field to find the base address of Kernel32, this is done by the following code:


.text:00406936                               get_Kernel32_base_via_Ldr proc near
.text:00406936 64 A1 30 00 00 00             mov     eax, large fs:30h ; read PEB
.text:0040693C 8B 40 0C                      mov     eax, [eax+0Ch]    ; read Ldr
.text:0040693F 8B 40 0C                      mov     eax, [eax+0Ch]    ; read InLoadOrderModuleList
.text:00406942 8B 00                         mov     eax, [eax]        ; read first entry (ntdll)
.text:00406944 8B 00                         mov     eax, [eax]        ; read second entry (kernel32)
.text:00406946 8B 40 18                      mov     eax, [eax+18h]    ; read DllBase
.text:00406949 C3                            retn 
.text:00406949                               get_Kernel32_base_via_Ldr endp


Even in this case you can just fake this value and write back the LDR_DATA_TABLE_ENTRY structure to memory but very soon you will discover that this strategy with fail (in fact, in our test the emulation raise an exception).

Dumping all strings from KPOT v2.0 (for real)

In the previous paragraph was introduced a feature that allows us to read objects from the process memory. In this paragraph we will see how to dump all encrypted strings in a very easy way. As said by Proofpoint all strings are encrypted with a very simple algorithm and stored in a struct that has the following layout:


public class EncryptedString
{
 public UInt16 EncryptionKey;
 public UInt16 StringLength;
 public UInt32 Buffer;

 public String Decrypt(IProcessContainer process)
 {
  var buffer = process.Memory.ReadMemory(this.Buffer, this.StringLength);
  var stringContent = new StringBuilder();
  foreach(var b in buffer)
  {
   stringContent.Append((Char)(b ^ this.EncryptionKey));
  }

  return stringContent.ToString();
 }
}


It would be very useful if we can read from the memory process an EncryptedString object instead that a raw byte array (as done by the Proofpoint python script). With Sojobo you can do it and the code to print all the decrypted strings is as simple as this one:


private static void DecryptStrings(IProcessContainer process)
{
 Console.WriteLine("-=[ Start Dump All Strings ]=-");
 
 // encrypted strings
 var encryptedStringsStartAddress = 0x00401288UL;
 var encryptedStringsEndAddress = 0x00401838UL;

 var currentOffset = encryptedStringsStartAddress;
 while (currentOffset < encryptedStringsEndAddress)
 {
  var encryptedString = process.Memory.ReadMemory<EncryptedString>(currentOffset);
  var decryptedString = encryptedString.Decrypt(process);
  Console.WriteLine("[+] {0}", decryptedString);

  // go to the next string
  currentOffset += 8UL; 
 }

 Console.WriteLine("-=[ Dump All Strings Completed ]=-");
}


In the GitHub repository you can find the full source code (to dump all strings pass --strings as first argument). The result it is the same as the one provided by Proofpoint (but with a cleaner code :P).

Conclusion and future development

Sojobo is still in its infancy but it can already be used for some initial analysis. In its future releases I'm going to add more emulated functions and the possibility to map other files in the process address space. By mapping external files (like Kernel32 or Ntdll) we can overcome problems related to an indirect referencing (like in the case above) while still maintaining control on how to emulate the function.

References

[01] B2R2: Building an Efficient Front-End for Binary Analysis - https://www.reddit.com/r/ReverseEngineering/comments/aultc1/b2r2_building_an_efficient_frontend_for_binary/
[02] B2R2: Building an Efficient Front-End for Binary Analysis (PDF) - https://ruoyuwang.me/bar2019/pdfs/bar2019-final51.pdf
[03] NDSS Workshop on Binary Analysis Research (BAR) 2019 - https://ruoyuwang.me/bar2019/
[04] Symbolic Execution component #question - https://github.com/B2R2-org/B2R2/issues/9
[05] Automatic Static Unpacking of Malware Binaries - https://www.researchgate.net/publication/221200507_Automatic_Static_Unpacking_of_Malware_Binaries
[06] MwEmu: Malware analysis emulator written in Python 3 (based on Unicorn) - ALPHA version - https://www.reddit.com/r/Malware/comments/bkb0p9/mwemu_malware_analysis_emulator_written_in_python/
[07] Convention over configuration - https://en.wikipedia.org/wiki/Convention_over_configuration
[08] New KPOT v2.0 stealer brings zero persistence and in-memory features to silently steal credentials - https://www.proofpoint.com/us/threat-insight/post/new-kpot-v20-stealer-brings-zero-persistence-and-memory-features-silently-steal