从内存中直接运行PE程序

简介:

效果是这样的,假设一个PE数据在内存里面了,我们利用下面我讲的技术可以直接建立一个进程并运行这个PE,当然直接在本进程运行在可以,这两钟技术在前些时日我都有实现,今天我只说关于建立进程并运行的,当然,为了防止无味的技术剽窃,我不准备给出完整代码,只给出部分关键性代码.

这种技术严格来说没有什么用处,不过对于用到木马病毒上效果缺非常不错,当然我并非是用到木马或者是病毒当中,我是用在直接从网络上同步并运行程序并且磁盘中不会出现任何相关的东西,这样对于保密数据来说有一定的效果.

首先我们要考虑进程问题,由于我们需要一个进程,windows不能平白无故的多出一个进程来,就好比我的钱包不会平白无故的多出几张三头将军一样.于是比较便利的就是直接再运行一个当前程序.

CreateProcess(plpFile,NULL,NULL,NULL,FALSE,DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&pStartInfo,&pProcInfo);

DEBUG_ONLY_THIS_PROCESS表示我们以调试方式运行,这样的好处在于我们可以从DEBUG_EVENT中得到程序的入口,这里要说一下的是如果到了vista这个时代,exe装入有一个Address Space Load Randomization技术,如果有重定位,那么windows将随机基址.

1     while (WaitForDebugEvent(&pdbgEvnt,INFINITE))

复制代码
复制代码
2     {
3         if (pdbgEvnt.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
4             break;
5         ContinueDebugEvent(pdbgEvnt.dwProcessId,pdbgEvnt.dwThreadId,DBG_CONTINUE);

6     }

我们等待程序的CREATE_PROCESS_DEBUG_EVENT事件,到这里PE已经像大家闺秀出嫁前一样梳妆打扮好了,于是我们可以取其入口的代码并保存,等到后面恢复,当然也可以不恢复,因为我们要hook掉入口的代码,让他转入我们写入的代码

到这里一个进程已经被我们创建了,创建了进程后我们需要在目标进程申请两段空间来把我们需要运行的PE的代码放进去,我们首先想到了VirtualAllocEx

ULONG_PTR plprMem = (ULONG_PTR)VirtualAllocEx(pProcInfo.hProcess,NULL,pimNH1->OptionalHeader.SizeOfImage,MEM_COMMIT,PAGE_EXECUTE_READWRITE);

首先申请一段来存放我们想要运行的PE程序代码,大小是Image大小而不是PE的文件大小.

申请好后我们来处理一下需要写入到目标进程的代码

 

复制代码
复制代码
    char* plpAlign = (char*)VirtualAlloc(0,pimNH1->OptionalHeader.SizeOfImage,MEM_COMMIT,PAGE_READWRITE);
    if (plpAlign == NULL)
        return NULL;

    PIMAGE_SECTION_HEADER    pimSH1 = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pimNH1+sizeof(IMAGE_NT_HEADERS));
    int    psizeHeader = 0x7FFFFFFF;
    for (int i=0;i<pimNH1->FileHeader.NumberOfSections;i++)
    {
        if (pimSH1->PointerToRawData != 0)
            psizeHeader = min(pimSH1->PointerToRawData,psizeHeader);
        memcpy(plpAlign+pimSH1->VirtualAddress,plpImage+pimSH1->PointerToRawData,pimSH1->SizeOfRawData);
        pimSH1++;
    }
    //将PE头复制回去
    memcpy(plpAlign,plpImage,psizeHeader);
    ULONG_PTR    pdelta    = (ULONG_PTR)pNewBase - pimNH1->OptionalHeader.ImageBase;

    if (pdelta != 0)//需要重新定位

        LoadVReloc((ULONG_PTR)plpAlign,TRUE,pdelta);

大概思路就是先在本进程申请一段空间,并且把代码按内存对齐,然后把代码重新定位,这样我们的代码放到目标进程就不需要重新定位和对齐了.

1     BOOL pbRet = WriteProcessMemory(pProcInfo.hProcess,(char*)plprMem,plpAlign,pimNH1->OptionalHeader.SizeOfImage,&pszWtd);

复制代码
复制代码
2     VirtualFree((void*)plpAlign,0,MEM_RELEASE);

3     if (!pbRet)    return NULL;

这里写入到目标进程空间,到这里还剩下最后一个问题,那就是输入表的处理,因为没有像LoadProcessLibrary这样的说法,所以我们要想办法写入另外一段处理输入表的代码,当然这里你可以写好一段汇编代码然后同样WriteProcessMemory到目标进程空间,我采用的是另外一种方法,因为考虑到x64直接拷贝汇编的麻烦事情,所以我直接又申请了一段空间,并把本进程整个PE Image一起拷贝了过去

 

复制代码
复制代码
1     char* plpAlign = (char*)VirtualAlloc(0,pimNH1->OptionalHeader.SizeOfImage,MEM_COMMIT,PAGE_READWRITE);
2     if (plpAlign == NULL)
3         return NULL;

