1.5 編寫自定位ShellCode彈窗
在筆者上一篇文章中簡單的介紹了如何運用匯編語言編寫一段彈窗代碼,雖然簡易ShellCode
可以被正常執(zhí)行,但卻存在很多問題,由于采用了硬編址的方式來調(diào)用相應API函數(shù)的,那么就會存在一個很大的缺陷,如果操作系統(tǒng)的版本不統(tǒng)或系統(tǒng)重啟過,那么基址將會發(fā)生變化,此時如果再次調(diào)用基址參數(shù)則會調(diào)用失敗,本章將解決這個棘手的問題,通過ShellCode
動態(tài)定位的方式解決這個缺陷,并以此設計出真正符合規(guī)范的ShellCode
代碼片段。
自定位代碼是一種常見的Shellcode
技術,它使Shellcode
能夠在任何系統(tǒng)上運行,而無需考慮系統(tǒng)內(nèi)存布局和代碼地址等問題。以下是Shellcode自定位代碼的流程:
??1.查找Kernel32.dll基址并在其中尋找LoadLibraryA
??2.計算函數(shù)名hash摘要并通過hash摘要判斷函數(shù)
??3.解析Kernel32.dll導出表
??4.最終動態(tài)調(diào)用系列函數(shù)
1.5.1 動態(tài)查找Kernel32基址
首先我們需要通過匯編的方式來實現(xiàn)動態(tài)定位Kernel32.dll
中的基址,你或許會有個疑問? 為什么要查找Kernel32.dll
的地址而不是User32.dll
,這是因為我們最終的目的是調(diào)用MessageBoxA
這個函數(shù),而該函數(shù)位于?User32.dll
這個動態(tài)鏈接庫里,在某些程序中User32模塊并不一定會被加載,而Kernel32則必然會被加載,為了能夠調(diào)用MessageBoxA
函數(shù),我們就需要調(diào)用LoadLibraryA
函數(shù)來加載User32.dll
這個模塊,而LoadLibraryA
恰巧又位于kernel32.dll
中,因此我們只需要找到LoadLibraryA
函數(shù),即可實現(xiàn)加載任意的動態(tài)鏈接庫,并調(diào)用任意的函數(shù)的目的。
由于我們需要動態(tài)獲取LoadLibraryA()
以及ExitProcess()
這兩個函數(shù)的地址,而這兩個函數(shù)又是存在于kernel32.dll
中的,因此這里需要先找到kernel32.dll
的基址,然后通過對其進行解析,從而查找兩個函數(shù)的動態(tài)地址。動態(tài)的查找Kernel32.dll
的地址可總結為如下:
??1.首先通過段
選擇子FS
在內(nèi)存中找到當前進程內(nèi)的線程環(huán)境塊結構體指針TEB。??2.線程環(huán)境塊偏移位置為
fs:[0x30]
的位置處存放著指向進程環(huán)境塊PEB結構的指針。??3.進程環(huán)境塊PEB偏移為
0x0c
的地址處存放著指向PEB_LDR_DATA
的結構體指針。??4.而在
PEB_LDR_DATA
偏移0x1c
的地址處存放著指向Ldr.InMemoryOrderModuleList
模塊初始化鏈表的頭指針。??5.在初始化鏈表中存放的就是所有進程的模塊信息,通過將偏移值加
0x08
讀者即可獲取到kernel32.dll
的基地址。
既然有了固定的查詢定位公式,接下我們就使用WinDBG
調(diào)試器來手工完成對Kernel32.dll
地址的定位:
小提示:Windbg是Windows Debugger的縮寫,是一種微軟提供的免費調(diào)試器工具,用于分析和調(diào)試Windows操作系統(tǒng)和應用程序。Windbg可以在不重啟系統(tǒng)的情況下,通過連接到正在運行的進程或者操作系統(tǒng)內(nèi)核,獲取并分析程序的運行信息、內(nèi)存狀態(tài)、寄存器狀態(tài)、線程狀態(tài)、調(diào)用堆棧等數(shù)據(jù),并可以使用符號文件來解析程序中的符號名,從而幫助開發(fā)者定位問題和進行深入調(diào)試。
讀者可通過附件獲取到WinDBG
程序,當用戶打開WinDBG
時讀者可通過Ctrl+E
快捷鍵任意打開一個可執(zhí)行程序,接著我們開始尋找吧;
1.通過段選擇子FS
在內(nèi)存中找到當前的線程環(huán)境塊TEB
。這里可以利用本地調(diào)試,并輸入!teb
指令,讀者可看到如下輸出:
小提示:TEB(Thread Environment Block)是Windows操作系統(tǒng)中的一個重要數(shù)據(jù)結構,每個進程都有一個對應的TEB。它主要用于存儲線程的環(huán)境信息和狀態(tài),包括線程局部存儲(TLS)指針、異常處理鏈、堆棧信息、Fiber信息等。TEB由Windows內(nèi)核自動創(chuàng)建和管理,可以通過系統(tǒng)調(diào)用和調(diào)試器工具來訪問和修改其內(nèi)容。
如上線程環(huán)境塊偏移位置為0x30
的地方存放著指向進程環(huán)境塊PEB
的指針。結合上圖可見,當前PEB
的地址為002bb000
小提示:PEB是Windows操作系統(tǒng)的進程環(huán)境塊(Process Environment Block)的縮寫。PEB是一個數(shù)據(jù)結構,其中包含了關于進程的許多信息,例如進程的模塊、堆、線程等等。PEB由操作系統(tǒng)內(nèi)核在創(chuàng)建進程時分配和初始化,并且只有在進程運行期間才可用。
2.在進程環(huán)境塊中偏移位置為0x0c
的地方存放著指向PEB_LDR_DATA
結構體的指針,其中存放著已經(jīng)被進程裝載的動態(tài)鏈接庫的信息,如下圖所示;
3.接著PEB_LDR_DATA
結構體偏移位置為0x1c
的地方存放著指向模塊初始化鏈表的頭指針InInitializationOrderModuleList
,如下圖所示;
4.模塊初始化鏈表InInitializationOrderModuleList
中按順序存放著PE裝入運行時初始化模塊的信息,第一個鏈表節(jié)點是ntdll.dll
,第二個鏈表結點就是kernel32.dll
。我們可以先看看?InInitializationOrderModuleList
中的內(nèi)容:
上圖中的0x005a3ad8
保存的是第一個鏈節(jié)點的指針,解析一下這個結點,可發(fā)現(xiàn)如下地址:
上圖中的0x77200000
為ntdll.dll
的模塊基地址,而0x005a4390
則是指向下一個模塊的指針,我們繼續(xù)跟隨0x005a4390
地址,則此處看到的標黃處是下一個模塊kernel32.dll
的基地址。
最后我們通過輸入!peb
命令,輸出當前所有載入模塊并驗證一下:
既然有了如上所述的方法,那么讀者可以很容易的實現(xiàn)這段功能,為了便于讀者理解,筆者先提供一段使用C語言書寫的實現(xiàn)方式,如下代碼所示;
int?main(int?argc,?char?*?argv[])
{
????DWORD?*PEB?=?NULL;
????DWORD?*Ldr?=?NULL;
????DWORD?*Init?=?NULL;
????DWORD?*Kernel32?=?NULL;
????__asm
????{
????????mov?eax,?fs:[0x30]
????????mov?PEB,eax
????}
????printf("得到PEB指針?=?%x?\n",?PEB);
????Ldr?=?*(DWORD?**)((unsigned?char?*)PEB?+?0x0c);
????printf("得到LDR結構指針?=?%x?\n",?Ldr);
????Init?=?*(DWORD?**)((unsigned?char?*)Ldr?+?0x1c);
????printf("得到InInitializationOrderModuleList結構指針?=?%x?\n",?Init);
????Kernel32?=?*(DWORD?**)((unsigned?char?*)Init?+?0x08);
????printf("得到Kernel32的基地址?=?%x?\n",?Kernel32);
????system("pause");
????return?0;
}
運行輸出效果如下圖所示,讀者可自行檢查讀取結果的準確性;
將此段代碼翻譯為匯編模式也很容易,如下是通過匯編實現(xiàn)的流程;
????.386p
????.model?flat,stdcall
????option?casemap:none
include?windows.inc
include?kernel32.inc
includelib?kerbcli.lib
assume?fs:nothing
.code
????main?PROC
????????xor?eax,eax
????????xor?edx,edx
????????mov?eax,fs:[30h]???????????;?得到PEB結構地址
????????mov?eax,[eax?+?0ch]????????;?得到PEB_LDR_DATA結構地址
????????mov?esi,[eax?+?1ch]????????;?得到?InInitializationOrderModuleList
????????lodsd??????????????????????;?得到KERNEL32.DLL所在LDR_MODULE結構的
????????mov?eax,[eax]??????????????;?Windows?7?以上要將這里打開
????????mov?edx,[eax?+?8h]?????????;?得到BaseAddress,既Kernel32.dll基址
????????ret
????main?ENDP
END?main
1.5.2 動態(tài)查找并枚舉進程模塊
在讀者閱讀過第一節(jié)中的內(nèi)容時,相信您已經(jīng)可以熟練的掌握WinDBG
調(diào)試器的基本使用了,本節(jié)我們將擴展一個知識點,以讓讀者能更好的理解WinDBG
調(diào)試命令,本次我們實現(xiàn)枚舉進程模塊的功能,本案例將不在解釋基本功能。
通過PEB/TEB
找到自身進程的所有載入模塊數(shù)據(jù),首先獲取TEB
線程環(huán)境塊。在編程的時候,TEB始終保存在寄存器FS
中。
得到LDR結構:
Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );
然后再找到PEB
結構偏移為0x30
從該命令的輸出可以看出,PEB結構體的地址位于TEB
結構體偏移0x30
的位置處。
找到了PEB
也就可以找到_PEB_LDR_DATA
結構 其位于PEB
偏移0c
的位置上。
Ldr =?
*( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );
從輸出結果可以看出,LDR
在PEB
結構體偏移的0x0C
處,該地址保存的地址是0x77325d80
通過該地址來解析LDR
結構體。
Flink =?
*( ( DWORD ** )( ( unsigned char * )Ldr + 0x14 ) );
位于LDR偏移14的位置就是InLoadOrderModuleList其所指向的就是模塊名稱表。
現(xiàn)在來手動遍歷[ 0x5a3bd0 - 0x5aa5b8 ]
第一條鏈表,輸入命令dd 0x5a3bd0
鏈表偏移0x18
的位置是模塊的映射地址 ImageBase,鏈表偏移?0x28
?的位置是模塊的路徑及名稱的地址,鏈表偏移?0x30
?的位置是模塊名稱的地址。
如上圖中的輸出結果,地址005a2480
保存有當前模塊詳細路徑信息,而005a24ae
則保存有當前模塊名,我們可以通過du
命令來進行驗證;
當讀者需要讀入下一個模塊鏈表時,則需要訪問0x005a3ac8
這個內(nèi)存地址,其中保存著下一個鏈表結構,依次遍歷。
當然這個鏈表結構其實訪問InMemoryOrderModuleList
同樣可以得到,這兩個都指向同一片區(qū)域。
上方介紹的結構,是微軟保留結構,只能從網(wǎng)上找到一個結構定義,根據(jù)該結構的定義做進一步解析即可。
typedef?struct?_LDR_DATA_TABLE_ENTRY?{
????PVOID?Reserved1[2];
????LIST_ENTRY?InMemoryOrderLinks;
????PVOID?Reserved2[2];
????PVOID?DllBase;
????PVOID?EntryPoint;
????PVOID?Reserved3;
????UNICODE_STRING?FullDllName;
????BYTE?Reserved4[8];
????PVOID?Reserved5[3];
????union?{
????ULONG?CheckSum;
????PVOID?Reserved6;
????};
????ULONG?TimeDateStamp;
}?LDR_DATA_TABLE_ENTRY,?*PLDR_DATA_TABLE_ENTRY;
根據(jù)如上分析細節(jié),那么描述枚舉模塊列表的核心代碼就可以寫成如下案例;
int?main(int?argc,?char*?argv[])
{
????DWORD?*PEB?=?NULL,?*Ldr?=?NULL,?*Flink?=?NULL,?*p?=?NULL;
????DWORD?*BaseAddress?=?NULL,?*FullDllName?=?NULL,*Ba?=?NULL;
????__asm
????{
????????mov?eax,?fs:[0x30]
????????mov?PEB,?eax
????}
????Ldr?=?*((DWORD?**)((unsigned?char?*)PEB?+?0x0c));
????Flink?=?*((DWORD?**)((unsigned?char?*)Ldr?+?0x14));
????p?=?Flink;
????p?=?*((DWORD?**)p);
????while?(Flink?!=?p)
????{
????????BaseAddress?=?*((DWORD?**)((unsigned?char?*)p?+?0x10));
????????FullDllName?=?*((DWORD?**)((unsigned?char?*)p?+?0x20));
????if?(BaseAddress?==?0)
????????break;
????printf("鏡像基址?=?%08x?\n?-->?模塊路徑?=?%S?\n",?BaseAddress,?(unsigned?char?*)FullDllName);
????????p?=?*((DWORD?**)p);
????}
????system("pause");
????return?0;
}
讀者編譯并運行該程序,則默認會枚舉出當前模塊所導入的所有模塊信息,其輸出效果如下圖所示;
1.5.3 計算函數(shù)Hash摘要值
案例介紹了如何使用Win32匯編語言和C語言計算字符串的hash
摘要值。字符串的hash
摘要值是通過一定的算法將字符串壓縮為一個固定長度的十六進制數(shù),用于在程序中進行快速的字符串比較。具體而言,該案例使用了循環(huán)移位hash計算法,并最終得到了字符串的 hash 值,并以十六進制數(shù)的形式輸出。
讀者一定有疑問為啥需要HASH壓縮處理? 原因是,如果直接將函數(shù)名壓棧的話,我們就需要提供更多的空間來存儲ShellCode
代碼,為了能夠讓我們編寫的ShellCode
代碼更加的短小精悍,所以我們將要對字符串進行hash
處理,將字符串壓縮為一個十六進制數(shù),這樣只需要比較二者hash
值就能夠判斷目標函數(shù),盡管這樣會引入額外的hash
算法,但是卻可以節(jié)省出存儲函數(shù)名字的空間。
為了能讓讀者理解計算原理,此處我們先使用C語言做摘要計算描述,如下代碼中的GetHash
函數(shù),該函數(shù)接受一個指向字符數(shù)組的指針,即一個字符串,然后對字符串進行哈希計算,并返回計算結果。
哈希計算的過程是通過循環(huán)遍歷字符串中的每個字符,對其進行位運算和加法運算,最終得到一個32
位的哈希值。對于字符串中的每個字符,程序首先將哈希值左移25
位,然后將結果右移7
位,相當于是對哈希值進行了循環(huán)右移25
位。然后程序?qū)⒃撟址?code>ASCII值加到哈希值上。循環(huán)遍歷完字符串中的所有字符后,哈希值即為最終的計算結果。
DWORD?GetHash(char?*fun_name)
{
????DWORD?digest?=?0;
????while?(*fun_name)
????{
????????digest?=?((digest?<<?25)?|?(digest?>>?7));
????????digest?+=?*fun_name;
????????fun_name++;
????}
????return?digest;
}
int?main(int?argc,?char?*argv[])
{
????DWORD?MessageBoxHash;
????DWORD?ExitProcessHash;
????DWORD?LoadLibraryAHash;
????MessageBoxHash?=?GetHash("MessageBoxA");
????printf("MessageBoxHash?=?0x%.8x\n",?MessageBoxHash);
????ExitProcessHash?=?GetHash("ExitProcess");
????printf("ExitProcessHash?=?0x%.8x\n",?ExitProcessHash);
????LoadLibraryAHash?=?GetHash("LoadLibraryA");
????printf("LoadLibraryAHash?=?0x%.8x\n",?LoadLibraryAHash);
????system("pause");
????return?0;
}
運行上方C語言實現(xiàn)代碼,則讀者可以此獲取到三個核心函數(shù)的Hash值,其輸出效果如下圖所示;
在理解了C語言版本的計算流程后,那么匯編語言版本的也應該很容易理解,如下是使用Win32
匯編語言的實現(xiàn)過程,并在MASM
上正常編譯,匯編版字符串轉(zhuǎn)換Hash
值。
????.386p
????.model?flat,stdcall
????option?casemap:none
????
include?windows.inc
include?kernel32.inc
include?msvcrt.inc
includelib?kernel32.lib
includelib?msvcrt.lib
.data
????data?db?"MessageBoxA",0h
????Fomat?db?"0x%x",0
.code
????main?PROC
????????xor?eax,eax???????????????;?清空eax寄存器
????????xor?edx,edx???????????????;?清空edx寄存器
????????lea?esi,data??????????????;?取出字符串地址
????loops:
????????movsx?eax,byte?ptr[esi]???;?每次取出一個字符放入eax中
????????cmp?al,ah?????????????????;?驗證eax是否為0x0即結束符
????????jz?nops???????????????????;?為0則說明計算完畢跳轉(zhuǎn)到nops
????????ror?edx,7?????????????????;?不為零,則進行循環(huán)右移7位
????????add?edx,eax???????????????;?將循環(huán)右移的值不斷累加
????????inc?esi???????????????????;?esi自增,用于讀取下一個字符
????????jmp?loops?????????????????;?循環(huán)執(zhí)行
????nops:
????????mov?eax,edx???????????????;?結果存在eax里面
????????invoke?crt_printf,addr?Fomat,eax
????????ret
????main?ENDP
END?main
1.5.4 枚舉Kernel32導出表
在文章開頭部分我們通過WinDBG
調(diào)試器已經(jīng)找到了Kernel32.dll
這個動態(tài)鏈接庫的基地址,而Dll文件本質(zhì)上也是PE文件,在Dll文件中同樣存在導出表,其內(nèi)部記錄著該Dll的導出函數(shù)。接著我們需要對Dll文件的導出表進行遍歷,不斷地搜索,從而找到我們所需要的API函數(shù),同樣的可以通過如下定式獲取到指定的導出表。
??1.從
kernel32.dll
加載基址算起,偏移0x3c
的地方就是其PE文件頭。??2.PE文件頭偏移
0x78
的地方存放著指向函數(shù)導出表的指針。??3.導出表偏移
0x1c
處的指針指向存儲導出函數(shù)偏移地址(RVA)的列表。??4.導出表偏移
0x20
處的指針指向存儲導出函數(shù)函數(shù)名的列表。
首先我們通過WinDBG來實現(xiàn)讀取導入表及導出表試試,我們以讀取ole32.dll
為例,首先讀者需要通過lmvm ole32.dll
查詢到該模塊的入口地址,如圖所示該模塊的入口地址為0x75830000
解析DOS頭,DOS頭通過_IMAGE_DOS_HEADER
結構被定義,在解析時讀者應傳入模塊入口0x75830000
地址,其次DOS頭中e_lfanew
字段指向了PE頭,該字段需要注意;
??執(zhí)行讀入DOS頭:
dt ole32!_IMAGE_DOS_HEADER 75830000
解析PE頭,PE頭通過DOS頭部的e_lfanew
中存儲的之加上模塊基地址獲取到,在本例中則是通過75830000+0n264
獲取到;
??讀入PE頭:
dt ole32!_IMAGE_NT_HEADERS 75830000+0n264
接著需要在_IMAGE_OPTIONAL_HEADER
可選頭中找到EXPORT
導出表基地址,通過PE頭基址75830108
加上0x018
也就是OptionalHeader
的偏移,即可定位到DataDirectory[0]
也就是導出表基地址,其地址為75830180
根據(jù)上述定義,繼續(xù)尋找EXPORT
導出表的實際地址,需要注意的是Evaluate expression
中的結果是根據(jù)ole32
模塊的基地址與VirtualAddress
當前地址相加后得到的,如下圖所示
當讀者需要枚舉特定模塊時,則可通過模塊基地址加上例如Name
字段偏移值,來讀入模塊名稱;
如果讀者需要枚舉所有導出函數(shù),則讀者可通過模塊基地址加上AddressOfNames
字段,并通過如下命令實現(xiàn)完整輸出;
??.foreach(place {dd 758e4088}){r @$t0=${place}+75830000; .if(@$t0<778e4088){da @$t0}}
導入表的枚舉與導出表類似,為了節(jié)約篇幅此處只給出調(diào)試數(shù)據(jù),讀者可根據(jù)自己的掌握情況自行分析學習;
#?根據(jù)模塊基地址獲取模塊e_lfanew
0:000>?dt?ole32!_IMAGE_DOS_HEADER?0x75830000
???+0x000?e_magic??????????:?0x5a4d
???+0x028?e_res2???????????:?[10]?0
???+0x03c?e_lfanew?????????:?0n264
#?定位到NT頭部
0:000>?dt?ole32!_IMAGE_NT_HEADERS?0x75830000?+?0n264
???+0x000?Signature????????:?0x4550
???+0x004?FileHeader???????:?_IMAGE_FILE_HEADER
???+0x018?OptionalHeader???:?_IMAGE_OPTIONAL_HEADER
#?基地址與e_lfanew相加得到OPTIONAL
0:000>??0x75830000?+?0n264
Evaluate?expression:?1971519752?=?75830108
#?查詢OPTIONAL
0:000>?dt?ole32!_IMAGE_OPTIONAL_HEADER?-v?-ny?DataDirectory?75830108+0x018
struct?_IMAGE_OPTIONAL_HEADER,?31?elements,?0xe0?bytes
???+0x060?DataDirectory?:?[16]?struct?_IMAGE_DATA_DIRECTORY,?2?elements,?0x8?bytes
0:000>???75830108+0x018+0x60
Evaluate?expression:?1971519872?=?75830180
#?得到數(shù)據(jù)目錄表地址
0:000>?dt?ole32!_IMAGE_DATA_DIRECTORY?75830180+8
???+0x000?VirtualAddress???:?0xbd9f8
???+0x004?Size?????????????:?0x460
0:000>???0x75830000+0xbd9f8
Evaluate?expression:?1972296184?=?758ed9f8
#?DataDirectory[1]即為導入表,地址為758ed9f8
0:000>?dt?ole32!_IMAGE_IMPORT_DESCRIPTOR?758ed9f8
???+0x000?Characteristics??:?0xbe700
???+0x000?OriginalFirstThunk?:?0xbe700
???+0x004?TimeDateStamp????:?0
???+0x008?ForwarderChain???:?0
???+0x00c?Name?????????????:?0xbe87a
???+0x010?FirstThunk???????:?0xbd8a8
0:000>?da?0x75830000+0xbe87a
758ee87a??"api-ms-win-crt-string-l1-1-0.dll"
#?每一個_IMAGE_IMPORT_DESCRIPTOR的大小為0x14
0:000>????sizeof(_IMAGE_IMPORT_DESCRIPTOR)
unsigned?int?0x14
#?也就是說,每次遞增14即可輸出下一個導入函數(shù)名
0:000>?dt?ole32!_IMAGE_IMPORT_DESCRIPTOR?758ed9f8+14
???+0x000?Characteristics??:?0xbe6f4
???+0x000?OriginalFirstThunk?:?0xbe6f4
???+0x004?TimeDateStamp????:?0
???+0x008?ForwarderChain???:?0
???+0x00c?Name?????????????:?0xbe89c
???+0x010?FirstThunk???????:?0xbd89c
0:000>?da?0x75830000+0xbe89c
758ee89c??"api-ms-win-crt-runtime-l1-1-0.dl"
0:000>?dt?ole32!_IMAGE_IMPORT_DESCRIPTOR?758ed9f8+28
???+0x000?Characteristics??:?0xbe64c
???+0x000?OriginalFirstThunk?:?0xbe64c
???+0x004?TimeDateStamp????:?0
???+0x008?ForwarderChain???:?0
???+0x00c?Name?????????????:?0xbeb88
???+0x010?FirstThunk???????:?0xbd7f4
0:000>?da?0x75830000+0xbeb88
758eeb88??"api-ms-win-crt-private-l1-1-0.dl"
#?分析第一個IID的IAT和INT
#?先看INT:?IMAGE_THUNK_DATA其實就是一個DWORD,如IID一樣,也是一個接一個,最后一個為NULL
第一個:
0:000>?dd?0xbe6f4+0x75830000?L1
758ee6f4??000be86c
#?最高位不為1(為1表示為序號輸入)指向_IMAGE_IMPORT_BY_NAME結構
.foreach(place?{dd?758ee6f4})?{r?@$t0?=?${place}+75830000+2;?.if?(@$t0<86d00000){da?@$t0;}}
758ee86e??"_initterm_e"
758ee862??"_initterm"
75830002??"."
758eeb80??"memset"
758ee84e??"wcsncmp"
758ee858??"strcspn"
我們將問題回歸到枚舉導出表上,函數(shù)的RVA地址和名字按照順序存放在上述兩個列表中,我們可以在列表定位任意函數(shù)的RVA地址,通過與動態(tài)鏈接庫的基地址相加得到其真實的VA,而計算的地址就是我們最終在ShellCode
中調(diào)用時需要的地址,其匯編核心枚舉代碼如下所示;
int?main(int?argc,?char?*?argv[])
{
????int?a;
????__asm
????{
????????mov?ebx,?dword?ptr?fs?:?[0x30]?????????;?獲取當前線程信息的地址
????????mov?ecx,?dword?ptr[ebx?+?0xc]??????????;?獲取PEB結構體的地址
????????mov?ecx,?dword?ptr[ecx?+?0x1c]?????????;?獲取PEB結構體中的LDR結構體的地址
????????mov?ecx,?[ecx]?????????????????????????;?獲取LDR結構體中的InMemoryOrderModuleList的頭節(jié)點地址
????????mov?edx,?[ecx?+?0x8]???????????????????;?獲取第一個模塊的基址,即ntdll.dll的基址
????????mov?eax,?[edx+0x3c]????????????????????;?獲取PE頭偏移地址
????????mov?ecx,?[edx?+?eax?+?0x78]????????????;?獲取導出表VA地址偏移
????????add?ecx,edx????????????????????????????;?將導出表的VA地址轉(zhuǎn)換成絕對地址
????????mov?ebx,?[ecx+0x20]????????????????????;?獲取導出表中的導出函數(shù)名偏移數(shù)組的地址
????????add?ebx,edx????????????????????????????;?將函數(shù)名偏移數(shù)組的VA地址轉(zhuǎn)換成絕對地址
????????xor?edi,edi????????????????????????????;?將edi清零,用于循環(huán)計數(shù)
????s1:
????????inc?edi????????????????????????????????;?計數(shù)器自增1
????????mov?esi,?[ebx+edi*4]???????????????????;?通過偏移獲取導出函數(shù)名的地址
????????add?esi,edx????????????????????????????;?將導出函數(shù)名的VA地址轉(zhuǎn)換成絕對地址
????????cmp?esi,edx????????????????????????????;?檢查導出函數(shù)名的地址是否合法,如果等于基址則跳過
????????je?no
????????loop?s1????????????????????????????????;?繼續(xù)查找導出函數(shù)名
????no:
????????xor?eax,eax????????????????????????????;?清零eax寄存器,用于返回值
????}
????system("pause");
????return?0;
}
1.5.5 整合自定位ShellCode
完整的匯編代碼如下,下方代碼是一個定式,這里就只做了翻譯,使用編譯器編譯如下代碼。
int?main(int?argc,?char?*argv)
{
????__asm
????{
????????//?將索要調(diào)用的函數(shù)hash值入棧保存
????????????CLD??????????????????????//?清空標志位DF
????????????push?0x1E380A6A??????????//?壓入MessageBoxA-->user32.dll
????????????push?0x4FD18963??????????//?壓入ExitProcess-->kernel32.dll
????????????push?0x0C917432??????????//?壓入LoadLibraryA-->kernel32.dll
????????????mov?esi,?esp?????????????//?指向堆棧中存放LoadLibraryA的地址
????????????lea?edi,?[esi?-?0xc]?????//?后面會利用edi的值來調(diào)用不同的函數(shù)
????????????//?開辟內(nèi)存空間,這里是堆棧空間
????????????xor?ebx,?ebx
????????????mov?bh,?0x04???????//?ebx為0x400
????????????sub?esp,?ebx???????//?開辟0x400大小的空間
????????????//?將user32.dll入棧
????????????mov?bx,?0x3233
????????????push?ebx???????????//?壓入字符'32'
????????????push?0x72657375????//?壓入字符?'user'
????????????push?esp
????????????xor?edx,?edx????????//?edx=0
????????????//?查找kernel32.dll的基地址
????????????mov?ebx,?fs:[edx?+?0x30]?????//?[TEB+0x30]?->?PEB
????????????mov?ecx,?[ebx?+?0xC]?????????//?[PEB+0xC]?->?PEB_LDR_DATA
????????????mov?ecx,?[ecx?+?0x1C]????????//?[PEB_LDR_DATA+0x1C]?->?InInitializationOrderModuleList
????????????mov?ecx,?[ecx]???????????????//?進入鏈表第一個就是ntdll.dll
????????????mov?ebp,?[ecx?+?0x8]?????????//ebp?=?kernel32.dll?的基地址
????????//?hash?的查找相關
????????find_lib_functions?:
???????????????????????????lodsd?????????????????????//?eax=[ds*10H+esi],讀出來是LoadLibraryA的Hash
???????????????????????????cmp?eax,?0x1E380A6A???????//?與MessageBoxA的Hash進行比較
???????????????????????????jne?find_functions????????//?如果不相等則繼續(xù)查找
???????????????????????????xchg?eax,?ebp
???????????????????????????call[edi?-?0x8]
???????????????????????????xchg?eax,?ebp
????????//?在PE文件中查找相應的API函數(shù)
????????find_functions?:
????????pushad
????????????mov?eax,?[ebp?+?0x3C]????????//?指向PE頭
????????????mov?ecx,?[ebp?+?eax?+?0x78]??//?導出表的指針
????????????add?ecx,?ebp?????????????????//?ecx=0x78C00000+0x262c
????????????mov?ebx,?[ecx?+?0x20]????????//?導出函數(shù)的名字列表
????????????add?ebx,?ebp?????????????????//?ebx=0x78C00000+0x353C
????????????xor?edi,?edi?????????????????//?清空edi中的內(nèi)容,用作索引
????????//?循環(huán)讀取導出表函數(shù)
????????next_function_loop?:
????????inc?edi????????????????????????????//?edi作為索引,自動遞增
????????????mov?esi,?[ebx?+?edi?*?4]???????//?從列表數(shù)組中讀取
????????????add?esi,?ebp???????????????????//?esi保存的是函數(shù)名稱所在的地址
????????????cdq
????????//?hash值的運算過程
????????hash_loop?:
????????movsx?eax,?byte?ptr[esi]?????????//?每次讀取一個字節(jié)放入eax
????????????cmp?al,?ah???????????????????//?eax和0做比較,即結束符
????????????jz?compare_hash??????????????//?hash計算完畢跳轉(zhuǎn)
????????????ror?edx,?7
????????????add?edx,?eax
????????????inc?esi
????????????jmp?hash_loop
????????//?hash值的比較函數(shù)
????????compare_hash?:
????????cmp?edx,?[esp?+?0x1C]
????????????jnz?next_function_loop?????????//?比較不成功則查找下一個函數(shù)
????????????mov?ebx,?[ecx?+?0x24]??????????//?ebx=序數(shù)表的相對偏移量
????????????add?ebx,?ebp???????????????????//?ebx=序數(shù)表的絕對地址
????????????mov?di,?[ebx?+?2?*?edi]????????//?di=匹配函數(shù)的序數(shù)
????????????mov?ebx,?[ecx?+?0x1C]??????????//?ebx=地址表的相對偏移量
????????????add?ebx,?ebp???????????????????//?ebx=地址表的絕對地址
????????????add?ebp,?[ebx?+?4?*?edi]???????//?添加到EBP(模塊地址庫)
????????????xchg?eax,?ebp??????????????????//?將func?addr移到eax中????
????????????pop?edi????????????????????????//?edi是pushad中最后一個堆棧
????????????stosd
????????????push?edi
????????????popad
????????????cmp?eax,?0x1e380a6a?????????????//?與MessageBox的hash值比較
????????????jne?find_lib_functions
????????//?下方的代碼,就是我們的彈窗
????????xor?ebx,?ebx??????????//?清空eb寄存器
????????????push?ebx??????????//?截斷字符串0
????????????push?0x2020206b
????????????push?0x72616873
????????????push?0x796c206f
????????????push?0x6c6c6568
????????????mov?eax,?esp
????????????push?ebx??????????//?push?0
????????????push?eax??????????//?push?"hello?lyshark"
????????????push?eax??????????//?push?"hello?lyshark"
????????????push?ebx??????????//?push?0
????????????call[edi?-?0x04]??//?call?MessageBoxA
????????????push?ebx??????????//?push?0
????????????call[edi?-?0x08]??//?call?ExitProcess
????}
????return?0;
}
運行后會彈出一個提示框hello lyshark
說明我們成功了,此列代碼就是所謂的自定位代碼,該代碼可以不依賴于系統(tǒng)環(huán)境而獨立運行;
本文作者: 王瑞 本文鏈接: https://www.lyshark.com/post/8c46c02d.html 版權聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請注明出處!