zoukankan      html  css  js  c++  java
  • 脱壳系列_2_IAT加密壳_详细分析(含脚本)

    1 查看壳程序信息

    • 使用ExeInfoPe

    1564497101459

    分析:

    发现这个壳的类型没有被识别出来,Vc 6.0倒是识别出来了,Vc 6.0的特征是 入口函数先调用GetVersion()

    2 用OD找OEP

    • 拖进OD

    1564555096667

    发现 这个壳和我们的正常程序很像。但是并不是我们的真正程序入口

    • 因为vc6.0特征的第一个调用的是GetVersion(),给GetVersion()下 硬件断点

    //第一次断下来,但是根据栈回溯,调用者并不是我们的模块

    1564555310396

    //第二次断下来,就应该是了

    1564555388065[4]

    //找到入口后 栈上右键 反汇编窗口跟随

    1564555463633

    //如下

    1564555543533

    • 在OD看内存布局,一般.rdata的最前面是放的IAT,而且OD数据窗口默认就是.rdata的起始位置。

      • 也可以点一个call /jmp  [];看一下来找IAT表

    1564555827970

    • 对那个地方下一个硬件写入断点 --DWORD,即当前面的壳程序在修改的时候就能段下来找到壳的加密算法的地方

      • 这里只是为了快速脱壳所以下硬件断点,快速定位加密修改IAT的地方,但后面部分将对整个壳详细分析:。

    3 对壳详细分析

    这个分析的过程,需要自己去啃是分享不了的。在分析的时候遇到不知道的变量、地址这些先标记留着,后面分析着分析着就知道了。

    00438450 >  55                    PUSH EBP
    00438451    8BEC                  MOV EBP,ESP
    00438453    83EC 0C               SUB ESP,0xC
    00438456    E8 45FFFFFF           CALL 02.004383A0
    0043845B    A1 40804300           MOV EAX,DWORD PTR DS:[<程序基址>]
    00438460    0305 44804300         ADD EAX,DWORD PTR DS:[<代码段偏移>]
    00438466    8945 F8               MOV DWORD PTR SS:[EBP-0x8],EAX
    00438469    C745 FC 00000000      MOV DWORD PTR SS:[EBP-0x4],0x0
    00438470    8D4D FC               LEA ECX,DWORD PTR SS:[EBP-0x4]
    00438473    51                    PUSH ECX
    00438474    6A 40                 PUSH 0x40
    00438476    8B15 4C804300         MOV EDX,DWORD PTR DS:[0x43804C]
    0043847C    52                    PUSH EDX
    0043847D    8B45 F8               MOV EAX,DWORD PTR SS:[EBP-0x8]
    00438480    50                    PUSH EAX
    00438481    FF15 C0924300         CALL DWORD PTR DS:[<virtualProtect>]                       ; kernel32.VirtualProtect
    00438487    E8 04FEFFFF           CALL 02.00438290
    0043848C    8D4D FC               LEA ECX,DWORD PTR SS:[EBP-0x4]
    0043848F    51                    PUSH ECX
    00438490    8B55 FC               MOV EDX,DWORD PTR SS:[EBP-0x4]
    00438493    52                    PUSH EDX
    00438494    A1 4C804300           MOV EAX,DWORD PTR DS:[0x43804C]
    00438499    50                    PUSH EAX
    0043849A    8B4D F8               MOV ECX,DWORD PTR SS:[EBP-0x8]
    0043849D    51                    PUSH ECX
    0043849E    FF15 C0924300         CALL DWORD PTR DS:[<virtualProtect>]                       ; kernel32.VirtualProtect
    004384A4    6A 04                 PUSH 0x4
    004384A6    68 2C814300           PUSH 02.0043812C                                           ; Hello 15PB
    004384AB    68 38814300           PUSH 02.00438138                                           ; 欢迎使用免费加壳程序,是否运行主程序?
    004384B0    6A 00                 PUSH 0x0
    004384B2    FF15 BC924300         CALL DWORD PTR DS:[<user.MessageBoxA>]                     ; user32.MessageBoxA
    004384B8    8945 F4               MOV DWORD PTR SS:[EBP-0xC],EAX
    004384BB    837D F4 06            CMP DWORD PTR SS:[EBP-0xC],0x6                             ; 当点击提示框的 IDYES 后 跳转
    004384BF    75 0B                 JNZ SHORT 02.004384CC                                      ; 如果不是 IDYES 那么就跳到退出程序标签
    004384C1    E8 1A000000           CALL <02.选择IDYES_THEN>
    004384C6  - FF25 3C804300         JMP DWORD PTR DS:[<拟定的真实入口>]                               ; 02.00409486
    004384CC    6A 00                 PUSH 0x0
    004384CE    FF15 B8924300         CALL DWORD PTR DS:[<MessageBoxA>]                          ; kernel32.ExitProcess
    004384D4    8BE5                  MOV ESP,EBP
    004384D6    5D                    POP EBP
    004384D7    C3                    RETN
    004384D8    CC                    INT3
    004384D9    CC                    INT3
    004384DA    CC                    INT3
    004384DB    CC                    INT3
    004384DC    CC                    INT3
    004384DD    CC                    INT3
    004384DE    CC                    INT3
    004384DF    CC                    INT3
    004384E0 >  53                    PUSH EBX                                                   ; 1. 将上一个函数的EBX保存
    004384E1    8BDC                  MOV EBX,ESP                                                ; /将当前main栈顶 保存到 EBX
    004384E3    83EC 08               SUB ESP,0x8                                                ; 2.开辟 8字节的局部空间 作用:将esp 4位对齐 ↓
    004384E6    83E4 F0               AND ESP,0xFFFFFFF0                                         ; /将ESP -- 4位 对齐
    004384E9    83C4 04               ADD ESP,0x4                                                ; /平4bytes,这三句的作用:将原来的ESP 4位对齐 ↑
    004384EC    55                    PUSH EBP                                                   ; 3.压入main的栈底
    004384ED    8B6B 04               MOV EBP,DWORD PTR DS:[EBX+0x4]                             ; 4.将return 地址给 EBP
    004384F0    896C24 04             MOV DWORD PTR SS:[ESP+0x4],EBP                             ; 5.将ebp再赋值给return,这两句其实是 mov esp+0x4,ebx+0x4. 也就是将原来未4位对齐之前的return 地址赋值给对齐之后理论应该存放(原对应)的地址
    004384F4    8BEC                  MOV EBP,ESP                                                ; *1 平栈,开辟新的栈帧
    004384F6    83EC 48               SUB ESP,0x48                                               ; *2 开辟局部空间
    004384F9    A1 20804300           MOV EAX,DWORD PTR DS:[<第一个未知使用:0x4384f9 -- [0x438020]>]    ; 0x438020 -- 是啥? 74062457 -->eax
    004384FE    33C5                  XOR EAX,EBP                                                ; 和return 地址异或   -- 应该是 判断是否相等
    00438500    8945 FC               MOV DWORD PTR SS:[EBP-0x4],EAX                             ; 把 函数返回地址 xor 未知数相与之后 -》.local1 像在算cookie 一样
    00438503    56                    PUSH ESI                                                   ; 保存ESI
    00438504    8B35 40804300         MOV ESI,DWORD PTR DS:[<程序基址>]                              ; 把程序基地址 放入ESI
    0043850A    8D45 C8               LEA EAX,DWORD PTR SS:[EBP-0x38]                            ; & local.14 -->eax
    0043850D    57                    PUSH EDI                                                   ; 保存EDI
    0043850E    8B3D 54804300         MOV EDI,DWORD PTR DS:[<遍历的动态偏移>]                           ; 将[0X43805]放入 EDI  当前是:28c00 看样子是一个PE偏移RVA
    00438514    50                    PUSH EAX                                                   ; 压入EAX 即 &local.14 : 经过后面分析 这是用来保存以前.rdata区段属性的局部变量
    00438515    A1 5C804300           MOV EAX,DWORD PTR DS:[<.rdata的RVA>]                        ; 将[0x43805c]放入EAX  当前是 22000 -- .rdata数据段RVA
    0043851A    6A 40                 PUSH 0x40                                                  ; push 0x40
    0043851C    FF35 60804300         PUSH DWORD PTR DS:[<.rdata的SIZE>]                          ; 退
    00438522    03C6                  ADD EAX,ESI                                                ; .rdata的真实VA eax = imageBase + EAX ---*****----到这儿恍然大悟:前面的局部数据是区段信息rva 、size
    00438524    8975 CC               MOV DWORD PTR SS:[EBP-0x34],ESI                            ; local.13 程序的基地址
    00438527    50                    PUSH EAX                                                   ; .rdata的VA 即IAT数组的地址 -- push &IAT
    00438528    C745 C8 00000000      MOV DWORD PTR SS:[EBP-0x38],0x0                            ; local.14 = 0
    0043852F    FF15 C0924300         CALL DWORD PTR DS:[<virtualProtect>]                       ; kernel32.VirtualProtect
    00438535    833C37 00             CMP DWORD PTR DS:[EDI+ESI],0x0                             ; 比较在程序虚拟空间偏移为0x28c00的地方的值 是否为0
    00438539    0F84 D7000000         JE <02.遍历结束恢复区段保护属性>
    0043853F    8B55 CC               MOV EDX,DWORD PTR SS:[EBP-0x34]
    00438542    83C6 10               ADD ESI,0x10                                               ; 程序基地址 + 16个字节? IMPORT_DESCRIPTOR 的FirstThunk字段的起始地址,也就是 IAT的指针
    00438545    03F7                  ADD ESI,EDI                                                ; 程序基地址 + DLL模块 当前的偏移
    00438547    8975 C4               MOV DWORD PTR SS:[EBP-0x3C],ESI                            ; 把IAT 的地址 放入 local.15
    0043854A    8D9B 00000000         LEA EBX,DWORD PTR DS:[EBX]                                 ; 这句 》混淆视听?
    00438550    8B46 FC               MOV EAX,DWORD PTR DS:[ESI-0x4]                             ; dllName字段的RVA
    00438553    03C2                  ADD EAX,EDX                                                ; dllName的VA
    00438555    50                    PUSH EAX                                                   ; 获取模块基址 第一个dll 从010Editor中查看来是 KERNEL32.DLL
    00438556    FF15 C8924300         CALL DWORD PTR DS:[<LoadLibraryA>]                         ; kernel32.LoadLibraryA
    0043855C    8B3E                  MOV EDI,DWORD PTR DS:[ESI]                                 ; IAT表的RVA 放在EDI
    0043855E    8B55 CC               MOV EDX,DWORD PTR SS:[EBP-0x34]                            ; 程序基地址 放入 EDX
    00438561    03FA                  ADD EDI,EDX                                                ; IAT 表的起始VA
    00438563    8945 C0               MOV DWORD PTR SS:[EBP-0x40],EAX                            ; DLL 模块基地址 放在 local.16的位置上
    00438566    8B0F                  MOV ECX,DWORD PTR DS:[EDI]                                 ; 获取第一个IAT 项 -- 放在 ECX
    00438568    85C9                  TEST ECX,ECX                                               ; 判断是否为0 ,为0 就跳转当前模块的IAT 遍历结束
    0043856A    0F84 93000000         JE <02.模块遍历结束>
    00438570    8BF7                  MOV ESI,EDI
    00438572 >  8B07                  MOV EAX,DWORD PTR DS:[EDI]                                 ; 还是第一个IAT 项  -- 第一个API地址 --》EAX 和前面ECX一样
    00438574    83C0 02               ADD EAX,0x2                                                ; EAX +=2 --- 函数地址 +=2 跳过import_by_name结构体 Hint字段 直接是 name 字段的地址
    00438577    85C9                  TEST ECX,ECX
    00438579    78 75                 JS SHORT <02.序号导出的函数>                                      ; 判断最高位是否为1 SF =1 则代表着是序号导出
    0043857B    03C2                  ADD EAX,EDX                                                ; 如果不是序号导出 计算函数名称的 VA
    0043857D    C745 D0 E8010000      MOV DWORD PTR SS:[EBP-0x30],0x1E8
    00438584    50                    PUSH EAX                                                   ; 函数字符串的地址 --押入站
    00438585    FF75 C0               PUSH DWORD PTR SS:[EBP-0x40]                               ; dll模块基地址 入站
    00438588    C745 D4 00E958EB      MOV DWORD PTR SS:[EBP-0x2C],0xEB58E900
    0043858F    66:C745 D8 01E8       MOV WORD PTR SS:[EBP-0x28],0xE801                          ; 0x25在下面一点,用来写入GetProcAddress的返回地址
    00438595    C645 DA B8            MOV BYTE PTR SS:[EBP-0x26],0xB8
    00438599    C745 DF EB011535      MOV DWORD PTR SS:[EBP-0x21],0x351501EB
    004385A0    C745 E3 15151515      MOV DWORD PTR SS:[EBP-0x1D],0x15151515
    004385A7    C745 E7 EB01FF50      MOV DWORD PTR SS:[EBP-0x19],0x50FF01EB
    004385AE    C745 EB EB02FF15      MOV DWORD PTR SS:[EBP-0x15],0x15FF02EB
    004385B5    C645 EF C3            MOV BYTE PTR SS:[EBP-0x11],0xC3
    004385B9    FF15 CC924300         CALL DWORD PTR DS:[<GetProcAddress>]                       ; kernel32.GetProcAddress
    004385BF    6A 40                 PUSH 0x40
    004385C1    68 00300000           PUSH 0x3000                                                ; 当IDYES 后那个CALL结束后
    004385C6    6A 20                 PUSH 0x20
    004385C8    35 15151515           XOR EAX,0x15151515
    004385CD    6A 00                 PUSH 0x0
    004385CF    8945 DB               MOV DWORD PTR SS:[EBP-0x25],EAX                            ; 放入地址 -- 0x25刚好是用来存放地址的
    004385D2    FF15 B4924300         CALL DWORD PTR DS:[<VirtualAlloc>]                         ; 申请32字节来存放硬编码(除了那4个地址字节都是死的,)
    004385D8    F30F6F45 D0           MOVDQU XMM0,DQWORD PTR SS:[EBP-0x30]
    004385DD    8B55 CC               MOV EDX,DWORD PTR SS:[EBP-0x34]
    004385E0    F30F7F00              MOVDQU DQWORD PTR DS:[EAX],XMM0
    004385E4    F30F6F45 E0           MOVDQU XMM0,DQWORD PTR SS:[EBP-0x20]
    004385E9    F30F7F40 10           MOVDQU DQWORD PTR DS:[EAX+0x10],XMM0                       ; 这几句是把解密IAT的opcode 写入申请的内存中
    004385EE    8907                  MOV DWORD PTR DS:[EDI],EAX                                 ; 申请的内存地址 放入IAT中
    004385F0 >  8B4E 04               MOV ECX,DWORD PTR DS:[ESI+0x4]                             ; 下一个IAT表项
    004385F3    83C6 04               ADD ESI,0x4                                                ; esi 此时指向下一个 IAT表项
    004385F6    8BFE                  MOV EDI,ESI                                                ; 把EDI 指向下一个表项
    004385F8    85C9                  TEST ECX,ECX                                               ; 判断是否结束
    004385FA  ^ 0F85 72FFFFFF         JNZ <02.IAT 遍历循环起始处>                                       ; 还没有结束的时候跳 回去继续遍历IAT↑
    00438600    8B75 C4               MOV ESI,DWORD PTR SS:[EBP-0x3C]                            ; IAT 如果结束了 就把上一个IMP_DESCRIPTOR的最后一个地址,放入ESI
    00438603 >  83C6 14               ADD ESI,0x14                                               ; ESI + 一个IMP_DESCRIPTOR结构体的大小 相当于解析下一个dll
    00438606    8975 C4               MOV DWORD PTR SS:[EBP-0x3C],ESI                            ; 再把新的当前的位置存回去0x3c -- local.15的位置
    00438609    837E F0 00            CMP DWORD PTR DS:[ESI-0x10],0x0
    0043860D  ^ 0F85 3DFFFFFF         JNZ 02.00438550
    00438613    8B75 CC               MOV ESI,DWORD PTR SS:[EBP-0x34]
    00438616 >  8D45 C8               LEA EAX,DWORD PTR SS:[EBP-0x38]
    00438619    50                    PUSH EAX                                                   ; 把 原来的区段属性弹出
    0043861A    FF75 C8               PUSH DWORD PTR SS:[EBP-0x38]
    0043861D    A1 5C804300           MOV EAX,DWORD PTR DS:[<.rdata的RVA>]
    00438622    FF35 60804300         PUSH DWORD PTR DS:[<.rdata的SIZE>]                          ; 退
    00438628    03C6                  ADD EAX,ESI
    0043862A    50                    PUSH EAX
    0043862B    FF15 C0924300         CALL DWORD PTR DS:[<virtualProtect>]                       ; 恢复原来的区段属性
    00438631    8B4D FC               MOV ECX,DWORD PTR SS:[EBP-0x4]                             ; 就是前面ebp 与一个未知的数的异或加密 类似cookie
    00438634    5F                    POP EDI
    00438635    33CD                  XOR ECX,EBP
    00438637    5E                    POP ESI

    //开始怀疑修改进IAT的函数内容--加密代码前面几句--写死的硬编码opcode 到底意欲何为

    1564503195183

    在内存窗口跳到[EBX-0X30]选择反汇编观察一下:

    1564503136206

    • 发现 这就是解密IAT代码。

    总结:用硬编码 opcode (用于解密IAT)的首地址替代IAT中的函数地址,然后每次IAT调用的时候都会去调用这个代码块使用。

    1564557348504

    填入IAT的解密函数 详解:

    进入填充硬编码后的 EBP-0X30 , 进去后发现里面包含了很多花指令,我一个个提取出来组合分析:

    注意: 数字标号,代表着我的分析顺序

    ;1.解密程序第一句:
    0012FF30    E8 01000000     CALL 0012FF36
    ;--------------------------------
    ;2.跳转到 0012ff36后
    0012FF36    58              POP EAX;其实这儿主要是为了弹出前面的返回地址,好自己push返回地址
    0012FF37    EB 01           JMP SHORT 0012FF3A
    ;----------------------------------
    ;3.简单pop EAX 后又添砖0012ff3a:
    0012FF3A    B8 00040000     MOV EAX,0x????;8.结合后面的代码,发现是取得的代码0x15异或加密后的代码
    0012FF3F    EB 01           JMP SHORT 0012FF42
    ;将EAX 赋值为0x400 ,然后又跳转 
    ;----------------------------------
    ;4.跳转到 0012FF42:
    0012FF42    35 15151515     XOR EAX,0x15151515;9.这样就迎刃而解了,这儿是解密。
    0012FF47    EB 01           JMP SHORT 0012FF4A
    ;将EAX和0x 15151515 异或计算加密后 继续跳转 0012FF4A
    ;-------------------------------------
    ;5.跳转到 0012ff4a:
    0012FF4A    50              PUSH EAX;10.把解密的真正地址作为返回地址
    0012FF4B    EB 02           JMP SHORT 0012FF4F
    ;push了现在的 40000 XOR 0X15151515后的值作为返回地址,又跳转
    ;--------------------------------------
    ​
    ​
    ;6.再结合后面一点的代码:
    004385B9    FF15 CC924300   CALL DWORD PTR DS:[<GetProcAddress>]; kernel32.GetProcAddress
    004385BF    6A 40           PUSH 0x40
    004385C1    68 00300000     PUSH 0x3000      
    004385C6    6A 20           PUSH 0x20
    004385C8    35 15151515     XOR EAX,0x15151515
    004385CD    6A 00           PUSH 0x0
    004385CF    8945 DB         MOV DWORD PTR SS:[EBP-0x25],EAX ; 7.放入地址 -- 0x25刚好是用来存放地址的
    ;------------------------------------------------------------

    综述:

    这段opcode 留了 EBP-0X25这个位置 来填充 xor 0x15 加密后的地址值,待真正call IAT[index]的时候,把call的返回地址pop弹出,把那个亦或的加密后的地址值用 xor 15 来解密,并且push进去代替前面pop出的地址,并且返回。

    4 修正前面分析的加密算法

    • 这时候我们只需要保存正确的函数地址值到IAT,那么这个程序就能脱掉了。

    1564503552816

    然后和前面几篇一样的流程dump到本地,用impREC修复一下IAT

    运行没毛病!!

    附:还可以使用OD脚本修正IAT

    思路:

    1. 调用getprocAddress之后,立刻使用一个临时变量保存起来,

    2. 再待壳修改IAT后,立刻修改回正确的函数地址(前面保存的临时变量)

    脚本如下:

    //1.定义变量
    MOV dwGetApiAddr,004385bf
    MOV dwWriteAddr,004385f0
    MOV dwOEP,00409486
    
    //2.初始化环境
    BC //清除软件断点
    BPHWC //清除硬件断点
    BPMC //清除内存断点
    
    //下硬件执行断点
    
    BPHWS dwGetApiAddr,"x"
    BPHWS dwWriteAddr,"x"
    BPHWS  dwOEP,"x"
    
    //3.构建逻辑
    /*
     -- 用一个临时变量来存储 真正的函数地址 
     -- 在加密逻辑代码执行完,并且写入IAT后,立刻改回
    */
    LOOP0:
      RUN //相当于od -- F9
      CMP  dwGetApiAddr,eip//如果是执行完GetProcAddress后
      JNZ CASE1
      mov dwTemp,eax
      JMP LOOP0    
    
    CASE1:
      CMP dwWriteAddr,eip 
      MOV [edi],dwTemp  
      JMP LOOP0  
    
    CASE2:
    
      CMP dwOEP,eip  
      JNZ LOOP0  
    
      MSG "改回来了哈哈"

     我发现另外一种解法,待会儿送上!

  • 相关阅读:
    嵌入式入门 -第1章 学嵌入式从STM32开始
    如何学习Java
    如何学习嵌入式linux
    Linux OR windows 的启动全过程详解
    Makefile 易忘规则记录
    Makefile中的自动化变量
    Makefile中的文件名操作函数
    Makefile中的字符串处理函数
    关于错误"ftok: No such file or directory"
    linux查看yuv图像
  • 原文地址:https://www.cnblogs.com/leibso-cy/p/SHELL_2_IAT.html
Copyright © 2011-2022 走看看