频道栏目
首页 > 安全 > 系统安全 > 正文

SetThreadContext注入Dll注入新知识

2017-09-08 09:19:08      个评论      
收藏   我要投稿

最近,有多种方式可用于将DLL注入到历程中,但每个都有其甜头和缺欠。在这些门径中,最简单的是运用CreateRemoteThread函数在指标进程中建设一个新线程,并将线程的启动函数指向LoadLibrary函数。这种办法最容易完成,但也是最容易被检测到,因为可以经适量种方式“感知”到建设的新线程,好比使用ETW事宜。假如零碎中存在一个驱动程序,而且该驱动程序正在hooking应用PsSetCreateThreadNotifyRoutine设立的线程,那么该举动人造会被平安检测工具识别到。
一种隐蔽的法子是运用现有的线程来试验DLL注入,此中一种门径是应用APC颠末调用QueueUserApc 将APC附加到方针进程的线程队列中去,并使用APC调用LoadLibrary函数。运用APC试验DLL注入具有的标题是被注入线程必需进入可唤醒形状才力“处置惩罚”APC并执行咱们的LoadLibrary挪用,但要包管一个线程永远处于可唤醒形态是很坚苦的。为了增多告捷的机会,可以向指定历程的每一个线程都插入一个APC,但这类做法在某些情况下是不起感化的。一个榜样的例子便是cmd.exe,据我所知其单线程从不进入可唤醒形态。
这篇文章将叙说另一种使方针过程调用LoadLibrary函数的方法,但这次咱们将经过利用现有线程的高低文来实验DLL注入,线程的指令指针被转移到一个自界说的代码段,今后被重定向归来回头。这种门径很难检测,因为这些独霸看起来就像是一个正常线程正在做的事情,下面让我来论述如安在x86与x64平台中完成这类DLL注入。
DLL注入
首先,咱们需要做的第一件事便是找到一个方针进程并在该历程被决定一个线程,从手艺上来讲,它可以是指标进程中的任何线程,然则一个处于“等待”状态的线程将不会运行咱们的代码,所以最佳照常决定一个正在运行或梗概马上就要运转的线程来尽大要早地加载咱们的DLL。一旦我们选定了过程中的指数线程,那末可使用上面的代码来会见它们:
//
// open handle to process
//
auto hProcess = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pid);
if (!hProcess)
    return Error("Failed to open process handle");
//
// open handle to thread
//   
auto hThread = ::OpenThread(THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, FALSE, tid);
if (!hThread)
    return Error("Failed to open thread handle");
