首页
社区
课程
招聘
[原创]一篇文章带你学会Armadillo脱壳
2019-5-3 14:28 23462

[原创]一篇文章带你学会Armadillo脱壳

2019-5-3 14:28
23462

0x0 前言

         最近在跟进一个APT组织的一次攻击,其中有一个样本使用了Arm加壳,所以花了差不多10多天的时间看看这方面的东西。并总结一下。

 

         这篇文章主要参考了FLY和刹那恍惚两位大佬的文章。和录制的视频。以及jcyhlh大侠在2008年写下的总结帖。那时候我估计还在玩泥巴呢。这是我写这篇文章的主要参考来源。前人栽树后人乘凉。此外还看了看雪的知识库。基本看了3.x和4.x所有师傅的文章。

 

         这篇文章的架构,文章架构主要参照了网上下载的视频教程的架构。并对此作出小小修改和注释以及归纳总结。更加方便我等小白学习成长。

 

         由于文章主要脱去的是3.x和4.x的Arm,可能有一些伪大佬又要说都发了几百遍了还在发。。对此我的处理意见是,把其直接挂在文章起始部分
这篇文章适合我等小白,所以伪大佬勿扰。真大佬可以daidaiwo
         最后,加油吧,小伙伴们。

0x1 Armadillo

0x1.1 保护机制

         Armadillo,中文名穿山甲,本意为犰狳,就是下面那个有点可爱的家伙。

 

         Armadillo主要采用了Debug-Blocker,CopyMem-II, Enable Import Table Elimination,Enable Nanomites Processing,Enable Memory-Patching Protections保护手段。同时也有单双进程之分,造成了保护手段的多样性。

 

         Debug-Blocker,称为阻止调试器,所谓反调试,基本只要开插件都可以过,所以这也是为什么大家脱穿山甲的时候打开IsProcessDebug去反调试选项和忽略异常的原因。

 

         CopyMem-II:双进程保护,最常使用的是bp OpenMutexA,然后转到401000 patch代码。另外一种是修改相反跳转的方法。(脚本方法就是不说了)

 

         Enable Import Table Elimination:IAT保护,修改Magic_Jmp。

 

         Enable Nanomites Processing就是CC保护,也是Armadillo最强大的保护机制。原理就是就是将程序中的部分代码改写为int3或者向其中插入int3代码。

0x1.2 前期侦壳

         知己知彼百战不殆,在脱壳最重要的就是侦壳。这里需要使用到的工具主要有:PEID(不推荐),exepeinfo,ArmaFP,任务管理器。

 

         其中,exepeinfo是用于查壳的,任务管理器是用于判断是单进程还是双进程,如果是双进程就需要双转单。ArmaFP是用于判断其保护模式,是标准模式,还是全保护模式(专业模式)。

 

         不过关于壳的版本,exepeinfo容易误报,所以可以使用这个方法:OD载入程序,下HE OutputDebugStringA断点。shift+F9中断后,看堆栈如果出现如下的,就是4.0以上的壳。这是由于Arm在4.0利用Od在调式保护格式串的消息时会奔溃而新增的反调试技术。

//4.01
0012EC70   021B580F  /CALL 到 OutputDebugStringA 来自 021B5809
0012EC74   0012F5E8  \String = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
/3.X
//此时程序运行起来。

0x2 Armadillo单进程脱壳

0x2.1 标准单进程Armadillo 3.78 - 4.xx 脱壳

         这是最简单的加密方法,只需要修改Magic_Jmp就可以了,因为这个版本单进程防护只是加密了IAT,(1)只需要绕过加密,(2)并让其解压压缩区段即可。

 

         绕过IAT加密的方法就是修改Magic_Jmp,这是脱穿山甲壳必须使用的方法。步骤如下:

  • step1:在GetModuleHandle下硬件断点,可以HE GetModuleHandle或者HE GetModuleHandle+5
  • Step2:然后Shift+F9,断下的时候,看堆栈窗口是否存在VirtualAlloc或者VirtualFree,只要出现这两个API函数,就表明快到了。
    000C94C8  |00726DF3  返回到 00726DF3 来自 kernel32.GetModuleHandleA
    000C94CC  |0073BC1C  ASCII "kernel32.dll"
    000C94D0  |0073CEC4  ASCII "VirtualAlloc"
    000C94D4  |0073FA98
    000C94D8  |77E22270  ntdll.RtlLeaveCriticalSection
    
  • Step3:继续Shift+F9,只要堆栈出现kernel32.dll,但是不包含任何其他函数名称,表示到达了返回的时机。此时执行到返回ctrl+F9。如何判断之前执行到返回的时机是否正确呢,就是看是否存在LoadLibrary这个API函数。此时就是正确的。
    000C9228  /000C94C8
    000C922C  |00715CE1  返回到 00715CE1 来自 kernel32.GetModuleHandleA
    000C9230  |000C937C  ASCII "kernel32.dll"
    000C9234  |000CEAB4
    000C9238  |3CBC24B7
    
    00715CE1    8B0D AC407400   mov     ecx, dword ptr [7440AC]
    00715CE7    89040E          mov     dword ptr [esi+ecx], eax
    00715CEA    A1 AC407400     mov     eax, dword ptr [7440AC]
    00715CEF    391C06          cmp     dword ptr [esi+eax], ebx
    00715CF2    75 16           jnz     short 00715D0A
    00715CF4    8D85 B4FEFFFF   lea     eax, dword ptr [ebp-14C]
    00715CFA    50              push    eax
    00715CFB    FF15 BC627300   call    dword ptr [7362BC]               ; kernel32.LoadLibraryA
    00715D01    8B0D AC407400   mov     ecx, dword ptr [7440AC]
    00715D07    89040E          mov     dword ptr [esi+ecx], eax
    
  • 此时就可以修改LoadLibrary函数下面的那个条件跳转(00715D12)为jmp,跳转到00425E5C,然后撤销之前的修改。
    00715CF4    8D85 B4FEFFFF   lea     eax, dword ptr [ebp-14C]
    00715CFA    50              push    eax
    00715CFB    FF15 BC627300   call    dword ptr [7362BC]               ; kernel32.LoadLibraryA
    00715D01    8B0D AC407400   mov     ecx, dword ptr [7440AC]
    00715D07    89040E          mov     dword ptr [esi+ecx], eax
    00715D0A    A1 AC407400     mov     eax, dword ptr [7440AC]
    00715D0F    391C06          cmp     dword ptr [esi+eax], ebx
    00715D12  - 0F84 2F010090   je      90715E47
    
    00425E53    395F FC         cmp     dword ptr [edi-4], ebx
    00425E56  ^ 0F85 49FEFFFF   jnz     00425CA5
    00425E5C    EB 03           jmp     short 00425E61
    00425E5E    D6              salc
    
  • Step4:因为外壳肯定需要将存储在某一区段的数据解压到text段,需要对该段进行访问,所以,在内存窗口的程序的text段下访问断点。然后shift+F9。中断在43468F,然后单步,在此代码段的最后一个call ecx处步入就是OEP。
    0043F68F    8B12            mov     edx, dword ptr [edx]
    0043F691    8955 DC         mov     dword ptr [ebp-24], edx
    0043F694    834D FC FF      or      dword ptr [ebp-4], FFFFFFFF
    0043F698    EB 11           jmp     short 0043F6AB
    0043F69A    6A 01           push    1
    
    0100739D    6A 70           push    70
    0100739F    68 98180001     push    01001898
    010073A4    E8 BF010000     call    01007568
    010073A9    33DB            xor     ebx, ebx
    010073AB    53              push    ebx
    010073AC    8B3D CC100001   mov     edi, dword ptr [10010CC]         ; kernel32.GetModuleHandleA
    010073B2    FFD7            call    edi
    010073B4    66:8138 4D5A    cmp     word ptr [eax], 5A4D
    010073B9    75 1F           jnz     short 010073DA
    010073BB    8B48 3C         mov     ecx, dword ptr [eax+3C]
    010073BE    03C8            add     ecx, eax
    010073C0    8139 50450000   cmp     dword ptr [ecx], 4550
    010073C6    75 12           jnz     short 010073DA
    010073C8    0FB741 18       movzx   eax, word ptr [ecx+18]
    010073CC    3D 0B010000     cmp     eax, 10B
    010073D1    74 1F           je      short 010073F2
    010073D3    3D 0B020000     cmp     eax, 20B
    010073D8    74 05           je      short 010073DF
    010073DA    895D E4         mov     dword ptr [ebp-1C], ebx
    010073DD    EB 27           jmp     short 01007406
    010073DF    83B9 84000000 0>cmp     dword ptr [ecx+84], 0E
    