5     memcpy(plpAlign,plpImage,pimNH1->OptionalHeader.SizeOfImage);
6     ULONG_PTR    pdelta    = (ULONG_PTR)pNewBase - (ULONG_PTR)plpImage;
7     if (pdelta != 0)//需要重新定位

8         LoadVReloc((ULONG_PTR)plpAlign,TRUE,pdelta);

同样的,首先我们需要重新定位本进程的代码,然后再写入目标进程空间,

 

复制代码
复制代码
1     pbRet = WriteProcessMemory(pProcInfo.hProcess,(char*)plpSelf,plpAlign,pimNH2->OptionalHeader.SizeOfImage,&pszWtd);
2     VirtualFree((void*)plpAlign,0,MEM_RELEASE);

3     if (!pbRet)    return NULL;

这里把代码写入到进程空间,我们本进程代码中留了一个函数,

1 void    InitClientProcess()

2 {
3     
4 }

这是我们将要把目标进程的入口转向的地方,再这里面对需要运行的PE做些必要的处理,正如上面所说的输入表的处理,当然这个时候就牵涉到了一些数据的交换,比如需要运行的PE装入的基址,这些数据只有父进程知道,我的方法是使用全局变量然后同样WriteProcessMemory过去

 

复制代码
复制代码
 1     pbRet = WriteProcessMemory(pProcInfo.hProcess,(char*)((ULONG_PTR)&gxVirtualCfg - pSelfImage + plpSelf),(char*)&gxVirtualCfg,sizeof(gxVirtualCfg),&pszWtd);
 2     if (!pbRet)    return NULL;
 3 
 4     ULONG_PTR plpEntryFunc = (ULONG_PTR)InitClientProcess - pSelfImage + plpSelf;
 5     char pCurBuf[16];
 6 
 7 #ifdef _M_IX86
 8     *pCurBuf = 0x68;
 9     *(DWORD*)(pCurBuf+1) = plpEntryFunc;
10     pCurBuf[5] = 0xC3;
11     pbRet = WriteProcessMemory(pProcInfo.hProcess,(char*)gxVirtualCfg.m_OrgEntry,pCurBuf,6,&pszWtd);
12     if (!pbRet)    return NULL;
13 #else
14 
15     pCurBuf[0] = 0xff;        // jmp [rip+addr]
16     pCurBuf[1] = 0x25;
17     *((DWORD *)(pCurBuf+2)) = 0; // addr = 0
18     *((ULONG_PTR *)(pCurBuf+6)) = plpEntryFunc;
19     pbRet = WriteProcessMemory(pProcInfo.hProcess,(char*)gxVirtualCfg.m_OrgEntry,pCurBuf,14,&pszWtd);
20     if (!pbRet)    return NULL;
21 

22 #endif

这里把子进程需要的信息就写过去,并且把入口代码修改为转向我们的InitClientProcess函数

在InitClientProcess函数里面,我们需要做的工作有

 

复制代码
复制代码
 1     PENTRY_LoadLibraryA ppLoadLibraryA = (PENTRY_LoadLibraryA)gxVirtualCfg.m_pLoadLibraryA;
 2     PENTRY_GetProcAddress ppGetProcAddress = (PENTRY_GetProcAddress)gxVirtualCfg.m_pGetProcAddress;
 3     ULONG_PTR phBase = gxVirtualCfg.m_CoreBase;
 4     PIMAGE_NT_HEADERS pimNH = EnterImageNtHeader((char*)phBase);
 5     ULONG_PTR plpImport = pimNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + phBase;
 6 
 7 处理输入表
 8 
 9 然后转入真正的我们需要运行的PE的入口
10      PENTRY_WinMain    ppWinMain = (PENTRY_WinMain)gxVirtualCfg.m_Entry;
11      ppWinMain((HINSTANCE)phBase,NULL,"",SW_SHOW);
复制代码
复制代码

 

复制代码
复制代码

 

到这里我们的PE就运行起来了,整个过程都不会牵涉到磁盘操作,当然要想做附加数据这些就要更麻烦点.

有空我上传一个例子,虽然作用不大,但是挺好玩

复制代码
复制代码

 

复制代码
复制代码

 

复制代码
复制代码
复制代码
复制代码

 

复制代码
复制代码
 
相关文章
|
2月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
179 6
|
25天前
|
NoSQL 测试技术
内存程序崩溃
【10月更文挑战第13天】
127 62
|
13天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
1月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
103 21
|
2月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
1月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
2月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
3月前
|
Java 容器
【Azure Function App】Java Function在运行中遇见内存不足的错误
【Azure Function App】Java Function在运行中遇见内存不足的错误
|
3月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
3月前
|
存储 缓存 NoSQL
Redis内存管理揭秘:掌握淘汰策略,让你的数据库在高并发下也能游刃有余,守护业务稳定运行!
【8月更文挑战第22天】Redis的内存淘汰策略管理内存使用,防止溢出。主要包括:noeviction(拒绝新写入)、LRU/LFU(淘汰最少使用/最不常用数据)、RANDOM(随机淘汰)及TTL(淘汰接近过期数据)。策略选择需依据应用场景、数据特性和性能需求。可通过Redis命令行工具或配置文件进行设置。
78 2