Windows核心编程 远程线程注入

目录

线程安全

C线程

C++ STL线程

远程线程注入概述

相关API

CreateRemoteThread

LoadLibrary

VirtualAllocEx

FreeLibrary

GetProcAddress

远程线程注入

DLL卸载

调试DLL


线程安全

变量在单线程和在多线程都不会出问题 - 线程安全

变量在多线程出问题,在单线程不会出问题 - 线程不安全

C线程

在使用多线程的时候,printf函数可能会被切,所以输出的值有可能对应不上。

下面的例子演示了在多线程环境中,printf 输出信息被切走一部分的情况:

#include <stdio.h>
#include <thread>void printThreadId(int id)
{printf("Thread %d starts.\n", id);printf("Thread %d prints some information.\n", id);printf("Thread %d continues to print some information.\n", id);printf("Thread %d finishes.\n", id);
}int main()
{std::thread t1(printThreadId, 1);std::thread t2(printThreadId, 2);t1.join();t2.join();return 0;
}

这个例子中,两个线程分别输出它们的信息,但由于没有使用线程同步机制,多个线程之间会发生竞争条件,输出的信息可能会错乱或被切走一部分。在我的测试环境中,有时会出现以下输出:

可以看到,线程 1 和线程 2 的输出信息被交叉输出,部分信息被切走了。这种情况是因为多个线程同时访问 printf 函数,导致输出信息被竞争条件影响。

这个问题的出现是随机的,并不是每次都会出现。在多线程环境中,输出信息被切走一部分的情况取决于多个线程之间的竞争条件和调度策略。如果竞争条件和调度策略不利于某个线程,那么这个线程的输出信息就有可能被切走一部分。如果你的环境中没有出现这个问题,那么可能是由于你的测试数据较少,或者你的测试数据并不足以导致竞争条件和调度策略的影响。故引出下文。

_beginthread 和 _beginthreadex 都是用于创建新的线程的函数,但是它们在一些方面有所不同。
_beginthread 是一个Windows API函数,它使用 C运行时库 来创建新的线程。它的函数原型如下:

uintptr_t _beginthread(void(__cdecl *start_address)(void*), unsigned stack_size, void* arglist);

其中参数的解释如下:

  • void (__cdecl *start_address)(void*):函数指针,指向线程函数的地址。线程函数应具有void返回类型,接受一个void*类型的参数,用于传递线程函数的参数。

  • unsigned stack_size:表示新线程的堆栈大小(以字节为单位)。如果指定为0,将使用默认堆栈大小。

  • void* arglist:指向传递给新线程函数的参数的指针。可以将该参数设置为NULL,如果不需要传递参数给线程函数。

返回值:如果成功创建线程,返回线程的标识符(线程ID);如果失败,返回-1。

请注意,_beginthread函数是C运行时库提供的函数,用于创建新的线程。在使用该函数时,请确保链接相应的运行时库。

_beginthread 只支持使用 __stdcall 调用约定的函数作为线程入口点,并且没有提供任何与线程安全性相关的参数。它还可以指定线程堆栈的大小,但是这个功能在较新的Windows版本中已经被废弃了,因为系统可以自动为每个线程分配默认的堆栈大小。
_beginthreadex 是一个更为通用和灵活的函数,它可以在 Windows 平台上创建一个线程,并且可以指定线程入口点、线程安全性、线程堆栈大小、线程标志和创建后立即启动线程等。它的函数原型如下:

uintptr_t _beginthreadex(void*         security,unsigned      stack_size,unsigned (__stdcall *start_address)(void*),void*         arglist,unsigned      initflag,unsigned*     thrdaddr
);

其中参数的解释如下:

  • security:指向SECURITY_ATTRIBUTES结构的指针,用于指定新线程的安全性。可以将该参数设置为NULL,以使用默认安全性。

  • stack_size:表示新线程的堆栈大小(以字节为单位)。如果指定为0,将使用默认堆栈大小。

  • start_address:函数指针,指向线程函数的地址。线程函数应具有unsigned返回类型,并接受一个void*类型的参数,用于传递线程函数的参数。

  • arglist:指向传递给新线程函数的参数的指针。可以将该参数设置为NULL,如果不需要传递参数给线程函数。

  • initflag:表示新线程的初始标志。可以将该参数设置为0,表示创建线程后立即启动线程。

  • thrdaddr:指向一个变量,用于接收新线程的ID。可以将该参数设置为NULL,如果不需要获取线程ID。

返回值:如果成功创建线程,返回线程的句柄;如果失败,返回NULL。注意,返回的线程句柄可以用于后续的线程操作,比如等待线程的结束或关闭线程句柄。

_beginthreadex函数创建一个新的线程,并开始执行指定的线程函数。线程函数的地址由start_address参数指定。可以将其他参数传递给线程函数,通过arglist参数传递。

