安全矩阵

 找回密码
 立即注册
搜索
查看: 4528|回复: 0

一个给新手进阶的IAT加密壳

[复制链接]

1

主题

1

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2020-6-4 22:15:23 | 显示全部楼层 |阅读模式
本帖最后由 mohe 于 2020-6-4 22:15 编辑

本文来自freebuff社区
原文链接:https://www.freebuf.com/articles/system/235918.html

前言

上篇文章(一份专供初学者食用的AES加密壳)中我们分析了一个简单的AES壳的加壳与解壳的过程,在加壳的过程中,直接将源程序加密后,追加到壳的最后一个节当中,这种壳可被秒脱。所以这节在之前的基础上进阶,加大解壳的难度,进行IAT表的加密。

这篇文章中介绍了IAT加壳与解壳的全过程,并用Ollydbg进行逆向分析,说明这个壳的鸡肋的之处,最后给出了核心源代码。

必备基础

必须很熟悉PE结构,特别是导入表的双桥结构。

IAT(Import Address Table),导入地址表。

节就是区段,区段就是节,但节和节表不是同一个概念。

RVA 相对(基址ImageBase)虚拟地址。

VA 虚拟地址。

实验环境:WIN10 、VS2017、DIE(Detect it Easy)、Ollydbg

PS:这里还不需要了解混淆、花指令、反调试等技术,掌握了上面提到的基础,新手就可以放心阅读了。

IAT加解壳的主要步骤一、加壳步骤

1、打开源程序

2、加载 Packer.dll

3、AES加密所有区段

4、清除目录表

5、添加新区段

6、修复重定位表

7、保存源程序的OEP

8、将 packer.dll 的 .text 段 拷贝到 .NewSec 段里

9、修改OEP为 Packer.dll函数入口点在.NewSec 段中的RVA

10、去掉随机基址

11、保存被加壳的程序

加壳完成。

示意图如下(从左到右是大致的加壳过程):图片仅供参考。

二、解壳步骤

解壳的步骤都封装在Packer.dll中,我们来解剖一下它。

1、动态获取函数的API地址

2、AES解密所有区段

3、恢复目录表

4、修复IAT表

5、密码验证对话框弹出

6、加密IAT表

7、跳转到原始OEP

解壳完成。

示意图如下(从左到右是大致的解壳过程):图片仅供参考。

IAT加解壳之3问3知一、加壳之3问3知

1、为什么要加密所有区段 & 清楚目录项呢?

那当然是为了防止别人分析PE文件,达到保护PE文件的目的。

2、为什么要添加新节?添加新节后,为何要修复重定位表?

如果PE文件不增加新节,也可以在其他空白区添加代码。但是这样会有两个不妙的情况:

(1)空白区根本放不下你的代码,

(2)即使空白区能放得下你的代码,可能空白区的节属性不能执行,修改属性,可能会导致程序执行出错。

明白了新增节的作用,也就知道为啥我们要把Packer.dll的 .text段拷贝到 .NewSec段了:

(1) .NewSec段是我们自己添加的,大小自己定。

(2)放在新节里,目的是让Packer.dll被执行起来,直接加载到主进程空间是不会运行的,需要获取主进程的控制权。在 .NewSec段 我们可以让节”RWE(可读、可写、可执行)”,这样才能让shellcode执行起来。

3、为什么要去掉随机基址?

观察这段嵌入汇编:

  1. //跳转到原始OEP
  2. __asm
  3. {
  4. mov eax, g_conf.srcOep; //跳转到源程序的OEP
  5. add eax,0x400000 //srcOep将RVA-->VA ,加上基址0x400000,所以选择的源程序需要选择0x400000,否则会水土不服
  6. jmp eax
  7. }
复制代码

在Packer.dll的入口函数Start()中,我采用固定基址0×400000的方法来计算src OEP的RV,也就是这段shellcode利用成功的前提要确定一个明确的跳转地址。无论是JMP ESP 等通用跳板指令还是Ret2Libc 使用的各指令,我们都要先确定这条指令的入口点。所谓惹不起躲得起,微软的ASLR(Address Space Layout Randomization)技术就是通过加载程序的时候不再使用固定的基址加载,从而干扰shellcode 定位的一种保护机制。

——引自《0day漏洞.软件漏洞分析技术(第二版)》

二、解壳之3问3知

1、为什么要动态获取函数的API地址?

通常我们使用windwos API都是直接获取IAT表中的函数地址,而这里的情况比较特殊:后面需要对IAT表进行加密,加了密后就不能从IAT表里获取函数地址。

附上一张IAT的表回顾一下:

那么有啥办法可以获取到函数地址呢?

常用的办法:1、LoadLibrary(),然后 GetMoudleName() 2、动态加载

我这里用的是第2种办法,为啥呢?

对于方法1: 加载进来后,调用的方法是 [0x12345678] 的形式,这是全局变量的调用方式,当需要重定位的时候,访问这个地址会出错。