0x2.2 单进程Armadillo v4.x脱壳

         首先需要判断加壳版本是否是4.xxx。关于这点如何判断呢,主要下硬件断点 HE OutputDebugStringA 。在堆栈窗口出现%s%s%s%s的标志,说明这是4.X的壳。

0012EC70   021C580F  /CALL 到 OutputDebugStringA 来自 021C5809
0012EC74   0012F5E8  \String = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"

         关于Armadillo v4.x单进程脱壳把握两点,第一,使用Magic_Jmp避过IAT加密保护,对GetCurrentThreadId下断点找到OEP

 

         关于第一条,就是上面2.1讲的原则,下面解释第二条。首先对GetCurrentThreadId下断。HE GetCurrentThreadId。查看堆栈窗口,会出现如下结果.中间省略多个,查看关于GetCurrentThreadId都是来自其他模块的调用,但是最后一个是来自程序的调用。这就是程序返回的时机,所以,F8步过,根据之前说的规则,OEP在该程序段最后一个call ecx中。

0236FE50   7339352D  /CALL 到 GetCurrentThreadId 来自 msvbvm60.73393527
//
0236FE30   76DE1434  /CALL 到 GetCurrentThreadId 来自 adsldpc.76DE142E
//
0236FE78   76F31298  /CALL 到 GetCurrentThreadId 来自 wldap32.76F31292
//....
0012F720   037560EC  /CALL 到 GetCurrentThreadId 来自 037560E6
0376F70F    3350 40         xor edx,dword ptr ds:[eax+0x40]
0376F712    3350 04         xor edx,dword ptr ds:[eax+0x4]
0376F715    2BCA            sub ecx,edx
0376F717    FFD1            call ecx                                 ; NOTEPAD_.004010CC
0376F719    8945 E4         mov dword ptr ss:[ebp-0x1C],eax
0376F71C    8B45 E4         mov eax,dword ptr ss:[ebp-0x1C]
0376F71F    8B4D F0         mov ecx,dword ptr ss:[ebp-0x10]
0376F722    64:890D 0000000>mov dword ptr fs:[0],ecx
0376F729    5F              pop edi
0376F72A    5E              pop esi
0376F72B    5B              pop ebx
0376F72C    C9              leave
0376F72D    C3              retn

2.3 加 PassWord单进程

         这是就比2.2多了一个密码验证,我们直接绕过密码验证就好。首先Shift+F9运行,通过查看导入表,在GetDlgItem处下断bpx GetDlgItem。然后在输入伪码按OK,程序中断在35359D0处.注意:先运行,在下断!在输入

035349D0    FF15 E8645403   call    dword ptr [35464E8]              ; USER32.GetDlgItem
035349D6    50              push    eax
035349D7    FF15 E0645403   call    dword ptr [35464E0]              ; USER32.GetWindowTextA
035349DD    8D85 00FFFFFF   lea     eax, dword ptr [ebp-100]
035349E3    50              push    eax
035349E4    E8 35060100     call    0354501E                         ; jmp 到 msvcrt.strlen
035349E9    85C0            test    eax, eax
035349EB    59              pop     ecx
035349EC    74 1A           je      short 03534A08
035349EE    8B0D E01E5503   mov     ecx, dword ptr [3551EE0]
035349F4    8D85 00FFFFFF   lea     eax, dword ptr [ebp-100]
035349FA    50              push    eax
035349FB    E8 7D77FEFF     call    0351C17D                         ; 比较原始密码
03534A00    84C0            test    al, al
03534A02    74 04           je      short 03534A08                   ; 不是原码,则跳转(不跳)
03534A04    6A 01           push    1
03534A06    EB 4C           jmp     short 03534A54                   ; 回到正常流程
03534A08    33F6            xor     esi, esi

         接着修改魔法跳,可以使用bp GetModueHandle或者HE GetModuleHandle。这里发现了kernel32.dll就可以执行到返回查看了。

 

         然后就是找OEP,这里还是可以使用2.2中对GetProcessId下断。这里介绍个新方法。**可以在内存窗口.text段按F2下断点。因为壳执行完肯定会执行代码段的内容。也就是说代码段是由外壳到源程序的一扇门。所以在此处下断必然成立。

004010CC    55               push    ebp
004010CD    8BEC             mov     ebp, esp
004010CF    83EC 44          sub     esp, 44
004010D2    56               push    esi
004010D3    FF15 E4634000    call    dword ptr [4063E4]               ; kernel32.GetCommandLineA

         总结一下,现在有两个方法可以找OEP。第一是对GetProcessId下断,第二个就是在.text下断。

