Writing Nimless

Download as pdf or txt
Download as pdf or txt
You are on page 1of 47

Writing Nimless Nim

Writing Offensive Tools Using Nim Without the Nim Runtime

Tyler Randolph (m4ul3r)


0x00 - whoami

● Vulnerability Researcher / Hackerman @ Kudu Dynamics


● Member of US Cyber Games (S1 athlete, S2-3 tech mentor)

● Average CTF enjoyer


● Average Maldev enjoyer
● Average Cat enjoyer
0x01 - nim runtime
What is Nim?
- Efficient, expressive, elegant
- Nim is a statically typed compiled systems programming language. It combines successful
concepts from mature languages like Python, Ada and Modula.
- Description from nim-lang.org

What is the Nim Runtime?


- Safety Checks, Memory Allocation, Garbage Collection, etc.

Why remove it?


- Nim runtime is flagged by a lot of AVs
- Write Nim programs kilobytes in size (~3-6 kb; stage 0 payloads/loaders)
- Write PIC shellcode
- Torqued Maldev
0x02 - basic nim program

Note: Default compiles with threads on and orc memory management (results in more IAT imports)
0x02 - basic nim program

main is called from __tmainCRTStartup.

PreMain does initialization

^ slightly modified for simplicity


0x02 - basic nim program
We always see GetModuleHandleA and GetProcAddress in a Nim program with runtime. Nim resolves
modules and proc addresses at runtime.
0x02 - basic nim program
main is called from __tmainCRTStartup.

main__hello95world_u2 has:
- Overflow checks
- Pseudo vPointer table
- Assertions
- Allocs/Deallocs
- etc.
0x03 - basic nim program (without runtime)

Removing Nim runtime results in writing the program more “C-like”:


- Manual memory and thread management
- Limit types

Limitations:
- Drastically different way of writing in Nim

Informations:
- Nim Compiler: Nim Compiler Version 2.0.2
- MingW Compiler: gcc.exe (MinGW-W64 x86_64-posix-seh, built by Brecht Sanders) 11.1.0
- (This should work with other Nim and gcc versions, might need slight modification to
code/nim.cfg)
0x03 - basic nim program (without runtime)
We use a nim.cfg that is modified config from Bitmancer repo to facilitate stripping away the
Nim and C runtime.

Use winim for typing, heavy lifting of


dynamic linking

Roll our own printf

From the config, we are dynamically


linking Kernel32.dll and user32.dll.
0x03 - basic nim program (without runtime)

References a Nim string

Does not exit cleanly: Required to call ExitProcess


0x03 - basic nim program (without runtime)
What do we get?

3kb binary (if compiled with strip)

Fewer imports from KERNEL32.dll (56 -> 12).


0x04 - self-deleting program
What if we don’t want any imports (IAT)? -> Custom GetModuleHandle & GetProcAddress

First let’s cover some basic utilities...

GetModuleHandle Replacement

Goto functionality

GetProcAddress Replacement gmh and gpa by hash - meant


Hashing Functions to support any arbitrary
hashing algo
Stack string allocations

Output (Debug use) Nim string / cstring


declarations end up in
.data section
0x04 - self-deleting program
Custom GetModuleHandle
0x04 - self-deleting program
Custom GetModuleHandle - How to use.

Note: static happens at compile time.

Allocated in .data (when in global


scope)
Allocated on stack (when in
functional scope)

Using the wrapper.


0x04 - self-deleting program
Custom GetProcAddress
0x04 - self-deleting program
Custom GetProcAddress - How to use.

Type define function

Resolve proc address

Use function

Using the wrapper.

Winim is doing heavy lifting for us on typecasting; if not in winim, operator has to define.
0x04 - self-deleting program

Redefine entry point for


stack adjustment (in nim.cfg).

deleteSelf() will utilize custom gmh and gpa to


delete itself from disk.
0x04 - self-deleting program
0x04 - self-deleting program

Final nim.cfg change to strip debug info.

We have a useless .data section?


0x04 - self-deleting program
We have forced _start to be at the beginning of the .text
section, which allows us for easy extraction for position
independent shellcode.

Using God’s preferred method of extraction.


0x05 - gpa addendum
Q: Oh cool, GetProcAddress Replacement, but HeapAlloc doesn’t work?
A: Forwarded Functions
0x06 - self-injecting loader with direct syscalls
Sometimes we have a stackStringA that
we want to hide.

Simple rolling xor with python.

Almost APT level encryption, just use imagination. (aka, floss finds this).
(see slides 0x9)
0x06 - self-injecting loader with direct syscalls
getPayloadFromUrlA uses the WinAPI to
download a file, it is not very interesting,
We’ve done everything inside of it.

localShellcodeInjection uses direct syscalls


with HellsGate.

Typical shellcode injection (1. VirtualAlloc, 2. CopyMem, 3. VirtualProtect, 4. CreateThreadEx).


0x06 - self-injecting loader with direct syscalls

global variable = .data sect

Probably doesn’t need to be its own proc. Add {.inline.}

varargs pragma to allow us to call multiple syscalls with


