Win32(3)

目录

1、动态链接库

(1)静态链接库

(2)动态链接库

(3)隐式链接

2、注入

(1)远程线程

(2)远程线程注入

(3)进程间的通信

(4)模块隐藏

(5)代码注入


1、动态链接库

(1)静态链接库

1、编写静态链接库文件

<1> 在 vs2019 中创建新项目,项目类型:静态库

<2> 在项目中创建 xxxx.h 和 xxxx.cpp , 编写代码,然后生成即可

<3> 在项目目录中会生成 xxxx.lib 文件

2、静态链接库的使用

方法一:将生成的 xxxx.h xxxx.lib 文件复制到项目根目录,然后在代码中引用

 

方法二在VS的项目设置中指定 xxxx.lib 文件的位置。右键单击项目,选择 Properties(属性),然后选择左侧 VC++目录 选项卡,编辑右侧 库目录 选项,把  xxxx.lib 文件所在目录添加进来

添加完成后,右击项目中的 引用,选择 添加引用 ,把刚才的 xxxx.lib 文件添加到引用中

3、静态链接库的缺点

  • 使用静态链接生成的可执行文件体积较大( 静态库会合并到 exe 文件中)
  • 包含相同的公共代码(如果多个程序引用同一个静态库,每个程序都将有一个静态库的副本),造成资源浪费

 

(2)动态链接库

1、什么是动态链接库

  • 动态链接库 Dynamic Link Library ,缩写为 DLL )
  • 动态链接库 是微软公司在微软 Windows 操作系统中,实现共享函数库概念的一种方式
  • 这些库函数的扩展名是 dllocx ( 包含 ActiveX 控制的库 )

2、创建动态链接库,使用 _declspec(dllexport)   

  • 在 vs2019 中创建新项目,项目类型:动态库
  • 在项目中创建 xxxx.h 和 xxxx.cpp ,编写代码,然后生成即可

  • 导出表

 

3、extern "C" _declspec(dllexport) 

<1> _declspec(dllexport)

_declspec(dllexport):将一个函数声名为导出函数,就是说这个函数要被其他程序调用,即作为DLL的一个对外函数接口

1. 通常与 extern "C" 合用,形式如下:

    extern "C" _declspec(dllexport) 返回类型 调用约定 函数名();    // 以 C语言编译

2. 这是由于在制作DLL导出函数时由于C++存在函数重载,因此 _declspec(dllexport) fun(int,int) 在DLL中会被decorate

3. 如被decorate成为:fun_int_int,而且不同编译器decorate的方法不同,造成使用GetProcAddress获取 fun地址时的不便

4. 使用 extern "C" 时,上述的 decorate不会发生,因为C没有函数重载,如此一来被 extern"C" 修饰的函数,就不具备重载能力

<2> 以下内容摘自 MSDN

1. 在 32位编译器版本中,可以使用 _declspec(dllexport) 关键字从 DLL导出数据、函数、类或类成员函数

2. _declspec(dllexport) 将导出指令添加到对象文件(obj文件)

3. 若要导出函数, _declspec(dllexport) 关键字必须出现在调用约定关键字的左边(如果指定了关键字)

    例如:_declspec(dllexport) void _cdecl Fun(void);

4. 若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:

    class __declspec(dllexport) CExampleExport : public CObject
    { ... class definition ... };

5. 生成 DLL 时,通常创建一个包含正在导出的函数原型和头文件,并将 _declspec(dllexport) 添加到头文件中的声明

6. 若要提高代码的可读性,请为 _declspec(dllexport) 定义一个宏并对正在导出的每个符号使用该宏:

    #define DllExport _declspec( dllexport )

<3> _declspec(dllexport) 与 .def

模块定义文件(.def)是包含一个或多个描述各种 DLL属性的 Module 语句的文本文件

1. 二者的目的都是将公共符号导入到应用程序中或从 DLL 导出函数

2. 添加 _declspec(dllexport) 是为了不使用 .def文件,从 .EXE 或 .DLL 导出函数

3. 如果不使用 _declspec(dllimport) 或 _declspec(dllexport) 导出 DLL 函数,则 DLL 需要.def文件

4. 并不是任何时候选择添加 _declspec(dllexport) 而放弃 .def 的方式都是好的

5. 如果DLL是提供给VC++用户使用的,只需要把编译DLL时产生的.lib提供给用户,它可以很轻松地调用你的DLL

6. 但是如果DLL是供VB、PB、Delphi用户使用,那么会产生一个小麻烦因为VC++对于 _declspec(dllexport) 声明的函数会进行名称转换
   如下面的函数:
   _declspec(dllexport) int _stdcall fun()  会转换为 fun@0 ,这样你在VB中必须这样声明:

   Declare Function fun Lib "my.dll" Alias "fun@0" () As Long

   @ 后面的数由于参数类型不同而可能不同,这显然不太方便,所以如果要想避免这种转换,就要使用.def文件方式

