本帖最后由 mistaked 于 2023-11-04 15:11 编辑
对Petools 这款软件加壳来分析IAT的处理,为了方便分析将保护选项设置如下:一. 两次断点法获取OEP位置 对加壳后程序的第二个段下硬件写入断点,第二次中断后显示如下: 在对第一个代码段下执行断点,就可以到达OEP。Scylla 搜索IAT 可以看到很多的导入函数可以正常搜索到。在查看IAT 内存可以看到一些零零散散的乱码,说明有部分IAT被做了加密处理。二. dump程序后,查看异常位置 使用 Scylla dump并修复程序后,使用x32dbg 运行程序断在第一次异常的位置: 通过堆栈观察可以发现这个异常位于OEP附近,硬编码以e8 开头的call 像壳对 call [0xXXXX] (这类调用导入函数call)处理。而e8 call比 call [0xXXXX] 少一个字节,可能导致后面的乱码。 尝试nop掉后面的一个字节,可以看到调试器分析变得正常,证明猜想是正确的。 与一般的加密壳不同,ASProtect 在处理了call [0xXXXX] 后,e8 call调用的全是相同的地址。 所以壳程序极有可能是根据 函数调用位置地址 来进行运算,查表得到 要调用的导入函数。 call 后的地址不位于exe的映射范围内,应该是壳程序申请了一段空间来存放另一段外壳程序。 三. e8 call 外壳函数的修改行为 x32dbg 运行外壳程序在上图异常call 调用前下断点。在第三个call之后下个断点。 运行后可以看到有部分的call 地址被修改指向另一块内存空间(有部分没有修改)。使用x32dbg 的命令搜索功能,对多个函数下断。分析修改后指向的代码,可知call 后的新地址,都是新分配的内存地址且代码量少,作用是对堆栈处理并跳转到另一段新分配的内存空间,这段空间的代码是 将导入函数的代码拷贝而来。这样也就不需要调用导入函数,且可以防止对导入函数下断。但是拷贝的内容有限制,如果导入函数中有call 和jcc指令就会将 这条指令之前的代码拷贝,之后直接跳转到dll中的后续代码。 无jcc和call类型 有jcc有call类型四. e8 call 外壳函数分析 0x026E0000 函数作用也是作为跳转,跳转到真实的外壳函数,同时会保存寄存器值和压入一些外壳函数需要的参数。 下面是手动去混淆后的代码: 026E0005 | 51 | push ecx | 保存ecx 026E0006 | 9C | pushfd | 保存eflags 026E000B | 83EC 20 | sub esp,20 | 026E001C | 8D4C24 1F | lea ecx,dword ptr ss:[esp+1F] | 026E0025 | 83E9 1F | sub ecx,1F | ecx等于esp 之后 充当esp作用,操作堆栈 026E0028 | 8951 08 | mov dword ptr ds:[ecx+8],edx | 保存edx 026E0032 | 57 | push edi | 026E0033 | 8F41 1C | pop dword ptr ds:[ecx+1C] | 保存edi 026E003F | 8941 00 | mov dword ptr ds:[ecx],eax | 保存eax 026E004C | 55 | push ebp | 026E004D | 8F41 14 | pop dword ptr ds:[ecx+14] | 保存ebp 026E005B | 8971 18 | mov dword ptr ds:[ecx+18],esi | 保存esi 026E006A | 53 | push ebx | 保存ebx 026E006B | 8F41 0C | pop dword ptr ds:[ecx+C] | 026E0090 | 8D542C 36 | lea edx,dword ptr ss:[esp+ebp+36] | 026E0094 | 2BD5 | sub edx,ebp | 026E0096 | 8D541A CA | lea edx,dword ptr ds:[edx+ebx-36] | 026E009F | 2BD3 | sub edx,ebx | 026E00A1 | 83C2 2C | add edx,2C | 026E00A4 | 52 | push edx | edx == esp+0x2C 026E00AC | 51 | push ecx | 寄存器被保存的起始位置 026E00B9 | 51 | push ecx | 026E00C1 | 5A | pop edx | 026E00C2 | 83C2 20 | add edx,20 | 指向栈中保存eflag寄存器位置 026E00CA | FF32 | push dword ptr ds:[edx] | 026E00D4 | 5A | pop edx | 026E00D5 | 52 | push edx | eflag在压入 026E00E8 | 51 | push ecx | 026E00EB | 5A | pop edx | 026E00EC | 8D543A 28 | lea edx,dword ptr ds:[edx+edi+28] | 026E00F0 | 2BD7 | sub edx,edi | edi:"瞂觝0" 026E00F7 | 8B12 | mov edx,dword ptr ds:[edx] | 指向call 压入的返回地址 026E00FD | 8D5432 FB | lea edx,dword ptr ds:[edx+esi-5] | 026E0101 | 2BD6 | sub edx,esi | 运行完这句,edx为call 调用位置 026E0103 | 81C2 08120000 | add edx,1208 | 这里1208 在后面分析可以知道是进程ID,每次运行不同,可能用来防止dump 026E0109 | 52 | push edx | 026E0115 | 33D2 | xor edx,edx | 026E0117 | 64:FF32 | push dword ptr fs:[edx] | 026E0130 | 8D9439 BC18EE02 | lea edx,dword ptr ds:[ecx+edi+2EE18BC] | 026E0137 | 2BD7 | sub edx,edi | edi:"瞂觝0" 026E0139 | 2BD1 | sub edx,ecx | 026E013B | 52 | push edx | 这里edx保存外壳程序获取导入函数需要的所有信息,被作为参数传递给函数 026E0150 | 68 50F5EB00 | push EBF550 | 外壳程序函数 026E0164 | FFD1 | call ecx | 调用外壳函数
五. 真实外壳函数分析 在call ecx调用外壳函数后,可以看到外壳函数是标准的函数形式,并没有加混淆,尝试dump 并拖入 IDA 分析。 IDA 分析出了大部分的代码。IDA reBase后找到外壳函数,f5生成伪代码,根据上面的堆栈情况对关键参数重命名。interface 是上面 push edx 压入的一个全局变量,存储了外壳函数查找导入函数需要的所有信息。 callerAddPID 是 call 调用位置加上 进程PID的值。 regContext 是call 调用后保存寄存器的位置。 orgESP 是调用call前ESP值。
往下一移动第一个函数里面比较简单可以看到是对保存寄存器修改的函数: 命名为SetSavRegContext 而下一行的callerAddPID - MEMORY[0x75EFE7A0](); ,0x75EFE7A0 就是GetCurrentProcId(),也就是前面推出加上pid的原因。 之后便是for循环下的两个call,两个call分别下断点,在回到call 调用位置,可以判断出分别对应 e8 call有修改行为和没有修改行为两种类型 上面的( *(int (__cdecl **)())(intface + 12 * *v24 + 104))(); 在外壳函数中有很多这样的函数调用,里面加了混淆指令,不过实际的行为是以 callBlock 作为参数,然后取成员值,所以分析只需要在进入call前记录 eax (以eax作为参数传递)值,在准备退出时记录最后计算得到的值,两种做差值就能得到偏移。 我们进入EntryCallAndRet函数分析(没有拷贝函数过程,更容易分析)。 最后可以看到跟callBlock相关的值都流向了sub_EBF05C,而sub_EBF05C 调用后v38 得到的为导入函数的地址。 最后根据v38 这个参数分析sub_EBF05C 函数,外壳函数会根据导入序号,导入函数名称等进行分类获取函数地址,具体情况可以看代码。
五. capstone 修复e8 call 使用capstone反汇编引擎遍历所有e8 call 并且筛选出 call 后的地址不在exe映像的值。 使用blackbone 库的 进程模块管理 ,根据 e8 call地址得到dll名称和函数名称 来获取导入函数地址,填充IAT中无效的地址,在修复e8 call 变成 call [0xXXXX]形式。
注:若转载请注明大神论坛来源(本贴地址)与作者信息。
下方隐藏内容为本帖所有文件或源码下载链接:
游客你好,如果您要查看本帖隐藏链接需要登录才能查看,
请先登录
|