one proc.
0x06 - self-injecting loader with direct syscalls
That looked easy; what was the catch?
0x07 - obfuscation
Add anti-debugging through HellsGate

Add syscall to VX_TABLE

Add syscall to initHG()

Add calling of syscall to


localShellcodeInjection()
0x07 - obfuscation
We use capa to see what capabilities the binary has statically.
0x07 - obfuscation
0xtriboulet’s string obfuscation article can give us a simple way of obfuscating our xor encryption.
0x07 - obfuscation
We adjust how we are accessing the PEB to trick static analysis into thinking it isn’t accessed.
0x07 - obfuscation
We can do some dumb stuff to trick capa into thinking we aren’t parsing a PE header.
(Adding 420 when checking signatures, comparing every value and jumping to end of function)

The PE header is being parsed in two places:


utils/gpa.nim and utils/hellsgate/hg.nim.

The same methodology is applied to hg.nim,


except a little more gross..

This brings us to…


0x07 - obfuscation
Various optimization levels (-O0, -O1, …) yields different results. For these results, -Os, was used.
0x08 - demo
0x08 - reverse_shell creates a powershell reverse through CreateProcessA and then calls deleteSelf()
through the previous example.
This is used as our shellcode.
0x07 - self_injection_loader_obfuscated will be our loader.

Target is a freshly installed and updated Windows 10 machine with Defender enabled.
0x08 - demo
0x09 - improving stackStringA/W macro
stackString macros are cool. Let’s improve it by including a single byte xor key at compile time.

We create a compileTime
function that generates us a
single byte xor key.

In assignChars, we xor each


char of the assignment string
and add it to the dotExpr
NimNode.
0x09 - improving stackStringA/W macro

We define our singleByteXor operation on the array that is generated (I,T are
generics to handle different lengths and CHAR/WCHAR). This can be defined with
the noinline pragma if we don’t want the proc inlined.

We define a genStuff template so we can inject it straight into


the AST with getAst. This is done for stackStringW as well,
passing in the correct bool for makeBracketExpression and
assignChars (that bool handles WCHAR).
0x09 - improving stackStringA/W macro

Stack string moved from RAX onto the stack, and


then xor’ed by 0x5f
0x10 - improving stackStringA/W macro pt 2
Rolling xor time. A multibyte xor key was attempted, but kept getting allocated in .rdata; which is
fine, but we are trying to avoid unnecessary usage in the data section.
updateHash() is similar to our single byte, but return a full uint (64-bits).

We use rotr (rotateRightBits) for our index value. This is matched in our complexXor proc.
0x10 - improving stackStringA/W macro pt 2
We want to xor everything except
the last byte, as done in previous
examples.
We can choose to inline or not.

Update the hash for each stackString, this is optional and


alternatively can be used globally.

Add the complexXor function into our AST


0x10 - improving stackStringA/W macro pt 2

Even though we defined inline, nim did not inline


our stackStringA call.

But, our stackStringW call got inline. With nim


generics, each proc will generate individual procs
for each type of passed in argument. In our case,
we will see one for CHAR and one for WCHAR.
0x10 - improving stackStringA/W macro pt 2
What if we want complexXor to always be
inlined?

The codegenDecl pragma will attempt to always_inline. Which is successful for our case.
0x11 - improving winim interoperability
We’ve seen winstrConverterCString
before, but what is it?

All it does is dereference a pointer?

According to winim’s source, it’s just a


converter, so we can use the same
codegenDecl or inline pragmas to force
this as inline.

It is now gone
0x12 - nim.cfg
One thing that was glossed over was the use of a config file. The config bootstraps stripping
away the Nim and C Runtime (NRT & CRT). Let’s discuss What’s needed and what everything is
doing. It’s already heavily documented from zimawhit3’s Bitmancer.

-d:danger - We don’t need any memory safety or checks.


-mm:none - We manage our own memory
–threads:off - Threads are included in the (NRT)
–cpu:amd64 - Specify 64-bit x86
–opt:none - Used so it doesn’t specify –opt:speed or –opt:size; therefore we can specify optimizations
0x12 - nim.cfg
One thing that was glossed over was the use of a config file. The config bootstraps stripping
away the Nim and C Runtime (NRT & CRT). Let’s discuss What’s needed and what everything is
doing. It’s already heavily documented from zimawhit3’s Bitmancer.
0x12 - damn, what else?
Can compile with cpp, this might yield various code sizes.

If needing to specify which optimization level (-O0, -O1..), make sure to use

View the cache/<projectname>/main.json file to see how the program is being compiled/linked.

Changing gcc versions allow for different results, gcc version 13.2.0 has access to -Oz, which results
in slightly smaller shellcode.
0x12 - damn, what else?
Play with your compiler

Enabling advanced
instruction set can make it
more difficult to RE, at the
cost of possibly inflating the
binary
0xffffffff - the talk ends

Slides and source:


● https://github.com/m4ul3r/writi
ng_nimless

Previous Lecture:
● https://github.com/us-cyber-tea
m/nim_for_hackers2

Contact:
● @m4ul3r_0x00 (twitter)

You might also like