前言
功能:一个源程序加壳与解壳的过程。
原理:
加壳过程,即在壳文件上加一个新节(加密后的源程序)。
解壳过程,即在加密壳的进程空间中,为解密后的源程序分配空间,再卸载壳程序将控制权交给源程序。
备注:
这里假设读者已经对PE文件的结构有所了解,所以对PE文件的操作不再赘述。
壳和源程序其实都是独立的程序,加载到内存执行都会有自己的4GB空间。
欢迎大佬指正,新人共勉!
符号说明:
src.exe 没有加壳的源程序
shell.exe 壳程序
shell.exe.encrypt.exe 加密壳程序
OEP 程序入口点
实现环境
win10,VC++6.0
加壳过程
加壳过程很简单,处理好新增的节基本就没啥问题了。步骤如下:
1、获取 shell.exe 的路径
2、获取 src.exe 的路径
3、将 src.exe 程序读取到内存中,加密
4、在 shell.exe 程序中新增一个节,并在新节中加入 加密后的src.exe 。
加壳过程如图:
解壳过程
1、获取 shell.exe.encrypt.exe 的路径
2、在shell.exe.encrypt.exe 中获取 src.exe 的数据
(0)定位到shell.exe.encrypt.exe的最后一个节
(1)取出最后一个节的数据,解密
3、拉伸解密后的PE文件,存储到缓冲区
PE文件有两种状态:磁盘中的文件映像、内存中拉伸的状态
为什么会有两种状态呢? 首先得知道PE文件的执行过程:
当一个PE文件被执行时,PE装载器首先根据PE header的偏移量,跳转到PE header的位置。
当PE装载器跳转到PE header后,检查PE header是否有效。如果该PE header有效,就跳转到PE header的尾部。
接着是PE header尾部的节表,PE 装载器开始读取节表中的信息,并采用文件映射方法将这些节段映射到内存。
PE文件映射入内存后,PE装载器将继续处理PE文件中其他的目录表。
如上可知,PE文件执行的过程中,有一个从文件映射到内存的过程,所以就有PE的两种状态。
我们要做的是模仿PE加载器,让PE文件执行起来,所以就需要在内存中做拉伸。
4、以挂起方式运行shell.exe.encrypt.exe 进程
挂起主线程,是为了创建一个子线程来修改主线程的运行环境(CONTEXT),修改的运行环境为 shell.exe的运行环境。
(0)以挂起的方式创建shell.exe.encrypt.exe 进程,并得到主线程的 CONTEXT
(1)卸载外壳程序的文件镜像
如果shell.exe和 shell.exe.encrypt.exe 进程有相同基址,并且shell.exe的内存镜像小于进程shell.exe.encrypt.exe 的内存镜像,
那么只需要调用WriteProcessMemory来把可执行程序shell.exe的镜像写到进程shell.exe.encrypt.exe的内存空间中,并从 基址 开始执行即可。
否则,需要调用ZwUnmapViewOfSection来取消shell.exe.encrypt.exe的映像映射,然后通过 VirtualAllocEx 在shell.exe.encrypt.exe 进程中为
可执行程序 shell.exe 分配足够的空间。调用VirtualAllocEx的时候,必须提供可执行程序 shell.exe 的 基址,用来分配从指定位置开始的的空间。
然后把可执行程序 shell.exe 的镜像复制到进程 shell.exe.encrypt.exe 的内存空间,并从分配的空间的起始地址开始执行。
(2)在指定的位置申请指定大小的内存
指定的位置:src.exe 的 基址
指定大小:src.exe 的 SizeOfImage
内存分配:VirtualAllocEx
CONTEXT对象的ebx寄存器指向进程的PEB,eax寄存器的值为进程的OEP,ebx+8 为进程的基址。
(3)如果申请内存失败,查看 src.exe 是否有重定位表的数据,如果有,就在任意位置申请指定大小的内存,然后修复重定位表。 指定大小:src.exe 的 SizeOfImage
(4)如果申请内存失败,并且没有重定位表的数据,返回失败
如果指定位置的内存申请失败,说明该位置已经被占用,如果这时候要让程序在别的位置也能够正常运行,就依赖于重定位表,如果没有重定位表,那么程序就无法正常执行了。
(5)如果内存申请成功,复制PE数据到shell的进程空间中
(6)修正运行环境的ImageBase和OEP
5、恢复执行主进程,解壳完成。
解壳过程如图:
备注:这里的难点在于理解 挂起创建进程 的过程,在挂起的过程中 涉及到的两个重点函数ZwUnmapViewOfSection VirtualAllocEx 也比较难理解,需要了解操作系统的内存机制。
(CreateProcessA)创建一个新的进程,新进程在调用进程的安全上下文中运行。
BOOL CreateProcessA(
LPCSTR lpApplicationName, //执行的模块名,可为null
LPSTR lpCommandLine, //要执行的命令行,可为null
LPSECURITY_ATTRIBUTES lpProcessAttributes, //该指针决定子进程是否可以继承新进程对象的返回句柄,如果为空,则不能继承句柄。
LPSECURITY_ATTRIBUTES lpThreadAttributes,//该指针决定子线程是否可以继承新进程对象的返回句柄,如果为空,则不能继承句柄。
BOOL bInheritHandles, //处理每个可继承句柄是否被新进程继承的选项,true表示可继承,否表示不可继承
DWORD dwCreationFlags, //控制优先级类和流程创建的标志。
LPVOID lpEnvironment, //指向新进程的环境块的指针。
LPCSTR lpCurrentDirectory, //进程当前目录的完整路径。
LPSTARTUPINFOA lpStartupInfo, //启动信息
LPPROCESS_INFORMATION lpProcessInformation //进程信息
);
(ZwUnmapViewOfSection)卸载内存中的文件映射
NTSYSAPI NTSTATUS ZwUnmapViewOfSection(
HANDLE ProcessHandle, //要取消映射的进程句柄
PVOID BaseAddress //从虚拟地址空间此处开始取消映射
);
LPVOID VirtualAllocEx(
HANDLE hProcess, //在此进程空间内分配内存
LPVOID lpAddress, //为要分配的页区域指定所需的起始地址的指针
SIZE_T dwSize, //分配的内存大小
DWORD flAllocationType, //分配的内存页的大小
DWORD flProtect //要分配的页区域的内存保护
);
几点思考
1、以前只会用工具,死记步骤,现在除了用工具,还可以写简单的工具(hai shi yong gong ju)(~~o(>_<)o ~~)。
2、更重要的收获是,从哪些大佬的写的工具当中汲取一点智慧。
3、以前老师逼着学都学不下去的操作系统,现在居然饶有兴趣地看了Over And Over Again (but, yi ran shi xiao cai)。
4、程序加壳是为了保护程序,那么这种"保护"是不是也可以成为恶意软件的”护身符“,绕过杀软的扫描?后续会带着这些思考继续学习逆向工程。
主要代码实现
代码中都有详细的备注,由于篇幅有限,没有贴上全部的代码。
1、AES加壳
#include "stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include "AES.h"
#include "PEOperate.h"
/* AES 加密*/
BOOL Packer()
{
TCHAR* shellPath = "shell.exe";
TCHAR* srcPath = "src.exe";
DWORD SrcFileSize=0;
LPVOID pSrcFileBuffer = LoadPEFile(srcPath,SrcFileSize);
CHAR* pOld = (CHAR*)pSrcFileBuffer;
//循环加密
//DWORD fileSize = 0;
LPVOID pSrcFileBufferEncode = malloc(SrcFileSize);
memset(pSrcFileBufferEncode,SrcFileSize,0);
CHAR* pNew = (CHAR*)pSrcFileBufferEncode;
//void TestAddSecToFile(LPSTR lpszFile)
//数据加密
unsigned char key[] =
{
0x2b, 0x7e, 0x15, 0x16,
0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88,
0x09, 0xcf, 0x4f, 0x3c
};
AES aes(key);
aes.Cipher(pSrcFileBuffer, SrcFileSize);
pSrcFileBufferEncode = pSrcFileBuffer;
/*
将加密代码加入到文件内部
shellPath 源壳文件
pSrcFileBufferEncode 源程序加密后的文件
SrcFileSize 加密文件的长度,由于这里使用的加密算法(AES)只是异或和移位循环,所以源文件和加密文件的长度都是一样的
*/
AddSecToFile(shellPath,pSrcFileBufferEncode,SrcFileSize);
return TRUE;
}
/* 异或加密
BOOL Packer()
{
TCHAR* shellPath = "shell.exe";
TCHAR* srcPath = "src.exe";
DWORD SrcFileSize=0;
LPVOID pSrcFileBuffer = LoadPEFile(srcPath,SrcFileSize);
CHAR* pOld = (CHAR*)pSrcFileBuffer;
//循环加密
//DWORD fileSize = 0;
LPVOID pSrcFileBufferEncode = malloc(SrcFileSize);
memset(pSrcFileBufferEncode,SrcFileSize,0);
CHAR* pNew = (CHAR*)pSrcFileBufferEncode;
//void TestAddSecToFile(LPSTR lpszFile)
//数据加密
for(int i=0;i<(int)SrcFileSize;i++)
{
pNew[i] = pOld[i]^KEY;
}
//将加密代码加入到文件内部
//shellPath 源壳文件
//pSrcFileBufferEncode 源程序加密后的文件
//SrcFileSize 加密文件的长度,由于这里使用的加密算法(AES)只是异或和移位循环,所以源文件和加密文件的长度都是一样的
AddSecToFile(shellPath,pSrcFileBufferEncode,SrcFileSize);
return TRUE;
}
*/
#ifndef DEBUG //测试
void main()
{
Packer();
}
#endif
2、AES解壳
#include "stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include "AES.h"
#include "PEOperate.h"
// 重定向PE用到的地址
void DoRelocation(LPVOID pFileBuffer, void *OldBase, void *NewBase)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS peH = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
unsigned long Delta = (unsigned long)NewBase - peH->OptionalHeader.ImageBase;
PImageBaseRelocation p = (PImageBaseRelocation)((unsigned long)OldBase
+ peH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while(p->VirtualAddress + p->SizeOfBlock)
网络安全学习资源分享:
给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
因篇幅有限,仅展示部分资料,朋友们如果有需要全套《网络安全入门+进阶学习资源包》,需要点击下方链接即可前往获取
读者福利 | CSDN大礼包:《网络安全入门&进阶学习资源包》免费分享(安全链接,放心点击)
同时每个成长路线对应的板块都有配套的视频提供:
大厂面试题
视频配套资料&国内外网安书籍、文档
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料
所有资料共282G,朋友们如果有需要全套《网络安全入门+进阶学习资源包》,可以扫描下方二维码或链接免费领取~
读者福利 | CSDN大礼包:《网络安全入门&进阶学习资源包》免费分享(安全链接,放心点击)
特别声明:
此教程为纯技术分享!本教程的目的决不是为那些怀有不良动机的人提供及技术支持!也不承担因为技术被滥用所产生的连带责任!本教程的目的在于最大限度地唤醒大家对网络安全的重视,并采取相应的安全措施,从而减少由网络安全而带来的经济损失。