4、dllexe 的区别

  • dllexe 都是一个模块,dll 为其他模块提供函数和数据
  • 每个 模块 存在一张导出表,导出表记录了 模块包含的函数名,dll 一定有导出表,exe 不一定有导出表
  • dllexe 都遵循 PE文件结构,dll 与 exe 本质上没有区别,exe 也可以为别的模块提供 函数和数据

5、使用 .def 文件创建 dll

  • 新建 .def 文件,在 .def 文件中 指定需要导出的函数

  • 生成 dll

  • 查看导出表 ( 使用 def 文件,可以达到隐藏函数的目的 
  • 序号 12 对应十六进制的 C 13 对应十六进制的 D

6、使用动态链接库

  • dll 放到项目的根目录 (显示链接
#include <stdio.h>
#include <Windows.h>

// 定义函数指针
typedef int(__stdcall* lpPlus)(int, int);
typedef int(__stdcall* lpSub)(int, int);

// 声明函数指针变量			
lpPlus myPlus;
lpSub mySub;

int main() {
// 动态加载dll到内存中					
    HINSTANCE hModule = LoadLibrary(TEXT("动态库.dll"));

// 获取函数地址
    myPlus = (lpPlus)GetProcAddress(hModule, "plus");		// 传入 函数名
    mySub = (lpSub)GetProcAddress(hModule, (char*)0xD);		// 传入 序号

// 调用函数
    int a = myPlus(10, 2);
    int b = mySub(10, 2);

// 释放动态链接库(将 dll从进程空间移除)				
    FreeLibrary(hModule);

    return 0;
}

 

(3)隐式链接

1、隐式链接

  • 将  dll  lib 放到项目的根目录
  • 静态库的 lib 文件包含代码,动态库的 lib 文件不包含代码,只包含描述性的信息
  • #pragma comment(lib,"xxxx.lib")  添加到调用文件中 
  • 加入函数的声明 (只有 dll,没有 lib,则只能使用显示链接)
  • 好处:不用获取函数地址
// 隐式链接
#pragma comment(lib,"动态库.lib")
// 加入函数的声明
// DLL 如果使用 _declspec(dllexport) 导出函数,函数声明需要加上 extern "C"
// 使用 def 文件导出函数,则不需要
_declspec(dllimport) int _stdcall plus(int x, int y);
_declspec(dllimport) int _stdcall sub(int x, int y);

int main() {
    int x = plus(1, 2);
    int y = sub(4, 2);
    return 0;
}
  • 注意
// 导出函数
extern "C" _declspec(dllexport) 返回类型 调用约定 函数名 (参数列表);

// 导入函数
extern "C" _declspec(dllimport) 返回类型 调用约定 函数名 (参数列表);  

2、隐式链接的实现

  • dll 拥有导出表,表中记录了当前模块提供了哪些函数
  • 使用隐式链接生成的 exe 文件,一定拥有导入表,表中记录了当前模块需要使用 哪些 dll 和 dll 中的哪些函数
  • dll 也拥有导入表, exe文件不一定拥有导出表

3、dll 的优点

  1. dll 不会把自己代码合并到 exe 文件中
  2. 对 dll 文件进行修改,不会影响其他进程(写拷贝)

4DllMain 函数

  • dll 和 exe 的入口函数和执行时间不同
  • exe 从 main 函数开始执行,dll 从 DllMain 开始执行
  • DllMain 可能执行多次
BOOL APIENTRY DllMain(
    HMODULE hModule,               // 当前 dll 被加载到哪个模块
    DWORD   ul_reason_for_call,    // reason for calling function
    LPVOID  lpReserved             // reserved
)

/*
DllMain 被调用的四种情况
    DLL_PROCESS_ATTACH: 某个进程第一次执行 LoadLibrary加载 dll到内存,会调用 DllMain函数
    DLL_PROCESS_DETACH: 某个进程调用 FreeLibrary释放 dll时,会调用 DllMain函数
    DLL_THREAD_ATTACH : 当前进程的另一个线程执行 LoadLibrary又加载了一次 dll,会调用 DllMain
    DLL_THREAD_DETACH : 当前进程的另一个线程执行结束,会调用 DllMain
*/
  • 示例代码
// 定义 DLL 应用程序的入口点,模拟不同调用情况
BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        OutputDebugString(TEXT("1. DLL_PROCESS_ATTACH\n"));
        break;
    case DLL_THREAD_ATTACH:
        OutputDebugString(TEXT("2. DLL_THREAD_ATTACH\n"));
        break;
    case DLL_THREAD_DETACH:
        OutputDebugString(TEXT("3. DLL_THREAD_DETACH\n"));
        break;
    case DLL_PROCESS_DETACH:
        OutputDebugString(TEXT("4. DLL_PROCESS_DETACH\n"));
        break;
    }
    return TRUE;
}

