Fresh from a Syscalls-fuelled BOF joyride, I had a chance to play with Nim. In particular, I was intrigued by a tweet from @byt3bl33d3r:
omg.... I did it! Win32 Syscalls from Nim!!! š pic.twitter.com/h8XeM5062a
— Marcello (@byt3bl33d3r) January 12, 2021
My immediate thought was that this looked syntactically similar to the inline assembly outputted by Outflankās InlineWhispers, and that we could achieve a similar thing for Nim. A few hours (and many nested for-loops) later, we have a proof-of-concept version of NimlineWhispers.
This blog will walk through a process of using NimlineWhispers to port an existing Nim project to use Syscalls and Native APIs.
Initial Code
For our project, weāll use an example from Marcelloās fantastic OffensiveNim repo, specifically this one. If you havenāt had a play with Nim yet, Marcelloās repo is a gold mine of information for installation, compilation, code examples and opsec tips - I highly recommend taking a look.
The code example weāll use spawns a target process, in this case notepad.exe
, and uses the classic CreateRemoteThread injection technique to allocate memory, write and launch our message box popping shellcode in that process. The original code can be seen below:
For simplicity, weāre going to spawn the notepad.exe
process as normal and just focus on porting the injection steps to our Syscalls functions.
Converting to Native functions
We wonāt go into the depths of high-level vs Native API calls, mainly because people that understand it infinitely more than me have put out great material on the subject already, particular with regards to its offensive applications.
For our purposes, we know though that our high-level functions need to be replaced with the following Native API calls:
OpenProcess
->NtOpenProcess
VirtualAllocEx
->NtAllocateVirtualMemory
WriteProcessMemory
->NtWriteVirtualMemory
CreateRemoteThread
->NtCreateThreadEx
CloseHandle
->NtClose
These functions will require slightly different arguments and data structures. Resources such as NTAPI Undocumented Functions are invaluable for giving us insight into what we need to define to get this off the ground, e.g. the CLIENT_ID
and OBJECT_ATTRIBUTES
structs needed for NtOpenProcess.
Using NimlineWhispers
With our list of required native functions, we can generate our inline assembly. Firstly, clone the NimlineWhispers
repository:
git clone https://github.com/ajpc500/NimlineWhispers.git
Modify functions.txt
to include our five Native API functions:
NtCreateThreadEx
NtOpenProcess
NtAllocateVirtualMemory
NtWriteVirtualMemory
NtClose
Run NimlineWhispers
using the following command:
python3 NimlineWhispers
This will produce us a syscalls.nim
file, complete with the {.passC:"-masm=intel".}
header weāll need to compile this with inline assembly.
To integrate this with our existing code, add it to the same directory and append include syscalls
to the end of our imports, as below.
Itās worth mentioning here that SysWhispers provides us with 64-bit assembly only, and as weāre feeding that into NimlineWhispers, that too is 64-bit only.
Adapting the Code
Now we can set about adding the necessary code to our project to call the inline assembly Native functions weāve included. Once syscalls.nim
has been added to the project, we can call our Native functions as normal, e.g. see the below for NtOpenProcess
.
Working through the project and adding the necessary variables and functions, we end up with code something like the below:
Now we can compile this with the following command:
nim c -d=mingw --app=console --cpu=amd64 SysCallsMessageBoxShellCodeInject.nim
If you give that a try, youāll likely see the following output:
Additional Structs
So, whatās gone wrong here? While weāre not calling the functions provided by Winim, weāre still including it for all of the structs provided in windef.nim
here. Except, we need two additional structs.
Specifically, PS_ATTRIBUTE
and PS_ATTRIBUTE_LIST
. We can take the definitions of these structs from @Jackson_Tās SysWhispers project add these to the top of our syscalls.nim
file.
If we attempt to recompile now:
Success! Running our compiled executable now, we should get our successful MessageBox.
And of course, if we substitute our harmless message box code for something more exciting, we can weaponise our example project!
Conclusions
In this blog, weāve seen how we can adapt a Nim project to use native API functions, included in our project as inline assembly and generated using NimlineWhispers. Itās fair to say the tool is very much a proof-of-concept, in part because Iām still learning Nimš
For me the obvious next step, and one also raised by Cas van Cooten, is to include the needed data types and structs as part of the NimlineWhispers syscalls.nim
output. This would allow you to use that output without also importing the Winim package and wrestling with any missing structs.
Hopefully, this is helpful for those looking to integrate Syscalls into Nim projects, iām very keen to improve NimlineWhispers, so pull requests are very welcome!