与 _beginthread 不同,_beginthreadex 支持使用 __stdcall 或 __cdecl 调用约定的函数作为线程入口点,并且可以通过参数指定线程安全性。它还可以指定线程堆栈的大小,但是如果未指定,则使用默认大小。_beginthreadex 还支持立即启动线程或者在稍后显式地启动线程,这可以让调用者有更多的控制权。
总的来说,_beginthread 是一个简单的API函数,适用于创建较为简单的线程,而 _beginthreadex 则更为灵活和通用,适用于创建更加复杂的线程。

#include <iostream>
#include <windows.h>
#include <process.h>unsigned int g_nVal = 0;DWORD WINAPI ThreadFunc(LPVOID lpParam)
{for (int i = 0; i < 0x10000; i++){InterlockedIncrement(&g_nVal);printf("g_nVal:%08X tid:%08X \r\n", g_nVal, GetCurrentThreadId());}return 0;
}VOID main(VOID)
{HANDLE aryThreads[4] = {};for (size_t i = 0; i < 4; i++){aryThreads[i] = (HANDLE)_beginthreadex(NULL,                        // no security attributes 0,                           // use default stack size  (_beginthreadex_proc_type)ThreadFunc,                  // thread function NULL,                // argument to thread function 0,                           // use default creation flags NULL);                // returns the thread identifier }WaitForMultipleObjects(4, aryThreads, TRUE, INFINITE);for (size_t i = 0; i < 4; i++){CloseHandle(aryThreads[i]);}printf("hello world");
}

C++ STL线程

STL(标准模板库)中提供了一些与线程相关的函数,包括以下几个:

  1. std::thread:创建并启动一个新线程。
  2. std::this_thread::get_id:获取当前线程的ID。
  3. std::this_thread::sleep_for:使当前线程休眠一段时间。
  4. std::mutex:互斥锁,用于实现线程同步。
  5. std::unique_lock:基于互斥锁的独占锁,用于实现更灵活的线程同步。
  6. std::condition_variable:条件变量,用于实现线程间的等待和通信。
  7. std::atomic:原子类型,用于实现线程安全的操作。

测试代码

#include <iostream>
#include <thread>
using namespace std;
unsigned int g_nVal = 0;
void ThreadFunc()
{for (size_t i = 0; i < 0x10000; i++){++g_nVal;}printf("%08X\r\n", g_nVal);
}
int main()
{thread t1(ThreadFunc);//创建线程   内部机制:function<void(void)> t1.join(); //等待线程结束

运行原理和机制:

当我们创建一个线程对象t1并传入一个函数ThreadFunc时,线程对象t1内部实际上是保存了一个函数对象,该函数对象的类型是std::function<void(void)>,它代表一个无参数、无返回值的函数对象。具体来说,这个过程包括以下几个步骤:

  1. 将传入的函数ThreadFunc封装成一个std::function<void(void)>类型的函数对象即创建一个可以调用ThreadFunc的函数对象。
  2. 创建一个线程对象t1,并将上一步中创建的函数对象作为构造函数参数传入。
  3. 线程对象t1会将std::function<void(void)>类型的函数对象保存到内部,以便在新线程启动时调用。
  4. 调用线程对象t1的成员函数start()启动新线程。
  5. 在新线程中,调用保存的std::function<void(void)>类型的函数对象作为线程入口函数,并不需要传入任何参数。
  6. 线程入口函数开始执行,并可以访问ThreadFunc中的外部变量等资源。

需要注意的是,std::function是一个通用的函数封装器,它可以保存任意可调用对象,包括函数指针、函数对象、lambda表达式等等。在使用线程对象时,将ThreadFunc封装成std::function类型的函数对象可以提高代码的灵活性和可重用性,因为我们可以传入不同的可调用对象,而不需要修改线程对象的定义。

此外,线程对象t1和std::function类型的函数对象的生命周期是相互独立的,也就是说,当std::function类型的函数对象执行完毕并退出后,线程对象t1仍然存在,可以继续使用和管理。因此,在使用线程对象时需要注意线程对象和std::function类型的函数对象的生命周期问题,避免使用已经退出的函数对象或者已经销毁的线程对象。

有参数的

void ThreadFunc(int n, string str, float f)
{for (size_t i = 0; i < 0x10000; i++){++g_nVal;}printf("%08X n:%d str:%s float:%f\r\n", g_nVal, n, str.c_str(), f);
}
int main()
{thread t(ThreadFunc, 1, "weewr", 2.0);//创建线程   内部机制:function<void(void)> t.join(); //等待线程结束
}

当参数不是普通变量

如果你的参数是普通的变量,而不是对象或成员函数,那么你可以直接将它们作为参数传递给std::thread构造函数,而不需要使用std::bind或Lambda表达式。

#include <thread>
#include <iostream>void printValue(int n) {std::cout << "The value is: " << n << std::endl;
}int main() {std::thread t(printValue, 42);t.join();return 0;
}

如果不使用std::bind,我们需要使用Lambda表达式或函数对象来传递函数和参数,而不是将参数传递给std::thread构造函数。例如,假设我们有一个类A和一个成员函数foo(int),我们可以使用Lambda表达式来创建一个可调用对象,并将其传递给std::thread构造函数,以便在新线程中执行该函数:

#include <thread>class A {
public:void foo(int n) {// do something}
};int main() {A obj;std::thread t([&obj]() { obj.foo(42); });t.join();return 0;
}

在这个例子中,我们使用Lambda表达式来创建一个可调用对象,该对象调用对象的成员函数foo,并将42作为参数传递给该函数。注意,我们需要使用引用捕获对象,以便在Lambda表达式中访问该对象。

当然,使用Lambda表达式和函数对象也可以实现std::bind的功能,但是通常需要更多的代码和更复杂的语法。因此,使用std::bind可以更简洁和直观地传递函数对象和参数。

在类中使用 bind

class CFoo
{
public:CFoo(){}~CFoo(){}
public:void Foo1(int n){for (size_t i = 0; i < 0x100; i++){cout << __FUNCTION__ << " n:" << m_n << endl;}}void Foo2(int n, string str){for (size_t i = 0; i < 0x100; i++){cout << __FUNCTION__ << " n:" << m_n << endl;}}
private:int m_n = 99;
};int main()
{CFoo foo;thread t1(bind(&CFoo::Foo1, &foo, 12));thread t2(bind(&CFoo::Foo2, &foo, 12, "Hello world"));t1.join(); //等待线程结束t2.join(); //等待线程结束
}

当函数重载时

void ThreadFunc(int n)
{for (size_t i = 0; i < 0x10000; i++){++g_nVal;}printf("%08X n:%d \r\n", g_nVal, n);
}
void ThreadFunc(int n, string str)
{for (size_t i = 0; i < 0x10000; i++){++g_nVal;}printf("%08X n:%d str:%s \r\n", g_nVal, n, str.c_str());
}
int main()
{// 使用lambda表达式传递参数std::thread t1([](int arg) { ThreadFunc(arg); }, 1); // 调用ThreadFunc(int arg)std::thread t2([](int arg1,std::string arg2) { ThreadFunc(arg1,arg2); }, 1,"hello"); // 调用ThreadFunc(const std::string& arg)t1.join();t2.join();// 使用std::bind函数传递参数std::thread t3(std::bind(static_cast<void(*)(int)>(ThreadFunc), 2)); // 调用ThreadFunc(int arg)std::thread t4(std::bind(static_cast<void(*)(int, std::string)>(ThreadFunc),2, "world")); // 调用ThreadFunc(const std::string& arg)t3.join();t4.join();
}

static_cast

static_cast是C++中的一种类型转换运算符,用于将一种类型的值强制转换为另一种类型。static_cast可以在编译时进行类型检查,因此它比较安全,但在某些情况下也可能会导致编译错误或运行时错误。
static_cast可以用于许多类型转换,例如将整数转换为浮点数、将指针类型转换为另一种指针类型、将基类指针或引用转换为派生类指针或引用等。在本例中,我们使用static_castThreadFunc的函数指针转换为具有指定函数签名的函数指针类型,以便在std::bind函数中明确指定要调用的函数版本。
static_cast的一般语法如下:

static_cast<目标类型>(源类型);

其中,目标类型源类型分别表示要转换的目标类型和源类型。在转换时,static_cast将源类型的值强制转换为目标类型,并返回转换后的值。
需要注意的是,static_cast并不适用于所有类型转换,特别是对于指针类型的转换,有时需要使用更具体的类型转换运算符,例如reinterpret_castconst_cast。因此,使用类型转换运算符时需要谨慎,并遵守语言标准中的规定。

join()

join()函数是用来等待一个线程结束的函数,它可以阻塞当前线程,直到被等待的线程执行完成。

在不同的编程语言和操作系统中,join()函数的具体实现方式可能会有所不同,下面以常见的C++标准库的std::thread为例进行说明。

在C++中,使用std::thread类创建线程,可以通过join()函数来等待线程的结束。join()函数的调用会阻塞当前线程,直到被等待的线程执行完成。

下面是一个简单的示例代码:

#include <iostream>
#include <thread>void myThreadFunc() {std::cout << "Hello from myThreadFunc!" << std::endl;
}int main() {std::thread myThread(myThreadFunc); // 创建线程// 等待线程执行完成myThread.join();std::cout << "Thread finished." << std::endl;return 0;
}

在上面的代码中,myThreadFunc()函数是我们要在新线程中执行的函数。通过std::thread类创建了一个名为myThread的线程,并传入了myThreadFunc作为线程函数。

main()函数中,调用myThread.join()等待线程执行完成。这会将当前线程阻塞,直到myThread线程执行完成,然后才继续执行后面的代码。

请注意,join()函数只能在已经开始执行的线程上调用,而不能在尚未启动的线程上调用。如果线程已经被detach(),即与std::thread实例解绑,那么就无法使用join()函数等待该线程的结束。

为什么要阻塞?

如果不加阻塞,对象出函数作用域的时候要析构,但线程未必结束,析构会把线程的相关属性给直接释放,线程就会出现问题,断言;或则认为线程没结束,不能析构对象,抛出异常;
方法:
用join函数阻塞;这样可以实时的监控到线程是否结束了;

立马结束线程   detach       t1.detach();

this_thread类
提供了一些静态成员函数:

printf("nId:%08X g_nVal:%08X tid:%08X \r\n", nID, g_nVal,  this_thread::get_id());
this_thread::sleep_for(std::chrono::seconds(2 * 1000));//睡两秒钟
//this_thread::sleep_for(std::chrono::milliseconds(2 * 1000));//精准睡眠

远程线程注入概述

原理:远程线程注入 DLL 的基本原理是利用目标进程中的某个 API 函数(例如CreateRemoteThread()函数)来创建一个远程线程,并在其中执行指向恶意 DLL 的代码。

与DLL劫持的区别:

  1. 劫持的原理:dll替换函数转发。是进程跑起来之前,加载我们的dll
  2. 远程注入:能够为已经运行的程序注入dll,和卸载dll。

远程线程注入思路

  1. 本进程要想给目标进程创建线程, 在线程里加载自己的dll;
  2. 巧合的是线程的回调函数是一个参数, 而LoadLibrary(加载dll)也是一个参数, 所以可以把LoadLibrary作为线程的回调函数, 现在就需要找到目标进程中的LoadLibrary
  3. 而LoadLibrary恒定在 kernel32.dll里在同一台电脑上,NTdll.dll和kernel32.dll在不同进程中地址是一样的
  4. 所以自己进程的LoadLibrary地址就是目标进程中的LoadLibrary地址
  5. 现在需要向目标进程的LoadLibrary中传入参数

给目标进程注入本进程dll的步骤

  1. 通过Findwindow函数传入进程窗口名,获取窗口句柄
  2. 通过窗口句柄用GetWindowThreadProcessId函数获取目标进程id
  3. 通过目标进程句柄用VirtualAllocEx函数打开目标进程
  4. 打开目标进程后用WriteProcessMemory函数向目标进程内存中写入自己要注入的dll全路径
  5. 通过GetModuleHandle函数获取同一台电脑每一个进程中地址都相同的kernel32的dll模块句柄:;
  6. 通过kernel32.dll的模块句柄用GetProcAddress函数获取kernel32.dllLoadLibraryA函数的地址;
  7. 通过CreateRemoteThread函数在目标进程创建线程,并把之前写到目标进程内存中的要注入的dll的路径作为CreateRemoteThread函数的与线程回调函数自定义参数相同的参数的值,把目标进程中的kernel32.dllLoadLibraryA函数的地址作为该跨进程创建的线程的回调函数,此时目标进程中的LoadLibraryA函数就可以加载需要注入的dll了

相关API

CreateRemoteThread

CreateRemoteThread是一个Windows API函数,用于在目标进程中创建一个远程线程。

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

参数解释如下:

  • hProcess:目标进程的句柄,表示在哪个进程中创建远程线程。获取目标进程句柄的方式通常是使用OpenProcess函数。

  • lpThreadAttributes:线程的安全属性,指向SECURITY_ATTRIBUTES结构体的指针,可以设置为NULL

  • dwStackSize:线程的堆栈大小,可以设置为0以使用默认大小。

  • lpStartAddress:线程函数的入口点,指向线程函数的函数指针。

  • lpParameter:传递给线程函数的参数,可以为NULL

  • dwCreationFlags:线程的创建标志,可以设置为0或其他标志,如CREATE_SUSPENDED等。

  • lpThreadId:用于接收新线程的ID的指针,可以为NULL

返回值为新创建线程的句柄。如果创建失败,返回NULL

使用CreateRemoteThread函数,可以在目标进程中创建一个远程线程,并指定线程函数的入口点和参数。这样就可以在目标进程中执行指定的代码。

需要注意的是,使用CreateRemoteThread函数需要获得目标进程的句柄,并具有足够的权限来进行线程创建。同时,在进行远程线程注入时,需要确保注入的代码在目标进程中被正确加载和执行。

LoadLibrary

LoadLibrary是一个Windows API函数,用于加载指定的动态链接库(DLL)到当前进程的地址空间中。函数原型如下:

HMODULE LoadLibrary(LPCTSTR lpFileName
);

参数解释如下:

  • lpFileName:指定要加载的DLL文件的路径。可以是绝对路径,也可以是相对路径。

返回值为加载成功时,返回DLL模块的句柄(HMODULE);加载失败时,返回NULL

使用LoadLibrary函数可以将一个DLL加载到当前进程的地址空间中,使得该DLL中的函数和数据可在当前进程中使用。

加载DLL后,可以使用GetProcAddress函数获取DLL中导出函数的地址,进而调用DLL中的函数。

测试代码

#include <iostream>
#include <Windows.h>int main() {// 加载DLLHMODULE hModule = LoadLibrary("mydll.dll");if (hModule != NULL) {std::cout << "DLL loaded successfully." << std::endl;// 获取导出函数的地址FARPROC funcAddress = GetProcAddress(hModule, "MyFunction");if (funcAddress != NULL) {// 调用导出函数typedef void (*MyFunctionType)();MyFunctionType myFunction = (MyFunctionType)funcAddress;myFunction();}// 卸载DLLFreeLibrary(hModule);} else {std::cout << "Failed to load DLL." << std::endl;}return 0;
}