对于方法2:动态的方式获取API的地址,兼容性好。获取的方式有3种:(1)利用PEB结构来查找 (2)利用堆栈暴力搜索 (3)使用SEH的链表来查找。

这里利用PEB结构来查找API的方式,接下来是动态获取API的代码,代码中有详细的解释:

  1. _asm
  2. {
  3. pushad;
  4. ; //获取kernel32.dll的加载基址;
  5. ;// 1. 找到PEB的首地址;
  6. mov eax, fs:[0x30]; //fs偏移0x30处为peb的首地址, fs为段寄存器
  7. ;// 2. 得到PEB_LDR_DATA的值;
  8. mov eax, [eax + 0ch]; //在PEB偏移的0x0c处是指向PEB_LDR_DATA结构的指针
  9. mov eax, [eax + 0ch]; //eax = > PEB.Ldr的值;
  10. ; //3. 得到_PEB_LDR_DATA.InLoadOrderMoudleList.Flink的值, 实际得到的就是主模块节点的首地址;
  11. mov eax, [eax]; //eax = > _PEB_LDR_DATA.InLoadOrderMoudleList.Flink(NTDLL);
  12. ; //4. 再获取下一个;
  13. mov eax, [eax]; _LDR_DATA_TABLE_ENTRY.InLoadOrderMoudleList.Flink(kernel32), ;
  14. mov eax, [eax + 018h]; _LDR_DATA_TABLE_ENTRY.DllBase;
  15. mov hKernel32, eax;;
  16. ; //遍历导出表;
  17. ;// 1.依次获取:dos头、 nt头、 扩展头、 数据目录表;
  18. mov ebx, [eax + 03ch]; //偏移到NT头;
  19. add ebx, eax; // NT头的首地址;
  20. add ebx, 078h; //引出表偏移
  21. ; //2. 得到导出表的RVA;
  22. mov ebx, [ebx];
  23. add ebx, eax; //ebx = 导出表首地址(VA);
  24. ; //3. 遍历名称表找到GetProcAddress;
  25. ; //3.1 找到名称表的首地址;
  26. lea ecx, [ebx + 020h]; //ebx=函数名地址,AddressOfName
  27. mov ecx, [ecx]; // ecx =名称表的首地址(RVA);
  28. add ecx, eax; // ecx =名称表的首地址(VA);
  29. xor edx, edx; // 作为index来使用.
  30. ; //3.2 遍历名称表;
  31. _WHILE:;
  32. mov esi, [ecx + edx * 4]; //esi= 名称的RVA;
  33. lea esi, [esi + eax];// esi =名称首地址;
  34. cmp dword ptr[esi], 050746547h; 47657450 726F6341 64647265 7373; //dword:  'PteG' 、'rocA' 、'ddre' 、'ss' =>GetProcAddress,如果是GetProcAddress,表示在AddressOfName中找到了
  35. jne _LOOP;
  36. cmp dword ptr[esi + 4], 041636f72h;
  37. jne _LOOP;
  38. cmp dword ptr[esi + 8], 065726464h;
  39. jne _LOOP;
  40. cmp word  ptr[esi + 0ch], 07373h;
  41. jne _LOOP;
  42. ; //找到GetProcAddress后;
  43. mov edi, [ebx + 024h]; // edi = 函数序号(RVA);
  44. add edi, eax;
  45. mov di, [edi + edx * 2]; //ecx=计算出序号值,序号表是2字节的元素, 因此是 * 2;
  46. and edi, 0FFFFh; //edi=GetProcAddress在地址表中的下标;
  47. ; //得到地址表首地址;
  48. mov edx, [ebx + 01ch]; //edx = 地址表的RVA;
  49. add edx, eax; //edx = 地址表的VA;
  50. mov edi, [edx + edi * 4]; //edi = GetProcAddress的RVA;
  51. add edi, eax; ; //edx =  GetProcAddress的VA;
  52. mov MyGetProcAddress, edi;
  53. jmp _ENDWHILE;
  54. _LOOP:;
  55. inc edx; // ++index;
  56. jmp _WHILE; //跳转
  57. _ENDWHILE:;
  58. popad; //平衡堆栈
  59. }
复制代码

2、为什么要选择加密IAT表?

个人从攻防的角度来思考,原因有2:

(1)对IAT表的函数地址加密后,API就不能一下子看得出来了(比如说Ollydbg就解析不出来dll名),增大逆向分析PE的难度。

(2)AT表PE程序动态执行依赖的dll,加了密之后,恶意代码也就不能用我们的IAT表来使坏了,这也是对程序的一种保护。

3、如何对IAT进行加解密?

IAT加解密原理如下:

(1)遍历导入表获取IAT表里的每个函数地址

(2)取出IAT的函数地址,该函数地址异或 0×12345678,得到加密后的地址

(3)申请一段内存,存放解密后的地址,然后调用该地址的代码。

(4)把申请的内存地址放入IAT表对应表项中。

如此这般,IAT就被加密了。