对于过程,由于咱们将在过程中编写目标代码,是以咱们在翻开进程的函数中应用了PROCESS_VM_OPERATION和PROCESS_VM_WRITE这两个参数。对付线程,由于我们需求篡改它的凹凸文,于是我们需要在篡改其上下文的时候使它处于“吊挂”形态。这类DLL注入办法须要几个程序:
首先,由于咱们的代码紧要在进程中履行,因而咱们在目的过程等分拨内存:
const auto page_size = 1
auto buffer = static_castchar*>(::VirtualAllocEx(hProcess, nullptr, page_size, MEM_CO美眉IT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
在上述的代码中我们调配一整页RWX内存,实践上并不必要这么大的内存空间,然而内存规画器是以页为单元来分拨内存空间,是以我们可以分派到一个完整的内存页面。我们使用上面的代码使线程处于“吊挂”外形,接下来捉拿实行线程的上下文:
if (::SuspendThread(hThread) == -1)
    return false;
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
if (!::GetThreadContext(hThread, &context))
    return false;
接下来,我们需求在方针过程中添加一些代码,这些代码必须使用汇编言语来写,而且必须与目的过程的bitness成婚(退职何情况下,注入的DLL必需与指数进程的bitness受室)。对于x86而言,我们可以在Visual Studio中编写以下形式,并复制生成的汇编代码:
void __declspec(naked) InjectedFunction() {
    __asm {
        pushad
        push        11111111h   ; the DLL path argument
        mov         eax, 22222222h  ; the LoadLibraryA function address
        call        eax
        popad
        push        33333333h   ; the code to return to
        ret
    }
}
该函数应用__declspec(naked)属性发展润色,该属性用来述说编译器函数代码中的汇编语言是咱们自己写的,不重要编译器添加任何汇编代码。在将代码添加到目标历程曩昔,我们紧要修正代码中的的占位符。在这个演示的源代码中,我将所生成的机器代码转换成一个字节数组,如下所示:
BYTE code[] = {
    0x60,
    0x68, 0x11, 0x11, 0x11, 0x11,
    0xb8, 0x22, 0x22, 0x22, 0x22,
    0xff, 0xd0,
    0x61,
    0x68, 0x33, 0x33, 0x33, 0x33,
    0xc3
};
字节数组对应于上述的指令,此刻我们修改编造地址:
auto loadLibraryAddress = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
// set dll path
*reinterpret_castPVOID*>(code + 2) = static_castvoid*>(buffer + page_size / 2);   
// set LoadLibraryA address
*reinterpret_castPVOID*>(code + 7) = static_castvoid*>(loadLibraryAddress);

// jump address (back to the original code)
*reinterpret_cast(code + 0xf) = context.Eip;
首先,咱们获得LoadLibraryA的所在,因为这是咱们用来在指标地点中加载DLL的函数。 LoadLibraryW也可以正常工作,然而ASCII版本的运用更简繁多些。 接下来,咱们将修正后的代码与DLL蹊径写入指标历程:
//
// copy the injected function into the buffer
//
if (!::WriteProcessMemory(hProcess, buffer, code, sizeof(code), nullptr))
    return false;
//
// copy the DLL name into the buffer
//
if (!::WriteProcessMemory(hProcess, buffer + page_size / 2, dllPath, ::strlen(dllPath) + 1, nullptr))
    return false;
收尾一件事是将新的指令指针指向添加的代码并恢单线程实行:
context.Eip = reinterpret_castDWORD>(buffer);
if (!::SetThreadContext(hThread, &context))
    return false;
::ResumeThread(hThread);
下面我们将以32位版本的DLL注入为例来阐述如何应用调试器械来调试咱们注入的过程。首先,咱们紧要附加到方针进程中去,并扈从指数中的代码试验流程。在以下示例中,我从\Windows\SysWow64目次(在64位零碎上)发动了32位版本的记事本。在演示项目(所在见文章劈头处)中,呼吁路途序批准设置目标进程ID与要注入的DLL的路径,这里我已经在Visual Studio设置装备摆设过了,并在挪用SetThreadContext从前放置了一个断点,管束台窗口展现了将代码复制到的编造地点,详细下列图所示:

此刻咱们可以将WinDbg附加到记事本历程,并查看该所在上的代码:
0:005> u 04A00000
04a00000 60              pushad
04a00001 680008a004      push    4A00800h
04a00006 b8805a3b76      mov     eax,offset KERNEL32!LoadLibraryAStub (763b5a80)
04a0000b ffd0            call    eax
04a0000d 61              popad
04a0000e 685c29e476      push    offset win32u!NtUserGetMessage+0xc (76e4295c)
04a00013 c3              ret
咱们可以清楚地看到咱们批改的代码,其中挪用了LoadLibraryA函数,从此代码恢复到NtUserGetMessage函数内的某个位子,我们以至可以在04A00000地点处设置一个断点,以下所示:

bp 04A00000
其时咱们可以让记事本程序持续实验,但我们设置装备摆设了一个断点,以下是断点和挪用堆栈的详细信息:
Breakpoint 0 hit
eax=00000001 ebx=01030000 ecx=00000000 edx=00000000 esi=0093fbe4 edi=01030000
eip=04a00000 esp=0093fba0 ebp=0093fbb8 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
04a00000 60              pushad
0:000> k
 # ChildEBP RetAddr 
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0093fb9c 7570fecc 0x4a00000
01 0093fbb8 01037219 USER32!GetMessageW+0x2c
02 0093fc38 0104b75c notepad!WinMain+0x18e
03 0093fccc 763b8744 notepad!__mainCRTStartup+0x142
04 0093fce0 7711582d KERNEL32!BaseThreadInitThunk+0x24
05 0093fd28 771157fd ntdll!__RtlUserThreadStart+0x2f
06 0093fd38 00000000 ntdll!_RtlUserThreadStart+0x1b
我们可以一步一步地调试 notepad,但也可以让 notepad历程去加载咱们的DLL,一旦DllMain被调用,我们就能够做任何事情了:
 

以下是我在64位机器上测试使用的代码,但我其实不克不及保证该段代码在职何情况下都可以畸形运行,是以该代码还需求进行更多测试:
BYTE code[] = {
    // sub rsp, 28h
    0x48, 0x83, 0xec, 0x28,                          
    // mov [rsp + 18], rax
    0x48, 0x89, 0x44, 0x24, 0x18,                    
    // mov [rsp + 10h], rcx
    0x48, 0x89, 0x4c, 0x24, 0x10,
    // mov rcx, 11111111111111111h
    0x48, 0xb9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,    
    // mov rax, 22222222222222222h
    0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
    // call rax

 

    0xff, 0xd0,
    // mov rcx, [rsp + 10h]
    0x48, 0x8b, 0x4c, 0x24, 0x10,
    // mov rax, [rsp + 18h]
    0x48, 0x8b, 0x44, 0x24, 0x18,
    // add rsp, 28h
    0x48, 0x83, 0xc4, 0x28,
    // mov r11, 333333333333333333h
    0x49, 0xbb, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
    // jmp r11
    0x41, 0xff, 0xe3
};
X64版本的代码看起来与x86版本不合,由于x64中的挪用商定与x86 __stdcall分歧。例如,前四个整数参数在RCX,RDX,R8与R9中通报,而不是货仓。在我们的例子中, 由于LoadLibraryA函数只须要一个参数就可,于是一个RCX就充实了。
对代码的修正自然须要使用不同的偏移量:
// set dll path
*reinterpret_cast
(code + 0x10) = static_cast(buffer + page_size / 2);
// set LoadLibraryA address
*reinterpret_cast
(code + 0x1a) = static_cast(loadLibraryAddress);
// jump address (back to the original code)
*reinterpret_cast(code + 0x34) = context.Rip;
总结
本文讲述了一种通过改动线程凹凸文来执行DLL注入的一种法子,由于加载DLL是一件很平凡的事宜,是以这类方式很难被检测到。一种或者的法子是定位可履行页面并将其地点与已知模块发展相比,可是注入过程可以在DLL注入完成后禁锢注入函数的内存,于是定位可执行页面也口角常难题的。
文中涉及到的代码可以在我的Github仓库中找到 :
上一篇:IARP与RARP:彻底遗忘的兄弟协议 图解ARP协议(6)
下一篇:Apache Struts2–052 漏洞研究警示
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站