// main.cpp
DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
    HINSTANCE hModule = LoadLibrary(TEXT("动态库.dll"));
    return 0;
}

int main() {
    // 动态加载 dll到内存中					
    HINSTANCE hModule = LoadLibrary(TEXT("动态库.dll"));
    // 获取函数地址
    // 调用函数
    // 创建新线程,加载 dll
    CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

    getchar();                      // 阻塞 main线程,让新线程执行
    // 释放动态链接库		
    FreeLibrary(hModule);

    return 0;
}

 

2、注入

(1)远程线程

1、线程

  • 线程是附属在进程上的执行实体,是 代码的执行流程
  • 代码必须通过线程才能执行

2、创建远程线程

  • 函数原型
// 在别的进程中,创建新线程
HANDLE CreateRemoteThread(
    HANDLE hProcess,                                // 进程句柄
    LPSECURITY_ATTRIBUTES lpThreadAttributes,       // SD
    SIZE_T dwStackSize,                             // initial stack size
    LPTHREAD_START_ROUTINE lpStartAddress,          // thread function
    LPVOID  lpParameter,                            // thread argument
    DWORD   dwCreationFlags,                        // creation option
    LPDWORD lpThreadId                              // thread identifier
);

HANDLE OpenProcess(
    DWORD dwDesiredAccess,        // 访问权限
    BOOL  bInheritHandle,         // 是否继承
    DWORD dwProcessId             // 进程 id
);
  • 代码示例

(1) 空项目.cpp

DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
        for (int i = 0; i < 10; i++) {
            Sleep(1000);
            printf("fun-------------\n");
        }
        return 0;
}

int main() {
// 创建 A线程,执行 ThreadProc
        HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        CloseHandle(hThread);

        getchar();      // 阻塞 main线程
        return 0;
}

(2)远程线程.cpp

// 在 空项目.exe 中创建 B线程,执行 ThreadProc
#include <stdio.h>
#include <Windows.h>

// dwProcessId:进程 id,要在哪个进程中创建线程
// dwProcAddr:线程需要执行的函数
BOOL MyCreateRemoteThread(DWORD dwProcessId, DWORD dwProcAddr) {
// 1.获取进程句柄
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
        if (hProcess == NULL) {
                OutputDebugString(TEXT("获取进程句柄失败\n"));
                return FALSE;
        }

// 2.创建远程线程
        DWORD dwThreadId = 0;
        HANDLE hThread = CreateRemoteThread(hProcess,NULL,0, (LPTHREAD_START_ROUTINE)dwProcAddr,NULL,0, &dwThreadId);
        if (hThread == NULL) {
                CloseHandle(hProcess);
                OutputDebugString(TEXT("创建远程线程失败\n"));
                return FALSE;
        }

// 3.释放资源
        CloseHandle(hThread);
        CloseHandle(hProcess);

        return true;
}


int main() {
/*
    1. 第一个参数:进程 id,通过任务管理器获取
    2. 第二个参数:需要提供 空项目.exe 中存在的线程函数的地址
                  在 ThreadProc函数入口处下断点,通过反汇编代码查看函数的地址
*/
        MyCreateRemoteThread(39808, 0x891740);
        return 0;
}

3、总结

  • 通过 CreateRemoteThread 函数,我们可以在其他进程中创建一个新线程,并调用该进程中包含的函数

 

(2)远程线程注入

1、什么是注入?

  • 所谓注入就是在第三方进程不知道或者不允许的情况下将 模块 或者 代码 写入对方进程空间,并设法执行的技术
  • 已知的注入方式:远程线程注入APC注入、消息钩子注入、注册表注入、导入表注入、输入法注入等等

2、远程线程注入的流程

/*
    线程函数和 Dll 装载函数具有相同的形式:参数 4字节,返回值 4字节
    两者都可以作为线程函数,被线程执行
*/
DWORD WINAPI ThreadProc(
    LPVOID lpThreadParameter
)

HMODULE LoadLibraryA(
    LPCSTR lpLibFileName
);

3、注入并执行代码

  • 动态库.dll DllMain 函数 (注入 dll 后,通过 DllMain 函数执行代码)
DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
    while (true) {
        Sleep(1000);
        printf("动态库.dll 代码执行-------------\n");
    }
    return 0;
}