我们再来仔细推敲一下步骤(3),NB的你可能早就发现:解密后的地址放在内存中,不做任何保护,恢复IAT表依然无障碍!!!

别着急,我们来看看这里的解密怎么处理?

(1)写一段具有迷惑性的代码,干扰逆向分析者对解密后地址的定位,也就是传说中的混淆+花指令操作。

(2)动态解密:在目标程序运行起来之后,动态地对代码段进行解密。

先运行一段代码、解密一部分的代码,然后再运行解密后的代码,循环直到解密完成。这种方式给逆向带来的挑战是:盯着运行着的代码及附近代码,同时又能兼顾隔得很远的加密状态的代码。

本人新手还是太菜了,现在只能理解混淆+花指令操作,代码如下:

  1. _asm
  2. {
  3. push eax;
  4. mov eax, dwFunAddr; //未加密的函数地址
  5. xor eax, 0x12345678; //eax = dwFunAddr 异或 0x15151515
  6. mov dwEncryptFunAddr, eax; //dwEncryptFunAddr=eax
  7. pop eax;
  8. }
  9. // 3.构造一段花指令shellcode,用来解密函数地址
  10. BYTE OpCode[] = {
  11. 0xE8, 0x01, 0x00, 0x00, //call
  12. 0x00, 0xE9, 0x58, 0xEB, //jmp
  13. 0x01, 0xE8, 0xB8, 0x85, //MOV EAX,
  14. 0xEE, 0xCB, 0x60, 0xEB, //JMP
  15. 0x01, 0x15, 0x35, 0x12,//ADC EAX,
  16. 0x34, 0x56, 0x78, 0xEB, //ADC EAX,
  17. 0x01, 0xFF, 0x50, 0xEB, //JMP SHORT 1F
  18. 0x02, 0xFF, 0x15, 0xC3  //CALL
  19.   };
  20. //把函数地址放到解密的OpCode里
  21. OpCode[11] = dwEncryptFunAddr;    // 0x85  假如:dwEncryptFunAddr = 0x12345678
  22. OpCode[12] = dwEncryptFunAddr >> 0x08;// 0xEE  十六进制右移8位刚好截掉低位的2位  0x00123456
  23. OpCode[13] = dwEncryptFunAddr >> 0x10;// 0xCB  十六进制右移16位刚好截掉低位的4位 0x00001234
  24. OpCode[14] = dwEncryptFunAddr >> 0x18;// 0x60  十六进制右移16位刚好截掉低位的4位 0x00001234
复制代码

Ollydbg逆向分析IAT加密壳

我们先用工具(DiE、importREC)来尝试一下侦壳、脱壳,然后再用Ollydbg分析一下。

未知壳信息

导入表修复失败

从壳的入口点F7跳进去,进入壳的Start()入口函数

真正的入口点,由于这个壳在入口处没加反调试、花指令保护,所以原始程序的OEP一眼就看得到

F4 跳到密码验证弹框处

输入密码123 验证,断下;再F7进入IAT加密

F7跳到真正的AT函数地址加密的地方,看见一堆花指令

解密密钥藏在花指令的这里

这是shellcode解密函数地址的地方

观察加密前和加密后eax的值:

看完了分析,是不是觉得没加保护的壳很鸡肋呢?只要nop填充密码验证弹框和IAT加密,轻而易举就绕过了壳到达真正的程序入口。

在这个基础上,我们该如何去隐藏我们这个入口呢?带着这个问题,和我一样的新手小菜就可以进一步进阶,做出更安全的壳了。

最后我还想说,其实加壳与解壳拼的就是谁对PE更了解,所以掌握基础还是至关重要的。

