NjRat

NjRat 样本分析

1. 样本信息

NjRat (Bladabindi) 是一种 .NET RAT(远程访问木马),允许攻击者控制受感染的机器。

IOC

类别 特征值
MD5 556FE886EDD2DB888EE3A33A103C2364
SHA1 9D58E7B157FE41D86398FF587E10AE2FF3FB3EE9
SHA256 833F86074592648C0A758098E34AB605A2B922D94DBAB7141E2CE87ACEC03C35
C2 44gang44.duckdns.org:2222

2. 样本分析

采用dnSpy加载样本,得知样本原始文件名为:ClassLibrary1.exe

2.1. 初始命令参数判断

样本执行后,会检查是否存在参数命令,如果参数命令为UP:[ProcessID],则设置HKEY_CURRENT_USER\di的值为!,关联指定进程并等待5000毫秒后退出;如果参数命令为..,则休眠5000毫秒:

2.2. 互斥锁检查

打开指定的已命名的互斥体OK.RG("49e91d08e684b1770e0cefa60401157a"),如果存在,关闭当前进程,如果不存在,则新建此互斥锁:

2.3. 准备工作

2.3.1. 检查文件路径

作者定义了一个OK.CompDir函数,将当前执行路径与%AppData%\services64.exe进行比较:

如果当前执行的文件不是%AppData%\services64.exe,且存在%AppData%\services64.exe,则将%AppData%\services64.exe删除,然后将自身复制到%AppData%\services64.exe,采用Process.Start启动目标进程,并采用ProjectData.EndApp()关闭当前进程:

OK.DROK.EXE的内容如下:

2.3.2. 关闭附件管理器检查

在检查完执行路径后,会修改环境变量SEE_MASK_NOZONECHECKS的值为1,已关闭关闭附件管理器检查的弹框提醒:

2.3.3. 修改防火墙规则

接着会执行netsh firewall add allowedprogram命令,添加出栈规则:

其中OK.LO便是当前执行的文件信息类(FileInfo类):

2.3.4. 开机自启项

在注册表HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunHKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run中,添加49e91d08e684b1770e0cefa60401157a项,指向样本的执行路径(%AppData%\services64.exe):

OK.sfOK.RG的值如下:

2.3.5. 文件复制功能

将文件复制到用户的“开始”程序组目录下,并已49e91d08e684b1770e0cefa60401157a.exe命名:

注:由于OK.IsF的定义为:public static bool IsF = Conversions.ToBoolean("False");,故该功能未执行!

2.4. 网络通信

准备工作结束后,将多线程执行OK.RC函数:

2.4.1. 连接C2

该函数用于TCP网络通信连接,其中存在一个OK.connect函数,用于同C244gang44.duckdns.org:2222)连接,并发送关于主机的相关信息(OK.inf()返回内容):

OK.HOK.P的值如下:

2.4.2. 发送本地主机信息

发送的内容是已lv开头,已|'|'|分割的字符串(OK.Y的定义为public static string Y = "|'|'|";)。

获取系统磁盘序号

首先检索注册表HKEY_CURRENT_USER\Software\49e91d08e684b1770e0cefa60401157a\vn的值是否为空,再通过OK.HWD()获取信息,然后将OK.VN与信息拼接起来再通过OK.ENB()编码:

其中OK.VN的定义是:c3BsaXRnYXRldWtyYXluYQ==,通过Base64解码得到的字符串是:splitgateukrayna(分裂门乌克兰)不知道这是不是组织的标签。

OK.HWD()将首先获取当前系统磁盘符,然后通过GetVolumeInformation获取磁盘序号:

获取主机名和用户名

通过调用系统APIEnvironment.MachineNameEnvironment.UserName,获取此本地计算机的 NetBIOS 名称和当前线程相关联的用户的用户名:

获取文件最后一次修改时间

然后将调用OK.FR()获取上次修改当前文件的时间:

获取操作系统名称

My.Computer.Info.OSFullName属性中获取完整的操作系统名称:

获取操作系统 ServicePack 版本

Environment.OSVersion属性中获取ServicePack版本:

判断操作系统版本

通过检索x86 Program Files文件夹(Program Files (x86))是否存在,判断操作系统版本并记录:

判断摄像头是否存在

调用Ok.Cam(),判断是否存在摄像头:

Ok.Cam()中调用了Win32APIcapGetDriverDescriptionA,检索捕获驱动程序的版本说明,成功则返回TRUE,否则返回FALSE

获取当前窗口信息

调用OK.ACT()函数,获取当前窗口的相关信息,并采用OK.ENB进行Base64编码,API调用如下:

  • 调用GetForegroundWindow(),检索前景窗口(用户当前正在使用的窗口)的句柄;
  • 调用GetWindowTextLength(),检索指定窗口标题栏文本的长度;
  • 调用GetWindowText(),将指定窗口标题栏的文本复制到缓冲区中;
  • 调用GetWindowThreadProcessId(),检索创建指定窗口的线程的标识符,以及创建该窗口的进程的标识符;
  • 调用Process.GetProcessById().MainWindowTitle,获取进程主窗口的标题;

创建注册表项

样本会创建HKEY_CURRENT_USER\Software\49e91d08e684b1770e0cefa60401157a

2.4.3. 响应内容处理

样本采用Receive将响应的内容存储到本地缓冲区,其长度为5121字节(private static byte[] b = new byte[5121];

创建新的线程执行OK.Ind以处理提取的响应命令(该部分将放在后面的命令解析中进行说明):

2.5. 键盘监控

新建一个kl类,用于存储键盘记录相关的信息:

然后创建线程执行WRK()函数:

使用File.ReadAllText()读取日志文件,然后进行按键捕获,最终通过File.WriteAllText()写入日志到当前路径下的services64.exe.tmp中:

调用GetAsyncKeyState()检测键盘是否被按下,然后调用this.Fix()捕获按键内容,特殊及组合按键的捕获方式如下:

普通按键的的捕获则是通过来kl.VKCodeToUnicode()实现的:

kl.VKCodeToUnicode()中使用GetKeyboardState()将256个虚拟键的状态复制到指定的缓冲区,然后通过MapVirtualKey()将虚拟键代码转换(映射)为扫描代码或字符值,再使用GetKeyboardLayout()检索活动输入区域的键盘布局,最后调用ToUnicodeEx()将指定的虚拟键代码和键盘状态转换为相应的 Unicode 字符:

调用this.AV(),将获取的信息整合起来,准备写入到日志中去:

2.6. 命令解析

OK.Ind函数中,会对响应内容进行分割,其中分割符OK.Y的值为|'|'|,这为命令的下发组合的反推提供了依据:

2.6.1. proc 命令

当命令组合为:proc|'|'|~

将发送当前活动进程的Id及数量到C2服务器:

采用process.MainModule.FileVersionInfo.FileDescription获取进程描述代码后进行Base64编码,然后构造成包含进程ID、完整进程路径、编码描述代码的字符串:

对于Windows系统进程处理略有不同:

最后将收集到的信息发送到C2服务器:

当命令组合为:proc|'|'|k|'|'|<ProcessID>..

当采用了k子命令时,将循环检索<ProcessID>,杀死进程,每次执行后都将反馈结果至C2服务器:

当命令组合为:proc|'|'|kd|'|'|<ProcessID>..

当采用了kd子命令时,将循环检索<ProcessID>,杀死进程并删除进程文件,每次执行后都将反馈结果至C2服务器(彩蛋:这里Delete后作者的标识依然是ER -.-!):

当命令组合为:proc|'|'|re|'|'|<ProcessID>..

当采用了re子命令时,将循环检索<ProcessID>,重启进程,每次执行后都将反馈结果至C2服务器:

2.6.2. rss 命令

当命令组合为:rss|'|'|

新建一个ProcessStartInfo类并启动,该类的作用是附加一个子进程到当前进程,其中进行了如下配置:

  1. 设置RedirectStandardInput属性为true,应用程序的输入是从StandardInput流中读取的值;
  2. 设置RedirectStandardOutput属性为true,将应用程序的文本输出写入StandardOutput流中的值;
  3. 设置RedirectStandardError属性为true,将应用程序的错误输出写入StandardError流中的值;
  4. 设置子进程名为cmd.exe
  5. 指定OK.RS()处理OutputDataReceived(输出)和ErrorDataReceived(报错)事件;
  6. 指定OK.ex()处理Exited(退出)事件;
  7. 设置UseShellExecute 设置为false ,表示子进程将继承调用进程的标准输入、标准输出和标准错误流;
  8. 设置CreateNoWindow属性为true,表示启动子进程而不创建包含它的新窗口;
  9. 设置启动进程时使用的窗口状态为Hidden(隐藏);
  10. 设置EnableRaisingEvents属性为true,表示如果关联的进程终止时应引发Exited事件。

OK.RS()函数从后期绑定值中检索Data字段,该字段包含子进程的StandardOutput/StandardError流,然后采用Base64编码后发送到C2服务器:

2.6.3. rs 命令

当命令组合为:rs|'|'|<EnBase64[command]>

将通过Base64解码第二个参数,然后通过StandardInput.WriteLine向之前创建的StandardInput流写入数据:

2.6.4. rsc 命令

当命令组合为:rsc|'|'|

将调用Kill(),杀死之前创建的子进程:

2.6.5. kl 命令

当命令组合为:rsc|'|'|

将对键盘记录的日志进行Base64编码后发送到C2服务器:

2.6.6. inf 命令

当命令组合为:inf|'|'|

检索注册表HKEY_CURRENT_USER\Software\49e91d08e684b1770e0cefa60401157a\vn的值是否为空,再通过OK.HWD()获取系统磁盘卷序列号,与OK.VN拼接后通过Base64编码,然后将C2域名、端口、AppData、可执行文件名、当前进程名拼接后发送到C2服务器:

2.6.7. prof 命令

当命令组合为:prof|'|'|~|'|'|<name>|'|'|<value>

将调用OK.STV()添加注册表项:

OK.STV()函数则是编辑注册表HKEY_CURRENT_USER\Software\49e91d08e684b1770e0cefa60401157a,为其添加新的项,并设置对应键值:

当命令组合为:prof|'|'|!|'|'|<name>|'|'|<value>

将调用OK.STV()添加注册表项,然后调用OK.GTV()获取!的键值,并将键值发送到C2服务器。

OK.GTV()函数内容如下:

当命令组合为:prof|'|'|@|'|'|<name>

将调用OK.DLV()删除对应名称的键值:

OK.DLV()函数内容如下:

2.6.8. rn 命令

当命令组合为:rn|'|'|<Extension_Name>|'|'|<URL>/<Base64[Gzip_Bit]>

首先将判断第三个参数是否是http开头的连接,如果不是,则调用FromBase64String解码第三个参数,再调用OK.ZIP()对解码内容进行处理:

OK.ZIP()函数会调用GZipStream压缩和解压缩数据内容,不过当前的执行流程是解压缩:

如果第三个参数是http开头的链接,则会使用WebClient.DownloadData()下载文件:

上述两个步骤二选一执行后,将对在%Temp%目录下,将所有字节写入随机命名的.<Extension_Name>文件,新建进程执行该文件并向C2服务器发送文件名:

2.6.9. inv 命令

当命令组合为:inv|'|'|<Registry_Name>|'|'|<String1>|'|'|<String2>

首先检索HKEY_CURRENT_USER\Software\49e91d08e684b1770e0cefa60401157a<Registry_Name>的键值,如果其不为空,则将该键值返回到C2服务器,并继续执行;

如果<Registry_Name>的键值为空,且<String2>长度为1,则返回C2服务器错误信息并结束此轮命令解析;如果<String2>长度不为1,则调用OK.ZIP()<String2>进行解压缩,并设置<Registry_Name>键值为<String2>解压缩后的内容,成功则发送信息至C2服务器;

但从整个流程来看,<Registry_Name>中存放的是一个插件。样本首先检索<Registry_Name>是否已包含插件,如果没有,则判断<String2>是否是插件内容,如果是则写入<Registry_Name>,然后通过OK.Plugin()进行插件调用:

OK.Plugin()函数首先调用Assembly.Load()加载插件程序集,然后通过Assembly.GetModules()枚举程序集的所有模块,再寻找.A结尾的类,找到后使用Assembly.CreateInstance()创建它的实例:

然后使用NewLateBinding.LateSet()方法向后期绑定字段写入:hC2域名)、pC2端口)、osk<String1>),然后调用NewLateBinding.LateCall()执行start函数,最后写入offtrue