// 当进程首次加载 dll,循环打印输出
// 执行 LoadLibrary 会调用 DllMain 函数
BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        break;
    case DLL_THREAD_ATTACH:  break;
    case DLL_THREAD_DETACH:  break;
    case DLL_PROCESS_DETACH: break;
    }
    return TRUE;
}
  • 空项目.exe 代码参考:( 空项目.cpp
  • 远程线程注入代码
// 参数:进程 id、 dll的完整路径名
BOOL LoadDll(DWORD dwProcessId, const char* szDllPathName) {
	BOOL bRet;
	HANDLE hProcess, hThread;
	DWORD dwLength, dwLoadAddr;
	LPVOID lpAllocAddr, dwThreadId;
	HMODULE hModule;

	// 1.获取进程句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	if (hProcess == NULL) {
		OutputDebugString(TEXT("获取进程句柄失败\n"));
		return FALSE;
	}

	// 2.计算 DLL路径名字长度,并且要加上 0结尾的长度
	dwLength = strlen(szDllPathName) + 1;

	// 3.在目标进程分配内存
		// 1. VirtualAlloc   只能在自己进程中申请内存
		// 2. VirtualAllocEx 可以在别的进程中申请分配内存
	lpAllocAddr = VirtualAllocEx(hProcess, NULL, dwLength, MEM_COMMIT, PAGE_READWRITE);
	if (lpAllocAddr == NULL) {
		CloseHandle(hProcess);
		OutputDebugString(TEXT("内存分配失败\n"));
		return FALSE;
	}

	// 4.拷贝 Dll到目标进程的内存空间
	bRet = WriteProcessMemory(hProcess, lpAllocAddr, szDllPathName, dwLength, NULL);
	if (!bRet) {
		CloseHandle(hProcess);
		OutputDebugString(TEXT("写入进程失败\n"));
		return FALSE;
	}

	// 5.获取模块地址
	hModule = GetModuleHandle(TEXT("kernel32.dll"));
	if (!hModule) {
		CloseHandle(hProcess);
		OutputDebugString(TEXT("获取模块地址失败\n"));
		return FALSE;
	}

	// 6.获取 LoadLibraryA 函数地址
	dwLoadAddr = (DWORD)GetProcAddress(hModule, "LoadLibraryA");
	if (!dwLoadAddr) {
		CloseHandle(hModule);
		CloseHandle(hProcess);
		OutputDebugString(TEXT("获取函数地址失败\n"));
		return FALSE;
	}

	// 7.创建远程线程,加载 dll
	hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)dwLoadAddr, lpAllocAddr, 0, NULL);
	if (!hThread) {
		CloseHandle(hModule);
		CloseHandle(hProcess);
		OutputDebugString(TEXT("创建远程线程失败\n"));
		return FALSE;
	}

	// 8.关闭进程句柄
	CloseHandle(hProcess);
	return TRUE;
}

int main() {
	LoadDll(9300,"D:\\VS2019\\动态库\\Debug\\动态库.dll");
	return 0;
}
  • 使用 OD 查找 LoadLibraryA 函数的地址
  1. LoadLibraryA 位于 kernel32.dll 中,任意一个 exe 的执行都需要包含这个 dll
  2. 所以我们用OD打开 扫雷.exe,选择 E(Executable Modules),双击 kernel32.dll,使用 ctrl + N ,查看此 dll 包含的函数

 

(3)进程间的通信

1、通信方式

  • 同一台电脑 两个进程的通信方式:管道、消息队列、信号量、共享内存 (本质上都是共享内存)
  • 不同的电脑 两个进程的通信方式:套接字

2、共享内存的通信方式

  • 模拟游戏.cpp
void Q() {
    printf("----- 斩钢闪 -------\n"); return;
}
void W() {
    printf("----- 风之障壁 -----\n"); return;
}
void E() {
    printf("----- 踏前斩 -------\n"); return;
}
void R() {
    printf("----- 狂风绝息斩 ---\n"); return;
}

int main() {
    printf("----- 游戏开始 -----\n");
    while (true) {
        char c = getchar();
        switch (c) {
            case 'Q': Q(); break;
            case 'W': W(); break;
            case 'E': E(); break;
            case 'R': R(); break;
        }
    }
    return 0;
}
  • 控制游戏.dll
#define _MAP_  "共享内存"
#define _Q_    0x6417A0
#define _W_    0x641860
#define _E_    0x641740
#define _R_    0x641800

HANDLE g_hModule, g_hMapFile;
LPTSTR lpBuff;
DWORD dwType;

