psycongroo 发表于 2020-8-17 18:36

萌新逆向学习笔记——远程线程注入DLL

# 前言
在[上一篇文章](https://www.52pojie.cn/thread-1244192-1-1.html)中,笔者学习并整理了有关消息钩子的原理,实现了一个键盘记录器。撇开键盘记录器这个具体的功能不说,从更加抽象的角度来看,消息钩子的利用让我们得以在"别人的EXE"中实现了自己的代码功能,只不过这功能仅限于类似鼠标,键盘的输入监听罢了。而今天的主题——远程线程注入DLL则是更为广泛的,用以在他人EXE中实现自己代码功能的操作,这次就不仅限于键盘记录了。

# 准备工作
阅读并实现本文主题,需要以下工具及知识:
1. C++/C语言的基本知识
2. 进程监控软件procexp
当然,倘若读者并没有掌C++/C语言,也可以继续阅读浏览。因为本文主要为萌新笔者的学习笔记,因此也并没有过于深奥晦涩难懂的地方。只要对基本原理,核心API函数留有印象即可。

# 原理
在讲解远程注入DLL原理之前,读者需要知道从代码层级上DLL加载时的函数调用流程。

## 代码中加载DLL
要在一个Win32 app中,也就是我们的exe程序中加载DLL只需要调用一个函数——```LoadLibraryA/W```
以下是(https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw):

```
HMODULE LoadLibraryA/W(
LPCWSTR lpLibFileName   //DLL文件的全路径
);
```
以下为一个控制台程序加载DLL示例:
```
#include <libloaderapi.h >
int main()
{
        LoadLibrary(L"F://Project//MyDll.dll");
}
```

参数LPCWSTR lpLibFileName为DLL文件的路径,需要说的是,当加载的DLL文件和EXE在同一文件夹下可只写DLL为文件名。

当DLL被使用``` LoadLibraryA/W```加载后,在DLL文件中的DllMain函数会被自动调用:
以下为创建的DLL文件:
```
BOOL APIENTRY DllMain( HMODULE hModule,
                     DWORDul_reason_for_call,
                     LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
      MessageBox(NULL, L"内容", L"标题", MB_OK);
      break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
      break;
    }
    return TRUE;
}
```

DllMain函数在我们创建DLL文件时会自动创建,没有编程基础的读者也不用过于害怕,我们只要记住,在EXE调用```LoadLibraryA/W```函数后,DLL文件就会执行```DllMain```函数中的代码。

**所以,如果我们想在"别人的EXE"中实现自己的代码功能,只要想办法,让别人的EXE调用LoadLibraryA/W 函数加载我们自己写的DLL文件即可。**

可,这要怎么做呢?而这正是本文记载的主要内容。

## 远程线程使别人加载自己的DLL
要使"别人的EXE"加载我们的DLL其实很简单,这是因为微软设计的时候提供了一个函数用来在"别人的EXE"里执行一个指定函数——```CreateRemoteThread```
```
HANDLE CreateRemoteThread(
HANDLE               hProcess,//进程句柄
LPSECURITY_ATTRIBUTESlpThreadAttributes,
SIZE_T               dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,//函数地址
LPVOID               lpParameter,//传递给函数的参数
DWORD                  dwCreationFlags,
LPDWORD                lpThreadId
);
```

虽然参数过多,但我们只需要关注三个参数即可:
1. HANDLE hProcess 为一个进程的句柄,我们只需要通过另一个函数```OpenProcess```获取即可。
2. LPTHREAD_START_ROUTINE lpStartAddress 为我们需要"别人EXE"执行的函数的地址,同时这个函数的返回值,参数必须符合给定的模板规格。这个稍后再讲是核心。
3. 传递给函数的参数,该参数值须存在于"别人EXE"的虚拟内存当中,我们可以通过```VirtualAllocEx```函数向EXE中申请内存空间并调用 ```WriteProcessMemory```函数写入参数,最后再传递过去。

### 传递被调用的函数
先前说过,我们传递给```CreateRemoteThread```的函数必须符合一定格式,这个格式可以在[微软文档](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms686736(v=vs.85))中查得到
```
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
```
请记住上面的这个只是模板规格,它要求我们传递的函数必须符合这样的格式。大家应该还记得我们的目标吧——调用```CreateRemoteThread```来迫使"别人的EXE"调用```LoadLibraryA/W```函数来加载我们的DLL。因此,如果我们能把```LoadLibraryA/W```函数传过去该多好。

可要传递过去就必须符合上面的格式呀,```LoadLibraryA/W```函数的格式符合吗?我们来再次看看:
```
HMODULE LoadLibraryA/W(
LPCWSTR lpLibFileName   //DLL文件的全路径
);
```

啊这,这简直就很像嘛,模板中返回值DWORD是表示一个unsigned long值,而参数中的LPVOID则可以指向任何类型。

所以除了返回值似乎有所不同以外,其他除了名字不同,简直就很像。因此我们可以通过把```LoadLibraryA/W```强行转换为模板的类型(微软定义了一个类型LPTHREAD_START_ROUTINE 来代指模板)传递过去即可。

可就算我们知道我们要传递的```LoadLibraryA/W```函数符合了模板格式,那要怎样才知道这个函数在"别人的EXE"中的地址呢?因为我们传递的```LoadLibraryA/W```函数地址必须是在"别人的EXE"中的地址,因此我们不能平白无故的把自己的地址传过去。

这里有一个常识,那就是每个EXE的虚拟地址是不一样,换句话来说我在自己程序里写了一个和别人EXE里相同的代码,加载出来的内存虚拟地址完全不同。

**但凡事都有例外,系统核心的函数地址在每个EXE中都是一样的,而```LoadLibraryA/W```正是系统核心的函数之一。其位于系统核心文件kernel32.dll之中**

所以我们只要:通过```GetProcAddress```函数获取系统核心函数```LoadLibraryA/W```的地址->转换成模板格式->传递到```CreateRemoteThread```中便可大功告成。

# 实践
只贴出部分核心源码,需要注意的是编码的程序涉及到两个文件,一个是药引子EXE,也就是我们调用```CreateRemoteThread```的地方,另一个是自己编写的DLL文件,也就是迫使"别人EXE"加载并执行的文件。
EXE中代码:

```
//参数1:获取别人的EXE的句柄。进程ID可用procexp查看并手动输入
mProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 进程ID);

// 参数2:获取地址,并转换为规定格式
HMODULE hMod = GetModuleHandle(L"kernel32.dll");
LPTHREAD_START_ROUTINE fun =(LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

// 参数3:传递给函数的参数,需申请并写入内存
// 调用VirtualAllocEx申请内存空间
CString path = "F:\\Projec\\MyDLL.dll";
SIZE_T pathSize = (path.GetLength()+1) * sizeof(TCHAR);
LPVOID mBuffer =VirtualAllocEx(mProcess, NULL, pathSize, MEM_COMMIT, PAGE_READWRITE);
//调用WriteProcessMemory写入内存
WriteProcessMemory(mProcess, mBuffer, path, pathSize, NULL)

//调用CreateRemoteThread使别人的EXE加载我们的DLL
mRemoteThread = CreateRemoteThread(mProcess, NULL, 0, fun, mBuffer, 0, NULL);

```
DLL中代码:
```
BOOL APIENTRY DllMain( HMODULE hModule,
                     DWORDul_reason_for_call,
                     LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
      MessageBox(NULL, L"内容", L"标题", MB_OK);
      break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
      break;
    }
    return TRUE;
}
```
一些结果:













# 总结:
作为使用者我们只要知道调用CreateRemoteThread函数来实现DLL注入即可,参数则靠其他方法去凑。

作为学习者,我们要知道CreateRemoteThread更广泛的含义并不只有DLL注入,就如同其文档所述是在另一个进程中新创建一个线程,并从参数中的起始地址运行。只是不知道是微软故意如此设计还是有奇人无意中的发现,才造就了这样的DLL注入技术。

其实笔者的c++编程技术有限,WIN32中的API理解也捉襟见肘,文中大部分内容是笔者阅读《逆向工程核心原理》后的总结,可能存在疏漏和错误,若可指出感激不尽。

# 附件
[工程源码以及示例下载地址](https://share.weiyun.com/cKlQVMCS)

psycongroo 发表于 2020-8-17 22:09

伟大的泽尔 发表于 2020-8-17 20:06
想问一下这个要学多久? 好多看得不太懂。

看不懂主要是代码看不懂。
不懂的情况下不要去看代码系列,只要看原理和流程。
如果不懂函数,变量等概念可以先去尝试微微学习一下一门编程语言(不用过于深入)。
剩余的就是看书了,我推荐【逆向工程核心原理】0基础都可以看

pizazzboy 发表于 2020-8-17 19:46

佩服,留个记号,有时间慢慢看。

伟大的泽尔 发表于 2020-8-17 20:06

想问一下这个要学多久? 好多看得不太懂。

npfs 发表于 2020-8-17 20:29


强,留个记号,留着慢慢看。

snaker-wapj 发表于 2020-8-17 21:24

留个脚印,支持一下

091316223 发表于 2020-8-17 21:25

感觉逆向好像很难

逆向汇编的小白 发表于 2020-8-18 10:20

老哥能发下源码吗 自己写有很多不知道的问题

多情的我 发表于 2020-8-18 10:25

支持支持

yunruifuzhu 发表于 2020-8-18 11:07

卸载DLL的方法呢。。
页: [1] 2 3 4
查看完整版本: 萌新逆向学习笔记——远程线程注入DLL