请选择 进入手机版 | 继续访问电脑版

挂宝网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 911|回复: 0

[编程技术] C注入技术源码C++线程注入

[复制链接]
累计签到:62 天
连续签到:1 天

207

主题

269

帖子

7816

积分

管理员

Rank: 16

积分
7816
发表于 2020-2-11 17:56:26 | 显示全部楼层 |阅读模式
什么是C辅助线程注入?

线程注入,是通过开启远程线程的方式,将DLL加载到目标宿主进程中的常用方式。



什么是动态链接库?

首先Windows中链接库分为两种:动态链接库DLL、静态链接库LIB。

① 静态链接库:在运行的时候就直接把代码全部加载到程序中,调用方式比如:

#prama comment (lib,"Psapi.lib")
② 动态链接库:在需要的时候加载,加载方式为:

——使用LoadLibrary动态加载DLL

——使用GetProcAddress获取DLL中导出函数的指针

——最后用FreeLibrary卸载指定的DLL

动态链接库设计的目的是为了动态加载功能,动态地释放内存,节约内存,方便每一个程序的4GB内存的管理,4GB有2GB用来放系统内核,即系统大量的DLL。其本质上也是一个可以被加载的程序。同时系统对于DLL只会保存一份。



在Visual Studio编译环境下,DLL又分为三类:

① 非MFC的DLL——使用SDK API进行编程,能被所有语言调用

② MFC规则DLL——使用MFC进行编程,能被所有语言调用

③ MFC扩展DLL——使用MFC进行编程,只能被MFC编写的程序调用

(下面使用第一种)



MFC——Microsoft Foundation Class-Library :微软用C++对API进行的封装,全部封装成了类,方便使



需要注意:

DLL的导出函数使用

extern "C" _declspec(dllexport)

而导入函数使用

extern "C" _declspec(dllimport)

extern "C" 的作用是作为一种编译约定



那么进程是如何调用DLL的呢?

①使用LoadLibrary加载进所需DLL

HMODULE hMod = LoadLibrary(DLL路径)

②定义导入函数指针

typedef int(*ADD_IMPORT) (int a,int b); // 定义一个指向返回值为int类型的函数指针

③使用GetProcAddress获得函数入口点

ADD_IMPORT add_proc = (ADD_IMPORT) GetProcAddress(hMod,"Add");

④然后就可以使用了:比如 int result = add_proc(1,2);



如下使用VS2019进行编写编译:

1、新建Win32项目,选择DLL


1.png 2.png


关于DLL入口主函数第二个参数ul_reason_for_call,即DLL四种当前的状态:

3.png

2、新建如下头文件

4.png

3、在dajiDLL.cpp中


5.png
然后同一解决方案下新建MFC项目

6.png



这里需要说一下注入的可行性:

7.png

①kernel32和user32是两个在大部分程序上都会调用的DLL

②同一个DLL,在不同的进程中,不一定被映射(加载)到同一个内存地址

③但是kernel32和user32除外,他们总是被映射到进程的内存首选地址

④因此在所有使用这两个DLL的进程中,这两个DLL的内存地址是相同的

⑤所以在本进程获取的kernel32.dll中的函数的地址,在目标进程中也是一样的

流程:目标进程-传入DLL地址-开启远程线程-加载DLL-实现DLL注入



具体使用函数如下:

OpenProcess    // 获取已知进程的句柄

VirtualAllocEx    // 在远程进程中申请内存空间

WriteProcessMemory    // 向进程中写入东西

GetProcAddress    // 取得函数在DLL中的地址

CreateRemoteThreadEx    // 创建远程线程——即在其他进程中创建新的线程

CloseHandle    // 关闭句柄

利用以上函数即可完成线程的注入



上面讲了这么多下面开始编写远程线程注入的代码:
① 在资源视图中找到主Dialog文件新建如下组件并命名,其中Inject按钮设为默认按钮,两个Button的ID分别为IDC_INJECT和IDC_OPEN

8.png

② 双击Open按钮编写如下代码,需要提前将编辑框1和2的ID分别命名为IDC_DLLPATH、IDC_EXENAME

// 获取DLL路径名,打开DLL文件
void CdajiInjecterDlg::OnBnClickedOpen()
{
        // 定义一个filedialog对象,通过它来获取dll的路径
        CFileDialog filedialog(TRUE,0,0,6UL,_T("DLL Files|*.dll|"));
        // 如果弹出了对话框并且选择了DLL,那么将获得的路径填充显示到DLLPath
        if (filedialog.DoModal() == IDOK)
        {
                CString DLLpath;
                DLLpath = filedialog.GetPathName();
                // 将路径填充到IDC_DLLPATH的对话框
                SetDlgItemText(IDC_DLLPATH, DLLpath);
        }
}
③ 双击Inject按钮,首先需要在inject按钮函数的前面添加注入功能的函数Inject