DWORD WINAPI ThreadProc(LPVOID lpParameter) {
    g_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, TEXT(_MAP_));
    if (g_hMapFile == NULL) {
        printf("打开映射文件失败:%d\n", GetLastError());
        return 0;
    }

    // 映射内存
    lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
    while (true) {
        if (lpBuff != NULL) {
            // 读取数据
            CopyMemory(&dwType, lpBuff, 4);
        }
        if (dwType == 1) {
            _asm {
                mov eax,_Q_
                call eax
            }
            dwType = 0;  CopyMemory(lpBuff, &dwType, 4);
        }
        if (dwType == 2) {
            _asm {
                mov eax, _W_
                call eax
            }
            dwType = 0;  CopyMemory(lpBuff, &dwType, 4);
        }
        if (dwType == 3) {
            _asm {
                mov eax, _E_
                call eax
            }
            dwType = 0;  CopyMemory(lpBuff, &dwType, 4);
        }
        if (dwType == 4) {
            _asm {
                mov eax, _R_
                call eax
            }
            dwType = 0;  CopyMemory(lpBuff, &dwType, 4);
        }
        if (dwType == 5) {
            // 卸载自己并退出
            FreeLibraryAndExitThread((HMODULE)g_hModule, 0);
        }
        Sleep(500);
    }
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    g_hModule = hModule;
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        break;
    case DLL_THREAD_ATTACH:  break;
    case DLL_THREAD_DETACH:  break;
    case DLL_PROCESS_DETACH: break;
    }
    return TRUE;
}
  • 注入dll.cpp
#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#define _MAP_ "共享内存"

HANDLE g_hMapFile;
LPTSTR lpBuff;

// 参数:进程 id、 dll的完整路径名
BOOL LoadDll(DWORD dwProcessId, const char* szDllPathName) {
    // 代码参考:远程线程注入
}

BOOL init() {
    // 创建事件、创建共享内存
        g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 0x1000, TEXT(_MAP_));
        if (g_hMapFile == NULL) {
            printf("创建映射文件失败\n");
            return FALSE;
        }
    // 映射内存
        lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
        if (lpBuff == NULL) {
            printf("关联文件失败\n");
            return FALSE;
        }
        return TRUE;
}

DWORD GetPid(CONST WCHAR* szName) {
        HANDLE hProcessSnap = NULL;
        PROCESSENTRY32 pe32 = { 0 };

        hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hProcessSnap == (HANDLE)-1) {
            return 0;
        }
        pe32.dwSize = sizeof(PROCESSENTRY32);
        if(Process32First(hProcessSnap,&pe32)) {
            do {
                if (!wcscmp(szName, pe32.szExeFile)) {
                    return (int)pe32.th32ProcessID;
                }
            } while (Process32Next(hProcessSnap, &pe32));
        }
        else
            CloseHandle(hProcessSnap);
        return 0;
}

int main() {
        DWORD dwCtrlCode, dwOrdeList[10];

    // 1.初始化 2.注入 DLL
        if(init())
        LoadDll(GetPid(L"模拟游戏.exe"), "D:\\VS2019\\游戏控制\\Debug\\游戏控制.dll");

    // 3.命令队列
        dwOrdeList[0] = 1;  dwOrdeList[1] = 1;  dwOrdeList[2] = 2;
        dwOrdeList[3] = 2;  dwOrdeList[4] = 3;  dwOrdeList[5] = 3;
        dwOrdeList[6] = 4;  dwOrdeList[7] = 4;  dwOrdeList[8] = 4;  dwOrdeList[9] = 5;

        for (int i = 0; i < 10; i++) {
            dwCtrlCode = dwOrdeList[i];
            CopyMemory(lpBuff, &dwCtrlCode, 4);
            Sleep(1000);
        }
        return 0;
}
  • FreeLibrary                                     卸载某个某块,不能卸载自己
  • FreeLibraryAndExitThread            卸载模块,可以卸载自己

 

(4)模块隐藏

1、TEB 和 PEB

  • TEB(Thread Environment Block ),它记录的相关线程的信息,每一个线程都有自己的TEB,FS:[0] 即是当前线程的TEB
  • PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB,TEB偏移 0x30 即当前进程的PEB
  • TEB 与 PEB 都在用户空间(用户层结构体)

2、获取 TEB 结构体的步骤

(1)用 OD 打开 扫雷.exe,打开后会在 winmine.exe 模块 处发生中断,此时 TEB的地址存储在 FS 寄存器

(2)使用 dd 命令跳转到对应的内存地址,该地址存储着当前线程的 TEB 对象

(3)TEB 结构体图解

续:

(4)在OD中解析 TEB 结构体:双击入口地址,查看内存的相对偏移量

(4)使用代码获取 TEB 首地址

// 使用汇编代码,将首地址存入 eax中
    mov eax,fs:[0]

 