0x2.4 包含Code Splicing和Import Table Elimination的修复

         Armadillo使用Code Splicing和Import Table Elimination两项技术使得程序修复变得更加困难。幸好有大佬开发了ArmInline工具可以使得修复变得简单一些。注意:本节只将修复,不讲程序优化。

 

         当我们寻找到OEP之后,就可以着手修复Code Splicing和Import Table Elimination了。

0040C434    55              push ebp
0040C435    8BEC            mov ebp,esp
0040C437    6A FF           push -0x1
0040C439    68 28334100     push fraps.00413328
0040C43E    68 30E94000     push fraps.0040E930
0040C443    64:A1 00000000  mov eax,dword ptr fs:[0]
0040C449    50              push eax
0040C44A    64:8925 0000000>mov dword ptr fs:[0],esp

         首先祭上大杀器ArmInline,欲要善其事,必先利其器。需要我们填写的就是上述三个区域,不过我这个版本可以自动填写修复的数据,只需要知道我们需要修复的进程,如图,目标进程ID为FC4,选中后依次删除拼接代码和巡回IAT基址。

 

         然后按照常规的方法dump和修复IAT就可以了。注意的是使用PELord一定要勾选从磁盘粘贴文件头(一般默认勾选上了)

 

         如果你的ArmInline不能自己修复(反正牛逼的师傅都是自己修复的,我不牛逼所以都是软件自动修复的),关于Code Splicing的修复可以这样,Alt+M到内存窗口,在fraps模块之后有一段内存没有被其他模块映射(不知道这样说对不对,反正对于Kernel32这样的dll来说肯定是对的。大家理解就好)。在最后一块内存处,就是拼接代码起点,这个值不是一个定值。(这个只是经验之谈,需要大佬解释一波的)

 

         接着修正IAT乱序,首先随便找个函数调用,在信息窗口点数据窗口跟随地址,然后向上拖动窗口(你最好改成显示地址)。找到IAT起始地址,然后找到结束地址,两者相减。计算大小即可。关于填充地址。可以考虑在一块没有读写的空白区域就好。不过大佬给的建议是在程序加壳前原来IAT的相近地方。可以这样寻找。Alt+M到内存窗口,因为IAT早rdata区域,又因为IAT肯定保存了一些IID成员,其中有个Name成员,也就是DllName。我们通过全局搜索确定

0x3 Armadillo双进程脱壳

         这一章节主要讲穿山甲的双进程保护手段。所以双进程保护,简单的来说就是创建两个进程,一个进程是另外一个进程的调试进程,又由于在R3下面一个进程只能被一个调试器附加。这样可以有效避免程序被调试。

0x3.1 标准保护

         接下来简单讲解一下关于双进程保护的原理,主要可以利用互斥体来判断进程列表是否存在相同的进程(即多开)。首先是利用CreateMutex创建一个互斥体。然后在利用OpenMutex打开那个互斥体,如果OpenMutex成功返回互斥体句柄,说明已经存在一个进程。如果不存在则在CreateProcess一个进程。而对于穿山甲壳双转单也是如此。如果提前创建了一个即将被打开的互斥体。那么程序就不会去创建新的进程。如下的脱壳方法就是基于这点考虑。

 

         首先对openMutex下断点(HE,bp皆可)。HE openMutexA,然后shift+F9。观察堆栈

0012F798   00434DB8  /CALL 到 OpenMutexA 来自 NOTEPAD_.00434DB2
0012F79C   001F0001  |Access = 1F0001
0012F7A0   00000000  |Inheritable = FALSE
0012F7A4   0012FDD8  \MutexName = "8A4::DABDC997F2"   ;这是是重点,标记了互斥体名称。记住堆栈地址0012FDD8,以后要用

         接着需要创建互斥体。转到401000处编写汇编代码,为什么需要401000,因为这是.text段,但是理论上在哪里修改都可以。然后将EIP修改到401000处,就可以在这里执行了,然后shitf+F9.

00401000    60                  pushad                    ;保存所有寄存器
00401001    9C                  pushfd                    ;保存标志寄存器
00401002    68 F8FB1200         push 0012FDD8             ;堆栈里看到的值MutexName
00401007    33C0                xor eax,eax
00401009    50                  push eax                  ;参数2
0040100A    50                  push eax                  ;参数1
0040100B    E8 B5A6A577         call kernel32.CreateMutexA  ;创建互斥体
00401010    9D                  popfd
00401011    61                  popad                      ;恢复
00401012  - E9 7A13A677         jmp kernel32.OpenMutexA    ;打开互斥体

         接着就是处理加密IAT和跳转OEP,DUMP的问题了。最后到达OEP如下:

004010CC    55              push ebp
004010CD    8BEC            mov ebp,esp
004010CF    83EC 44         sub esp,0x44
004010D2    56              push esi
004010D3    FF15 E4634000   call dword ptr ds:[0x4063E4]             ; kernel32.GetCommandLineA
004010D9    8BF0            mov esi,eax