2.6.10. ret 命令

当命令组合为:ret|'|'|<Registry_Name>|'|'|<String>

从函数结构可以看出功能同inv命令类似,首先查找本地注册表<Registry_Name>的键值,不存在则将<String>写入并进行插件加载:

然后调用NewLateBinding.LateGet()获取GT的值,编码后发送给C2服务器:

2.6.11. CAP 命令

当命令组合为:CAP|'|'|<Width>|'|'|<Height>

将通过Bitmap.GetThumbnailImage()方法,截取<Width>宽,<Height>高的屏幕,然后使用OK.getMD5Hash()将截图加密,最后通过OK.Sendb()发送到C2服务器:

OK.getMD5Hash()函数内容如下:

OK.Sendb()函数内容如下:

2.6.12. P 命令

当命令组合为:P

将向C2服务器发送P字符:

2.6.13. un 命令

un命令存在三个子项:~!@

当命令组合为:un|'|'|~

调用OK.UNS()会首先执行OK.pr(0)将当前进程设置为非关键进程,然后删除被修改的注册表项及防火墙规则,删除启动文件夹下的样本文件,调用cmd隐藏窗口删除自身文件,最后结束当前进程:

OK.pr(0)调用了未公开的API函数NtSetInformationProcess()将当前进程设置为非关键进程,防止进程结束时触发BSOD

