Offensive Nim - Cheatsheet

Using Nim Offensively

The following things are useful snippets for programming in offensive Nim.

  • [[#Compilation)
  • [[#Dividing Functionality)
  • [[#Define Shellcode as sequence)
  • [[#Get direct address to a seq)
  • [[#Casting Windows Types)
  • [[#Instantiating Windows Structs)
  • [[#Create Windows Structs from Addr)
  • [[#Formatting)

Compilation

Taken from the Nim FAQ: https://nim-lang.org/faq.html

For the standard configuration file, -d:danger -d:strip --opt:size does the trick. If supported by your compiler, you can also enable link-time optimization the same way as described in the previous answer.

Dividing Functionality

The when keyword can be used as compile-time if.

When compiling: nim.exe -d:finalimplant

And can be used in the code:

1
2
when defined(finalimplant):
    do_final_implant_stuff()

This is perfect for having several different injections and other stuff in a library and only compile a certain subset of those

Often used Code References

Define Shellcode as sequence

The benefit of using seq instead of an array is that the size is dynamic and can be easily loaded from a file/network:

let shellcode: seq[byte] = @[byte 0x41, 0x41, 0x41]

Get direct address to a seq

It is not possible to use the addr keyword to get a direct address of a seq[byte], hower the unsafeAddr can be used, when indexing the sequence:

1
2
3
4
5
6

let shellcode: seq[byte] = @[byte 0x41, 0x41, 0x41]

VirtualAllocEx(
	unsafeAddr shellcode[0]
)

Info: It is important to index the variable (shellcode[0])

Casting Windows Types

Most Windows types can be casted quite easily:

1
2
3
# DWORD as integer
let pid: int = 1336
let dPid: DWORD = cast[DWORD](pid)

Instantiating Windows Structs

Structs/Typedefs are objects in nim and defined like that:

1
2
3
4
CLIENT_ID* {.pure.} = object
    UniqueProcess*: HANDLE
    UniqueThread*: HANDLE
PCLIENT_ID* = ptr CLIENT_ID

There are two ways of intantiating such objects:

  1. Full
1
2
3
4
5
6
7
let client_id: CLIENT_ID = CLIENT_ID(
    UniqueProcess: 1337,
    UniqueThread: 0
)

# Pass by reference
NtOpenProcess(.., ..., ..., unsafeAddr client_id)

It seems that for initialized objects, it is neccessary to pass the object with the unsafeAddr keyword as the addr isn’t working (does this make sense?)

  1. Partially/Empty

The benefit is that its shorter to declare, and the keyword addr can be used

1
2
3
4
var client_id: CLIENT_ID

# Pass by reference
NtOpenProcess(.., ..., ..., addr client_id)

Dereferencing structs from a pointer

  • To instantiate a Windows struct (e.g. DOS_HEADER) from an address:
1
2
var addr: PVOID
var header: PIMAGE_DOS_HEADER = cast[PIMAGE_DOS_HEADER](addr)
  • This works for aprsing bytes from a byte sequence:
1
2
3
4
5
6
7
var bytes: seq[byte] = @[0x41, 0x41, 0x00, ....]

var addr: PVOID = unsafeAddr bytes[0]
var header: PIMAGE_DOS_HEADER = cast[PIMAGE_DOS_HEADER](addr)

# access certain attributes:
echo header.e_magic

Calculating with Windows Types

  • PVOID (something weird)
    • Cast to int (or uint64) when dealing with addresses
    • Int seems to represent the length of the compiled binary

Formatting

  • Outputing large integers as hex:
1
2
3
4
5
import strformat

echo fmt"This pointer is in hex format: {ptr:#X}"

# Output: This pointer is in hex format:  0x19367340000

Reading Strings

In C its trivial to read a char * from an address as its nothing else but raw bytes in memory. In Nim however, a function is required to actually read the bytes from memory into a string.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
proc toString*(p: pointer): string =
  var c: byte = 1
  var idx = 0
  while c != 0x00:
    var fp = cast[pointer](cast[int](p) + idx)
    copyMem(addr c, fp, 1)
    if c != 0x00:
      result.add(char(c))
    idx += 1
  return result

Some Windows API structs contain arrays of WCHAR or CHAR. To obtain a Nim string from those, the following snippet can be used:

1
2
3
4
5
6
proc toString(chars: openArray[WCHAR]): string =
    result = ""
    for c in chars:
        if cast[char](c) == '\0':
            break
        result.add(cast[char](c))