0x3.2 CopyMem-II

         去除CopyMem-ll 保护通常有两个方法。

 

         方法1:首先寻找OEP,然后对WaitForDebugEvent下断点bp WaitForDebugEvent,接着运行程序,看堆栈,当出现pDebugEvent字符的时候,选择在数据窗口跟随,然后对WriteProcessMemory下断bp WriteProcessMemory,中断后,在数据窗口发现OEP。

 

         这里重点讲一下第二个方法:

  • bp WaitForDebugEvent,shift+F9运行起来,删除断点,然后执行到程序领空,大概停在0060F8BA

    0060F8BA   ?  15 E0406400   adc eax,<&KERNEL32.WaitForDebugEvent>
    0060F8BF   .  85C0          test eax,eax
    0060F8C1   .  0F84 2B270000 je MAGCT.00611FF2
    0060F8C7   .  8B85 FCFDFFFF mov eax,dword ptr ss:[ebp-0x204]
    0060F8CD   .  25 FF000000   and eax,0xFF
    0060F8D2   .  85C0          test eax,eax
    0060F8D4   .  74 13         je XMAGCT.0060F8E9
    0060F8D6   .  8B0D 44AF6400 mov ecx,dword ptr ds:[0x64AF44]
    0060F8DC   .  8379 20 00    cmp dword ptr ds:[ecx+0x20],0x0
    0060F8E0   .  74 07         je XMAGCT.0060F8E9
    0060F8E2   .  C685 FCFDFFFF>mov byte ptr ss:[ebp-0x204],0x0
    0060F8E9   >  68 38AE6400   push MAGCT.0064AE38                      ; /pCriticalSection = MAGCT.0064AE38
    0060F8EE   .  FF15 A4416400 call dword ptr ds:[<&KERNEL32.EnterCriti>; \EnterCriticalSection
    
  • 然后Ctrl+F搜索命令:or eax,0FFFFFFF8,想上看有两个比较,一个是cmp dword ptr ss:[ebp-0xA34],另外一个是cmp ecx,dword ptr ds:[0x64AF48],然后对第一个cmp下断点,F9运行。这一步你需要记住以下内容,等下patch的时候需要用到内容,第一:第一个cmp的地址0060FE43,第二:第一个cmp【】内的值ebp-0xA34,第三:第二个cmp【】的值:0x64AF48.除此以外,需要将次一个cmp栈里面的数据清0

0060FE43   > \83BD CCF5FFFF 00     cmp dword ptr ss:[ebp-0xA34],0x0         ;  3
0060FE4A   .  0F8C A8020000        jl MAGCT.006100F8                        ;在目的地址006100F8下断点
0060FE50   .  8B8D CCF5FFFF        mov ecx,dword ptr ss:[ebp-0xA34]
0060FE56   .  3B0D 48AF6400        cmp ecx,dword ptr ds:[0x64AF48]          ;  2
0060FE5C   .  0F8D 96020000        jge MAGCT.006100F8
0060FE62   .  8B95 40F6FFFF        mov edx,dword ptr ss:[ebp-0x9C0]
0060FE68   .  81E2 FF000000        and edx,0xFF
0060FE6E   .  85D2                 test edx,edx
0060FE70   .  0F84 AD000000        je MAGCT.0060FF23
0060FE76   .  6A 00                push 0x0
0060FE78   .  8BB5 CCF5FFFF        mov esi,dword ptr ss:[ebp-0xA34]
0060FE7E   .  C1E6 04              shl esi,0x4
0060FE81   .  8B85 CCF5FFFF        mov eax,dword ptr ss:[ebp-0xA34]
0060FE87   .  25 07000080          and eax,0x80000007
0060FE8C   .  79 05                jns XMAGCT.0060FE93
0060FE8E   .  48                   dec eax
0060FE8F   .  83C8 F8              or eax,0xFFFFFFF8                        ;  1
0060FE92   .  40                   inc eax
  • 接下来patch数据,我们向下看,找到add eax,0xff语句,在这里就可以patch了

    //原始数据
    0060FF16      25 FF000000          and eax,0xFF                             ;  patch
    0060FF1B      85C0                 test eax,eax
    0060FF1D      0F84 D5010000        je MAGCT.006100F8
    0060FF23      837D D8 00           cmp dword ptr ss:[ebp-0x28],0x0
    0060FF27      75 27                jnz XMAGCT.0060FF50
    0060FF29      8B15 D0436400        mov edx,dword ptr ds:[0x6443D0]
    //
    //patch模块
    inc dword ptr ds:[] //第一个CMP内的值
    mov dword ptr ds:[XXXX+4],1  //XXXX为第二个CMP[]内的值
    jmp XXXX  //第一个CMP前的地址
    //
    //patch后的数据
    0060FF16      FF85 CCF5FFFF        inc dword ptr ss:[ebp-0xA34]             ;  patch
    0060FF1C      C705 4CAF6400 010000>mov dword ptr ds:[0x64AF4C],0x1
    0060FF26    ^ E9 18FFFFFF          jmp MAGCT.0060FE43
    0060FF2B      90                   nop
    0060FF2C      90                   nop
    0060FF2D      90                   nop
    
  • 接着shift+F9,中断在006100F8处,就可以dump处子进程了

    006100EE   > \C785 D8F5FFFF 020001>mov dword ptr ss:[ebp-0xA28],0x10002     ;  UNICODE "::=::\"
    006100F8   >  E9 D4100000          jmp MAGCT.006111D1
    006100FD   >  8B0D B0436400        mov ecx,dword ptr ds:[0x6443B0]
    00610103   .  81F1 050000C0        xor ecx,0xC0000005
    00610109   .  398D D4F5FFFF        cmp dword ptr ss:[ebp-0xA2C],ecx
    0061010F   .  0F85 92040000        jnz MAGCT.006105A7
    00610115   .  70 07                jo XMAGCT.0061011E
    00610117   .  7C 03                jl XMAGCT.0061011C
    00610119   >  EB 05                jmp XMAGCT.00610120
    

         接下来就是还原IAT

  • 首先对DebugActiveProcess下断点BP DebugActiveProcess这样是为了寻找子进程,在堆栈窗口发现子进程ID为D84(不定)。接着重新打开一个OD,附加子进程,然后F9+F12,中断在入口点

    0012BCBC   0060F71A  /CALL 到 DebugActiveProcess 来自 MAGCT.0060F714
    0012BCC0   00000DB4  \ProcessId = DB4
    
    0061F743 >/$- EB FE         jmp XMAGCT.<ModuleEntryPoint>
    0061F745  |?  EC            in al,dx
    0061F746  |.  6A FF         push -0x1
    0061F748  |.  68 209B6400   push MAGCT.00649B20
    0061F74D  |.  68 80F46100   push MAGCT.0061F480                      ;  SE 处理程序安装
    0061F752  |.  64:A1 0000000>mov eax,dword ptr fs:[0]
    0061F758  |.  50            push eax
    
  • 将死跳转字节EB FE正常指令字节55 8B,然后就可以执行我们上节讲的双变单了。在401000修改完双转单代码后,shift+F9跑起来,再次中断在OpenMutexA处。

    0061F743 >    55            push ebp
    0061F744      8BEC          mov ebp,esp
    0061F746  |.  6A FF         push -0x1
    0061F748  |.  68 209B6400   push MAGCT.00649B20
    
  • 然后对GetModuleHandle下硬件断点。HE GetModuleHandle,经过VirtualAlloc和VirtualFree到达,然后返回,修改Magic_JMp.在此之前关闭硬件断点
    001265F4   00F9ACC1  /CALL 到 GetModuleHandleA 来自 00F9ACBB
    001265F8   00126738  \pModule = "kernel32.dll"
    
  • 初次以外还有一个时间校验,对GetTickCount下断,执行到返回
    00E89116    FF15 AC22E900   call dword ptr ds:[E922AC] ; kernel32.GetTickCount
    00E8911C    2B85 8CC3FFFF   sub eax,dword ptr ss:[ebp-3C74]
    00E89122    8B8D 90C3FFFF   mov ecx,dword ptr ss:[ebp-3C70]
    00E89128    6BC9 32         imul ecx,ecx,32
    00E8912B    81C1 D0070000   add ecx,7D0
    00E89131    3BC1            cmp eax,ecx
    00E89133    76 07           jbe short 00E8913C  //修改为:JMP 00E8913C 
    00E89135    C685 20C8FFFF 0>mov byte ptr ss:[ebp-37E0],1
    00E8913C    83BD D0C6FFFF 0>cmp dword ptr ss:[ebp-3930],0
    00E89143    0F85 8A000000   jnz 00E891D3
    
  • 然后用ImportRCE修复即可!

0x4 带KEY的Armadillo

0x4.1 单进程

         带KEY的Armadillo相当于给软件多了一层保护,我们此时还不能通过爆破的方式解决这个KEY,原因有2,第一,OD对于这类情况不提供修改的选项,第二,就算爆破成功了,后期软件中还存在暗桩。所以可以逆向算法的方式得到一组合适的KEY。

 

         首先shitf+F9运行起来,不要管出现的对话框,首先随便输入个Key,然后下HE GetDlgItem断点即可。取消断点,ALT+F9执行返回。大概停在此处。

021B44FC    85C0            test eax,eax
021B44FE    74 33           je X021B4533
021B4500    BB 00010000     mov ebx,0x100
021B4505    8D85 00FFFFFF   lea eax,dword ptr ss:[ebp-0x100]
021B450B    53              push ebx
021B450C    50              push eax
021B450D    57              push edi
021B450E    FF75 08         push dword ptr ss:[ebp+0x8]
021B4511    FFD6            call esi
021B4513    8B3D E0641C02   mov edi,dword ptr ds:[0x21C64E0]         ; USER32.GetWindowTextA
021B4519    50              push eax
021B451A    FFD7            call edi
021B451C    8D85 00FEFFFF   lea eax,dword ptr ss:[ebp-0x200]

         然后向上找,找到这个函数开始地方,也就是上一个ret的下个指令.然后下硬件执行断点。然后重新载入,shitf+F9

021B4462    40              inc eax
021B4463    C3              retn
021B4464    55              push ebp
021B4465    8BEC            mov ebp,esp
021B4467    81EC 00040000   sub esp,0x400
021B446D    8B45 0C         mov eax,dword ptr ss:[ebp+0xC]
021B4470    53              push ebx
021B4471    56              push esi
021B4472    2D 10010000     sub eax,0x110
021B4477    57              push edi

         此时中断在之前下的执行断点处。单步走到021B4478处的第一个大跳转je 021B45F2右键跟随。

021B4464    55              push ebp
021B4465    8BEC            mov ebp,esp
021B4467    81EC 00040000   sub esp,0x400
021B446D    8B45 0C         mov eax,dword ptr ss:[ebp+0xC]
021B4470    53              push ebx
021B4471    56              push esi
021B4472    2D 10010000     sub eax,0x110
021B4477    57              push edi
021B4478    0F84 74010000   je 021B45F2
021B447E    48              dec eax
021B447F    74 07           je X021B4488

         跟随到021B45F2处,F2下断点,执行到此处,继续单步跟。

021B45F2    F645 17 80      test byte ptr ss:[ebp+0x17],0x80
021B45F6    8B7D 08         mov edi,dword ptr ss:[ebp+0x8]
021B45F9    74 12           je X021B460D
021B45FB    8065 17 7F      and byte ptr ss:[ebp+0x17],0x7F
021B45FF    6A 01           push 0x1
021B4601    68 5CC91C02     push 0x21CC95C
021B4606    57              push edi
021B4607    FF15 C0641C02   call dword ptr ds:[0x21C64C0]        ; USER32.SetPropA
021B460D    8B35 E8641C02   mov esi,dword ptr ds:[0x21C64E8]         ; USER32.GetDlgItem
021B4613    6A 01           push 0x1
021B4615    57              push edi
021B4616    FFD6            call esi

         一直到021B4689处

021B467D   /0F84 B7000000   je 021B473A
021B4683   |53              push ebx
021B4684   |B9 98FA1C02     mov ecx,0x21CFA98
021B4689   |E8 253CFEFF     call 021982B3                            ; 跟入
021B468E   |53              push ebx
021B468F   |B9 98FA1C02     mov ecx,0x21CFA98
021B4694   |8945 08         mov dword ptr ss:[ebp+0x8],eax
021B4697   |E8 353CFEFF     call 021982D1
021B469C   |837D 14 01      cmp dword ptr ss:[ebp+0x14],0x1
021B46A0   |75 27           jnz X021B46C9
021B46A2   |8B45 08         mov eax,dword ptr ss:[ebp+0x8]

         在021982C2步入,执行到021A59FA处可以发现EAX就是硬件号3C6663B2。接着在一个可以执行的代码段打补丁

021982B3    56              push esi
021982B4    8BF1            mov esi,ecx
021982B6    FF7424 08       push dword ptr ss:[esp+0x8]
021982BA    8B8E 5C060000   mov ecx,dword ptr ds:[esi+0x65C]
021982C0    6A 00           push 0x0
021982C2    E8 24D70000     call 021A59EB                    ;步入
021982C7    3386 5C200000   xor eax,dword ptr ds:[esi+0x205C]
021982CD    5E              pop esi
//
021A59EB    8B4424 04       mov eax,dword ptr ss:[esp+0x4]
021A59EF    C1E0 06         shl eax,0x6
021A59F2    034424 08       add eax,dword ptr ss:[esp+0x8]
021A59F6    8B4481 18       mov eax,dword ptr ds:[ecx+eax*4+0x18]
021A59FA    35 8AC0E665     xor eax,0x65E6C08A
//

         ctrl+g,输入00401000然后输入如下内容

xor     eax, 65E6C08A  
cmp     eax, 5980A338   ; 判断 EAX 是否是我的机器码( )
jnz     00401011         ;不是则返回
mov     eax, 5F48DD41    ;如果是,则修改为5F48DD41
retn    8

         打好补丁后,在401000处F2下下断点,然后返回,剩下的使用上面讲的方法就可以脱去。

0x4.1 双进程

         得鸽一下。

0x5 DLL脱壳

0x5.1 DLL脱壳

         修改Magic_Jmp绕过IAT加密。

02995E53    395F FC         cmp dword ptr ds:[edi-0x4],ebx
02995E56  ^ 0F85 49FEFFFF   jnz 02995CA5
02995E5C    EB 03           jmp X02995E61
02995E5E    D6              salc

         接下来就是和exe脱壳不一样的地方,处理重定位表。大佬这边的操作有点不明白,哪位师傅如果知道告知一下。首先对GetTickCount下硬件断点HE GetTickCount,然后shitf+f9,观察堆栈是这个结果的话,删除断点,并返回.程序停在029AC3C8处。

00129220   029AC3C8  /CALL 到 GetTickCount 来自 029AC3C2
029AC3C8    2B85 A4D4FFFF   sub eax,dword ptr ss:[ebp-0x2B5C]
029AC3CE    8B8D A8D4FFFF   mov ecx,dword ptr ss:[ebp-0x2B58]
029AC3D4    6BC9 32         imul ecx,ecx,0x32
029AC3D7    81C1 D0070000   add ecx,0x7D0
029AC3DD    3BC1            cmp eax,ecx
029AC3DF    76 07           jbe X029AC3E8
029AC3E1    C685 34D9FFFF 0>mov byte ptr ss:[ebp-0x26CC],0x1
029AC3E8    83BD E4D7FFFF 0>cmp dword ptr ss:[ebp-0x281C],0x0

         然后ctrl+s,搜索,之后在找到的地址下断运行。就会出现如下黄色字体,记住标记的重定位RVA=6000和size=3B0.并将029ACFB8处跳转改为绝对跳转

PUSH EAX
XCHG CX,CX
POP EAX
STC
029ACF59    C705 E0C09B02 6>mov dword ptr ds:[0x29BC0E0],0x29BCB60   ; 重定位表RVA为6000
029ACF63    A1 E49F9C02     mov eax,dword ptr ds:[0x29C9FE4]
029ACF68    8B00            mov eax,dword ptr ds:[eax]
029ACF6A    8985 3CD9FFFF   mov dword ptr ss:[ebp-0x26C4],eax
029ACF70    A1 E49F9C02     mov eax,dword ptr ds:[0x29C9FE4]
029ACF75    83C0 04         add eax,0x4
029ACF78    A3 E49F9C02     mov dword ptr ds:[0x29C9FE4],eax
029ACF7D    A1 E49F9C02     mov eax,dword ptr ds:[0x29C9FE4]
029ACF82    8B00            mov eax,dword ptr ds:[eax]               ; 重定位表的大小为03B0
029ACF84    8985 78D9FFFF   mov dword ptr ss:[ebp-0x2688],eax
029ACF8A    A1 E49F9C02     mov eax,dword ptr ds:[0x29C9FE4]
029ACF8F    83C0 04         add eax,0x4
029ACF92    A3 E49F9C02     mov dword ptr ds:[0x29C9FE4],eax
029ACF97    83BD 3CD9FFFF 0>cmp dword ptr ss:[ebp-0x26C4],0x0        ; 重定位表为0??
029ACF9E    74 6F           je X029AD00F
029ACFA0    83BD 78D9FFFF 0>cmp dword ptr ss:[ebp-0x2688],0x0
029ACFA7    74 66           je X029AD00F
029ACFA9    8B85 FCD7FFFF   mov eax,dword ptr ss:[ebp-0x2804]
029ACFAF    8B8D 0CD8FFFF   mov ecx,dword ptr ss:[ebp-0x27F4]
029ACFB5    3B48 34         cmp ecx,dword ptr ds:[eax+0x34]
029ACFB8    74 55           je X029AD00F                             ; 重定位处理,此处需要跳过
029ACFBA    FFB5 78D9FFFF   push dword ptr ss:[ebp-0x2688]
029ACFC0    8B85 0CD8FFFF   mov eax,dword ptr ss:[ebp-0x27F4]
029ACFC6    0385 3CD9FFFF   add eax,dword ptr ss:[ebp-0x26C4]
029ACFCC    50              push eax
029ACFCD    8B85 FCD7FFFF   mov eax,dword ptr ss:[ebp-0x2804]
029ACFD3    FF70 34         push dword ptr ds:[eax+0x34]
029ACFD6    FFB5 0CD8FFFF   push dword ptr ss:[ebp-0x27F4]
029ACFDC    E8 3C150000     call 029AE51D                            ; 处理重定位
029ACFE1    83C4 10         add esp,0x10
029ACFE4    0FB6C0          movzx eax,al
029ACFE7    85C0            test eax,eax
029ACFE9    75 24           jnz X029AD00F
029ACFEB    8B45 08         mov eax,dword ptr ss:[ebp+0x8]
029ACFEE    8B00            mov eax,dword ptr ds:[eax]
029ACFF0    C700 07000000   mov dword ptr ds:[eax],0x7
029ACFF6    68 50CB9B02     push 0x29BCB50                           ; ASCII "Location CPG"
029ACFFB    8B45 08         mov eax,dword ptr ss:[ebp+0x8]
029ACFFE    FF70 04         push dword ptr ds:[eax+0x4]
029AD001    E8 24800000     call 029B502A                            ; jmp 到 msvcrt.strcpy

         此处提供另外一种方法,首先在内存窗口,在PE文件头下访问断点,然后shift+F9中断到029A9BC7,这里是DLL文件的文件头区域

Memory map, 条目 35
 地址=00A20000
 大小=00001000 (4096.)
 属主=EdrLib   00A20000 (自身)
 区段=
 包含=PE 文件头
 类型=Imag 01001002
 访问=R
 初始访问=RWE
029A9BC7    0348 3C         add ecx,dword ptr ds:[eax+0x3C]
029A9BCA    898D FCD7FFFF   mov dword ptr ss:[ebp-0x2804],ecx        ; EdrLib.00A200D8
029A9BD0    A1 FC009C02     mov eax,dword ptr ds:[0x29C00FC]
029A9BD5    8985 B0AAFFFF   mov dword ptr ss:[ebp+0xFFFFAAB0],eax
029A9BDB    8B85 B0AAFFFF   mov eax,dword ptr ss:[ebp+0xFFFFAAB0]
029A9BE1    8985 0CD8FFFF   mov dword ptr ss:[ebp-0x27F4],eax
029A9BE7    8B85 FCD7FFFF   mov eax,dword ptr ss:[ebp-0x2804]
029A9BED    8B40 50         mov eax,dword ptr ds:[eax+0x50]

         接着ctrl+s搜索如下指令

mov edx,dword ptr ds:[ecx+C]
add edx,dword ptr ds:[ecx+8]

         我们先来跟一下。这里是为了获取pdata,reloc

029AFABD    3BCE            cmp ecx,esi                              ; 和pdata比较
029AFABF    73 11           jnb X029AFAD2
029AFAC1    8B51 0C         mov edx,dword ptr ds:[ecx+0xC]
029AFAC4    0351 08         add edx,dword ptr ds:[ecx+0x8]
029AFAC7    3BD0            cmp edx,eax
029AFAC9    76 02           jbe X029AFACD
029AFACB    8BC2            mov eax,edx
029AFACD    83C1 28         add ecx,0x28                             ; next dir
029AFAD0  ^ EB EB           jmp X029AFABD
029AFAD2    5E              pop esi

         此时,当我们遍历到了reloc的时候,也就是eac为reloc的时候,在信息窗口显示的数据就是reloc的RVA=6000。size=3B0

ds:[00A20254]=00006000    edx=000058E0

         绕过重定位表处理之后,直接在 EdrLib .text段上下F2断点,然后shitf+f9直达OEP,注意并不是在LoadDll.exe的text段,而是需要脱壳的DLL的text段下断点。

Memory map, 条目 36
 地址=00A21000
 大小=00003000 (12288.)
 属主=EdrLib   00A20000
 区段=.text
 类型=Imag 01001002
 访问=R
 初始访问=RWE
00A211C9    55              push ebp
00A211CA    8BEC            mov ebp,esp
00A211CC    53              push ebx
00A211CD    8B5D 08         mov ebx,dword ptr ss:[ebp+0x8]
00A211D0    56              push esi
00A211D1    8B75 0C         mov esi,dword ptr ss:[ebp+0xC]
00A211D4    57              push edi
00A211D5    8B7D 10         mov edi,dword ptr ss:[ebp+0x10]
00A211D8    85F6            test esi,esi

         接下来就可以dump程序了,在LordPE目录中修改重定位信息。因为没有处理重定位表,所以只需要修复DLL原来的重定位表的RVA和大小就行了。

RVA=6000
SIZE=3B0

         因为没有修改IAT表,所以IAT表的数据是正确的。所以直接Ctrl+M,选中.rdata处,双击,为了方便查看选择地址显示.可以判断起始地址为A24000,结束地址为A240C8,大小为C8

00A24000  77EF7F9D  GDI32.GetTextExtentPoint32W
00A24004  77EF7EAC  GDI32.TextOutW
00A24008  77EFBA3F  GDI32.TextOutA
00A2400C  77F0C63D  GDI32.GetTextExtentPoint32A
00A24010  029970A0
00A24014  7C809A99  kernel32.lstrlenW
00A24018  7C814B77  kernel32.FreeEnvironmentStringsW
00A2401C  7C812FAD  kernel32.GetCommandLineA
00A24020  7C81126A  kernel32.GetVersion
00A24024  7C81CAFA  kernel32.ExitProcess
00A24028  7C801E1A  kernel32.TerminateProcess
00A2402C  7C80DE85  kernel32.GetCurrentProcess
00A24030  7C8097B8  kernel32.GetCurrentThreadId
00A24034  7C809C55  kernel32.TlsSetValue
00A24038  7C812E2F  kernel32.TlsAlloc
00A2403C  7C813767  kernel32.TlsFree

         接下有有一个很秀的操作,将我们获得从A24000-A240C8的IAT数据,复制到新打开的notepad中的404000-4040C8处.这叫借鸡生蛋。然后在ImportRCE中IAT的RVA填写404000,大小填写C8就好了

00404020  6A 12 81 7C FA CA 81 7C 1A 1E 80 7C 85 DE 80 7C  j亅亅€|呣€|
00404030  B8 97 80 7C 55 9C 80 7C 2F 2E 81 7C 67 37 81 7C  笚€|U渶|/.亅g7亅
00404040  D0 97 80 7C 27 CD 80 7C C9 2F 81 7C E1 0E 81 7C  袟€|'蛝|?亅?亅
00404050  F2 1E 80 7C 5A 13 93 7C 5F B5 80 7C D7 D6 81 7C  ?€|Z搢_祤|字亅
00404060  46 BE 80 7C 64 A1 80 7C 7B CC 81 7C 98 2F 81 7C  F線|d|{虂|?亅
00404070  88 0F 81 7C 46 2C 81 7C 74 9B 80 7C 0D FF 92 7C  ?亅F,亅t泙|.抾
00404080  17 0E 81 7C 81 9F 80 7C 00 10 92 7C E0 10 92 7C  亅仧€|.抾?抾
00404090  A4 00 93 7C 06 2F 81 7C A5 99 80 7C 37 28 81 7C  ?搢/亅€|7(亅
004040A0  E1 9A 80 7C 80 9B 93 7C 30 AE 80 7C 7B 1D 80 7C  釟€|€洆|0畝|{€|
004040B0  88 9C 80 7C 00 8E 83 7C 38 CD 80 7C 24 8A 83 7C  垳€|.巸|8蛝|$妰|
004040C0  20 A5 80 7C A5 AB 94 7C 3B C6 7D 1B FF 15 CC 63   |カ攟;苶蘡
004040D0  40 00 83 F8 3F 0F 85 A2 03 00 00 C7 45 FC FD FF  @.凐?參..荅
004040E0  FF FF E9 96 03 00 00 33 F6 A1 00 50 40 00 56 50  闁..3觥.P@.VP

0x6 非常规方法应对

0x6.1 应对IAT加密

         这里使用了非常简单的Arm作为示范,只含有IAT加密,不涉及其他,第二常规方法下建议使用增强版OD。

 

         通常规避IAT加密的方法就是Magic_Jmp,然后.text下断到OEP。这里前辈给出新方法。介绍一下。

 

         首先须要了解到Arm并不是对所有的API函数进行加密,前辈这里的思路是先直接到达OEP,在根据里面一直的函数地址寻找IAT地址,然后寻找出IAT中被加密的地方,下硬件断点。重新运行之后到达被修改的的地址,然后分析加密IAT的过程,使用jmp或者nop规避即可。

 

         在.text下断,然后F9运行,程序到达OEP,只是IAT被加密了。

004010CC    55              push    ebp
004010CD    8BEC            mov     ebp, esp
004010CF    83EC 44         sub     esp, 44
004010D2    56              push    esi
004010D3    FF15 E4634000   call    dword ptr [4063E4]
004010D9    8BF0            mov     esi, eax
004010DB    8A00            mov     al, byte ptr [eax]
004010DD    3C 22           cmp     al, 22
004010DF    75 1B           jnz     short 004010FC
004010E1    56              push    esi
004010E2    FF15 F4644000   call    dword ptr [4064F4]               ; USER32.CharNextA
004010E8    8BF0            mov     esi, eax
004010EA    8A00            mov     al, byte ptr [eax]
004010EC    84C0            test    al, al

         大家应该都知道IID中所有函数应该是连续的,但是这些是不连续的,应该是被加密的。但是也说过其只是对IAT部分函数地址(有歧义自行理解)进行加密。但是IAT的RVA应该是一致的。所以将IAT的起始RVA=62E4,结束RVA=6524,大小应该为240,oep为4010cc记录下来。

0040645C  77D3C972  USER32.SetDlgItemTextA
00406460  77D5A5E5  USER32.TabbedTextOutA
00406464  021AB517
00406468  77D29849  USER32.EnableWindow
0040646C  021AA871
00406470  77D3C2E7  USER32.SendDlgItemMessageA
00406474  77D2AF1B  USER32.GetDlgCtrlID

         我们在0040645C处下硬件断点,然后重新载入,接着按shitf+9,即可到达0218CF28处

0218CF28    8B85 10D9FFFF   mov     eax, dword ptr [ebp-26F0]        ; PackEd.004062EC
0218CF2E    83C0 04         add     eax, 4
0218CF31    8985 10D9FFFF   mov     dword ptr [ebp-26F0], eax
0218CF37  ^ E9 4DFCFFFF     jmp     0218CB89
0218CF3C    FF15 84721902   call    dword ptr [2197284]              ; kernel32.GetTickCount
0218CF42    2B85 A4D4FFFF   sub     eax, dword ptr [ebp-2B5C]
0218CF48    8B8D A8D4FFFF   mov     ecx, dword ptr [ebp-2B58]

         接着往下跟,在0218CD6B处发现比较一个,可以发现第一轮他是和RegCreateKeyA比较

0218CD63    50              push    eax
0218CD64    8D85 68C2FFFF   lea     eax, dword ptr [ebp-3D98]
0218CD6A    50              push    eax
0218CD6B    FF15 78731902   call    dword ptr [2197378]              ; msvcrt._stricmp
0218CD71    59              pop     ecx
0218CD72    59              pop     ecx
0218CD73    85C0            test    eax, eax
0218CD75    75 11           jnz     short 0218CD88
001294D4   0012AE7C  |s1 = "RegOpenKeyA"
001294D8   0012AD6C  \s2 = "RegCreateKeyA"
001294DC   021A0F88

         经过如下分析,我们可以知道0218CF1A处就是我们加密IAT的操作。同时也知道Arm只是针对部分IAT进行加密的。所以只需要修改之前在0218CD6B处的jnz short 0218CD88,或者位于0218CF18处的jnb short 0218CF37即可!

0218CF1A    8B85 10D9FFFF   mov     eax, dword ptr [ebp-26F0]        ; eax为IAT地址
0218CF20    8B8D 68CAFFFF   mov     ecx, dword ptr [ebp-3598]        ; ecx是加密的值
0218CF26    8908            mov     dword ptr [eax], ecx             ; 将值写入IAT中
0218CF28    8B85 10D9FFFF   mov     eax, dword ptr [ebp-26F0]        ; 定位IAT
0218CF2E    83C0 04         add     eax, 4                           ; 指向下一个地址
0218CF31    8985 10D9FFFF   mov     dword ptr [ebp-26F0], eax        ; 写入
0218CF37  ^ E9 4DFCFFFF     jmp     0218CB89                         ; 便利下一个

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞10
打赏
分享
最新回复 (13)
雪    币: 20913
活跃值: (4824)
能力值: ( LV12,RANK:460 )
在线值:
发帖
回帖
粉丝
鬼手56 9 2019-5-3 14:37
2
1
精前留名
雪    币: 88
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fawcgzmg 2019-5-3 19:29
3
0
1.能否分享一下范例
2.0x2.1 step3撤销之前的修改是什么意思
谢谢
雪    币: 2753
活跃值: (1603)
能力值: ( LV9,RANK:850 )
在线值:
发帖
回帖
粉丝
wofan[OCN] 21 2019-5-3 20:52
4
0
你找个软件脱一次就会明白的,之前在OD中把je改成jmp,跳过之后,再恢复现状啊,把jmp改回je
雪    币: 2375
活跃值: (433)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
petersonhz 2019-5-3 21:45
5
0
wofan[OCN] 你找个软件脱一次就会明白的,之前在OD中把je改成jmp,跳过之后,再恢复现状啊,把jmp改回je
vmp是否比Armadillo更恶心啊?
雪    币: 2938
活跃值: (18)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
fqbqrr 2019-5-3 22:23
6
0
收藏不了了么.
雪    币: 314
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
皮豪 2019-5-4 21:48
7
0
既然前辈是精华,那我就给你点赞
雪    币: 17848
活跃值: (59913)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2019-5-5 09:40
8
0
fqbqrr 收藏不了了么.
雪    币: 1725
活跃值: (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
vincen9931 2019-5-6 09:14
9
0
小白我还是看不懂,还需要继续学习
雪    币: 485
活跃值: (2119)
能力值: ( LV12,RANK:356 )
在线值:
发帖
回帖
粉丝
findreamwang 6 2019-5-6 10:58
10
0
fawcgzmg 1.能否分享一下范例 2.0x2.1 step3撤销之前的修改是什么意思 谢谢
链接:https://pan.baidu.com/s/1IXJ35suU5cqpOSBbvpchGw 
提取码:bzrl 
用例和视频基本在上面,
雪    币: 485
活跃值: (2119)
能力值: ( LV12,RANK:356 )
在线值:
发帖
回帖
粉丝
findreamwang 6 2019-5-6 10:58
11
0
fawcgzmg 1.能否分享一下范例 2.0x2.1 step3撤销之前的修改是什么意思 谢谢
链接:https://pan.baidu.com/s/1IXJ35suU5cqpOSBbvpchGw 
提取码:bzrl 
用例和视频基本在上面,
雪    币: 565
活跃值: (40)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
灿烂阳光 2019-8-25 18:50
12
0
楼主链接已经失效,能再发一次吗,谢谢~
雪    币: 245
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yangjiajun 2019-10-8 17:31
13
0
楼主链接已经失效,能再发一次吗?
雪    币:
活跃值: (391)
能力值: ( LV5,RANK:73 )
在线值:
发帖
回帖
粉丝
三猫 1 2020-5-1 15:01
14
0
6
游客
登录 | 注册 方可回帖
返回