VirtualAllocEx

VirtualAllocEx是一个Windows API函数,用于在指定的进程中分配内存空间。

函数原型如下:

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

参数解释如下:

  • hProcess:目标进程的句柄,表示在哪个进程中分配内存。获取目标进程句柄的方式通常是使用OpenProcess函数。

  • lpAddress:分配内存的起始地址,可以设置为NULL,让系统自动选择一个合适的地址。

  • dwSize:要分配的内存大小,以字节为单位。

  • flAllocationType:内存分配的类型,可以设置为以下标志的组合:

    • MEM_COMMIT:将分配的内存提交,使之可用。
    • MEM_RESERVE:为分配的内存保留地址空间,但不进行物理或交换文件上的分配。
    • MEM_RESET:将分配的内存内容重置为0。
    • MEM_RESET_UNDO:将分配的内存内容重置为0,但可通过调用VirtualFreeEx来还原。
  • flProtect:内存的保护属性,指定内存区域的访问权限,可以设置为以下标志之一:

    • PAGE_EXECUTE:可执行代码。
    • PAGE_EXECUTE_READ:可执行和可读的代码。
    • PAGE_EXECUTE_READWRITE:可执行、可读和可写的代码。
    • PAGE_NOACCESS:无法访问。

返回值为分配到的内存的起始地址,如果分配失败,返回NULL