// 注入函数,同时返回值为是否注入成功,默认返回成功
BOOL Inject(LPCTSTR DLLPath,DWORD ProcessID)
{
        // 定义一个句柄获取目标进程的所有权限,包括子进程
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, ProcessID);
        // 判读是否获取到
        if (!hProcess)
        {
                return FALSE;
        }
        // 计算一下获取到的DLLpath究竟有多长,要在内存空间中申请内存来存放它,注意+1,为\0
        SIZE_T PathSize = (_tcslen(DLLPath) + 1) * sizeof(TCHAR); // 宽字节
        // 在晋城中申请内存存放此DLLpath
        LPVOID StartAddress = VirtualAllocEx(hProcess, NULL, PathSize, MEM_COMMIT, PAGE_READWRITE);
        // 如果未能获取到地址,返回失败
        if (!StartAddress)
        {
                return FALSE;
        }
        // 获取到地址则将DLLPath写入此地址
        if (!WriteProcessMemory(hProcess, StartAddress, DLLPath, PathSize, NULL))
        {
                return FALSE;
        }
        // 获取LoadLibrary的入口点地址,需要进行强制类型转换
        PTHREAD_START_ROUTINE pfnStartAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryW");
        // 在这里获取到的地址,在目标进程中也一样,因此直接传递过去即可
        // 先判断是否获取到
        if (!pfnStartAddress)
        {
                return FALSE;
        }
        // 最重要的一步,创建远程线程,首先获取句柄,createremotethreadex函数只在win7以上系统有
        HANDLE hThread = CreateRemoteThreadEx(hProcess,NULL,NULL,pfnStartAddress,StartAddress,NULL,NULL,NULL);
        // 判断线程是否创建成功
        if (!hThread)
        {
                return FALSE;
        }
        // 最后等待线程结束,然后清理线程和进程
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
        CloseHandle(hProcess);
        return TRUE;
}
④ 在上面的注入函数之前还需要添加一个进程查找函数,如下

// 获取进程,其参数为进程名——在进程列表中获取我们想要的参数
DWORD ProcessFind(LPCTSTR Exename)
{
        // 第一个参数只获取进程名
        HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
        // 对获取的进程进行判断
        if(!hProcess)
        {
                return FALSE;
        }
        // 获取成功
        PROCESSENTRY32 info;
        info.dwSize = sizeof(PROCESSENTRY32);
        if (!Process32First(hProcess,&info))
        {
                return FALSE;
        }
        while (true) // 遍历进程中的所有项
        {
                // 判断是否与所给进程名相符,就是输入的进程名
                if(_tcscmp(info.szExeFile,Exename)==0)
                {
                        // 相符的话返回该进程的PID
                        return info.th32ProcessID;
                }
                // 如果遍历了整个进程列表都没有找到,那么返回FALSE
                if (!Process32Next(hProcess, &info))
                {
                        return FALSE;
                }
        }
        // 函数默认返回FALSE
        return FALSE;
}
⑤ 最后是注入按钮Inject的函数代码:


// 点击按钮执行注入
void CdajiInjecterDlg::OnBnClickedInject()
{
        CString Dllpath;
        CString Exename;
        GetDlgItemText(IDC_EXENAME, Exename);
        GetDlgItemText(IDC_DLLPATH, Dllpath);

        // 如果用户什么进程名都没输入,那么提示
        if (Exename.GetLength() == 0)
        {
                MessageBox(_T("请输入进程名!"));
                return;  // 未填写进程名,直接返回,不让此函数继续执行
        }
        // DWORD类型变量用于存储PID
        DWORD ProcessID = ProcessFind(Exename);
        // 如果在进程列表中没找到此程序,提示
        if (!ProcessID)
        {
                MessageBox(_T("未找到该程序!"));
                return;  // 不再执行下面的
        }
        // 执行注入,并且对注入结果进行获取
        BOOL IsInjected = Inject(Dllpath, ProcessID);
        // 注入成功与失败,提示
        if (IsInjected)
        {
                MessageBox(_T("注入成功!"));
        }
        else
        {
                MessageBox(_T("注入失败!"));
        }
}
最后在dllmain.cpp中添加一个清楚的注入成功提醒标识:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        // 进程连接时进程提示
    case DLL_PROCESS_ATTACH:
                MessageBox(NULL,L"Hello daji!",L"Welcome!",NULL);
                break;
        // 注意到此只能注入32位的进程,如果想要注入64位的进程
        // 需要在配置管理器中进行修改


    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


如下进行测试,选择x64版本DLL(注意编译的时候选择编译的版本):
9.png



目标进程为Everything.exe


10.png
11.png


可以在任务栏的Evething中发现DLL的线程,二者此时属于同一个进程。
————————————————
版权声明:本文为CSDN博主「Apollooooo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Cody_Ren/article/details/100041660


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋| 挂宝网  |网站地图

挂宝网 辅助论坛 X3.4© 2019-2020 GuaB.Net

快速回复 返回顶部 返回列表