核心代码
一、加壳
  1. #include <windows.h>
  2. #include <stdio.h>
  3. #include "PeFileOperator.h"
  4. #include <stdio.h>
  5. int main()
  6. {
  7. PeFileOperator myPE;//PE文件操作类对象
  8. char path[MAX_PATH] = "src.exe";
  9. //1、打开被加壳程序
  10. int nTargetSize = 0;
  11. char* pTargetBuff = myPE.GetFileData(path, &nTargetSize);
  12. //2、加载Packer.dll
  13. StubInfo packer = { 0 };
  14. myPE.LoadStub(&packer); ///这里对IAT表已经加了密,具体查看packer的 Start() 函数
  15. //3、加密所有区段
  16. myPE.Encrypt(pTargetBuff, packer);
  17. //4、清除目录表
  18. myPE.ClearDataDir(pTargetBuff, packer);
  19. //添加新节
  20. char cNewSectionName[] = {".NewSec"};//新节表名
  21. myPE.AddSection(pTargetBuff, nTargetSize, cNewSectionName,
  22. myPE.GetSection(packer.dllbase,".text")->Misc.VirtualSize);
  23. //修复重定位
  24. myPE.FixStubRelocation((DWORD)packer.dllbase,
  25. myPE.GetSection(packer.dllbase,".text")->VirtualAddress,
  26. myPE.GetOptionHeader(pTargetBuff)->ImageBase,
  27. myPE.GetSection(pTargetBuff, cNewSectionName)->VirtualAddress);
  28. //保存目标文件的OEP到packer的全局变量中
  29. //如果不知道为什么移步到这里看一下手动方式注入shellcode,修改OEP  https://www.cnblogs.com/Erma/p/12593860.html
  30. packer.pStubConf->srcOep = myPE.GetOptionHeader(pTargetBuff)->AddressOfEntryPoint;
  31. //将packer.dll的代码段复制到新加的NewSec段中(注意:packer.dll也是个PE文件,主进程加载packer.dll时,作为一个模块附加在主程序的4GB地址空间)
  32. memcpy(myPE.GetSection(pTargetBuff, cNewSectionName)->PointerToRawData+pTargetBuff,
  33. myPE.GetSection(packer.dllbase,".text")->VirtualAddress+packer.dllbase,
  34. myPE.GetSection(packer.dllbase,".text")->Misc.VirtualSize);
  35. //修改OEP ( OEP = Start(RV)-dll加载基址)-段首RVA+新区段的段首RVA ) ,注意:packer.dll 加载进来是不会自己执行的,一定要获得控制权才可以
  36. //Start(RV)-dll加载基址)-段首RVA: Start()在.text内的偏移
  37. /////因为获取到的 start 函数的地址是在dll中的地址,现在这个区段被拷贝到了
  38.     /////被加壳程序中,所以需要重新计算 start 的 RVA 并设置为 OEP
  39. myPE.GetOptionHeader(pTargetBuff)->AddressOfEntryPoint =
  40. packer.pfnStart-(DWORD)packer.dllbase
  41. -myPE.GetSection(packer.dllbase,".text")->VirtualAddress
  42. +myPE.GetSection(pTargetBuff, cNewSectionName)->VirtualAddress;
  43. //去掉随机基址:利于shellcode的定位 具体解释见《0day安全:软件漏洞分析技术(第二版)》
  44. myPE.GetOptionHeader(pTargetBuff)->DllCharacteristics &= (~0x40);
  45. //保存被加壳的程序
  46. myPE.SavePEFile(pTargetBuff,nTargetSize,"AES.Packed.exe");
  47. return 0;
  48. }
复制代码