使用VirtualAllocEx函数可以在指定的进程中分配一块内存空间,以供该进程使用。通常与WriteProcessMemory函数一起使用,将数据写入分配的内存空间。

测试代码

#include <iostream>
#include <Windows.h>int main() {// 打开目标进程HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcessId);if (hProcess == NULL) {std::cout << "Failed to open target process." << std::endl;return 1;}// 在目标进程中分配内存LPVOID pRemoteBuffer = VirtualAllocEx(hProcess, NULL, bufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);if (pRemoteBuffer == NULL) {std::cout << "Failed to allocate memory in target process." << std::endl;CloseHandle(hProcess);return 1;}// 将数据写入目标进程的内存空间if (!WriteProcessMemory(hProcess, pRemoteBuffer, bufferData, bufferSize, NULL)) {std::cout << "Failed to write to target process memory." << std::endl;VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE);CloseHandle(hProcess);return 1;}// ...// 清理资源VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE);CloseHandle(hProcess);return 0;
}

在上述示例中,我们使用VirtualAllocEx函数在目标进程中分配了一块内存空间。然后,我们通过WriteProcessMemory函数将数据写入目标进程的内存空间。

FreeLibrary

FreeLibrary是一个Windows API函数,用于释放通过LoadLibrary函数加载的动态链接库(DLL)模块。函数原型如下:

BOOL FreeLibrary(HMODULE hModule
);

参数解释如下:

  • hModule:要释放的DLL模块的句柄(HMODULE),这个句柄是在调用LoadLibrary函数时返回的。

返回值为释放成功时,返回非零值;释放失败时,返回0。

使用FreeLibrary函数可以卸载一个已加载的DLL模块,释放该模块占用的系统资源。在卸载前,应该确保不再使用该DLL中的函数和数据,以避免引起未定义的行为或内存泄漏。

GetProcAddress

FARPROC GetProcAddress(HMODULE hModule,      // DLL文件句柄LPCSTR lpProcName     // 要获取的函数名
);

该函数用于获取DLL文件中指定函数的地址。函数的参数包括:

  • hModule:DLL文件的句柄。
  • lpProcName:要获取的函数名。

该函数返回指定函数的地址。如果获取失败,则返回NULL。

远程线程注入

获取LoadLibrary 在模块中的地址

  • 方法一:扫模块,查看LoadLibary导出函数在哪个DLL里面。发现在Kernerl32.dll里面。【目标进程中Kermer32.DLL映射的首地址 + 导出函数地址偏移=LoadLibary函数地址 】
  • 方法二:同一台电脑上,Kernerl32,ntdll,kernerbase的地址相同。因为他们先于进程被加载,加载时机特别早。所以他们的地址是相同的