当命令组合为:un|'|'|!

调用OK.pr(0)将当前进程设置为非关键进程,然后退出当前进程。

当命令组合为:un|'|'|@

调用OK.pr(0)将当前进程设置为非关键进程,然后新进程启动自身并退出当前进程,可以理解为安全的重启。

2.6.14. up 命令

当命令组合为:up|'|'|<URL>/<Base64[Gzip_Bit]>

前半段与rn命令下载功能类似,不过up命令指向的文件只能是exe,首先会根据第二个参数的内容获取目标文件的二进制,设置注册表di为空,将二进制文件保存到本地%temp%目录下,然后将随机生成的文件名发送到C2服务器,然后调用Process.Start()启动该文件,参数命令为UP:<当前进程ID>,然后进入一个循环,当di被设置为!时,卸载自身:

2.6.15. RG 命令

当命令组合为:RG|'|'|~|'|'|<Registry_Item>

首先会调用OK.GetKey()获取<Registry_Item>项,然后通过GetSubKeyNames()GetValueNames()枚举该项中的所有子项和对应键值,最后发送到C2服务器:

OK.GetKey()函数构造如下:

当命令组合为:RG|'|'|!|'|'|<Registry_Item>|'|'|<Registry_Name>|'|'|<Registry_Value>|'|'|<Registry_Kind>

设置<Registry_Name>项的键值为<Registry_Value>,类型为<Registry_Kind>

当命令组合为:RG|'|'|@|'|'|<Registry_Item>|'|'|<Registry_Name>

将删除<Registry_Name>的项:

当命令组合为:RG|'|'|#|'|'|<Registry_Item>|'|'|<Registry_Name>

将创建<Registry_Name>的项:

当命令组合为:RG|'|'|$|'|'|<Registry_Item>|'|'|<Registry_Name>

将递归删除<Registry_Name>项和它的其它子项:

3. 参考资料

https://cybergeeks.tech/just-another-analysis-of-the-njrat-malware-a-step-by-step-approach/
https://www.secpulse.com/archives/73878.html
https://app.any.run/tasks/78913e0b-1419-4571-8611-ac3372ffd578/#
https://www.virustotal.com/gui/file/833f86074592648c0a758098e34ab605a2b922d94dbab7141e2ce87acec03c35