二、解壳
  1. //************************************************************
  2. // 函数名称: Start
  3. // 函数说明: dll的OEP
  4. // 参 数: void
  5. // 返 回 值: void
  6. //************************************************************
  7. extern "C" __declspec(dllexport) __declspec(naked)
  8. void Start()
  9. {
  10. //获取函数的API地址
  11. GetApis();
  12. //解密所有区段
  13. Decrypt();
  14. //恢复数据目录表
  15. RecoverDataDir();
  16. //修复IAT
  17. FixImportTable();
  18. //密码验证对话框弹出
  19. AlertPasswdBox();
  20. //加密IAT
  21. EncryptIAT();
  22. //跳转到原始OEP
  23. __asm
  24. {
  25. mov eax, g_conf.srcOep; //跳转到源程序的OEP
  26. add eax,0x400000 //srcOep将RVA-->VA ,加上基址0x400000,所以选择的源程序需要选择0x400000,否则会水土不服
  27. jmp eax
  28. }
  29. }
  30. //************************************************************
  31. // 函数名称: Decrypt
  32. // 函数说明: 解密所有段
  33. // 参 数: void
  34. // 返 回 值: void
  35. //************************************************************
  36. void  Decrypt()
  37. {
  38. //获取当前程序的基址
  39. DWORD dwBase = (DWORD)pfnGetMoudleHandleA(NULL);
  40. AES aes(g_conf.key);
  41. //循环解密所有区段
  42. DWORD old = 0;
  43. for (int i = 0; i < g_conf.index-1; i++)
  44. {
  45. //拿到所有区段的首地址和大小
  46. unsigned char* pSection = (unsigned char*)g_conf.data[i][0]+ dwBase;
  47. DWORD dwSectionSize = g_conf.data[i][1];
  48. //修改区段属性
  49. MyVirtualProtect(pSection, dwSectionSize, PAGE_EXECUTE_READWRITE, &old);
  50. //解密代码段
  51. aes.InvCipher(pSection, dwSectionSize);
  52. //把属性修改回去
  53. MyVirtualProtect(pSection, dwSectionSize, old, &old);
  54. }
  55. }
  56. //************************************************************
  57. // 函数名称: GetApis
  58. // 函数说明: 获取API函数地址
  59. // 参 数: void
  60. // 返 回 值: void
  61. //************************************************************
  62. void GetApis()
  63. {
  64. HMODULE hKernel32;
  65. _asm
  66. {
  67. pushad;
  68. ; //获取kernel32.dll的加载基址;
  69. ;// 1. 找到PEB的首地址;
  70. mov eax, fs:[0x30]; //fs偏移0x30处为peb的首地址, fs为段寄存器
  71. ;// 2. 得到PEB_LDR_DATA的值;
  72. mov eax, [eax + 0ch]; //在PEB偏移的0x0c处是指向PEB_LDR_DATA结构的指针
  73. mov eax, [eax + 0ch]; //eax = > PEB.Ldr的值;
  74. ; //3. 得到_PEB_LDR_DATA.InLoadOrderMoudleList.Flink的值, 实际得到的就是主模块节点的首地址;
  75. mov eax, [eax]; //eax = > _PEB_LDR_DATA.InLoadOrderMoudleList.Flink(NTDLL);
  76. ; //4. 再获取下一个;
  77. mov eax, [eax]; _LDR_DATA_TABLE_ENTRY.InLoadOrderMoudleList.Flink(kernel32), ;
  78. mov eax, [eax + 018h]; _LDR_DATA_TABLE_ENTRY.DllBase;
  79. mov hKernel32, eax;;
  80. ; //遍历导出表;
  81. ;// 1.依次获取:dos头、 nt头、 扩展头、 数据目录表;
  82. mov ebx, [eax + 03ch]; //偏移到NT头;
  83. add ebx, eax; // NT头的首地址;
  84. add ebx, 078h; //引出表偏移
  85. ; //2. 得到导出表的RVA;
  86. mov ebx, [ebx];
  87. add ebx, eax; //ebx = 导出表首地址(VA);
  88. ; //3. 遍历名称表找到GetProcAddress;
  89. ; //3.1 找到名称表的首地址;
  90. lea ecx, [ebx + 020h]; //ebx=函数名地址,AddressOfName
  91. mov ecx, [ecx]; // ecx =名称表的首地址(RVA);
  92. add ecx, eax; // ecx =名称表的首地址(VA);
  93. xor edx, edx; // 作为index来使用.
  94. ; //3.2 遍历名称表;
  95. _WHILE:;
  96. mov esi, [ecx + edx * 4]; //esi= 名称的RVA;
  97. lea esi, [esi + eax];// esi =名称首地址;
  98. cmp dword ptr[esi], 050746547h; 47657450 726F6341 64647265 7373; //dword:  'PteG' 、'rocA' 、'ddre' 、'ss' =>GetProcAddress,如果是GetProcAddress,表示在AddressOfName中找到了
  99. jne _LOOP;
  100. cmp dword ptr[esi + 4], 041636f72h;
  101. jne _LOOP;
  102. cmp dword ptr[esi + 8], 065726464h;
  103. jne _LOOP;
  104. cmp word  ptr[esi + 0ch], 07373h;
  105. jne _LOOP;
  106. ; //找到GetProcAddress后;
  107. mov edi, [ebx + 024h]; // edi = 函数序号(RVA);
  108. add edi, eax;
  109. mov di, [edi + edx * 2]; //ecx=计算出序号值,序号表是2字节的元素, 因此是 * 2;
  110. and edi, 0FFFFh; //edi=GetProcAddress在地址表中的下标;
  111. ; //得到地址表首地址;
  112. mov edx, [ebx + 01ch]; //edx = 地址表的RVA;
  113. add edx, eax; //edx = 地址表的VA;
  114. mov edi, [edx + edi * 4]; //edi = GetProcAddress的RVA;
  115. add edi, eax; ; //edx =  GetProcAddress的VA;
  116. mov MyGetProcAddress, edi;
  117. jmp _ENDWHILE;
  118. _LOOP:;
  119. inc edx; // ++index;
  120. jmp _WHILE; //跳转
  121. _ENDWHILE:;
  122. popad; //平衡堆栈
  123. }
  124. //给函数指针变量赋值
  125. //Kernel32
  126. MyLoadLibraryA = (FnLoadLibraryA)MyGetProcAddress(hKernel32, "LoadLibraryA");
  127. MyVirtualProtect = (FnVirtualProtect)MyGetProcAddress(hKernel32, "VirtualProtect");
  128. pfnGetMoudleHandleA = (fnGetMoudleHandleA)MyGetProcAddress(hKernel32, "GetModuleHandleA");
  129. pfnExitProcess = (fnExitProcess)MyGetProcAddress(hKernel32, "ExitProcess");
  130. pfnVirtualAlloc = (FnVirtualAlloc)MyGetProcAddress(hKernel32, "VirtualAlloc");
  131. pfnRtlMoveMemory = (FnRtlMoveMemory)MyGetProcAddress(hKernel32, "RtlMoveMemory");
  132. //Use***
  133. HMODULE hUse*** = (HMODULE)MyLoadLibraryA((char*)"use***.dll");
  134. pfnRegisterClassEx = (fnRegisterClassEx)MyGetProcAddress(hUse***, "RegisterClassExW");
  135. pfnCreateWindowEx = (fnCreateWindowEx)MyGetProcAddress(hUse***, "CreateWindowExW");
  136. pfnUpdateWindow = (fnUpdateWindow)MyGetProcAddress(hUse***, "UpdateWindow");
  137. pfnShowWindow=(fnShowWindow)MyGetProcAddress(hUse***, "ShowWindow");
  138. pfnGetMessage=(fnGetMessage)MyGetProcAddress(hUse***, "GetMessageW");
  139. pfnTranslateMessage=(fnTranslateMessage)MyGetProcAddress(hUse***, "TranslateMessage");
  140. pfnDispatchMessageW =(fnDispatchMessageW)MyGetProcAddress(hUse***, "DispatchMessageW");
  141. pfnGetWindowTextW =(fnGetWindowTextW)MyGetProcAddress(hUse***, "GetWindowTextW");
  142. pfnSendMessageW =(fnSendMessageW)MyGetProcAddress(hUse***, "SendMessageW");
  143. pfnDefWindowProcW =(fnDefWindowProcW)MyGetProcAddress(hUse***, "DefWindowProcW");
  144. pfnPostQuitMessage =(fnPostQuitMessage)MyGetProcAddress(hUse***, "PostQuitMessage");
  145. pfnFindWindowW =(fnFindWindowW)MyGetProcAddress(hUse***, "FindWindowW");
  146. pfnMessageBoxW =(fnMessageBoxW)MyGetProcAddress(hUse***, "MessageBoxW");
  147. pfnBeginPaint =(fnBeginPaint)MyGetProcAddress(hUse***, "BeginPaint");
  148. pfnEndPaint =(fnEndPaint)MyGetProcAddress(hUse***, "EndPaint");
  149. }
  150. //************************************************************
  151. // 函数名称: AlertPasswdBox
  152. // 函数说明: 弹框:要求密码验证
  153. // 参 数: void
  154. // 返 回 值: void
  155. //************************************************************
  156. void AlertPasswdBox()
  157. {
  158. //注册窗口类
  159. g_hInstance = (HINSTANCE)pfnGetMoudleHandleA(NULL);
  160. WNDCLASSEX ws;
  161. ws.cbSize = sizeof(WNDCLASSEX);
  162. ws.hInstance = g_hInstance;
  163. ws.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  164. ws.hIcon = NULL;
  165. ws.hIconSm = NULL;
  166. ws.hCursor = NULL;
  167. ws.style = CS_VREDRAW | CS_HREDRAW;
  168. ws.lpszMenuName = NULL;
  169. ws.lpfnWndProc = (WNDPROC)WndPrco;//回调函数
  170. ws.lpszClassName = TEXT("NewSec");
  171. pfnRegisterClassEx(&ws);
  172. //创建窗口
  173. HWND hWnd = pfnCreateWindowEx(0,TEXT("NewSec"),TEXT("密码验证对话框"),
  174. WS_OVERLAPPED|WS_VISIBLE,
  175. 100,100,400,200,NULL,NULL,g_hInstance,NULL);
  176. //更新窗口
  177. //pfnUpdateWindow(hWnd);
  178. pfnShowWindow(hWnd, SW_SHOW);
  179. //消息处理
  180. MSG msg = { 0 };
  181. while (pfnGetMessage(&msg,NULL,NULL,NULL))
  182. {
  183. pfnTranslateMessage(&msg);
  184. pfnDispatchMessageW(&msg);
  185. }
  186. }
  187. //设置属性可写
  188. void SetFileHeaderProtect(bool nWrite)
  189. {
  190. //获取当前程序的加载基址
  191. DWORD ImageBase = (DWORD)pfnGetMoudleHandleA(NULL);
  192. DWORD nOldProtect = 0;
  193. if (nWrite)
  194. MyVirtualProtect((LPVOID)ImageBase, 0x400, PAGE_EXECUTE_READWRITE, &nOldProtect);
  195. else
  196. MyVirtualProtect((LPVOID)ImageBase, 0x400, nOldProtect, &nOldProtect);
  197. }
  198. //************************************************************
  199. // 函数名称: FixImportTable
  200. // 函数说明: 修复IAT
  201. // 参 数: void
  202. // 返 回 值: void
  203. //************************************************************
  204. void FixImportTable()
  205. {
  206. //设置文件属性为可写
  207. SetFileHeaderProtect(true);
  208. //获取当前程序的加载基址
  209. DWORD ImageBase = (DWORD)pfnGetMoudleHandleA(NULL);
  210. IMAGE_THUNK_DATA* pInt = NULL;
  211. IMAGE_THUNK_DATA* pIat = NULL;
  212. SIZE_T impAddress = 0;
  213. HMODULEhImpModule = 0;
  214. DWORD dwOldProtect = 0;
  215. IMAGE_IMPORT_BY_NAME* pImpName = 0;
  216. if (!GetOptionHeader((char*)ImageBase)->DataDirectory[1].VirtualAddress)return;
  217. //导入表=导入表偏移+加载基址
  218. IMAGE_IMPORT_DESCRIPTOR* pImp = (IMAGE_IMPORT_DESCRIPTOR*)(GetOptionHeader((char*)ImageBase)->DataDirectory[1].VirtualAddress + ImageBase);
  219. while (pImp->Name)
  220. {
  221. //IAT=偏移+基址
  222. pIat = (IMAGE_THUNK_DATA*)(pImp->FirstThunk + ImageBase);
  223. if (pImp->OriginalFirstThunk == 0) // 如果不存在INT则使用IAT
  224. {
  225. pInt = pIat;
  226. }
  227. else
  228. {
  229. pInt = (IMAGE_THUNK_DATA*)(pImp->OriginalFirstThunk + ImageBase);
  230. }
  231. // 加载dll
  232. hImpModule = (HMODULE)MyLoadLibraryA((char*)(pImp->Name + ImageBase));
  233. //导入函数地址
  234. while (pInt->u1.Function)
  235. {
  236. //判断导入的方式、序号还是名称
  237. if (!IMAGE_SNAP_BY_ORDINAL(pInt->u1.Ordinal))
  238. {
  239. pImpName = (IMAGE_IMPORT_BY_NAME*)(pInt->u1.Function + ImageBase);
  240. impAddress = (SIZE_T)MyGetProcAddress(hImpModule, (char*)pImpName->Name);
  241. }
  242. else
  243. {
  244. impAddress = (SIZE_T)MyGetProcAddress(hImpModule, (char*)(pInt->u1.Function & 0xFFFF));
  245. }
  246. MyVirtualProtect(&pIat->u1.Function, sizeof(pIat->u1.Function), PAGE_READWRITE, &dwOldProtect);
  247. pIat->u1.Function = impAddress;
  248. MyVirtualProtect(&pIat->u1.Function, sizeof(pIat->u1.Function), dwOldProtect, &dwOldProtect);
  249. ++pInt;
  250. ++pIat;
  251. }
  252. ++pImp;
  253. }
  254. SetFileHeaderProtect(false);
  255. }
  256. DWORD EncryptFun(DWORD dwFunAddr)
  257. {
  258. // 1.申请内存空间
  259. DWORD dwNewMem = (DWORD)pfnVirtualAlloc(NULL, 0x20, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  260. // 2.加密函数地址
  261. DWORD dwEncryptFunAddr = 0;
  262. _asm
  263. {
  264. push eax;
  265. mov eax, dwFunAddr; //未加密的函数地址
  266. xor eax, 0x12345678; //eax = dwFunAddr 异或 0x15151515
  267. mov dwEncryptFunAddr, eax; //dwEncryptFunAddr=eax
  268. pop eax;
  269. }
  270. // 3.构造一段花指令shellcode,用来解密函数地址
  271. BYTE OpCode[] = {
  272. 0xE8, 0x01, 0x00, 0x00, //call 0x6
  273. 0x00, 0xE9, 0x58, 0xEB, //jmp  0xE801EB62
  274. 0x01, 0xE8, 0xB8, 0x85, //MOV EAX,60CBEE85
  275. 0xEE, 0xCB, 0x60, 0xEB, //JMP SHORT 12
  276. 0x01, 0x15, 0x35, 0x12,//ADC EAX,
  277. 0x34, 0x56, 0x78, 0xEB, //ADC EAX,50FF01EB
  278. 0x01, 0xFF, 0x50, 0xEB, //JMP SHORT 1F
  279. 0x02, 0xFF, 0x15, 0xC3  //CALL DWORD PTR DS:[C3]
  280. };
  281. //把函数地址放到解密的OpCode里
  282. OpCode[11] = dwEncryptFunAddr;// 0x85  假如:dwEncryptFunAddr = 0x12345678
  283. OpCode[12] = dwEncryptFunAddr >> 0x08;// 0xEE  十六进制右移8位刚好截掉低位的2位  0x00123456
  284. OpCode[13] = dwEncryptFunAddr >> 0x10;// 0xCB  十六进制右移16位刚好截掉低位的4位 0x00000012
  285. OpCode[14] = dwEncryptFunAddr >> 0x18;// 0x60  十六进制右移16位刚好截掉低位的4位 0x00000000
  286. // 4.将数据拷贝到申请的内存
  287. pfnRtlMoveMemory((LPVOID)dwNewMem, OpCode, 0x20);
  288. // 5.返回新的函数地址
  289. return dwNewMem;
  290. }
  291. //************************************************************
  292. // 函数名称: EncryptIAT
  293. // 函数说明: 加密IAT
  294. // 参 数: void
  295. // 返 回 值: void
  296. //************************************************************
  297. void EncryptIAT()
  298. {
  299. //设置文件属性为可写
  300. SetFileHeaderProtect(true);
  301. //获取当前程序的加载基址
  302. DWORD ImageBase = (DWORD)pfnGetMoudleHandleA(NULL);
  303. IMAGE_THUNK_DATA* pInt = NULL;
  304. IMAGE_THUNK_DATA* pIat = NULL;
  305. SIZE_T impAddress = 0;
  306. HMODULEhImpModule = 0;
  307. DWORD dwOldProtect = 0;
  308. IMAGE_IMPORT_BY_NAME* pImpName = 0;
  309. if (!GetOptionHeader((char*)ImageBase)->DataDirectory[1].VirtualAddress)return;
  310. //导入表=导入表偏移+加载基址
  311. IMAGE_IMPORT_DESCRIPTOR* pImp = (IMAGE_IMPORT_DESCRIPTOR*)(GetOptionHeader((char*)ImageBase)->DataDirectory[1].VirtualAddress + ImageBase);
  312. while (pImp->Name)
  313. {
  314. //IAT=偏移加加载基址
  315. pIat = (IMAGE_THUNK_DATA*)(pImp->FirstThunk + ImageBase);
  316. if (pImp->OriginalFirstThunk == 0) // 如果不存在INT则使用IAT
  317. {
  318. pInt = pIat;
  319. }
  320. else
  321. {
  322. pInt = (IMAGE_THUNK_DATA*)(pImp->OriginalFirstThunk + ImageBase);
  323. }
  324. // 加载dll
  325. hImpModule = (HMODULE)MyLoadLibraryA((char*)(pImp->Name + ImageBase));
  326. //导入函数地址
  327. while (pInt->u1.Function)
  328. {
  329. //判断导入的方式、序号还是名称
  330. if (!IMAGE_SNAP_BY_ORDINAL(pInt->u1.Ordinal))
  331. {
  332. pImpName = (IMAGE_IMPORT_BY_NAME*)(pInt->u1.Function + ImageBase);
  333. impAddress = (SIZE_T)MyGetProcAddress(hImpModule, (char*)pImpName->Name);
  334. }
  335. else
  336. {
  337. impAddress = (SIZE_T)MyGetProcAddress(hImpModule, (char*)(pInt->u1.Function & 0xFFFF));
  338. }
  339. MyVirtualProtect(&pIat->u1.Function, sizeof(pIat->u1.Function), PAGE_READWRITE, &dwOldProtect);
  340. pIat->u1.Function = EncryptFun(impAddress);
  341. MyVirtualProtect(&pIat->u1., sizeof(pIat->u1.Function), dwOldProtect, &dwOldProtect);
  342. ++pInt;
  343. ++pIat;
  344. }
  345. ++pImp;
  346. }
  347. SetFileHeaderProtect(false);
  348. }
  349. //************************************************************
  350. // 函数名称: RecoverDataDir
  351. // 函数说明: 恢复数据目录表
  352. // 参 数: DWORD funcAddress 函数地址
  353. // 返 回 值: void
  354. //************************************************************
  355. void RecoverDataDir()
  356. {
  357. //获取当前程序的加载基址
  358. char* dwBase = (char*)pfnGetMoudleHandleA(NULL);
  359. //获取数据目录表的个数
  360. DWORD dwNumOfDataDir = g_conf.dwNumOfDataDir;
  361. DWORD dwOldAttr = 0;
  362. PIMAGE_DATA_DIRECTORY pDataDirectory = (GetOptionHeader(dwBase)->DataDirectory);
  363. //遍历数据目录表
  364. for (DWORD i = 0; i < dwNumOfDataDir; i++)
  365. {
  366. if (i == 2)
  367. {
  368. pDataDirectory++;
  369. continue;
  370. }
  371. //修改属性为可读可写
  372. MyVirtualProtect(pDataDirectory, 0x8, PAGE_EXECUTE_READWRITE, &dwOldAttr);
  373. //还原数据目录表项
  374. pDataDirectory->VirtualAddress = g_conf.dwDataDir[i][0];
  375. pDataDirectory->Size = g_conf.dwDataDir[i][1];
  376. //把属性修改回去
  377. MyVirtualProtect(pDataDirectory, 0x8, dwOldAttr, &dwOldAttr);
  378. pDataDirectory++;
  379. }
  380. }
  381. // 壳程序
  382. int packerDoNum = 1; //这个执行次数可以加大脱壳的难度,这里设为1,方便做实验
  383. void AllFunc()
  384. {
  385. // 递归执行packerDoNum次后执行壳程序
  386. if (!packerDoNum)
  387. {
  388. _asm
  389. {
  390. nop
  391. mov   ebp, esp
  392. push - 1
  393. push   0
  394. push   0
  395. mov   eax, fs:[0]
  396. push   eax
  397. mov   fs : [0], esp
  398. sub   esp, 0x68
  399. push   ebx
  400. push   esi
  401. push   edi
  402. pop   eax
  403. pop   eax
  404. pop   eax
  405. add   esp, 0x68
  406. pop   eax
  407. mov   fs : [0], eax
  408. pop   eax
  409. sub packerDoNum, 1
  410. pop   eax
  411. pop   eax
  412. pop   eax
  413. mov   ebp, eax
  414. push AllFunc
  415. }
  416. }
  417. }
复制代码


参考资料

《0day安全.软件漏洞分析(第二版)》

《Windows黑客编程技术》

动态地址的获取方式

C++写壳之高级篇

获取GetProcAddress函数地址遇到的的有关问题



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-12-12 13:34 , Processed in 0.018872 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表