CreateRemoteThread函数和DLL注入技术是一种常用的远程注入方式,可以将一个DLL模块注入到另一个进程中,从而实现在另一个进程中执行恶意代码的目的。

具体的使用步骤如下:

  1. 打开目标进程并获取其句柄,可以使用OpenProcess函数来打开目标进程,该函数的参数为目标进程的PID和所需的访问权限。
  2. 在目标进程的虚拟地址空间中分配一段内存空间,可以使用VirtualAllocEx函数来分配内存,该函数的参数为目标进程的句柄、分配的内存大小、内存保护属性和分配内存的起始地址。
  3. 将DLL模块的路径名写入到目标进程的内存空间中,可以使用WriteProcessMemory函数来将DLL路径名写入到目标进程的内存空间中,该函数的参数为目标进程的句柄、目标进程的内存地址、要写入的数据、数据大小和实际写入的字节数。
  4. 在目标进程中加载注入的DLL模块,可以使用LoadLibrary函数来加载DLL模块,该函数的参数为DLL模块的路径名。
  5. 在目标进程中创建一个新的线程,执行DLL模块中的代码,可以使用CreateRemoteThread函数来创建远程线程,该函数的参数为目标进程的句柄、线程的安全描述符、线程堆栈大小、线程入口点地址和传递给线程的参数。
  6. 清理内存和句柄,可以使用VirtualFreeEx函数释放在目标进程中分配的内存空间,使用CloseHandle函数关闭目标进程的句柄。

