设为首页收藏本站 今天是: 2023-11-29    美好的一天,从现在开始

复仇者黑客组织

 找回密码
 立即注册

QQ登录

只需一步,快速开始

    查看: 231|回复: 9

    tvm分析与还原

    [复制链接]

    该用户从未签到

    2

    主题

    2

    帖子

    8

    积分

    新手上路

    Rank: 1

    积分
    8
    发表于 2023-11-20 05:25:42 | 显示全部楼层 |阅读模式
    tvm简介

    一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产品拉入 PE 工具,看到区段中有 .tvm0 那就没跑了。
    demo

    这次还原用到的demo是前段时间 游戏安全技术竞赛的决赛附加题一个非常好的demo,驱动基本上全vm了。
    还要特别感谢 这位大佬放出来的脱壳版,给我节省了许多验证还原效果的时间。
    资料:

    还原脚本项目地址:xx_tvm
    文档我也只说明了一些明显的点,还是看代码更加清晰。
    然后给你的idapython安装以下的库:
    1. import capstone
    2. import keystone
    3. import copy
    4. import unicorn
    复制代码
    混淆

    1.
    1. 1400d302c : not     r10                               ,R10 <-- ffffffffffffffff  
    2. 1400d302f : xchg    rax, r10                          ,RAX <-- ffffffffffffffff  ,R10 <-- 0  
    3. 1400d3031 : mov     [rbp+var_s8], r10                 
    4. 1400d3035 : not     rax                               ,RAX <-- 0  
    5. 1400d3038 : xchg    rax, r10                          
    复制代码
    可优化成:
    1. 1400d3031 : mov     [rbp+var_s8], rax                 
    复制代码
    直接特征识别即可,请参考idapython/TVMunicornTrace.py .tvmFunTask.mabe_1()
    2.
    1. 1400d5b36 : xchg    rax, r11                          ,RAX <-- 14006024c  ,R11 <-- 0  
    2. 1400d5b38 : mov     rax, [rbp+98h]                    
    3. 1400d5b3f : not     rax                               ,RAX <-- fffffffebff9fdb3  
    4. 1400d9148 : xchg    rax, r11                          ,RAX <-- 0  ,R11 <-- fffffffebff9fdb3  
    5. 1400d914a : not     r11                               ,R11 <-- 14006024c  
    复制代码
    可优化成:
    1. 1400d5b38 : mov     r11, [rbp+98h]                    
    复制代码
    直接特征识别即可,请参考idapython/TVMunicornTrace.py .tvmFunTask.mabe_2()
    3.
    1. 1400d9156 : push    r10                               ,RSP <-- 1500  
    2. 1400d9158 : lea     r10, loc_1400E1D70+2              ,R10 <-- 1400e1d72  
    3. 1400d915f : lea     r10, [r10-0A812h]                 ,R10 <-- 1400d7560  
    4. 1400d9166 : jmp     r10                              
    5. 1400d7560 : pop     r10                               ,RSP <-- 1508  ,R10 <-- 0  
    复制代码
    可优化成:
    1. 1400d9156 : jmp     1400d7560
    2. 1400d7560 : nop
    复制代码
    直接特征识别即可,请参考idapython/TVMunicornTrace.py .tvmFunTask.mabe_3_4()
    4.(类似 3)
    1. 1400d7240 : push    r10                               ,RSP <-- 1500  
    2. 1400d7242 : mov     r10, 14011A470h                   ,R10 <-- 14011a470  
    3. 1400d724c : pushfq                                    ,RSP <-- 14f8  
    4. 1400d724d : add     r10, 0FFFFFFFFFFFBF569h           ,R10 <-- 1400d99d9  ,RF <-- 3  
    5. 1400d7254 : popfq                                     ,RSP <-- 1500  ,RF <-- 12  
    6. 1400d7255 : jmp     r10                              
    7. 1400d99d9 : pop     r10                               ,RSP <-- 1508  ,R10 <-- 1638  
    复制代码
    可优化成:
    1. 1400d7240 : jmp 1400d99d9
    2. 1400d99d9 : nop
    复制代码
    直接特征识别即可,请参考idapython/TVMunicornTrace.py .tvmFunTask.mabe_3_4()
    去混淆前:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(1)
    去混淆后:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(2)
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(3)
    TVM

    虚拟机的大致架构如下,非常标准。
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(4)
    实际上tvm有多个handle分发器和多张handleTable,但是它们的作用、内容完全一致,所以我就只画出一个handle分发器,下面也只讲解一张handleTable
    如何进入虚拟机

    使用unicorn 跟一次tvm入口到出口,脚本参考:idapython/TVMunicornTrace.py
    初始rsp设置为 0x1800
    入口:(已去混淆)  完整文件请查看trace_file/tvm入口到出口 去混淆.log ,
    未去混淆的查看trace_file/tvm入口到出口 未去混淆.log
    1. 140086efa : call    sub_1400085E8                     ,RSP <-- 17f8  //这个函数被vm
    2. 1400085e8 : jmp     sub_1400D2FF4                     
    3. 1400d2ff4 : lea     rsp, [rsp-248h]                   ,RSP <-- 15b0  //开辟虚拟机栈空间
    4. 1400d2ffc : mov     [rsp+10h], rbp                    //保存进入虚拟机时的寄存器状态
    5. 1400d3001 : mov     rbp, rsp                          ,RBP <-- 15b0  
    6. 1400d3004 : pushfq                                    ,RSP <-- 15a8  
    7. 1400d3005 : pop     [rbp+0h]                                ,RSP <-- 15b0  
    8. 1400d3008 : mov     [rbp+78h], r14               
    9. 1400d3012 : mov     [rbp+30h], rdx               
    10. 1400d3022 : mov     [rbp+50h], r9                 
    11. 1400d3031 : mov     [rbp+8h], rax                 
    12. 1400d303f : mov     [rbp+60h], r11               
    13. 1400d3043 : jmp     short loc_1400D3054               
    14. 1400d305a : mov     [rbp+18h], rbx                    
    15. 1400d305e : mov     [rbp+40h], rsp                    
    16. 1400d3062 : mov     [rbp+28h], rdi                    
    17. 1400d3066 : mov     [rbp+70h], r13                    
    18. 1400d306f : mov     [rbp+48h], r8                     
    19. 1400d3078 : mov     [rbp+80h], r15                    
    20. 1400d3085 : mov     [rbp+58h], r10                    
    21. 1400d308f : mov     [rbp+38h], rsi                    
    22. 1400d3093 : mov     [rbp+68h], r12                    
    23. 1400d3097 : jmp     loc_1400D3145                     
    24. 1400d314c : mov     [rbp+20h], rcx                    
    25. 1400d3156 : pushfq                                    ,RSP <-- 15a8  
    26. 1400d3157 : add     qword ptr [rbp+40h], 248h         #恢复成原来的栈顶
    27. 1400d315f : popfq                                     ,RSP <-- 15b0  
    28. 1400d3160 : lea     r11, [rbp+90h]                    ,R11 <-- 1640  
    29. 1400d3167 : push    qword ptr [rbp+8]                 ,RSP <-- 15a8  
    30. 1400d316a : pop     qword ptr [r11]                   ,RSP <-- 15b0  
    31. 1400d316d : push    qword ptr [rbp+18h]               ,RSP <-- 15a8  
    32. 1400d3170 : pop     qword ptr [r11+8]                 ,RSP <-- 15b0  
    33. 1400d3174 : push    qword ptr [rbp+20h]               ,RSP <-- 15a8  
    34. 1400d3177 : pop     qword ptr [r11+10h]               ,RSP <-- 15b0  
    35. 1400d317b : push    qword ptr [rbp+30h]               ,RSP <-- 15a8  
    36. 1400d317e : pop     qword ptr [r11+18h]               ,RSP <-- 15b0  
    37. 1400d3182 : push    qword ptr [rbp+40h]               ,RSP <-- 15a8  
    38. 1400d3185 : pop     qword ptr [r11+20h]               ,RSP <-- 15b0  
    39. 1400d3189 : push    qword ptr [rbp+10h]               ,RSP <-- 15a8  
    40. 1400d318c : jmp     loc_1400D30DC                     
    41. 1400d30dd : pop     qword ptr [r11+28h]               ,RSP <-- 15b0  
    42. 1400d30e1 : push    qword ptr [rbp+38h]               ,RSP <-- 15a8  
    43. 1400d30e4 : pop     qword ptr [r11+30h]               ,RSP <-- 15b0  
    44. 1400d30e8 : push    qword ptr [rbp+28h]               ,RSP <-- 15a8  
    45. 1400d30eb : pop     qword ptr [r11+38h]               ,RSP <-- 15b0  
    46. 1400d30ef : push    qword ptr [rbp+48h]               ,RSP <-- 15a8  
    47. 1400d30f2 : pop     qword ptr [r11+40h]               ,RSP <-- 15b0  
    48. 1400d30f6 : push    qword ptr [rbp+50h]               ,RSP <-- 15a8  
    49. 1400d30f9 : pop     qword ptr [r11+48h]               ,RSP <-- 15b0  
    50. 1400d30fd : push    qword ptr [rbp+58h]               ,RSP <-- 15a8  
    51. 1400d3100 : pop     qword ptr [r11+50h]               ,RSP <-- 15b0  
    52. 1400d3104 : push    qword ptr [rbp+60h]               ,RSP <-- 15a8  
    53. 1400d3107 : pop     qword ptr [r11+58h]               ,RSP <-- 15b0  
    54. 1400d310b : push    qword ptr [rbp+68h]               ,RSP <-- 15a8  
    55. 1400d310e : pop     qword ptr [r11+60h]               ,RSP <-- 15b0  
    56. 1400d3112 : push    qword ptr [rbp+70h]               ,RSP <-- 15a8  
    57. 1400d3115 : pop     qword ptr [r11+68h]               ,RSP <-- 15b0  
    58. 1400d3119 : push    qword ptr [rbp+78h]               ,RSP <-- 15a8  
    59. 1400d311c : pop     qword ptr [r11+70h]               ,RSP <-- 15b0  
    60. 1400d3120 : push    qword ptr [rbp+80h]               ,RSP <-- 15a8  
    61. 1400d3126 : pop     qword ptr [r11+78h]               ,RSP <-- 15b0  
    62. 1400d312a : push    qword ptr [rbp+0]                 ,RSP <-- 15a8  
    63. 1400d312d : jmp     loc_1400D30A8                     
    64. 1400d30aa : pop     qword ptr [r11+80h]               ,RSP <-- 15b0  
    65. 1400d30b1 : lea     r11, byte_14006024B+1             ,R11 <-- 14006024c  
    66. 1400d30b8 : lea     r10, [rbp+88h]                    ,R10 <-- 1638  
    67. 1400d30bf : lea     rsp, [rsp-8]                      ,RSP <-- 15a8  
    68. 1400d30c4 : mov     [rsp], r10                        
    69. 1400d30c8 : lea     rsp, [rsp-8]                      ,RSP <-- 15a0  
    70. 1400d30cd : mov     [rsp], r11                        
    71. 1400d30d1 : call    sub_1400D5B02                     ,RSP <-- 1598  
    72. 1400d5b02 : lea     rsp, [rsp-8]                      ,RSP <-- 1590  
    73. 1400d5b07 : mov     [rsp+8+var_8], rbx               
    74. 1400d5b0b : lea     rsp, [rsp-8]                      ,RSP <-- 1588  
    75. 1400d5b10 : mov     [rsp+10h+var_10], rsi            
    76. 1400d5b14 : lea     rsp, [rsp-8]                      ,RSP <-- 1580  
    77. 1400d5b19 : mov     [rsp+18h+var_18], rdi            
    78. 1400d5b1d : lea     rsp, [rsp-8]                      ,RSP <-- 1578  
    79. 1400d5b22 : mov     [rsp+20h+var_20], rbp            
    80. 1400d5b26 : lea     rsp, [rsp-8]                      ,RSP <-- 1570  
    81. 1400d5b2b : mov     [rsp+28h+var_28], r15            
    82. 1400d5b2f : sub     rsp, 68h                          ,RSP <-- 1508  ,RF <-- 12  
    83. 1400d5b33 : mov     rbp, rsp                          ,RBP <-- 1508  
    84. 1400d5b38 : mov     r11, [rbp+98h]                    ,R11 <-- V_RIP
    85. 1400d5b42 : jmp     loc_1400D9146                     
    86. 1400d914f : mov     r10, [rbp+0A0h]                   ,R10 <-- V_REG_p
    87. 1400d9156 : jmp     loc_1400D7560                     
    88. 1400d756a : call    loc_1400DA21F                     ,RSP <-- 1500  
    89. 1400da21f : lea     rsp, [rsp+8]                      ,RSP <-- 1508  
    90. 1400da224 : lea     r9, loc_1400E47B0                 ,R9 <-- 1400e47b0  
    91. 1400da22e : jmp     loc_1400D8689                     
    92. 1400d868e : mov     [rbp+0], r9                       
    93. 1400d8695 : jmp     loc_1400DB455                     
    94. 1400db45f : mov     [rbp+8], r11                     
    复制代码
    从 1400d2ffc到1400d315f,tvm第一次保存进入虚拟机前的寄存器状态:
    1. rsp = rbp = 15b0
    2. [15b0](rbp+0)        rflag                                                        
    3. [15b8](rbp+8)        rax
    4. [15c0](rbp+10)        rbp            (原来的栈底)
    5. [15c8](rbp+18)        rbx
    6. [15d0](rbp+20)        rcx
    7. [15d8](rbp+28)        rdi
    8. [15e0](rbp+30)        rdx
    9. [15e8](rbp+38)        rsi
    10. [15f0](rbp+40)        rsp         (原来的栈顶)
    11. [15f8](rbp+48)        r8
    12. [1600](rbp+50)        r9
    13. [1608](rbp+58)        r10
    14. [1610](rbp+60)        r11
    15. [1618](rbp+68)        r12
    16. [1620](rbp+70)        r13
    17. [1628](rbp+78)        r14
    18. [1630](rbp+80)        r15
    复制代码
    接下来是第二次保存进入虚拟机前的寄存器状态:
    1. 1400d3160 : lea     r11, [rbp+90h]                    ,R11 <-- 1640  #接着往栈上保存原始寄存器状态
    复制代码
    从1400d3160到1400d30aa,保存状态如下:
    1. [1640](r11+0)        rax
    2. [1648](r11+8)        rbx
    3. [1650](r11+10)        rcx
    4. [1658](r11+18)        rdx
    5. [1660](r11+20)        rsp
    6. [1668](r11+28)        rbp
    7. [1670](r11+30)        rsi
    8. [1678](r11+38)        rdi
    9. [1680](r11+40)        r8
    10. [1688](r11+48)        r9
    11. [1690](r11+50)        r10
    12. [1698](r11+58)        r11
    13. [16a0](r11+60)        r12
    14. [16a8](r11+68)        r13
    15. [16b0](r11+70)        r14
    16. [16b8](r11+78)        r15
    17. [16c0](r11+80)        rflag
    复制代码
    接下来保存 虚拟机指令起始点和虚拟机寄存器的起始指针
    1. 1400d30b1 : lea     r11, byte_14006024B+1             ,R11 <-- 14006024c  //这个是V_RIP (即虚拟指令起始点)
    2. 1400d30b8 : lea     r10, [rbp+88h]                    ,R10 <-- 1638            //这个是 V_REG_P
    3. 1400d30bf : lea     rsp, [rsp-8]                      ,RSP <-- 15a8  
    4. 1400d30c4 : mov     [rsp], r10                        
    5. 1400d30c8 : lea     rsp, [rsp-8]                      ,RSP <-- 15a0  
    6. 1400d30cd : mov     [rsp], r11                        
    复制代码
    结构如下:
    1. [15a0]        V_RIP                                                        虚拟指令起始点
    2. [15a8]        V_REG_P                                                        1638(虚拟机寄存器)
    复制代码
    14006024c这是当前函数的虚拟指令起始点,可以先记住,后面就知道为什么我这么说了。
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(5)
    接下来进入
    1. 1400d30d1 : call    sub_1400D5B02                     ,RSP <-- 1598  
    复制代码
    进call,return地址入栈:
    1. [1598] = return add(call    sub_1400D5B02 下一行的地址 是int3)
    复制代码
    这个函数,它也会保存一下寄存器状态,但是没啥用:
    1. 1400d5b02 : lea     rsp, [rsp-8]                      ,RSP <-- 1590  
    2. 1400d5b07 : mov     [rsp+8+var_8], rbx               
    3. 1400d5b0b : lea     rsp, [rsp-8]                      ,RSP <-- 1588  
    4. 1400d5b10 : mov     [rsp+10h+var_10], rsi            
    5. 1400d5b14 : lea     rsp, [rsp-8]                      ,RSP <-- 1580  
    6. 1400d5b19 : mov     [rsp+18h+var_18], rdi            
    7. 1400d5b1d : lea     rsp, [rsp-8]                      ,RSP <-- 1578  
    8. 1400d5b22 : mov     [rsp+20h+var_20], rbp            
    9. 1400d5b26 : lea     rsp, [rsp-8]                      ,RSP <-- 1570  
    10. 1400d5b2b : mov     [rsp+28h+var_28], r15            
    复制代码
    保存的状态如下:
    1. [1570]  r15                                                                rbp = 15b0
    2. [1578]  rbp
    3. [1580]  rdi
    4. [1588]  rsi
    5. [1590]  rbx
    复制代码
    然后会把V_RIP换个位置;
    1. 1400d5b2f : sub     rsp, 68h                          ,RSP <-- 1508  ,RF <-- 12  
    2. 1400d5b33 : mov     rbp, rsp                          ,RBP <-- 1508  
    3. 1400d5b38 : mov     r11, [rbp+98h]                    ,R11 <-- V_RIP
    4. 1400d5b42 : jmp     loc_1400D9146                     
    5. 1400d914f : mov     r10, [rbp+0A0h]                   ,R10 <-- V_REG_p
    6. 1400d9156 : jmp     loc_1400D7560                     
    7. 1400d756a : call    loc_1400DA21F                     ,RSP <-- 1500  
    8. 1400da21f : lea     rsp, [rsp+8]                      ,RSP <-- 1508  //进call又rsp+8,假装是jmp
    9. 1400da224 : lea     r9, loc_1400E47B0                 ,R9 <-- 1400e47b0  //这是int3 指令的地址
    10. 1400da22e : jmp     loc_1400D8689                     
    11. 1400d868e : mov     [rbp+0], r9                       
    12. 1400d8695 : jmp     loc_1400DB455                     
    13. 1400db45f : mov     [rbp+8], r11                      V_RIP 放到 [1510]
    复制代码
    接下来就开始处理虚拟指令了。先把栈空间的格式整理一下:(这个地方非常重要)
    1. 虚拟机内RBP = RSP = 1508 ,R10 = V_REG_P
    2. [1508] int3指令指针
    3. [1510] V_RIP                                                                        //当前执行到的位置
    4. .....
    5. [1570]  r15                                                                rbp = 15b0
    6. [1578]  rbp
    7. [1580]  rdi
    8. [1588]  rsi
    9. [1590]  rbx
    10. [1598] = return add(sub_1400D5B02 下一行的地址 是int3)
    11. [15a0]        V_RIP                                                                        //虚拟指令起始点,这是不会变的
    12. [15a8]        V_REG_P                                                                        //1638(虚拟机寄存器),这是不会变的
    13. [15b0](rbp+0)        rflag                                                        //这里以下是进入虚拟机前的寄存器状态
    14. [15b8](rbp+8)        rax
    15. [15c0](rbp+10)        rbp            (原来的栈底)
    16. [15c8](rbp+18)        rbx
    17. [15d0](rbp+20)        rcx
    18. [15d8](rbp+28)        rdi
    19. [15e0](rbp+30)        rdx
    20. [15e8](rbp+38)        rsi
    21. [15f0](rbp+40)        rsp         (原来的栈顶)
    22. [15f8](rbp+48)        r8
    23. [1600](rbp+50)        r9
    24. [1608](rbp+58)        r10
    25. [1610](rbp+60)        r11
    26. [1618](rbp+68)        r12
    27. [1620](rbp+70)        r13
    28. [1628](rbp+78)        r14
    29. [1630](rbp+80)        r15
    30. [1638]        UnKnow                            V_REG_P = r10 = 1638 //虚拟机的虚拟寄存器从 1638 开始往下都是
    31. [1640](r11+0)        rax                                                                //这里以下是进入虚拟机前的寄存器状态
    32. [1648](r11+8)        rbx
    33. [1650](r11+10)        rcx
    34. [1658](r11+18)        rdx
    35. [1660](r11+20)        rsp
    36. [1668](r11+28)        rbp
    37. [1670](r11+30)        rsi
    38. [1678](r11+38)        rdi
    39. [1680](r11+40)        r8
    40. [1688](r11+48)        r9
    41. [1690](r11+50)        r10
    42. [1698](r11+58)        r11
    43. [16a0](r11+60)        r12
    44. [16a8](r11+68)        r13
    45. [16b0](r11+70)        r14
    46. [16b8](r11+78)        r15
    47. [16c0](r11+80)        rflag
    复制代码
    其实只需要记住 R10放着V_REG_P 和 [rbp+8] 放着 V_RIP 。
    handle分发

    这是一段 tvmhandle的分发(tvmopcode的处理方式)
    1. 1400d7234 : mov     r9, [rbp+8]                       ,R9 <-- 14006024c  
    2. 1400d7240 : jmp     loc_1400D99D9                     
    3. 1400d99db : mov     r8b, [r9]                         ,R8 <-- e8  
    4. 1400d99de : xor     r8b, 5Dh                          ,R8 <-- b5  ,RF <-- 82  
    5. 1400d99e2 : mov     rdx, 25E9ECA9BDE22AEAh            ,RDX <-- 25e9eca9bde22aea  
    6. 1400d99ec : not     rdx                               ,RDX <-- da161356421dd515  
    7. 1400d99ef : lea     rdx, [r9+rdx]                     ,RDX <-- da1613578223d761  
    8. 1400d99f3 : jmp     loc_1400D86A6                     
    9. 1400d86a8 : mov     r9, 0DA161356421DD513h            ,R9 <-- da161356421dd513  
    10. 1400d86b2 : not     r9                                ,R9 <-- 25e9eca9bde22aec  
    11. 1400d86b5 : lea     r9, [rdx+r9]                      ,R9 <-- 14006024d  
    12. 1400d86bf : mov     [rbp+8], r9                       
    13. 1400d86c9 : movzx   r8, r8b                           
    14. 1400d86cd : sub     r8, 1                             ,R8 <-- b4  ,RF <-- 6  
    15. 1400d86d1 : jmp     loc_1400D7E10                     
    16. 1400d7e11 : cmp     r8, 0C8h                          ,RF <-- 93  
    17. 1400d7e18 : jnb     loc_1400D9954                     
    18. 1400d7e1e : lea     r9, word_1400DB5AA                ,R9 <-- 1400db5aa  
    19. 1400d7e25 : mov     r8, [r9+r8*8]                     ,R8 <-- d60f2  
    20. 1400d7e29 : lea     r9, cs:140000000h                 ,R9 <-- 140000000  
    21. 1400d7e30 : jmp     loc_1400D6558                     
    22. 1400d6559 : add     r8, r9                            ,R8 <-- 1400d60f2  ,RF <-- 2  
    23. 1400d655c : jmp     r8                                //进入handle
    复制代码
    [rbp + 8] 是 V_RIP,放入r9,然后从[R9] 取出 tvmopcode 放入r8b 然后异或 0x5D,然后 r9 +1(上面是混淆过的,实际效果就是 + 1 )。
    R8b - 1 如果大于0xC8,就跳转到1400D9954,说明这是未知的tvmopcode,出错。1400D9954是int3指令。
    然后 handleTable 放入 R9,取表项 [R9 + R8 *8],即为这个handle的偏移,加上基址0x140000000,即为这个handle 的实际处理地址:1400d60f2,通过 jmp r8 跳转过去。
    (可以看到 取得第一个 tvmopcode 是 0xe8 ,和上面图中的是一样的)
    小总结:

    1.从 tvmopcode --> handle 的方式:
    1. handleTableBase = 0x1400db5aa
    2. Dllbase = 0x140000000
    3. handle = Dllbase + qword[(tvmopcode^0x5d - 1)*8 + handleTableBase]
    复制代码
    注意,tvm有多张handleTable,但是里面的内容都是一样的。所以拿到一张表就行了
    2.handle的个数:

    从 tvmopcode^0x5d - 1 < 0xC8 可以推测一共有 0xc8 ( 0~0xc7 )个 tvmopcode,例:
    1. handleTable的第 0x00 项: tvmopcode = (0x00 + 1) ^ 0x5d = 0x5c
    2. ......
    3. handleTable的第 0x3c 项: tvmopcode = (0x3c + 1) ^ 0x5d = 0x60
    4. ......
    5. handleTable的第 0xc7 项: tvmopcode = (0xc7 + 1) ^ 0x5d = 0x95
    复制代码
    导出所有handle

    现在我们知道,tvm的handleTable有0xc8个有效项,我们就可以遍历handleTable,并且静态跟踪出handle。看它是如何处理的:
    导出handle代码(idapython/TVMHandleOut.py)
    补充:虽然handle有0xc8个有效项,但是很多是重复的,是留作拓展用的,真正有效的handle就 80个:
    左边 是 handle处理地址,右边是 tvmopcode
    1. 1400d9954 : 9a  9b  98  99  9c  9d  e2  e3  e0  e1  e6  e7  e4  eb  ec  ed  f2  f3  f0  f1  f6  f7  f5  fa  fb  f9  fe  ff  c2  c0  c1  c6  c7  c4  c5  c8  ce  cf  cc  cd  db  d8  de  df  dc  dd  23  26  27  24  2a  28  2e  2c  2d  32  33  30  31  36  37  34  35  3a  3b  38  39  3e  3f  3d  02  03  00  07  04  05  0a  0b  08  0e  0f  0d  12  13  10  17  14  15  1a  1b  18  1e  1f  1c  63  61  66  67  64  6b  69  6f  6c  71  76  77  74  7b  79  7e  7f  7c  53  50  55  5b  58  59  5e  5f  5c
    2. 1400d8eb1 : 5a
    3. 1400d5b5a : 54
    4. 1400d5cd8 : 57
    5. 1400db3d3 : 56
    6. 1400d761b : 51
    7. 1400da3e5 : 52
    8. 1400dabb0 : 4d
    9. 1400d8196 : 4c
    10. 1400d6c8c : 4f
    11. 1400d82fa : 4e
    12. 1400d6e7d : 49
    13. 1400d7172 : 48
    14. 1400d9374 : 4b
    15. 1400daa8e : 4a
    16. 1400db0f9 : 45
    17. 1400dae51 : 44
    18. 1400d8be2 : 47
    19. 1400d946b : 46
    20. 1400d995a : 41
    21. 1400daf90 : 40
    22. 1400d7a8a : 43
    23. 1400d5e10 : 42
    24. 1400d7258 : 7d
    25. 1400daecb : 78
    26. 1400d5d02 : 7a
    27. 1400d974c : 75
    28. 1400d7f26 : 70
    29. 1400d8e1b : 73
    30. 1400d8dbf : 72
    31. 1400d8362 : 6d
    32. 1400d6fc9 : 6e
    33. 1400d7c1f : 68
    34. 1400d63cd : 6a
    35. 1400d9abb : 65
    36. 1400d8431 : 60
    37. 1400d7797 : 62
    38. 1400da161 : 1d
    39. 1400d7313 : 19
    40. 1400d92b0 : 16
    41. 1400d9c5c : 11
    42. 1400dab1e : 0c
    43. 1400d86e2 : 09
    44. 1400d7352 : 06
    45. 1400dad5e : 01
    46. 1400d9524 : 3c
    47. 1400dad2c : 2f
    48. 1400d8b8d : 29
    49. 1400d7d39 : 2b
    50. 1400d8f7b : 25
    51. 1400d696f : 21
    52. 1400d71f5 : 20
    53. 1400d71b3 : 22
    54. 1400d770e : d9
    55. 1400da48c : da
    56. 1400d74d2 : d5
    57. 1400d70ee : d4
    58. 1400d95ad : d7
    59. 1400d6f6c : d6
    60. 1400d84d0 : d1
    61. 1400da357 : d0
    62. 1400d78f9 : d3
    63. 1400d7998 : d2
    64. 1400d8561 : c9
    65. 1400d624c : cb
    66. 1400d9169 : ca
    67. 1400d64ac : c3
    68. 1400d5c7b : fd
    69. 1400d9dd5 : fc
    70. 1400d6913 : f8
    71. 1400dafcf : f4
    72. 1400d7bc7 : ef
    73. 1400d7fc4 : ee
    74. 1400d9446 : e9
    75. 1400d60f2 : e8
    76. 1400d69e8 : ea
    77. 1400d9584 : e5
    78. 1400da10c : 9f
    79. 1400d8e89 : 9e
    80. 1400d8cdd : 95
    复制代码
    并在在 当前文件夹/handleout 文件夹内,输出全部handle静态跟踪(运行脚本的同时会对handle去简单的混淆)
    :(刚好80个不相同的handle,文件名用 tvmopcode)
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(6)
    一共有80个不同功能的tvmopcode,为节省篇幅,我这里挑一个常见的讲解,全部的handle分析,其余的全部放在(handle_out/)
    (那个特别多的是int3,应该是预留以后更新用的)
    我静态跟踪handle是以 jnb 为结尾的(就是判断是否大于0xc8后的jnb),所以跟踪文件后半段有一些不用看。
    1. 0xe8 p_a b_ULONG64
    2. 0x3F26
    3. *(PULONG64)p_a = b_ULONG64;
    4. v_mov_iregll_ll
    5. ----------------------------------------//虚线以上是分析出来的
    6. 0x1400d60f5 : mov     r9, [rbp+8]                     
    7. 0x1400d6102 : mov     r8w, [r9]                       这里取2字节 (取虚拟寄存器都是取2字节)
    8. 0x1400d6106 : xor     r8w, 3F26h                      异或 3F26(不同的handl异或的值不同) 得到 a,
    9. 0x1400d610c : mov     rdx, 0F84A86395161A270h         所以就是取虚拟机寄存器  V_REG_P + a
    10. 0x1400d6116 : not     rdx                             
    11. 0x1400d6119 : jmp     loc_1400D9077                  
    12. 0x1400d9078 : lea     rdx, [r10+rdx]                  
    13. 0x1400d907c : movzx   r8, r8w                        
    14. 0x1400d9080 : mov     rcx, 7B579C6AE9E5D8Eh           
    15. 0x1400d908a : not     rcx                             
    16. 0x1400d908d : add     r8, rcx                        
    17. 0x1400d9090 : lea     r8, [rdx+r8]                    p_a = r8 = V_REG_P + a
    18. 0x1400d9094 : lea     r9, [r9+2]                      V_RIP+=2
    19. 0x1400d909b : mov     rdx, [r9]                       b_ULONG64  从tvm指令表中取8字节
    20. 0x1400d909e : jmp     loc_1400DA2DD                  
    21. 0x1400da2ed : mov     [r8], rdx                       放入 [r8],即放入 p_a
    22. 0x1400da2f0 : jmp     loc_1400DA9AC                  
    23. 0x1400da9b4 : lea     r9, [r9+8]                      V_RIP+=8
    24. 0x1400da9be : mov     [rbp+8], r9                     V_RIP放回[rbp+8]
    25. 0x1400da9c8 : jmp     loc_1400DAC79                  
    26. 0x1400dac7c : mov     r9, [rbp+8]                     这里以下属于下一个handle分发,不用看
    27. 0x1400dac89 : mov     r8b, [r9]                       
    28. 0x1400dac8c : xor     r8b, 5Dh                        
    29. 0x1400dac90 : mov     rdx, 0D3676A56DAFF3C65h         
    30. 0x1400dac9a : jmp     loc_1400D7763                  
    31. 0x1400d7764 : not     rdx                             
    32. 0x1400d7767 : lea     rdx, [r9+rdx]                  
    33. 0x1400d776b : mov     r9, 2C9895A92500C398h           
    34. 0x1400d7775 : not     r9                              
    35. 0x1400d7778 : lea     r9, [rdx+r9]                    
    36. 0x1400d777f : jmp     loc_1400D5B91                  
    37. 0x1400d5b95 : mov     [rbp+8], r9                     
    38. 0x1400d5b9e : movzx   r8, r8b                        
    39. 0x1400d5ba2 : sub     r8, 1; switch 200 cases         
    40. 0x1400d5ba6 : jmp     loc_1400D98A8                  
    41. 0x1400d98aa : cmp     r8, 0C8h                        
    42. 0x1400d98b1 : jnb     def_1400D655C;
    复制代码
    我对 tvmAsm的命名规则:
    1. v_opcode_op0_op1_op2_....._opn
    2. 例如:
    3. v_mov_iregw_iregw :
    4. 从虚拟寄存器op1 取 2字节 放入虚拟寄存器op0
    5. w 表示 USHORT
    6. v_mov_ipreg_iregl :
    7. 从虚拟机寄存器op1 取4字节,放入以虚拟机寄存器op0作为地址的空间,有点类似与:
    8. mov [reg],reg
    9. v_mov_iregb_b :
    10. 从tvm指令表中取1字节放入虚拟机寄存器op0中,类似于Asm中的立即数赋值:
    11. mov reg,0xff
    12. v_and_oregl_iregl_iregl_oregl:(i 就是 in ,o 就是 out 的意思)
    13. 取虚拟寄存器op1 和 虚拟寄存器op2进行与运算,并将结果放入虚拟寄存器op0,与运算后的 rflag 放入 虚拟寄存器op3
    14. l 表示 ULONG32
    15. v_cmp_iregll_iregll_oregl:
    16. 即为 cmp 虚拟寄存器op0,虚拟寄存器op1 ,cmp后的rflag 放入 虚拟寄存器op2
    17. ll 表示 ULONG64
    复制代码
    我这里直接将全部tvmAsm展示出来:(参考 idapython/deTvm.py . tvmHandleTableInit())
    1. TVMTABEL.append("v_sar_oregll_iregll_iregb_oregl",0x01,0xF8BE)
    2.     TVMTABEL.append("v_or_oregll_iregll_iregll_oregl",0x1D,0x4AA7)
    3.     TVMTABEL.append("v_mov_iregll_iregl",0x2B,0xBF3E)
    4.     TVMTABEL.append("v_movzx_iregl_iregb",0x2F,0x7EE9)
    5.     TVMTABEL.append("v_ror_oregb_iregb_iregb_oregl",0x3C,0xF8E1)
    6.     TVMTABEL.append("v_mov_iregl_iregl",0x4A,0x564B)
    7.     TVMTABEL.append("v_mov_iregw_iregw",0x4B,0xD916)
    8.     TVMTABEL.append("v_add_oregb_iregb_iregb_oregl",0x4C,0xDD9D)
    9.     TVMTABEL.append("v_add_oregll_iregll_iregll",0x4D,0x477D)
    10.     TVMTABEL.append("v_add_oregl_iregl_iregl_oregl",0x4E,0xA9C7)
    11.     TVMTABEL.append("v_add_oregw_iregw_iregw_oregl",0x4F,0x82BC)
    12.     TVMTABEL.append("v_sub_oregl_iregl_iregl_oregl",0x5A,0xC198)
    13.     TVMTABEL.append("v_sar_oregl_iregl_iregb_oregl",0x06,0x8374)
    14.     TVMTABEL.append("v_and_oregl_iregl_iregl_oregl",0x6A,0x5CF0)
    15.     TVMTABEL.append("v_and_oregll_iregll_iregll_oregl",0x6D,0xD9B1)
    16.     TVMTABEL.append("v_and_oregl_iregl_iregl",0x6E,0xA1CE)
    17.     TVMTABEL.append("v_xor_oregl_iregl_iregl_oregl",0x7A,0xD8ED)
    18.     TVMTABEL.append("v_mov_ipreg_iregll",0x7D,0xD878)
    19.     TVMTABEL.append("v_shr_oregll_iregll_iregb_oregl",0x09,0x9D87)
    20.     TVMTABEL.append("v_int3",[0x9a,0x9b,0x98,0x99,0x9c,0x9d,0xe2,0xe3,0xe0,0xe1,0xe6,0xe7,0xe4,0xeb,0xec,0xed,0xf2,0xf3,0xf0,0xf1,0xf6,0xf7,0xf5,0xfa,0xfb,0xf9,0xfe,0xff,0xc2,0xc0,0xc1,0xc6,0xc7,0xc4,0xc5,0xc8,0xce,0xcf,0xcc,0xcd,0xdb,0xd8,0xde,0xdf,0xdc,0xdd,0x23,0x26,0x27,0x24,0x2a,0x28,0x2e,0x2c,0x2d,0x32,0x33,0x30,0x31,0x36,0x37,0x34,0x35,0x3a,0x3b,0x38,0x39,0x3e,0x3f,0x3d,0x2,0x3,0x0,0x7,0x4,0x5,0xa,0xb,0x8,0xe,0xf,0xd,0x12,0x13,0x10,0x17,0x14,0x15,0x1a,0x1b,0x18,0x1e,0x1f,0x1c,0x63,0x61,0x66,0x67,0x64,0x6b,0x69,0x6f,0x6c,0x71,0x76,0x77,0x74,0x7b,0x79,0x7e,0x7f,0x7c,0x53,0x50,0x55,0x5b,0x58,0x59,0x5e,0x5f,0x5c],0x0000)                                    
    21.     TVMTABEL.append("v_jmp_iregxR11",0x9E,0x0AD7)                           
    22.     TVMTABEL.append("v_jmp_iregxR10",0x9F,0x2E72)                           
    23.     TVMTABEL.append("v_shl_oregll_iregll_iregb_oregl",0x11,0x5403)
    24.     TVMTABEL.append("v_shl_oregl_iregl_iregb_oregl",0x16,0xEEF7)
    25.     TVMTABEL.append("v_not_oregll_iregll",0x19,0x1400)
    26.     TVMTABEL.append("v_setz_oregb_iregl",0x20,0x0D45)
    27.     TVMTABEL.append("v_movsxd_iregll_iregl",0x21,0x8BC8)
    28.     TVMTABEL.append("v_setR8d_iregl",0x22,0x77D7)
    29.     TVMTABEL.append("v_movsx_iregl_iregb",0x25,0xD8E4)
    30.     TVMTABEL.append("v_movzx_iregl_iregw",0x29,0xF10A)
    31.     TVMTABEL.append("v_mov_ipreg_iregb",0x40,0xE304)
    32.     TVMTABEL.append("v_mov_iregll_ipreg",0x41,0xE229)
    33.     TVMTABEL.append("v_mov_ipreg_iregl",0x42,0x5431)
    34.     TVMTABEL.append("v_mov_ipreg_iregw",0x43,0x02CB)
    35.     TVMTABEL.append("v_mov_iregb_ipreg",0x44,0xBE8C)
    36.     TVMTABEL.append("v_mov_iregll_iregll",0x45,0x58FB)
    37.     TVMTABEL.append("v_mov_iregl_ipreg",0x46,0x10BC)
    38.     TVMTABEL.append("v_mov_iregw_ipreg",0x47,0x6F62)
    39.     TVMTABEL.append("v_mov_iregb_iregb",0x48,0xCFFE)
    40.     TVMTABEL.append("v_add_oregll_iregll_iregll_oregl",0x49,0x41AA)
    41.     TVMTABEL.append("v_not_oregll_iregll",0x51,0xDB42)
    42.     TVMTABEL.append("v_add_oregl_iregl_iregl",0x52,0x77C6)
    43.     TVMTABEL.append("v_not_oregb_iregb",0x54,0xDCF3)
    44.     TVMTABEL.append("v_not_oregl_iregl",0x56,0xE297)
    45.     TVMTABEL.append("v_not_oregw_iregw",0x57,0x666D)
    46.     TVMTABEL.append("v_or_oregb_iregb_iregb_oregl",0x60,0xFBFD)
    47.     TVMTABEL.append("v_or_oregl_iregl_iregl_oregl",0x62,0x7819)
    48.     TVMTABEL.append("v_and_oregll_iregll_iregll_oregl",0x65,0x2954)
    49.     TVMTABEL.append("v_and_oregb_iregb_iregb_oregl",0x68,0xD8A5)
    50.     TVMTABEL.append("v_and_oregb_iregb_iregb_oregl",0x70,0x64D1)
    51.     TVMTABEL.append("v_and_oregl_iregl_iregl_oregl",0x72,0x4A64)
    52.     TVMTABEL.append("v_and_oregw_iregw_iregw_oregl",0x73,0xB562)
    53.     TVMTABEL.append("v_xor_oregll_iregll_iregll_oregl",0x75,0x69C2)
    54.     TVMTABEL.append("v_xor_oregb_iregb_iregb_oregl",0x78,0x19C1)
    55.     TVMTABEL.append("v_ret_iregx",0x95,0x805C)                                 
    56.     TVMTABEL.append("v_shr_oregb_iregb_iregb_oregl",0x0c,0x62D7)
    57.     TVMTABEL.append("v_dec_oregl_iregl_oregl",0xc3,0x467C)
    58.     TVMTABEL.append("v_inc_oregb_iregb_oregl",0xc9,0x4267)
    59.     TVMTABEL.append("v_inc_oregll_iregll_oregl",0xca,0x6EE0)
    60.     TVMTABEL.append("v_inc_oregl_iregl_oregl",0xcb,0x7FB4)
    61.     TVMTABEL.append("v_test_iregw_iregw_oregl",0xd0,0x0499)
    62.     TVMTABEL.append("v_test_iregb_iregb_oregl",0xd1,0x2A99)
    63.     TVMTABEL.append("v_test_iregll_iregll_oregl",0xd2,0x8606)
    64.     TVMTABEL.append("v_test_iregl_iregl_oregl",0xd3,0x7FDE)
    65.     TVMTABEL.append("v_cmp_iregw_iregw_oregl",0xd4,0x87DF)
    66.     TVMTABEL.append("v_cmp_iregb_iregb_oregl",0xd5,0x3728)
    67.     TVMTABEL.append("v_cmp_iregll_iregll_oregl",0xd6,0x637D)
    68.     TVMTABEL.append("v_cmp_iregl_iregl_oregl",0xd7,0xCBEF)
    69.     TVMTABEL.append("v_sbb_oregb_iregb_iregb_oregl",0xd9,0x3F78)
    70.     TVMTABEL.append("v_sbb_oregll_iregll_iregll_oregl",0xda,0x0E0D)
    71.     TVMTABEL.append("v_jmp_iregxRax",0xe5,0xCD84)
    72.     TVMTABEL.append("v_mov_iregll_ll",0xe8,0x3F26)
    73.     TVMTABEL.append("v_mov_iregl_l",0xe9,0x448A)
    74.     TVMTABEL.append("v_mov_iregll_ll",0xea,0x43EF)
    75.     TVMTABEL.append("v_mov_iregw_w",0xee,0x44B1)
    76.     TVMTABEL.append("v_mov_iregb_b",0xef,0x144C)
    77.     TVMTABEL.append("v_mul_oregll_oregll_iregll_iregll",0xf4,0xEB97)
    78.     TVMTABEL.append("v_jmp_ll",0xf8,0x0000)
    79.     TVMTABEL.append("v_rep stosb_iregll_iregb_iregll",0xfc,0x8D54)
    80.     TVMTABEL.append("v_je_iregb_ll_ll",0xfd,0xDE5E)
    复制代码
    解释:
    TVMTABEL.append 第一个参数是我给tvmAsm取的名字,第二个参数就是 tvmopcode,第三个参数是:如果这个handle要取虚拟寄存器,就必须通过 这个值解密取得虚拟寄存器,就像是我上文中解释的:
    1. TVMTABEL.append("v_mov_iregll_ll",0xe8,0x3F26)
    复制代码
    这里再挑几个特殊说明一下:
    1. TVMTABEL.append("v_jmp_ll",0xf8,0x0000):
    2. 因为有一些指令,tvm并没有对其进行模拟,所以需要临时退出虚拟机,然后执行那种指令,再返回虚拟机。(下文细说)
    3. TVMTABEL.append("v_je_iregb_ll_ll",0xfd,0xDE5E)
    4. 当虚拟机寄存器op0为0x1时,V_RIP + op1 否则 V_RIP + op2,以实现虚拟机内的跳转
    5. TVMTABEL.append("v_ret_iregx",0x95,0x805C)
    6. 这是退出虚拟机的tvmasm,具体实现是恢复原始寄存器,然后通过ret退出虚拟机。
    7. TVMTABEL.append("v_jmp_iregxR11",0x9E,0x0AD7)
    8. TVMTABEL.append("v_jmp_iregxR10",0x9F,0x2E72)
    9. TVMTABEL.append("v_jmp_iregxRax",0xe5,0xCD84)
    10. 这三个tvmasm也是恢复原始寄存器,但是是通过jmp 跳出虚拟机 jmp r11、jmp r10、jmp rax
    11. TVMTABEL.append("v_mul_oregll_oregll_iregll_iregll",0xf4,0xEB97)
    12. 乘法,虚拟机寄存器op2 * 虚拟机寄存器op3
    13. 结果高64位放入 虚拟机寄存器op0 ,低64位放入 虚拟机寄存器op1
    复制代码
    把tvm的跟踪规则写好后,就可以跟踪导出这个函数的虚拟化控制流:跟踪参考
    idapython/deTvm.py . traceTask.track()
    获取 tvmAsmTrace:

    这是我挑的一个短的函数:0x140001250 参考trace_file/sub_0x140001250.log
    使用函数traceTask.track(0) + traceTask.traceOut(),输出如下
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(7)
    可能会好奇这些 PO_reg 怎么来的,其实这是我对 traceCode的优化:
    1. XXREGNAME = {"PO_rax":0x08,
    2.              "PO_rbx":0x10,
    3.              "PO_rcx":0x18,
    4.              "PO_rdx":0x20,
    5.              "PO_rsp":0x28,
    6.              "PO_rbp":0x30,
    7.              "PO_rsi":0x38,
    8.              "PO_rdi":0x40,
    9.              "PO_r8":0x48,
    10.              "PO_r9":0x50,
    11.              "PO_r10":0x58,
    12.              "PO_r11":0x60,
    13.              "PO_r12":0x68,
    14.              "PO_r13":0x70,
    15.              "PO_r14":0x78,
    16.              "PO_r15":0x80,
    17.              "PO_rf":0x88}
    复制代码
    正常的取 虚拟机寄存器 都是 [r10 + xxx],r10 就是 V_REG_P,前面说过了,看下面这一段(前面也出现过)。
    1. [1638]        UnKnow                            V_REG_P = r10 = 1638 //虚拟机的虚拟寄存器从 1638 开始往下都是
    2. [1640](r11+0)        rax                                                                //这里以下是进入虚拟机前的寄存器状态
    3. [1648](r11+8)        rbx
    4. [1650](r11+10)        rcx
    5. [1658](r11+18)        rdx
    6. [1660](r11+20)        rsp
    7. [1668](r11+28)        rbp
    8. [1670](r11+30)        rsi
    9. [1678](r11+38)        rdi
    10. [1680](r11+40)        r8
    11. [1688](r11+48)        r9
    12. [1690](r11+50)        r10
    13. [1698](r11+58)        r11
    14. [16a0](r11+60)        r12
    15. [16a8](r11+68)        r13
    16. [16b0](r11+70)        r14
    17. [16b8](r11+78)        r15
    18. [16c0](r11+80)        rflag
    复制代码
    例如 PO_r8 其实就是 [r10 + 0x48]
    所以 tvmAsm对 PO_reg 操作 可以理解为对虚拟机外的真实寄存器操作。
    tvmAsm to Asm

    这里我使用了 标记working + 赋值表记录 的方法,将所有有意义的 tvmAsm找出来。
    先说哪种tvmAsm会被标记为 working:(标记为working表明至少可以还原出一条原始Asm)
    1. v_mov_iregll_iregll                ( iregll :PO_rsp         ,iregll :[ r10 + 0xa8 ]  );
    2. 第一个参数必须为 原始寄存器 PO_reg
    3. 这一句 ,会将 [r10 + 0xa8]的值放入 PO_rsp ,
    4. 那么我们就可以推测,原Asm 可能为 mov rsp,xxx (也不一定对,但至少可以还原出一条Asm)
    5. v_mov_ipreg_iregll                ( iregll :PO_rsp         ,iregll :[ r10 + 0xa8 ]  );
    6. 同理可以推测 原Asm 为 mov qword ptr[rsp],xxx(也不一定对,但至少可以还原出一条Asm)
    7. v_jmp_ll                           ( ll     :0x1400c7588     );
    8. 上文说过,tvm并不能模拟全部指令,有一些指令需要临时退出虚拟机去执行,所以遇到这种指令,必然可以还原出一条Asm
    9. v_ret_iregx、v_jmp_iregxR11、v_jmp_iregxR10、v_jmp_iregxRax
    10. 上文说过,这几个指令可以直接还原成 ret、jmp r11、jmp r10、jmp rax
    11. v_je_iregb_ll_ll
    12. 这个可以还原出 jcc + jmp (下文详解)
    13. v_rep stosb_iregll_iregb_iregll
    14. 这个可以还原出 rep stosb ,不用管参数
    15. v_test_iregx_iregx_oregl
    16. 可以还原成  test
    17. v_cmp_iregx_iregx_oregl
    18. 可以还原成 cmp
    复制代码
    只要标记好这几个点,就能还原出全部的Asm。参考(idapython/deTvm.py . traceTask.track())
    那么我们标记好后,这段 tvm指令就如下:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(8)
    箭头指着的就是标记为working的TraceCode。那么接下来要干嘛,就很清晰了,把相关的traceCode找出来(变量溯源)。
    例子1:
    这一句被标记为working,我们找他使用的参数的赋值语句,直到 找到 整数 或 PO_reg
    1. 140059e46 : v_mov_iregll_iregll                ( iregll :PO_rsp         ,iregll :[ r10 + 0xa8 ]  );<-----
    复制代码
    我们把 [r10 + 0xa8]的赋值语句找出来(往上找):
    1. 140059e28 : v_and_oregll_iregll_iregll_oregl   ( oregll :[ r10 + 0xa8 ] ,iregll :[ r10 + 0xb8 ] ,iregll :[ r10 + 0xb8 ] ,oregl  :PO_rf           );
    复制代码
    这一句用到了 [r10 + 0xb8],找它的赋值语句:
    1. 140059e23 : v_not_oregll_iregll                ( oregll :[ r10 + 0xb8 ] ,iregll :[ r10 + 0xb8 ]  );
    复制代码
    又是 [r10 + 0xb8],再往上找:
    1. 140059e07 : v_add_oregll_iregll_iregll_oregl   ( oregll :[ r10 + 0xb8 ] ,iregll :[ r10 + 0xb0 ] ,iregll :[ r10 + 0xa0 ] ,oregl  :PO_rf           );
    复制代码
    用到 [r10 + 0xb0] 和 [r10 + 0xa0],往上找:
    1. [r10 + 0xb0]:
    2. 140059e02 : v_not_oregll_iregll                ( oregll :[ r10 + 0xb0 ] ,iregll :[ r10 + 0xa8 ]  );
    3. [r10 + 0xa0]:
    4. 140059df7 : v_mov_iregll_ll                    ( iregll :[ r10 + 0xa0 ] ,ll     :0x28            );
    复制代码
    [r10 + 0xa0]找到尽头了,[r10 + 0xb0]还没找到尽头,继续网上找[ r10 + 0xa8 ]的赋值语句:
    1. 140059df2 : v_mov_iregll_iregll                ( iregll :[ r10 + 0xa8 ] ,iregll :PO_rsp          );
    复制代码
    找到尽头,是将 PO_rsp 放入。我们把这些 traceCode放在一起:
    参考 idapython/deTvm.py . traceTask.VRegRecord()
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(9)
    这样也有点不好看,用变量传播优化一下:
    参考idapython/deTvm.py . tvmToAsm.optimize()
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(10)
    这一些 traceCode,就可以还原出一句 Asm:(以下我们对这一段可还原成Asm的TraceCode集合统称为一个 tvmToAsm 结构
    1. [r10 + 0xb8] =  ~(~rsp + 0x28) = rsp - 0x28           //前五句结合
    2. [r10 + 0xa8] = [r10 + 0xb8] & [r10 + 0xb8] = [r10 + 0xb8]    //没变化
    复制代码
    于是这一段就可以还原成:
    1. sub rsp,0x28
    复制代码
    为什么是 sub rsp,0x28而不是 lea rsp,[rsp - 0x28]是有讲究的:
    看traceCode中的一句 v_add_oregll_iregll_iregll_oregl,他是有输出 rflag的,并且放入的位置就是 PO_rf,说明这一句ASM是会影响标志位,而 lea 是不影响标志位的,所以将它还原成 sub。
    手动还原 Asm

    我们顺势对这个函数的所有被标记为working 的traceCode进行变量溯源+优化,那么最后就可以得到:tvmToAsmAll
    一个函数内的所有 tvmToAsm 组成一个 tvmToAsmAll ,下图中一段一段的就是 tvmToAsm
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(11)
    看,真正有效的就这一些,其余的都可以看作花指令。所以这个函数的原始ASM就是:(手动还原)
    1. sub rsp,0x28
    2. mov rcx,0x1400011a0
    3. call    sub_140005E38                                #下文解释为什么 V_jmp_ll(0x1400c7588) 可以转换成 这个
    4. mov byte ptr[0x14000d264],0x0
    5. add rsp,0x28
    6. ret
    复制代码
    关于v_jmp_ll

    上文说过,tvm不能模拟全部的Asm,所以有些Asm需要暂时退出虚拟机执行,然后再返回虚拟机:
    我们到0x1400c7588,然后往下跟(中间是还原真实寄存器),
    直到出现 mov rsp,[rsp] ,之后的下一句就是真实需要执行的ASM了。即为 call    sub_140005E38。
    接下来会重新进入虚拟机,步骤和上文进入虚拟机的步骤大致相同。
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(12)
    所以 v_jmp_ll 是最容易还原成 Asm 之一的 tvmAsm了。
    参考idapython/deTvm.py . tvmToAsm.vjmp_handle()
    补充:

    关于如何找到workingTraceCode的相关traceCode,我的方案如下:(如果你有其他方案,可以不用看这一段)
    把全部traceCode的赋值语句找出来,然后给相关虚拟机寄存器添加赋值记录,形成一张赋值表,例如:
    1. 140059dc2 : v_mov_iregb_b                      ( iregb  :[ r10 + 0x90 ] ,b      :0x6             );
    2. 140059dc6 : v_mov_iregll_ll                    ( iregll :[ r10 + 0x98 ] ,ll     :0x1             );
    3. 140059dd1 : v_mov_iregll_ll                    ( iregll :[ r10 + 0xa0 ] ,ll     :0x1             );
    4. 140059ddc : v_mov_iregll_ll                    ( iregll :[ r10 + 0xa8 ] ,ll     :0x600           );
    5. 140059de7 : v_mov_iregll_ll                    ( iregll :[ r10 + 0x0 ]  ,ll     :0x140001250     );
    6. 140059df2 : v_mov_iregll_iregll                ( iregll :[ r10 + 0xa8 ] ,iregll :PO_rsp          );
    7. 140059df7 : v_mov_iregll_ll                    ( iregll :[ r10 + 0xa0 ] ,ll     :0x28            );
    8. 140059e02 : v_not_oregll_iregll                ( oregll :[ r10 + 0xb0 ] ,iregll :[ r10 + 0xa8 ]  );
    9. 140059e07 : v_add_oregll_iregll_iregll_oregl   ( oregll :[ r10 + 0xb8 ] ,iregll :[ r10 + 0xb0 ] ,iregll :[ r10 + 0xa0 ] ,oregl  :PO_rf           );
    复制代码
    我就可以得到这么一张表:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(13)
    1. traceTaskRegTable为总赋值表,
    2. traceTaskRegList为单个虚拟机寄存器的赋值栈,
    3. traceTaskReg为入栈的单个记录。
    4. 相关定义参考:idapython/deTvm.py 的三个类,命名同上
    复制代码
    当需要检索这一句traceCode的相关traceCode时:
    1. 140059e02 : v_not_oregll_iregll                ( oregll :[ r10 + 0xb0 ] ,iregll :[ r10 + 0xa8 ]  );
    复制代码
    就可以直接查表,先找到[ r10 + 0xa8 ]的赋值记录栈traceTaskRegList,然后通过地址找到最近的一次赋值traceTaskReg,然后traceTaskReg记录了这一句traceCode,就可以找到了。
    于是就找到了相关traceCode:
    1. 140059df2 : v_mov_iregll_iregll                ( iregll :[ r10 + 0xa8 ] ,iregll :PO_rsp          );
    复制代码
    因为是将 PO_reg 赋值给它,所以到此检索完毕,如果不是,则按照相同的方法继续往上找。
    相关代码参考:idapython/deTvm.py . traceTask.VRegRecord()
    一些特殊的例子

    例如有两个连续的 tvmToAsm,导出的相关traceCode如下:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(14)
    我们可以发现,有两段相关traceCode是相同的,这就出现问题了:
    第一个tvmToAsm可以翻译成 xor edi,edi,那么第二个tvmToAsm能翻译成什么呢?
    如果我们人为识别,就可以将其翻译成 mov ecx,edi,因为 [r10 + 450]在前面已经放入了 PO_rdi,下面又取它放入 PO_rcx。
    于是我们可以进行优化,如果 有一句 v_mov_iregx_iregx(PO_reg , xxxx)那么我们就可以将 xxxx的上一次赋值标记为PO_reg,
    还是以上面的那一段代码为例:
    1. v_mov_iregll_iregll                ( iregll:PO_rdi         ,iregll:[ r10 + 0x450 ] ); <-----------------
    复制代码
    [r10 + 0x450]上一次赋值语句为:
    1. v_mov_iregll_iregl                 ( iregll:[ r10 + 0x450 ],iregl :[ r10 + 0x98 ]  );
    复制代码
    我们可以将其标记为 PO_rdi,那么当其他的workingTraceCode向上进行查找赋值表的时候,就可以找到PO_rdi,于是就优化为了:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(15)
    实现代码参考:idapython/deTvm.py . traceTask.VRegRecord()
    关于push和pop

    push:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(16)
    这是两个连续的 tvmToAsm,
    如果按照一般的分析方式,那么这两句可以翻译为:
    1. mov qword ptr[rsp - 0x8],r13
    2. lea rsp,[rsp - 8]
    复制代码
    乍一看没什么问题,就是将 push r13分开成两句执行,可是如果是pop,那么情况就有点不同了:
    pop:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(17)
    如果按照一般的分析方式,那么这两句可以翻译为:
    1. lea rsp,[rsp + 8]
    2. mov r13,qword ptr[rsp]
    复制代码
    这就出问题了,这两句并不等于 pop r13 指令,那么问题出在哪呢,我们取消掉变量传播优化再看看:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(18)
    问题就出在,这两句 workingTraceCode在进行变量溯源时,都找到了 0x14004dca7 这一句,
    将 PO_rsp 放入虚拟机寄存器 [r10 + 0xa8],并且在第一句workingTraceCode中,又更改了 PO_rsp的值,所以导致出错。
    幸运的是这种情况只会出现在 push 和 pop 中(参考idapython/deTvm.py . tvmToAsmAll.outerror()),
    所以我们要对 push 和 pop 特殊处理,请参考:idapython/deTvm.py . tvmToAsmAll.findPushAndPop()
    特殊处理,优化后的 push 和 pop:
    push:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(19)
    pop:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(20)
    这就清晰很多了。
    关于 jcc

    前置知识:
    各个标志位的位置:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(21)
    1. JO                                jmp if OF = 1
    2. JNO                                jmp if OF = 0
    3. JB JC JNAE                jmp if CF = 1
    4. JNB JNC JAE                jmp if CF = 0
    5. JZ JE                        jmp if ZF = 1
    6. JNZ JNZ                        jmp if ZF = 0
    7. JBE JNA                        jmp if CF = 1 or  ZF = 1
    8. JNBE JA                        jmp if CF = 0 and ZF = 0
    9. JS                                jmp if SF = 1
    10. JNS                                jmp if SF = 0
    11. JP JPE                        jmp if PF = 1
    12. JNP JPO                        jmp if PF = 0
    13. JL JNGE                        jmp if SF != OF
    14. JNL JGE                        jmp if SF = OF
    15. JLE JNG                        jmp if ZF = 1 or SF != OF
    16. JNLE JG                        jmp if ZF = 0 and SF = OF
    复制代码
    tvm巧妙的利用了and sub  setz je 这四种指令模拟一个jcc,例如:
    JE:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(22)
    (v_je 的跳转是通过 加减 V_RIP 实现的,我这里直接优化成 绝对地址,省了我们去计算)
    上面的代码,先保留 rflag 的zf位,然后再减去zf位,如果结果为0,那么 V_RIP 就变成 v_je  的第二个操作数 0x14003c274否则 V_RIP变为0x14003c1fe
    于是,上面这段tvmasm可以翻译为:
    1. je xxx(V_RIP = 0x14003c274)
    2. jmp xxx(V_RIP = 0x14003c1fe)
    复制代码
    JG

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(23)
    第一个tvmToAsm:
    如果 zf = sf = of = 0,则V_RIP = 0x1400369ec ,否则 V_RIP = 0x140036ac6 (其实就是下面那一段,因为优化了所以地址对不上)
    第二个tvmToAsm:
    如果 zf = 0 且 sf = of = 1,则V_RIP = 0x1400369ec,否则 VRIP = 0x1400368ef
    于是,上面这段tvmasm可以翻译为:
    1. jg xxx(V_RIP = 0x1400369ec)
    2. jmp xxx(V_RIP = 0x1400368ef)
    复制代码
    提取特征

    JE 的特征 0x40 0x40 (看上图你就知道是什么特征了)
    JG 的特征 0x8c0 0x0 0x8c0 0x880(看上图你就知道是什么特征了)
    全部的jcc特征:(代码参考idapython/deTvm.py . tvmToAsm.vjcc_handle())
    1. JBE JNA  
    2. 0x41 0x1 0x41 0x40 (这其实是错误的,tvm的bug?)
    3. JGE JNL
    4. 0x880 0x0 0x880 0x880
    5. JL JNGE
    6. 0x880 0x800 0x880 0x80
    7. JG JNLE
    8. 0x8C0 0x0 0x8C0 0x880
    9. JZ JE
    10. 0x40 0x40
    11. JNZ JNE
    12. 0x40 0x0
    13. JS
    14. 0x80 0x80
    15. JNS
    16. 0x80 0x0
    17. JC JB JNAE
    18. 0x1 0x1
    19. JNC JNB JAE
    20. 0x1 0x0
    21. JA JNBE
    22. 0x41 0x0
    23. JP JPE
    24. 0x4 0x4 0x0 0x0
    25. JNP JPO
    26. 0x4 0x0
    27. JO
    28. 0x800 0x800
    29. JNO
    30. 0x800 0x0
    31. JLE JNG
    32. 0x8C0 0x40          zf
    33. 0x8C0 0x800         of
    34. 0x8C0 0x80                 sf
    35. 0x8C0 0xC0                 zf+sf
    36. 0x8C0 0x840         zf+of
    37. 0x8C0 0x8C0         zf+of+sf
    复制代码
    上面我说JBE JNA 的特征是0x41 0x1 0x41 0x40这其实是错误的:
    因为 这只是 jmp if ZF != CF,真正的 JBE JNA 是 jmp if CF = 1 or ZF = 1,
    对应的特征应该是 0x41 0x1 0x41 0x40 0x41 0x41,在这个版本的 tvm 中 ,它将 JBE JNA错误处理成了jmp if ZF != CF
    我逆了较新版本的 tvm ,JBE JNA这里的bug就被修复了,就是0x41 0x1 0x41 0x40 0x41 0x41。
    (看来ACE部门用的tvm版本不够新啊)
    脚本还原ASM

    参考idapython/deTvm.py . tvmToAsmAll.AllTvmAsmToAsm()
    第一步,先识别所有的AsmOpcode:

    push 和 pop 在上文已经识别出来了,
    jcc 在上文也识别出来了,
    tvm没有模拟的Asm,也可以通过跟踪 v_jmp_ll 得到,上文也说了。
    一些明显的 tvmAsm也可以直接识别原本的AsmOpcode:
    1. asmOpcode = [        
    2.                                 "sar","or","ror","xor","shr","shl","movsxd",
    3.                                 "movsx","movzx","dec","inc","test",
    4.                         "cmp","rep stosb","sbb","int3"
    5.             ]
    复制代码
    如果tvmToAsm中的traceCode的tvmAsm含有以上的字符串,
    那么可以直接将这个tvmtoAsm的AsmOpcode设置为对应项,例如:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(24)
    可以直接将Asm的Opcode 设置成 xor,如果要翻译成Asm的话,就翻译成了 xor edx,edx
    1. 如果tvmToAsm只出现了 and,没有出现 not add 那么可以识别成 and
    2. 如果tvmToAsm只出现了 not,没有出现 and add 那么可以识别成 not
    复制代码
    以上部分,参考代码:idapython/deTvm.py . tvmToAsm.setASMOpcode_1()
    识别 lea mov add sub ,这部分比较复杂,如果是人为识别就简单。
    我们先对每一个tvmToAsm内的traceCode再进行一次变量分析,列出一个赋值表,类似于上文赋值表的结构。
    除此之外,我们还要对其进行标记设置,例如:
    sub:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(25)
    标记的结构:[handle,tvmPara,IsUseRflag],依次是处理手段、虚拟机寄存器、是否使用(输出)标志位
    1. 14004a4c3 : 将 [r10 + 0xb0] 标记为 [not,PO_rsp,False]
    2. 14004a4c8 : 将 [r10 + 0xb8] 标记为 [not,PO_rsp,False],[add,0x80,True]                        //会拷贝前一个的标记
    3. 14004a4e4 : 将 [r10 + 0xb8] 标记为 [not,PO_rsp,False],[add,0x80,True],[not,None,False]
    4.                         -优化-> [None,PO_rsp,False],[sub,0x80,True]                                                 //会拷贝前一个的标记
    5. 14004a4e9 : 将 [r10 + 0x98] 标记为 [None,PO_rsp,False],[sub,0x80,True]                        //会拷贝前一个的标记
    复制代码
    有了这些标记,再加上14004a507这一句指令,我们就可以识别这个tvmToAsm的AsmOpcode了:
    1. 出现 sub ,且影响标志位,所以为 sub
    复制代码
    add:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(26)
    1. 1400627da : [r10 + 0xa8] 标记 [None,PO_r9,False],[add,0x9,False]
    2. 1400627e1 : 同上
    3. 1400627e6 : [r10 + 0x91] 标记 [None,PO_r9,False],[add,0x9,False],[mem,None,False]
    4. 1400627eb : [r10 + 0x90] 标记 [None,PO_r9,False],[add,0x9,False],[mem,None,False],[add,PO_rax,True]
    5. 1400627ff : [r10 + 0xa8] 标记 [None,PO_r9,False],[add,0x9,False]
    6. 140062806 : 同上
    复制代码
    有了这些标记,再加上14006280b这一句指令,我们就可以识别这个tvmToAsm的AsmOpcode了:
    1. 出现了 add 标记,并且影响标志位,只能是 add
    复制代码
    lea:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(27)
    1. 14004a825 : [r10 + 0xa0] 标记 [None,PO_rsp,False],[add,0x50,False]
    复制代码
    有了这些标记,再加上14004a82c这一句指令,我们就可以识别这个tvmToAsm的AsmOpcode了:
    1. 标记中出现了add,且不影响标志位,就只能是 lea
    复制代码
    mov:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(28)
    1. 140062826 : [r10 + 0x98] 标记 [None,PO_r9,False],[add,0x9,False]
    2. 14006282d : 同上
    3. 140062832 : [r10 + 0x90] 标记 [None,PO_r9,False],[add,0x9,False],[mem,None,False]
    复制代码
    有了这些标记,再加上140062837这一句指令,我们就可以识别这个tvmToAsm的AsmOpcode了:
    1. 标记中出现了 add,但不影响标志位,并且出现了 mem ,那么只能是 mov
    复制代码
    以上部分都只是一些简单例子,关于更加严格的 sub add lea mov 分类,参考代码:
    idapython/deTvm.py . tvmToAsm.record_tage() 负责跟踪标记
    idapython/deTvm.py . tvmToAsm.setASMOpcode_2()负责分类 sub add lea mov
    第二步,操作数识别:

    到这里的时候,全部 tvmToAsm 的 AsmOpcode都已经全部识别。
    首先介绍一个工具函数 :GetAsmPara可以将标记转换成Asm操作数的格式。
    mov:

    例子和上图是一样的。转换为标记如下:
    1. 140062826 : [r10 + 0x98] 标记 [None,PO_r9,False],[add,0x9,False]
    2. 14006282d : 同上
    3. 140062832 : [r10 + 0x90] 标记 [None,PO_r9,False],[add,0x9,False],[mem,None,False]
    复制代码
    看到140062837这一条workingTraceCode:
    v_mov_iregb_iregb          ( iregb  :PO_rax     ,iregb  :[ r10 + 0x90 ]  );
    1. 看到第二个参数的类型为 iregb,是8bit宽的操作数。
    2. GetAsmPara( PO_rax ) 返回 "al"
    3. GetAsmPara( [r10 + 0x90]) :
    4. 通过标记[None,PO_r9,False],[add,0x9,False],[mem,None,False],
    5. 返回 "[r9 + 0x9]"
    6. 我们知道这个tvmToAsm的AsmOpcode为mov,且workingTraceCode的 第二个参数类型为 iregb,
    7. 那么我们就可以知道:第一个操作数为 al ,第二个操作数为 byte ptr[r9 + 0x9]
    8. 于是这个tvmToAsm就可以翻译为:
    9. mov al,byte ptr[r9 + 0x9]
    复制代码
    代码参考:idapython/deTvm.py . tvmToAsm.mov_handle()
    lea:

    例子和上图是一样的。转换为标记如下:
    1. 14004a825 : [r10 + 0xa0] 标记 [None,PO_rsp,False],[add,0x50,False]
    复制代码
    看到14004a82c这一条workingTraceCode:
    v_mov_iregll_iregll                ( iregll :PO_r8          ,iregll :[ r10 + 0xa0 ]  );
    1. GetAsmPara( PO_r8 ) 返回 "r8"
    2. GetAsmPara( [r10 + 0xa0 ]) :
    3. 通过标记[None,PO_rsp,False],[add,0x50,False]
    4. 返回 "rsp + 0x50" //没有中括号,标记中出现mem才会有中括号,lea要后面自己加中括号
    5. 我们知道这个tvmToAsm的AsmOpcode为lea,且workingTraceCode的 第二个参数类型为 iregll,
    6. 那么我们就可以知道:第一个操作数为 r8 ,第二个操作数为 [rsp + 0x50]
    7. 于是这个tvmToAsm就可以翻译为:
    8. lea r8,[rsp + 0x50]
    复制代码
    代码参考:idapython/deTvm.py . tvmToAsm.lea_handle()
    add sub:

    用上面add的例子,但是进行GetAsmPara的是红框框起来的这两个:(对于sub的处理是一样的)
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(29)
    1. 1400627ff : [r10 + 0xa8] 标记 [None,PO_r9,False],[add,0x9,False]
    2. 140062806 : 同上
    复制代码
    对红框的两个参数进行GetAsmPara:
    1. 看类型 iregb ,8bit宽度
    2. GetAsmPara( PO_rax ) 返回 "al"
    3. GetAsmPara( [r10 + 0xa8] ):
    4. 标记为:[None,PO_r9,False],[add,0x9,False]
    5. 因为 [r10 + 0xa8]类型为 ipreg,且8bit宽度
    6. 所以返回 "byte ptr[r9 + 0x9]"
    7. 于是这个tvmToAsm就可以翻译为:
    8. add byte ptr[r9 + 0x9],al
    复制代码
    代码参考:idapython/deTvm.py . tvmToAsm.add_sub_handle()
    以下都是 对 红框框框起来的 tvmPara 进行GetAsmPara
    push:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(30)
    代码参考:idapython/deTvm.py . tvmToAsm.push_handle()
    pop:

    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(31)
    代码参考:idapython/deTvm.py . tvmToAsm.pop_handle()
    cmp test:

    cmp:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(32)
    test:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(33)
    代码参考:idapython/deTvm.py . tvmToAsm.cmp_test_handle()
    movzx movsx movsxd

    举例如果 opcode是 movzx :
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(34)
    其余的一样,取 opcode 对应的 第二个参数,然后 workingTraceCode的第一个参数进行GetAsmPara
    代码参考:idapython/deTvm.py . tvmToAsm.movzx_movsx_movsxd_handle()
    not

    取 tvmAsmOpcode内含有 "not"字符串的那一句traceCode,取第二个参数进行 GetAsmPara
    代码参考:idapython/deTvm.py . tvmToAsm.not_handle()
    xor sar shr shl sbb ror or and

    取 tvmAsmOpcode含有 上述opcode 的traceCode,取第三个参数进行 GetAsmPara
    取workingTraceCode的第一个参数进行GetAsmPara,以xor为例:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(35)
    代码参考:idapython/deTvm.py . tvmToAsm.xxx_handle()
    jcc

    上面我们已经获取了 jcc的类型,和它的两个跳转地址,虽然都是 V_RIP,
    我们对这两个V_RIP往下找第一个 workingTrace,它所在的 tvmToAsm 就是对应的跳转地址,
    我们可以给这个地址的的Asm打上跳转目的地标签,例如:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(36)
    可以翻译为:
    1. ......
    2. je lable1
    3. jmp label2
    4. ......
    5. label2:        ;(0x14003c0e6往下找第一个tvmToAsm)
    6. mov rax,1      ;只是举个例子
    7. ......
    8. label1:        ;(0x14003c13b往下找第一个tvmToAsm)
    9. mov rcx,2      ;只是举个例子
    10. ......
    复制代码
    参考代码:idapython/deTvm.py . tvmToAsm.jcc_handle()
    ASM 2 HEX

    这部分略,比较简单,就是通过keystone库函数将Asm编译成十六进制机器码,然后再创建一个段,把内存写进去。详情参考:idapython/deTvm.py . tvmToAsmAll.WriteHex()和main0函数。
    值得一提的是,我发现了keystone的一个bug,你们可以试一下:
    1. import keystone
    2. ASM2HEX = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_64)
    3. asm = """
    4. mov rcx,qword ptr ds:[0x14000d250]
    5. """
    6. byte,con = ASM2HEX.asm(asm,addr=0x1400ef00a)
    7. for by in byte:
    8.     print("%02x"%by,end="")
    复制代码
    输出的结果是:
    1. 48 8b 0d 50 d2 00 40
    复制代码
    将其转换成 ASM,是
    1. 1400ef00a  mov rcx, qword ptr [rip + 0x4000d250]
    复制代码
    这明显是错误的,0x1400ef00a+0x7+0x4000d250 != 0x14000d250
    1. asm = """
    2. mov qword ptr ds:[0x14000d250],rcx
    3. """
    4. byte,con = ASM2HEX.asm(asm,addr=0x1400ef00a)
    复制代码
    就能输出正确的字节码。
    所以我采用的方法是一句一句将Asm转换成HEX,如果遇到 mov reg,qword ptr[xxx],
    就把格式改成 mov reg,qword ptr[rip + yyy],详情参考代码:
    idapython/deTvm.py . Asm.AsmToHex()
    deTvm.py脚本玩法

    main0是对全部ida识别的函数进行特征分析,如果符合tvm函数特征,就对它进行还原。
    可以算是一键还原全部tvm函数了,有可能有些tvm函数不符合特征,你也可以手动添加还原函数。
    例如:
    你知道一个函数0x140001250它是被vm的,那么你这么写,脚本就会自动特征识别V_RIP。
    1. testTrace = traceTask(0x140001250,tvm0base)          #tvm0base是tvm0段的起始地址
    2. testTrace.track(0)                                                                #开始跟踪 得到traceCode
    3. testTrace.traceOut()                                                        #输出原始traceCode
    复制代码
    如果你这个被vm的函数不符合我写的特征,但它确实是tvm的函数,那么可以这么写,自己设置V_RIP:
    1. #上一个例子的 函数 0x140001250 它的 V_RIP 就是 0x140059dc2
    2. testTrace = traceTask(0,tvm0base)
    3. testTrace.VStart = 0x140059dc2                #自己找这个vm函数的起始地址 例如V_RIP = 0x140059dc2
    4. testTrace.track(0)                                        #开始跟踪 得到traceCode
    5. testTrace.traceOut()                                #输出原始traceCode
    复制代码
    traceOut(0)输出的结果如下:( 基本没做处理)
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(37)
    如果想看 对标记working的traceCode进行变量溯源的结果,你可以这么写:
    1. testTrace = traceTask(0x140001250,tvm0base)          #tvm0base是tvm0段的起始地址
    2. testTrace.track(0)                                                                #开始跟踪 得到traceCode
    3. testTrace.VRegRecord(True)                                                #如果是False就是不使用标记(上文说过)
    4. testTrace.tvmToAsmAll.printAll()                                #输出
    复制代码
    输出:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(38)
    如果想进一步的进行变量传播优化还有 push、pop 优化,可以这么写:
    1. testTrace = traceTask(0x140001250,tvm0base)          #tvm0base是tvm0段的起始地址
    2. testTrace.track(0)                                                                #开始跟踪 得到traceCode
    3. testTrace.VRegRecord(True)                                                #如果是False就是不使用标记(上文说过)
    4. testTrace.tvmToAsmAll.optimizeAll()                                #变量传播优化,push、pop优化
    5. testTrace.tvmToAsmAll.printAll()                                #输出
    复制代码
    输出:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(39)
    如果想看还原成 ASM是什么样的,可以这样写:
    1. testTrace = traceTask(0x140001250,tvm0base)          #tvm0base是tvm0段的起始地址
    2. testTrace.track(0)                                                                #开始跟踪 得到traceCode
    3. testTrace.VRegRecord(True)                                                #如果是False就是不使用标记(上文说过)
    4. testTrace.tvmToAsmAll.optimizeAll()                                #变量传播优化,push、pop优化
    5. testTrace.tvmToAsmAll.AllTvmAsmToAsm()                         #转换成ASM  注意,一定要VRegRecord + optimizeAll 后才可以调用
    6. testTrace.tvmToAsmAll.printAsmAll()                                #输出ASM
    复制代码
    输出:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(40)
    如果想看 tvmToAsm和Asm对应起来的输出,可以这样写:
    1. testTrace = traceTask(0x140001250,tvm0base)          #tvm0base是tvm0段的起始地址
    2. testTrace.track(0)                                                                #开始跟踪 得到traceCode
    3. testTrace.VRegRecord(True)                                                #如果是False就是不使用标记(上文说过)
    4. testTrace.tvmToAsmAll.optimizeAll()                                #变量传播优化,push、pop优化
    5. testTrace.tvmToAsmAll.AllTvmAsmToAsm()                         #转换成ASM
    6. tvmToAsm_P = testTrace.tvmToAsmAll.tvmToAsmHead #结构为tvmToAsm
    7. while (tvmToAsm_P != None):
    8.     tvmToAsm_P.printAsm()                       #输出Asm
    9.     tvmToAsm_P.print()                          #输出traceCodeAll
    10.     print("")                                   #隔开
    11.     tvmToAsm_P = tvmToAsm_P.BLink               #下一个
    复制代码
    输出:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(41)
    还原例子:

    总所周知 ACE-BASE.sys 的DriverUnload函数是被vm了的,那么我们就用它来看看还原效果:
    TBC茶馆-复仇者黑客组织tvm简介一句话概括就是腾讯自家的虚拟化加密壳。把腾讯的安全产物拉入 PE 工具,看到区段中有 .tvm0 那就复仇者黑客组织(42)
    不错,很符合我对DriverUnload的想象。
    左边 命名为 icxxx 的函数均为还原成功的函数。
    还原脚本项目地址:xx_tvm
    一些补充

    这个脚本只适用于这个版本的 tvm (ACE用的版本)。
    新一点的tvm,虽然用的是同一套虚拟化指令集,但它会对整数进行加密,读取时进行简单的xor解密,并且新增了一些 虚拟指令。
    而且 进入虚拟机的特征也有点不一样,不过对脚本进行简单的修改即可兼容。
    一些闲话

    ACE你快用新版的tvm,你现在用的版本是有bug的(JBE JNA的模拟是错的)。




    上一篇:SE壳过时间限制快速破解(无技术含量)
    下一篇:Windows下小白学Frida逆向1:安装特定版本Frida

    该用户从未签到

    2

    主题

    1623

    帖子

    1245

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1245
    发表于 2023-11-20 05:25:50 | 显示全部楼层
    感谢楼主的付出 给我们提供一个思路
    回复

    使用道具 举报

    该用户从未签到

    2

    主题

    1623

    帖子

    1245

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1245
    发表于 2023-11-20 05:25:55 | 显示全部楼层
    膜拜一下,多谢分享
    回复

    使用道具 举报

    该用户从未签到

    4

    主题

    1607

    帖子

    1497

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1497
    发表于 2023-11-20 05:26:51 | 显示全部楼层
    学习了,虽然看不懂1
    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    1504

    帖子

    890

    积分

    高级会员

    Rank: 4

    积分
    890
    发表于 2023-11-20 05:27:18 | 显示全部楼层
    向大佬学习、致敬~!
    回复

    使用道具 举报

    该用户从未签到

    4

    主题

    1651

    帖子

    1010

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1010
    发表于 2023-11-20 05:28:07 | 显示全部楼层
    逆还原VM啊,大佬
    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    1504

    帖子

    890

    积分

    高级会员

    Rank: 4

    积分
    890
    发表于 2023-11-20 05:28:29 | 显示全部楼层
    @南山必胜客
    回复

    使用道具 举报

    该用户从未签到

    3

    主题

    1627

    帖子

    1538

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1538
    发表于 2023-11-20 05:28:53 | 显示全部楼层
    感谢分享
    回复

    使用道具 举报

    该用户从未签到

    2

    主题

    1623

    帖子

    1245

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1245
    发表于 2023-11-20 05:29:35 | 显示全部楼层
    太强了,直接偷家了。
    回复

    使用道具 举报

    该用户从未签到

    4

    主题

    1651

    帖子

    1010

    积分

    金牌会员

    Rank: 6Rank: 6

    积分
    1010
    发表于 2023-11-20 05:29:57 | 显示全部楼层
    欢迎分析讨论交流,吾爱破解论坛有你更精彩!
    回复

    使用道具 举报

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

    本版积分规则

    关闭Powered by ©科大讯飞语音云
    嗨!您好:
    欢迎来到 复仇者黑客组织。
    我的名字叫小光
    很高兴能够为您服务!
    如果已经注册【立即登录】
    还没有账号请立即注册
    Loading...
    快速回复 返回顶部 返回列表