Reprogrammed

Why Nim?

Code

Preamble

At this point I’ve had a few people ask me about Nim in general and why I’ve chosen it for malware development as it is a relatively unknown language, so I thought it might be a good idea to talk about it and hopefully get more people interested in using it.

As a bonus, more people programming in Nim will also force lazy antivirus developers to come up with real solutions to detecting Nim-based malware that don’t involve flagging anything written in the language.

What is Nim?

So, first things first. What is Nim? Nim-lang.org

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula.

For a little more background Wikipedia:

Nim’s initial development was started in 2005 by Andreas Rumpf. It was originally named Nimrod when the project was made public in 2008.  The first version of the Nim compiler was written in Pascal using the Free Pascal compiler. In 2008, a version of the compiler written in Nim was released. The compiler is free and open-source software, and is being developed by a community of volunteers working with Andreas Rumpf.

The language was officially renamed from Nimrod to Nim with the release of version 0.10.2 in December 2014. On September 23, 2019, version 1.0.0 of Nim was released, signifying the maturing of the language and its toolchain.

So, Nim has been around for a while, overshadowed by the likes of Rust and Go, which is unfortunate, but expected when you have the backing of huge corporations like Google, Mozilla, and Microsoft.

So, what exactly does Nim have to offer?

Python-like Syntax

Nim has Python-inspired syntax, which means easy to read and easy to write. Want to do “Hello World” in Nim? Easy.

echo "Hello World!"

What about reading from stdin?

var input = readLine(stdin)
echo input

What if you want to safely read a password from stdin?

import std/terminal
var pass = readPasswordFromStdin(prompt = "Password: ")
echo "Your password is: $1" % [pass]

Another fuller example can be found on the Nim website itself. Nim-lang.org

import std/strformat

type
  Person = object
    name: string
    age: Natural # Ensures the age is positive

let people = [
  Person(name: "John", age: 45),
  Person(name: "Kate", age: 30)
]

for person in people:
  # Type-safe string interpolation,
  # evaluated at compile time.
  echo(fmt"{person.name} is {person.age} years old")

# Thanks to Nim's 'iterator' and 'yield' constructs,
# iterators are as easy to write as ordinary
# functions. They are compiled to inline loops.
iterator oddNumbers[Idx, T](a: array[Idx, T]): T =
  for x in a:
    if x mod 2 == 1:
      yield x

for odd in oddNumbers([3, 6, 9, 12, 15, 18]):
  echo odd

# Use Nim's macro system to transform a dense
# data-centric description of x86 instructions
# into lookup tables that are used by
# assemblers and JITs.
import macros, strutils

macro toLookupTable(data: static[string]): untyped =
  result = newTree(nnkBracket)
  for w in data.split(';'):
    result.add newLit(w)

const
  data = "mov;btc;cli;xor"
  opcodes = toLookupTable(data)

for o in opcodes:
  echo o

But wait, didn’t we say Nim is a statically typed language? So, where are the types? A great question that has an easy answer. Nim also has type inference in most cases. Meaning we don’t always have to define the types. Case in point, both the readLine and readPasswordFromStdin functions (procedures, actually) return a string, so Nim is smart enough to know our input variables are also strings.

We cannot rely on type inference in all situations though. The rule is (for most, if not all, statically typed languages) the type must be known at compile-time.

Nim also has a bit of a flexible syntax. We don’t always need to include the parentheses when we call our procedures. We can call them like so:

proc doEcho(strToEcho: string) =
    echo strToEcho
"Hello World".doEcho

This is called the Uniform Function Call Syntax (UFCS). It only works with the first parameter of a procedure (beyond that we must use parentheses), but it is a cool feature allows us to do things like this:

type Vector = tuple[x, y: int]
 
proc add(a, b: Vector): Vector =
  (a.x + b.x, a.y + b.y)
 
let
  v1 = (x: -1, y: 4)
  v2 = (x: 5, y: -2)
 
  # all the following are correct
  v3 = add(v1, v2)
  v4 = v1.add(v2)
  v5 = v1.add(v2).add(v4)

Transpiling

Another cool feature Nim has is transpiling. Because I’ve learned this single word has the potential to make developers and Computer Science majors cringe, I’ll just be saying compiling instead. So, as we mentioned above, Nim is a flexible language, and this extends down to its compilation process.

We won’t be diving too deep into the intricacies of how compilers and linkers work (mostly because I don’t have a good, or even decent, understanding of it myself), but Nim produces another language’s code prior to becoming machine code.

Which language’s code does it produce? That’s entirely up to you! Nim can compile down to C, C++, Objective-C or even Javascript! We can write valid C code without ever having to touch C itself. How cool is that? One language to rule them all. Maybe that’s why the logo is a crown.

Side note: I think everyone should still learn C (or C++). Abstractions are cool and all, and make all our lives much easier, but it’s good to know what is really happening under the hood.

Language Interoperability and Win32 API

In a similar vein to Nim’s ability to compile down to other language’s code, its language interoperability is topnotch as well. This means we can interface with other languages. The easiest languages to do this with is C and C++, but we can also use Assembly, .Net (C#, Powershell), VBA, and Python.

Want to use C/C++? Throw it into the .emit pragma:

{.emit: """
static int cvariable = 420;
""".}

{.push stackTrace:off.}
proc embedsC() =
  var nimVar = 89
  # access Nim symbols within an emit section outside of string literals:
  {.emit: ["""fprintf(stdout, "%d\n", cvariable + (int)""", nimVar, ");"].}
{.pop.}

embedsC()

Or use the importc pragma like so:

proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}

Likewise, other “import” pragmas also exist for Objective-C, C++, and Javascript.

Want to run Assembly? Easy! This example is from the Offensive Nim Github repo

proc runsc(): void =
    # msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -f csharp
    asm """
        .byte 0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,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,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,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,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,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,0x48,0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xaa,0xc5,0xe2,0x5d,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00
        ret
    """

Powershell? Create your own runspace and go!

C#? Host the CLR and full speed ahead!

Python? SciNim Example

import nimpy
let py = pyBuiltinsModule()
discard py.print("Hello world from Python..")

Win32? Other languages can make accessing this a pain in the behind, but not Nim. Offensive Nim Github repo example

type
    HANDLE* = int
    HWND* = HANDLE
    UINT* = int32
    LPCSTR* = cstring

proc MessageBox*(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT): int32 
  {.discardable, stdcall, dynlib: "user32", importc: "MessageBoxA".}

MessageBox(0, "Hello, world !", "Nim is Powerful", 0)

We can do things this way, which works perfectly fine, and for languages other than C/C++ and APIs other than Win32 may be necessary, but we can also use the winim library like so:

import winim/com
MessageBox(0, "Hello, world !", "Nim is Powerful", 0)

And this brings us to the final benefit of Nim we will discuss.

Dynamic Function Resolution

If we take a look at the winim library or even our first MessageBox example from earlier, we will notice something very interesting. We notice usage of the “dynlib” pragma. The dynlib pragma (and associated module) performs dynamic function resolution! This means Win32 API calls we make are not included in the Import Address Table (IAT)!

While there are numerous other ways malware can be caught by AV and EDR, taking a look at what API calls the executable will make, even before running it, is an easy win for them. With Nim, it’s also an easy bypass!

That concludes everything I wanted to discuss regarding the benefits of Nim. If there’s any negatives, I would say it’s just that Nim, being a relatively unknown language, does not have a large community behind it.

That means libraries to make certain tasks simpler, like PKCS7 padding or UUID generation, do not exist and must be created manually.