3、获取 PEB 结构体的步骤

(1)TEB 结构体首地址偏移 0x30 的内存地址,存放着 PEB 的首地址

(2)在 OD 中右键此地址,Fllow in Dump 找到 PEB 的首地址(和 dd 命令效果相同)

(3)win32API 可以遍历当前进程所有的模块,实际上这些 API查询的是 PEB的 Ldr 成员

(4)在 PEB 结构体首地址偏移 0x0C 的内存地址,Fllow in Dump,找到 _PEB_LDR_DATA 结构体

(5)在 _PEB_LDR_DATA 结构体的 0x0C 偏移处,Fllow in Dump,找到 _LDR_DATA_TABLE_ENTRY 结构体

(6)结构体图解

  • PEB 和 _PEB_LDR_DATA

  • PEB续

  • _LDR_DATA_TABLE_ENTRY

(7)使用代码获取 PEB

mov eax,fs:[0x30]

mov PEB,eax

4、代码示例

// 通过断链的方式,可以使通过 Win32API 查询模块的方式失效
typedef struct _UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} UNICODE_STRING,*PUNICODE_STRING;

typedef struct _PEB_LDR_DATA {
	ULONG       Length;
	BOOLEAN     Initialized;
	PVOID       SsHandle;
	LIST_ENTRY  InLoadOrderModuleList;
	LIST_ENTRY  InMemoryOrderModuleList;
	LIST_ENTRY  InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY {
	LIST_ENTRY		InLoadOrderModuleList;
	LIST_ENTRY		InMemoryOrderModuleList;
	LIST_ENTRY		InInitializationOrderModuleList;
	void*			BaseAddress;				// 模块的句柄
	void*		    EntryPoint;
	ULONG           SizeOfImage;
	UNICODE_STRING  FullDllName;
	UNICODE_STRING  BaseDllName;
	ULONG           Flags;
	SHORT           LoadCount;
	SHORT           TlsIndex;
	HANDLE          SectionHandle;
	ULONG           CheckSum;
	ULONG           TimeBaseStamp;
} LDR_MODULE, *PLDR_MODULE;

void HideModule(CONST WCHAR* szModuleName) {
	HMODULE hMod = ::GetModuleHandle(szModuleName);
	PLIST_ENTRY Head, Cur;		// 双链表
	PPEB_LDR_DATA ldr;
	PLDR_MODULE ldm;
        _asm {
            mov eax, fs:[0x30]
            mov ecx, [eax+0x0c]
            mov ldr, ecx
        }
	Head = &(ldr->InLoadOrderModuleList);
	Cur = Head->Flink;
	do {
            // 宏 CONTAINING_RECORD 根据结构体中的某成员的地址来推算处该结构体整体的地址
            ldm = CONTAINING_RECORD(Cur, LDR_MODULE, InLoadOrderModuleList);
            if (hMod == ldm->BaseAddress) {
            // 双向链表断链
                ldm->InLoadOrderModuleList.Blink->Flink = ldm->InLoadOrderModuleList.Flink;
                ldm->InLoadOrderModuleList.Flink->Blink = ldm->InLoadOrderModuleList.Blink;
                ldm->InInitializationOrderModuleList.Blink->Flink = ldm->InInitializationOrderModuleList.Flink;
                ldm->InInitializationOrderModuleList.Flink->Blink = ldm->InInitializationOrderModuleList.Blink;
                ldm->InMemoryOrderModuleList.Blink->Flink = ldm->InMemoryOrderModuleList.Flink;
                ldm->InMemoryOrderModuleList.Flink->Blink = ldm->InMemoryOrderModuleList.Blink;
                break;
            }
            Cur = Cur->Flink;
        } while (Head != Cur);
}

int main() {
	printf("按任意键隐藏模块\n");
	getchar();
	HideModule(L"kernel32.dll");
	printf("隐藏已模块\n");
	getchar();
	return 0;
}

5、内核层 —— VAD树

  • 遍历 VAD树(是一棵搜索二叉树)可以查询当前进程虚拟内存占用情况
  • VAD树 每个成员都是一个结构体,其中包含指针成员指向 dll 的名称
  • 把指针置空,VAD树 遍历也无法找到对应的 dll

6、模块隐藏之 PE指纹

  • 任何模块都有 PE 指纹

  1. 先找前2个字节,对应的值是 MZ
  2. 在找第 64个字节,对应的值为 D8
  3. 再找第 0xD8 个字节,对应的值为 PE,这就是一个 PE指纹,说明该内存地址中存放着一个模块
  • 通过 PE指纹找模块
  1. 遍历程序内存,如果找到 MZ 字符,则加上第64字节存放的偏移量
  2. 在对应偏移处存放的是 PE 字符,则说明该处存放着一个模块
  3. 将 PE指纹 置0,可监控 LoadLibrary 函数的调用,仍能发现模块的载入

7、最好的隐藏:代码注入

 

(5)代码注入

1、代码注入的思路

  • 将自己定义的函数复制到进程内存空间

  • 复制到目标进程的内容

将想要执行函数的 机器码 复制到目标进程的内存空间

  • 复制代码的编写原则
// 不能有全局变量
    int g;
    int Fun(int x.int y){
        g = 1;
            C7 05 38 A1 46 00 01       mov  dword ptr [g (046A138h)],1
        return x+y;
    }

// 不能使用常量字符串
    int Fun(int x.int y){
        char* c = (char*)"china";
            C7 45 F8 D0 7B E9 00       mov  dword ptr [c],offset string "china" (0E97BD0h)
        return x+y;
    }

// 不能使用系统调用( Win32API )
    // 导入表 会告诉编译器 MessageBox 的地址,在目标进程的导入表中 MessageBox 可能不存在
    int Fun(int x.int y){
        MessageBox(0,0,0,0);
            8B F4                mov       esi,esp  
            6A 00                push      0  
            6A 00                push      0  
            6A 00                push      0  
            6A 00                push      0  
            FF 15 98 B0 34 00    call      dword ptr [__imp__MessageBoxW@16 (034B098h)]  
            3B F4                cmp       esi,esp  
            E8 D8 FA FF FF       call      __RTC_CheckEsp (0341217h)
        return x+y;
    }

// 不能嵌套调用其他函数
    int Fun(int x, int y) {
    	Test();
        // E8 52 FC FF FF       call      Test (042137Fh)
    	return x + y;
    }

2、向目标进程复制函数,并传递参数

3、代码示例

#include <stdio.h>
#include <Windows.h>

typedef struct {
	// CreateFile 函数的地址
	DWORD   dwCreateAPIAddr;
	// CreateFile 函数的参数
	LPCSTR  lpFileName;
	DWORD   dwDesireAccess;
	DWORD   daShareMode;
	LPSECURITY_ATTRIBUTES lpSecurityAttributes;
	DWORD   dwCreationDispositon;
	DWORD   dwFlagsAndAttributes;
	HANDLE  hTemplateFile;
} CREATEFILE_PARAM;

// CreateFile的函数指针
typedef HANDLE(WINAPI* PFN_CreateFile)(
	LPCSTR  lpFileName,
	DWORD   dwDesireAccess,
	DWORD   daShareMode,
	LPSECURITY_ATTRIBUTES lpSecurityAttributes,
	DWORD   dwCreationDispositon,
	DWORD   dwFlagsAndAttributes,
	HANDLE  hTemplateFile
);

// 要复制到目标进程的函数,参数:结构体指针
DWORD _stdcall CreateFileThreadProc(LPVOID lParam) {
	CREATEFILE_PARAM* Gcreate = (CREATEFILE_PARAM*)lParam;
	// 获取 CreateFile函数地址
	PFN_CreateFile pfnCreateFile= (PFN_CreateFile)Gcreate->dwCreateAPIAddr;
	pfnCreateFile(
		Gcreate->lpFileName,
		Gcreate->dwDesireAccess,
		Gcreate->daShareMode,
		Gcreate->lpSecurityAttributes,
		Gcreate->dwCreationDispositon,
		Gcreate->dwFlagsAndAttributes,
		Gcreate->hTemplateFile
	);
	return 0;
}

// 远程创建文件
BOOL RemotCreateFile(DWORD dwProcessID, char* szFilePathName) {
	BOOL     bRet = 0;
	DWORD    dwThread;
	HANDLE   hProcess = 0;
	HANDLE   hThread;
	DWORD    dwThreadFunSize = 0x400;
	CREATEFILE_PARAM GCreateFile;
	LPVOID   lpFilePathName;
	LPVOID   lpRemotThreadAddr;
	LPVOID   lpFileParamAddr;
	DWORD    dwFunAddr;
	HMODULE  hModule;

	// 1.获取进程句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
	if (hProcess == NULL)    return FALSE;

	// 2.分配3段内存:存储参数、线程函数、文件
	// 2.1用来存储文件名
	lpFilePathName = VirtualAllocEx(hProcess, NULL, strlen(szFilePathName) + 1, MEM_COMMIT, PAGE_READWRITE);
	// 2.2用来存储线程函数
	lpRemotThreadAddr = VirtualAllocEx(hProcess, NULL, dwThreadFunSize, MEM_COMMIT, PAGE_READWRITE);
	// 2.3用来存储参数
	lpFileParamAddr = VirtualAllocEx(hProcess, NULL, sizeof(CREATEFILE_PARAM), MEM_COMMIT, PAGE_READWRITE);

	// 3.初始化 CreateFile 参数
	GCreateFile.dwDesireAccess = GENERIC_READ | GENERIC_WRITE;
	GCreateFile.daShareMode = 0;
	GCreateFile.lpSecurityAttributes = NULL;
	GCreateFile.dwCreationDispositon = OPEN_ALWAYS;
	GCreateFile.dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
	GCreateFile.hTemplateFile = NULL;

	// 4.获取 CreateFile 函数地址
	hModule = LoadLibrary(TEXT("kernel32.dll"));
	GCreateFile.dwCreateAPIAddr = (DWORD)GetProcAddress(hModule, "CreateFileA");
	FreeLibrary(hModule);

	// 5.初始化 CreateFile 文件名,文件名存储在目标进程的内存空间
	GCreateFile.lpFileName = (LPSTR)lpFilePathName;

	// 6.修正线程函数起始地址
	// 6.1在 main中调用的函数地址,与真正的函数地址有一个 jmp 指令
	dwFunAddr = (DWORD)CreateFileThreadProc;
	if (*(BYTE*)dwFunAddr == 0xE9) {
		dwFunAddr = dwFunAddr + 5 + *(DWORD*)(dwFunAddr + 1);
	}

	// 7.开始复制
	// 7.1拷贝文件名
	WriteProcessMemory(hProcess, lpFilePathName, szFilePathName, strlen(szFilePathName) + 1, 0);
	// 7.2拷贝线程函数
	WriteProcessMemory(hProcess, lpRemotThreadAddr, (LPVOID)dwFunAddr, dwThreadFunSize, 0);
	// 7.3拷贝线程参数
	WriteProcessMemory(hProcess, lpFileParamAddr, &GCreateFile, sizeof(CREATEFILE_PARAM), 0);

	// 8.创建远程线程
	hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemotThreadAddr,
                                    lpFileParamAddr, 0, &dwThread);

	// 9.关闭进程句柄
	CloseHandle(hProcess);
	return TRUE;
}

