在筆者上一篇文章中簡單的介紹了如何運用匯編語言撰寫一段彈窗代碼,雖然簡易ShellCode
可以被正常執行,但卻存在很多問題,由于采用了硬編址的方式來呼叫相應API函式的,那么就會存在一個很大的缺陷,如果作業系統的版本不統或系統重啟過,那么基址將會發生變化,此時如果再次呼叫基址引數則會呼叫失敗,本章將解決這個棘手的問題,通過ShellCode
動態定位的方式解決這個缺陷,并以此設計出真正符合規范的ShellCode
代碼片段,
自定位代碼是一種常見的Shellcode
技術,它使Shellcode
能夠在任何系統上運行,而無需考慮系統記憶體布局和代碼地址等問題,以下是Shellcode自定位代碼的流程:
- 1.查找Kernel32.dll基址并在其中尋找LoadLibraryA
- 2.計算函式名hash摘要并通過hash摘要判斷函式
- 3.決議Kernel32.dll匯出表
- 4.最終動態呼叫系列函式
1.5.1 動態查找Kernel32基址
首先我們需要通過匯編的方式來實作動態定位Kernel32.dll
中的基址,你或許會有個疑問? 為什么要查找Kernel32.dll
的地址而不是User32.dll
,這是因為我們最終的目的是呼叫MessageBoxA
這個函式,而該函式位于 User32.dll
這個元件里,在某些程式中User32模塊并不一定會被加載,而Kernel32則必然會被加載,為了能夠呼叫MessageBoxA
函式,我們就需要呼叫LoadLibraryA
函式來加載User32.dll
這個模塊,而LoadLibraryA
恰巧又位于kernel32.dll
中,因此我們只需要找到LoadLibraryA
函式,即可實作加載任意的元件,并呼叫任意的函式的目的,
由于我們需要動態獲取LoadLibraryA()
以及ExitProcess()
這兩個函式的地址,而這兩個函式又是存在于kernel32.dll
中的,因此這里需要先找到kernel32.dll
的基址,然后通過對其進行決議,從而查找兩個函式的動態地址,動態的查找Kernel32.dll
的地址可總結為如下:
- 1.首先通過段
選擇子FS
在記憶體中找到當前行程內的執行緒環境塊結構體指標TEB, - 2.執行緒環境塊偏移位置為
fs:[0x30]
的位置處存放著指向行程環境塊PEB結構的指標, - 3.行程環境塊PEB偏移為
0x0c
的地址處存放著指向PEB_LDR_DATA
的結構體指標, - 4.而在
PEB_LDR_DATA
偏移0x1c
的地址處存放著指向Ldr.InMemoryOrderModuleList
模塊初始化鏈表的頭指標, - 5.在初始化鏈表中存放的就是所有行程的模塊資訊,通過將偏移值加
0x08
讀者即可獲取到kernel32.dll
的基地址,
既然有了固定的查詢定位公式,接下我們就使用WinDBG
除錯器來手工完成對Kernel32.dll
地址的定位:
小提示:Windbg是Windows Debugger的縮寫,是一種微軟提供的免費除錯器工具,用于分析和除錯Windows作業系統和應用程式,Windbg可以在不重啟系統的情況下,通過連接到正在運行的行程或者作業系統內核,獲取并分析程式的運行資訊、記憶體狀態、暫存器狀態、執行緒狀態、呼叫堆疊等資料,并可以使用符號檔案來決議程式中的符號名,從而幫助開發者定位問題和進行深入除錯,
讀者可通過附件獲取到WinDBG
程式,當用戶打開WinDBG
時讀者可通過Ctrl+E
快捷鍵任意打開一個可執行程式,接著我們開始尋找吧;
1.通過段選擇子FS
在記憶體中找到當前的執行緒環境塊TEB
,這里可以利用本地除錯,并輸入!teb
指令,讀者可看到如下輸出:
小提示:TEB(Thread Environment Block)是Windows作業系統中的一個重要資料結構,每個行程都有一個對應的TEB,它主要用于存盤執行緒的環境資訊和狀態,包括執行緒區域存盤(TLS)指標、例外處理鏈、堆疊資訊、Fiber資訊等,TEB由Windows內核自動創建和管理,可以通過系統呼叫和除錯器工具來訪問和修改其內容,
如上執行緒環境塊偏移位置為0x30
的地方存放著指向行程環境塊PEB
的指標,結合上圖可見,當前PEB
的地址為002bb000
小提示:PEB是Windows作業系統的行程環境塊(Process Environment Block)的縮寫,PEB是一個資料結構,其中包含了關于行程的許多資訊,例如行程的模塊、堆、執行緒等等,PEB由作業系統內核在創建行程時分配和初始化,并且只有在行程運行期間才可用,
2.在行程環境塊中偏移位置為0x0c
的地方存放著指向PEB_LDR_DATA
結構體的指標,其中存放著已經被行程裝載的元件的資訊,如下圖所示;
3.接著PEB_LDR_DATA
結構體偏移位置為0x1c
的地方存放著指向模塊初始化鏈表的頭指標InInitializationOrderModuleList
,如下圖所示;
4.模塊初始化鏈表InInitializationOrderModuleList
中按順序存放著PE裝入運行時初始化模塊的資訊,第一個鏈表節點是ntdll.dll
,第二個鏈表結點就是kernel32.dll
,我們可以先看看
InInitializationOrderModuleList
中的內容:
上圖中的0x005a3ad8
保存的是第一個鏈節點的指標,決議一下這個結點,可發現如下地址:
上圖中的0x77200000
為ntdll.dll
的模塊基地址,而0x005a4390
則是指向下一個模塊的指標,我們繼續跟隨0x005a4390
地址,則此處看到的標黃處是下一個模塊kernel32.dll
的基地址,
最后我們通過輸入!peb
命令,輸出當前所有載入模塊并驗證一下:
既然有了如上所述的方法,那么讀者可以很容易的實作這段功能,為了便于讀者理解,筆者先提供一段使用C語言書寫的實作方式,如下代碼所示;
#include <windows.h>
#include <stdio.h>
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;
}
運行輸出效果如下圖所示,讀者可自行檢查讀取結果的準確性;
將此段代碼翻譯為匯編模式也很容易,如下是通過匯編實作的流程;
.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 動態查找并列舉行程模塊
在讀者閱讀過第一節中的內容時,相信您已經可以熟練的掌握WinDBG
除錯器的基本使用了,本節我們將擴展一個知識點,以讓讀者能更好的理解WinDBG
除錯命令,本次我們實作列舉行程模塊的功能,本案例將不在解釋基本功能,
通過PEB/TEB
找到自身行程的所有載入模塊資料,首先獲取TEB
執行緒環境塊,在編程的時候,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其所指向的就是模塊名稱表,
現在來手動遍歷[ 0x5a3bd0 - 0x5aa5b8 ]
第一條鏈表,輸入命令dd 0x5a3bd0
鏈表偏移0x18
的位置是模塊的映射地址 ImageBase,鏈表偏移 0x28
的位置是模塊的路徑及名稱的地址,鏈表偏移 0x30
的位置是模塊名稱的地址,
如上圖中的輸出結果,地址005a2480
保存有當前模塊詳細路徑資訊,而005a24ae
則保存有當前模塊名,我們可以通過du
命令來進行驗證;
當讀者需要讀入下一個模塊鏈表時,則需要訪問0x005a3ac8
這個記憶體地址,其中保存著下一個鏈表結構,依次遍歷,
當然這個鏈表結構其實訪問InMemoryOrderModuleList
同樣可以得到,這兩個都指向同一片區域,
上方介紹的結構,是微軟保留結構,只能從網上找到一個結構定義,根據該結構的定義做進一步決議即可,
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;
根據如上分析細節,那么描述列舉模塊串列的核心代碼就可以寫成如下案例;
#include <Windows.h>
#include <stdio.h>
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 計算函式Hash摘要值
案例介紹了如何使用Win32匯編語言和C語言計算字串的hash
摘要值,字串的hash
摘要值是通過一定的演算法將字串壓縮為一個固定長度的十六進制數,用于在程式中進行快速的字串比較,具體而言,該案例使用了回圈移位hash計演算法,并最終得到了字串的 hash 值,并以十六進制數的形式輸出,
讀者一定有疑問為啥需要HASH壓縮處理? 原因是,如果直接將函式名壓堆疊的話,我們就需要提供更多的空間來存盤ShellCode
代碼,為了能夠讓我們撰寫的ShellCode
代碼更加的短小精悍,所以我們將要對字串進行hash
處理,將字串壓縮為一個十六進制數,這樣只需要比較二者hash
值就能夠判斷目標函式,盡管這樣會引入額外的hash
演算法,但是卻可以節省出存盤函式名字的空間,
為了能讓讀者理解計算原理,此處我們先使用C語言做摘要計算描述,如下代碼中的GetHash
函式,該函式接受一個指向字符陣列的指標,即一個字串,然后對字串進行哈希計算,并回傳計算結果,
哈希計算的程序是通過回圈遍歷字串中的每個字符,對其進行位運算和加法運算,最終得到一個32
位的哈希值,對于字串中的每個字符,程式首先將哈希值左移25
位,然后將結果右移7
位,相當于是對哈希值進行了回圈右移25
位,然后程式將該字符的ASCII
值加到哈希值上,回圈遍歷完字串中的所有字符后,哈希值即為最終的計算結果,
#include <stdio.h>
#include <windows.h>
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語言實作代碼,則讀者可以此獲取到三個核心函式的Hash值,其輸出效果如下圖所示;
在理解了C語言版本的計算流程后,那么匯編語言版本的也應該很容易理解,如下是使用Win32
匯編語言的實作程序,并在MASM
上正常編譯,匯編版字串轉換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則說明計算完畢跳轉到nops
ror edx,7 ; 不為零,則進行回圈右移7位
add edx,eax ; 將回圈右移的值不斷累加
inc esi ; esi自增,用于讀取下一個字符
jmp loops ; 回圈執行
nops:
mov eax,edx ; 結果存在eax里面
invoke crt_printf,addr Fomat,eax
ret
main ENDP
END main
1.5.4 列舉Kernel32匯出表
在文章開頭部分我們通過WinDBG
除錯器已經找到了Kernel32.dll
這個元件的基地址,而Dll檔案本質上也是PE檔案,在Dll檔案中同樣存在匯出表,其內部記錄著該Dll的匯出函式,接著我們需要對Dll檔案的匯出表進行遍歷,不斷地搜索,從而找到我們所需要的API函式,同樣的可以通過如下定式獲取到指定的匯出表,
- 1.從
kernel32.dll
加載基址算起,偏移0x3c
的地方就是其PE檔案頭, - 2.PE檔案頭偏移
0x78
的地方存放著指向函式匯出表的指標, - 3.匯出表偏移
0x1c
處的指標指向存盤匯出函式偏移地址(RVA)的串列, - 4.匯出表偏移
0x20
處的指標指向存盤匯出函式函式名的串列,
首先我們通過WinDBG來實作讀取匯入表及匯出表試試,我們以讀取ole32.dll
為例,首先讀者需要通過lmvm ole32.dll
查詢到該模塊的入口地址,如圖所示該模塊的入口地址為0x75830000
決議DOS頭,DOS頭通過_IMAGE_DOS_HEADER
結構被定義,在決議時讀者應傳入模塊入口0x75830000
地址,其次DOS頭中e_lfanew
欄位指向了PE頭,該欄位需要注意;
- 執行讀入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
根據上述定義,繼續尋找EXPORT
匯出表的實際地址,需要注意的是Evaluate expression
中的結果是根據ole32
模塊的基地址與VirtualAddress
當前地址相加后得到的,如下圖所示
當讀者需要列舉特定模塊時,則可通過模塊基地址加上例如Name
欄位偏移值,來讀入模塊名稱;
如果讀者需要列舉所有匯出函式,則讀者可通過模塊基地址加上AddressOfNames
欄位,并通過如下命令實作完整輸出;
- .foreach(place {dd 758e4088}){r @$t0=${place}+75830000; .if(@$t0<778e4088){da @$t0}}
匯入表的列舉與匯出表類似,為了節約篇幅此處只給出除錯資料,讀者可根據自己的掌握情況自行分析學習;
# 根據模塊基地址獲取模塊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
# 得到資料目錄表地址
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即可輸出下一個匯入函式名
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"
我們將問題回歸到列舉匯出表上,函式的RVA地址和名字按照順序存放在上述兩個串列中,我們可以在串列定位任意函式的RVA地址,通過與元件的基地址相加得到其真實的VA,而計算的地址就是我們最終在ShellCode
中呼叫時需要的地址,其匯編核心列舉代碼如下所示;
#include <stdio.h>
#include <Windows.h>
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的頭節點地址
mov edx, [ecx + 0x8] ; 獲取第一個模塊的基址,即ntdll.dll的基址
mov eax, [edx+0x3c] ; 獲取PE頭偏移地址
mov ecx, [edx + eax + 0x78] ; 獲取匯出表VA地址偏移
add ecx,edx ; 將匯出表的VA地址轉換成絕對地址
mov ebx, [ecx+0x20] ; 獲取匯出表中的匯出函式名偏移陣列的地址
add ebx,edx ; 將函式名偏移陣列的VA地址轉換成絕對地址
xor edi,edi ; 將edi清零,用于回圈計數
s1:
inc edi ; 計數器自增1
mov esi, [ebx+edi*4] ; 通過偏移獲取匯出函式名的地址
add esi,edx ; 將匯出函式名的VA地址轉換成絕對地址
cmp esi,edx ; 檢查匯出函式名的地址是否合法,如果等于基址則跳過
je no
loop s1 ; 繼續查找匯出函式名
no:
xor eax,eax ; 清零eax暫存器,用于回傳值
}
system("pause");
return 0;
}
1.5.5 整合自定位ShellCode
完整的匯編代碼如下,下方代碼是一個定式,這里就只做了翻譯,使用編譯器編譯如下代碼,
#include <stdio.h>
#include <windows.h>
int main(int argc, char *argv)
{
__asm
{
// 將索要呼叫的函式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的值來呼叫不同的函式
// 開辟記憶體空間,這里是堆疊空間
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 // 如果不相等則繼續查找
xchg eax, ebp
call[edi - 0x8]
xchg eax, ebp
// 在PE檔案中查找相應的API函式
find_functions :
pushad
mov eax, [ebp + 0x3C] // 指向PE頭
mov ecx, [ebp + eax + 0x78] // 匯出表的指標
add ecx, ebp // ecx=0x78C00000+0x262c
mov ebx, [ecx + 0x20] // 匯出函式的名字串列
add ebx, ebp // ebx=0x78C00000+0x353C
xor edi, edi // 清空edi中的內容,用作索引
// 回圈讀取匯出表函式
next_function_loop :
inc edi // edi作為索引,自動遞增
mov esi, [ebx + edi * 4] // 從串列陣列中讀取
add esi, ebp // esi保存的是函式名稱所在的地址
cdq
// hash值的運算程序
hash_loop :
movsx eax, byte ptr[esi] // 每次讀取一個位元組放入eax
cmp al, ah // eax和0做比較,即結束符
jz compare_hash // hash計算完畢跳轉
ror edx, 7
add edx, eax
inc esi
jmp hash_loop
// hash值的比較函式
compare_hash :
cmp edx, [esp + 0x1C]
jnz next_function_loop // 比較不成功則查找下一個函式
mov ebx, [ecx + 0x24] // ebx=序數表的相對偏移量
add ebx, ebp // ebx=序數表的絕對地址
mov di, [ebx + 2 * edi] // di=匹配函式的序數
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
說明我們成功了,此列代碼就是所謂的自定位代碼,該代碼可以不依賴于系統環境而獨立運行;
文章出處:https://www.cnblogs.com/LyShark/p/17521843.html
本博客所有文章除特別宣告外,均采用 BY-NC-SA 許可協議,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/556547.html
標籤:訊息安全
上一篇:前端Vue基于騰訊地圖Api實作的選擇位置組件 回傳地址名稱詳細地址經緯度資訊
下一篇:返回列表