被注入的DLL

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "resource.h"//窗口回调函数
BOOL CALLBACK DeleteItemProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:switch (LOWORD(wParam)){case IDC_BUTTON1:MessageBox(NULL,"测试","标题",MB_OK);return TRUE;case IDC_BUTTON2:   FreeLibrary(GetModuleHandle("Dll1"));   //会崩溃,卸载DLL后,还在使用DLL资源代码,所以会C05return TRUE;}break;case WM_CLOSE:EndDialog(hwndDlg, 0);return TRUE;}return FALSE;
}DWORD WINAPI ThreadFunc(LPVOID lpParam)
{DialogBox(GetModuleHandle("Dll1"),MAKEINTRESOURCE(IDD_DIALOG1), NULL,DeleteItemProc);    //实例句柄:资源在哪个模块填哪个模块的实例句柄。//崩溃原因:调用FreeLibrary时,dll还有其他代码执行。如果能够等线程运行完了后再调用free,就可解决。//新建一个线程来FreeLibraryAndExitThread(GetModuleHandle("Dll1"),1);//卸载指定dll,并退出当前线程。return 0;
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:{HANDLE hThread;char szMsg[80];hThread = CreateThread(NULL,                        // no security attributes 0,                           // use default stack size  ThreadFunc,                  // thread function NULL,                // argument to thread function 0,                           // use default creation flags NULL);                // returns the thread identifier }OutputDebugString("DLL_PROCESS_ATTACH: 你被注入了!!!");break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:OutputDebugString("DLL_PROCESS_DETACH: 我走了!!!");break;}return TRUE;
}

注入DLL

// RemoteInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <windows.h>/*
步骤:
1. 拿进程句柄
2. 写入参数a. 别的进程申请内存,参数选提交 MEM_COMMIT。b. 将dll路径写入申请的内存地址
3. 拿取LoadLirabry地址GetProcAddress(模块基址,导出函数名称) - 获取模块基址
4. 在目标进程创建新线程CreateRemoteThread - 线程函数地址填 获取的LoadLirabry地址。
*/int main()
{HWND hWndCff = FindWindow(NULL, "Dependencies (WoW64)");if (hWndCff == NULL){return 0;}DWORD dwProcId;GetWindowThreadProcessId(hWndCff, &dwProcId);   //拿进程id//拿进程句柄HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcId);if (hProc == NULL){return 0;}//写入参数LPVOID pDllPath = VirtualAllocEx(hProc, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);if (pDllPath == NULL){return 0;}char szDllPath[] = {"G:\\CR40C++程序设计\\二阶段\\Windows核心编程\\10\\信号灯\\Debug\\Dll1.dll"};BOOL bRet = WriteProcessMemory(hProc, pDllPath, szDllPath, sizeof(szDllPath),NULL); //写入进程内存if (!bRet){return 0;}//拿去LoadLirabry地址HMODULE hKernel32 = GetModuleHandle("kernel32");    //获取模块基址if (hKernel32 == NULL){return 0;}auto pfnLoadLibrary = GetProcAddress(hKernel32,"LoadLibraryA");if (pfnLoadLibrary == NULL){return 0;}//在目标进程创建新线程HANDLE hMod = CreateRemoteThread(hProc,NULL,0,(LPTHREAD_START_ROUTINE)pfnLoadLibrary, pDllPath,0,NULL);if (hMod == NULL){std::cout << "注入失败" << std::endl;return 0;}WaitForSingleObject(hMod,INFINITE);DWORD dwRetVal;GetExitCodeThread(hMod, &dwRetVal);std::cout << "Hello World!\n";
}

DLL卸载

调用FreeLibrary函数卸载崩溃的原因:卸载DLL后,还在使用DLL资源代码

FreeLibraryAndExitThread是一个Windows API函数,用于释放通过LoadLibrary函数加载的动态链接库(DLL)模块,并终止当前线程。

函数原型如下:

VOID FreeLibraryAndExitThread(HMODULE hModule,DWORD   dwExitCode
);

参数解释如下:

  • hModule:要释放的DLL模块的句柄(HMODULE),这个句柄是在调用LoadLibrary函数时返回的。

  • dwExitCode:指定当前线程的退出代码。

该函数没有返回值。

使用FreeLibraryAndExitThread函数可以在释放DLL模块的同时,终止当前线程的执行。该函数会先释放DLL模块,然后调用ExitThread函数终止当前线程。

#include <iostream>
#include <Windows.h>DWORD WINAPI ThreadProc(LPVOID lpParam) {// 加载DLLHMODULE hModule = LoadLibrary("mydll.dll");if (hModule != NULL) {std::cout << "DLL loaded successfully." << std::endl;// 使用DLL中的函数...// 释放DLL并终止线程FreeLibraryAndExitThread(hModule, 0);} else {std::cout << "Failed to load DLL." << std::endl;}return 0;
}int main() {// 创建线程HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);if (hThread != NULL) {// 等待线程结束WaitForSingleObject(hThread, INFINITE);std::cout << "Thread finished." << std::endl;// 关闭线程句柄CloseHandle(hThread);} else {std::cout << "Failed to create thread." << std::endl;}return 0;
}

在上述示例中,我们使用CreateThread函数创建了一个新线程,并在新线程中加载了名为mydll.dll的DLL文件。在DLL加载成功后,我们可以使用DLL中的函数。然后,通过调用FreeLibraryAndExitThread函数来释放DLL模块并终止当前线程。

调试DLL

DLL属性 -> 调试 -> 命令 ->路径(加载dll的程序。谁加载dll就填谁)运行DLL程序,在运行注入程序。下断点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/188495.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CCC数字车钥匙(八)——BLE配对相关字段

2.1 配对连接协议 2.1.3 所有者配对广播 对于所有者配对&#xff0c;仅支持Legacy LE 1M PHY。ADV_IND需要按照Section 2.3.1.1 Volume 6 Part B。 事件类型&#xff1a;无指向可连接和可扫描。 ADV_IND中包含广播地址和广播数据&#xff0c;如下所示&#xff0c;其中广播地址…

TZOJ 1402 Bitset

答案&#xff1a; #include <stdio.h> int main() {int n 0, j 0; while (scanf("%d", &n) ! EOF && (n>0 && n<1000)) //多组输入{int arr[32], i 0;while (n > 0) {arr[i] n % 2; //除2取余法n / 2;}for (j i -…

力扣题:字符的统计-12.1

力扣题-12.1 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;451. 根据字符出现频率排序 解题思想&#xff1a;统计字符出现的个数&#xff0c;进行排序即可 class Solution(object):def frequencySort(self, s):""":type s: str:…

【数据结构】哈夫曼树(Huffman Tree)和哈夫曼编码(Huffman Coding)

哈夫曼树&#xff08;Huffman Tree&#xff09;和哈夫曼编码&#xff08;Huffman Coding&#xff09;是数据压缩领域常用的技术。哈夫曼树是一种特殊的二叉树&#xff0c;用于构造哈夫曼编码&#xff0c;而哈夫曼编码则是一种变长编码&#xff0c;用于压缩数据。 在解释哈夫曼…

文心一言 VS 讯飞星火 VS chatgpt (147)-- 算法导论12.2 2题

二、用go语言&#xff0c;写出 TREE-MINIMUM 和 TREE-MAXIMUM 的递归版本。 灵捷3.5 。 文心一言&#xff1a; 以下是用 Go 语言编写的树的最大值和最小值的递归版本代码&#xff1a; package main import ( "fmt" ) type TreeNode struct { Val int Le…

【tower-boot 系列】MybatisPlus 集成

Mybatis、MybatisPlus 简单介绍 MybatisPlus 集成 一、pom 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><exclusions><exclusion><groupId>com.zaxxer…

python简单进阶之web框架:fastapi使用教程

原文&#xff1a;python简单进阶之web框架&#xff1a;fastapi使用教程 - 知乎 这是简单进阶教程系列第四篇&#xff0c;本系列文章主要介绍那些可以很快上手的进阶库。 我其实学过一段时间Django框架&#xff0c;但是半途而废了&#xff0c;我觉得可能还是简单一点的框架比较适…

智能优化算法应用:基于象群算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于象群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于象群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.象群算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

网络类型解析(基础):探索通信世界的多样面貌

在当今数字化时代&#xff0c;网络已经成为人们生活和工作中不可或缺的一部分。从个人设备之间的直接通信到全球范围的数据传输&#xff0c;不同类型的网络为我们提供了多种连接方式和通信选择。透过对这些网络类型的解析&#xff0c;我们将更好地理解它们的特点、优势和适用场…

JMX的使用

1. 定义和意义 JMX是Java Management Extention的缩写&#xff0c;出发点是让外部通过属性/方法来读取或设置程序状态。对于提供对外服务的程序来说&#xff0c;天生就有这样的能力&#xff0c;Web程序通过HTTP接口对外暴露&#xff0c;RPC应用通过RPC接口暴露。不过带来的问…

ESP32-Web-Server编程- 使用表格(Table)实时显示设备信息

ESP32-Web-Server编程- 使用表格&#xff08;Table&#xff09;实时显示设备信息 概述 上节讲述了通过 Server-Sent Events&#xff08;以下简称 SSE&#xff09; 实现在网页实时更新 ESP32 Web 服务器的传感器数据。 本节书接上会&#xff0c;继续使用 SSE 机制在网页实时显…

深度学习手势识别 - yolo python opencv cnn 机器视觉 计算机竞赛

文章目录 0 前言1 课题背景2 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 3 YOLOV53.1 网络架构图3.2 输入端3.3 基准网络3.4 Neck网络3.5 Head输出层 4 数据集准备4.1 数据标注简介4.2 数据保存 5 模型训练5.1 修…

C++模板—函数模板、类模板

目录 一、函数模板 1、概念 2、格式 3、实例化 4、模板参数的匹配 二、类模板 1、定义格式 2、实例化 交换两个变量的值&#xff0c;针对不同类型&#xff0c;我们可以使用函数重载实现。 void Swap(double& left, double& right) {double tmp left;left ri…

黑马一站制造数仓实战1

1. 项目目标 一站制造 企业中项目开发的落地&#xff1a;代码开发 代码开发&#xff1a;SQL【DSL SQL】 SparkCore SparkSQL 数仓的一些实际应用&#xff1a;分层体系、建模实现 2. 内容目标 项目业务介绍&#xff1a;背景、需求 项目技术架构&#xff1a;选型、架构 项目环境…

SpringBootWeb案例_03

Web后端开发_06 SpringBootWeb案例_03 登录认证 智能学习辅助系统登录时需要身份验证 1.登录功能 先实现简单的登录功能&#xff0c;在进一步优化。 1.1需求 若账户或密码不存在/密码不正确&#xff0c;则登录失败。 账户密码正确&#xff0c;则登录成功 1.2接口文档 …

git基本概念

一、版本控制概念 1.1 什么是版本控制 1.1.1 手动管理文件版本 1.1.2 版本控制软件 概念&#xff1a;版本控制软件是一个用来记录文件发生的变化&#xff0c;以便将来查阅特定版本修订情况的系统&#xff0c;有时也叫“版本控制系统”。通俗的理解就是把手工管理文件版本的方…

关于电脑提示vcruntime140_1.dll无法继续执行代码的解决办法

vcruntime140_1.dll是Visual C运行时库的一个组成部分&#xff0c;它包含了大量用于支持C应用程序运行时的功能。这个文件通常在开发和使用C程序时被调用&#xff0c;特别是在使用Microsoft Visual Studio进行开发时。vcruntime140_1.dll文件丢失或损坏会导致C程序无法正常运行…

信息化,数字化,智能化是3种不同概念吗?与机械化,自动化矛盾吗?

先说结论&#xff1a; 1、信息化、数字化、智能化确实是3种不同的概念&#xff01; 2、这3种概念与机械化、自动化并不矛盾&#xff0c;它们是制造业中不同发展阶段和不同层次的概念。 机械化&#xff1a;是指在生产过程中使用机械技术来辅助人工完成一些重复性、单一性、劳…

助力android面试2024【面试题合集】

转眼间&#xff0c;2023年快过完了。今年作为口罩开放的第一年大家的日子都过的十分艰难&#xff0c;那么想必找工作也不好找&#xff0c;在我们android开发这一行业非常的卷&#xff0c;在各行各业中尤为突出。android虽然不好过&#xff0c;但不能不吃饭吧。卷归卷但是还得干…

Pytorch——多卡GPU训练与单卡GPU训练相互切换

部分深度学习网络默认是多卡并行训练的&#xff0c;由于某些原因&#xff0c;有时需要指定在某单卡上训练&#xff0c;最近遇到一个&#xff0c;这里总结如下。 目录 一、多卡训练1.1 修改配置文件1.2 修改主训练文件1.3 显卡使用情况 二、单卡训练2.1 修改配置文件2.2 显卡使…