int main() {
	// 目标进程创建文件后,要等待目标进程关闭,创建的文件才能删除
	RemotCreateFile(160, (char*)"D:\\a.txt");
	return 0;
}

4、修正线程函数起始地址

void Test(){
// 此处才是真实的函数地址,所以要进行修正
        000A1700   55                   push        ebp  
        000A1701   8B EC                mov         ebp,esp  
        000A1703   81 EC C0 00 00 00    sub         esp,0C0h  
        000A1709   53                   push        ebx  
        000A170A   56                   push        esi  
        000A170B   57                   push        edi  
        000A170C   8D BD 40 FF FF FF    lea         edi,[ebp-0C0h]  
        000A1712   B9 30 00 00 00       mov         ecx,30h  
        000A1717   B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
        000A171C   F3 AB                rep stos    dword ptr es:[edi]  
        000A171E   B9 15 C0 0A 00       mov         ecx,offset _43B7F706_源@cpp (0AC015h)  
        000A1723   E8 E0 FA FF FF       call        @__CheckForDebuggerJustMyCode@4 (0A1208h)
    return;  
        000A1728   5F                   pop         edi  
        000A1729   5E                   pop         esi  
        000A172A   5B                   pop         ebx  
        000A172B   81 C4 C0 00 00 00    add         esp,0C0h  
        000A1731   3B EC                cmp         ebp,esp  
        000A1733   E8 DF FA FF FF       call        __RTC_CheckEsp (0A1217h)  
        000A1738   8B E5                mov         esp,ebp  
        000A173A   5D                   pop         ebp  
        000A173B   C3                   ret  
}

int main() {
    Test();
    000A1768    E8 12 FC FF FF       call   Fun (0A137Fh)    
/*
    call 与真实的函数地址之间还有一个 jmp指令,E9是 jmp,7C是相对偏移
    000A137F    E9 7C 03 00 00       jmp    Test (0A1700h)
    
    000A1384 + 037C = 000A1700    
*/
    return 0;
}
  1. 多次调试均注入失败,并且导致目标进程崩溃,起初认为是堆栈溢出,查阅资料后发现:
  2. 使用 DeBug 版本运行,会导致目标进程崩溃
  3. Debug 版本和 Release 版本最大的不同在于,每次调用一个函数后,Debug 版本会进行堆栈检测。而目标进程显然是处于Release模式
  4. 此时,如果注入的是Debug版本的代码,那么调用函数之后,会有一句检测堆栈的机器指令,这很可能就是进程崩溃的原因
  5. 改为 Release 环境测试,一切正常,成功创建文件

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值