写壳1

摘要:
FileBase=(DWORD)malloc(sizeof(BYTE)*文件大小);#包括“header.h”//合并。数据和。rdata进入。text area#pragmacomment(linker;//查找导出的名称表、序列号表和地址表autoNameTable=(DWORD*)(ExportTable->AddressOfNames+模块);

写壳的步骤

  1. 编写加壳器,加载被加壳程序和壳dll程序

  2. 将 dll 程序中 .text 拷贝到被加壳程序

  3. 将被加壳程序的 eip 指向stub 代码

    1. 需要让 stub 提供一个入口点

1. 加载 PE 文件
5. 加载 Stub 文件
8. 加载共享数据,写入了原始OE篇
2. 添加了一个区段
4. 实现了一个 stub 提供了 start
7. 提供了一个共享数据结构
9. 重新跳转到 oep
3. 将区段的内容进行了拷贝
6. 重新设置了 oep
10. 因为没有进行壳代码重定位,所以跳转失败
11. 对壳代码进行了重定位
12. 加密代码段,保存了 key rva size
13. 壳代码根据提供的内容进行解密
14. 解密时分页没有访问属性
15. 提供了获取函数的功能,添加了一个  VirtualProtect 函数
16. 设置属性,修复了加密的后的代码,最终跳转oep

首先,加载要加壳的PE文件

// 加载一个 PE 文件
void CMyPack::LoadFile(LPCSTR FileName)
{
// 打开一个文件,理论上应该对文件进行判断,是否是一个 PE 文件,位数是多少
HANDLE FileHandle = CreateFileA(FileName, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

// 获取到文件的大小,并申请相应的堆空间
FileSize = GetFileSize(FileHandle, NULL);
FileBase = (DWORD)malloc(sizeof(BYTE) * FileSize);

// 读取 PE 文件的内容
DWORD BytesRead = 0;
ReadFile(FileHandle, (LPVOID)FileBase, FileSize, &BytesRead, NULL);

// 关闭句柄,防止句柄泄露
CloseHandle(FileHandle);
}

提供一个 DLL 作为 Stub 区域使用

  1. 【切记,否则程序跑不起来】将 DLL 设置为 Release 版本进行编译、小、内联了一些函数

  2. CC++ -> 代码生成 -> GS安全检查 -> 禁用 (取消一些库函数的调用)

  3. CC++ -> 所有选项 -> 运行库 -> MT (取消一些库函数的调用)

  4. 合并区段,并设置属性为可读可写可执行(0xE00000E0)

main.cpp

#include <windows.h>
#include "header.h"

// 将 .data 和 .rdata 合并到 .text 区域
#pragma comment(linker, "/merge:.data=.text")
#pragma comment(linker, "/merge:.rdata=.text")
// 并且设置 .text 区属性为壳读壳写壳执行
#pragma comment(linker, "/section:.text,RWE")

DWORD MyGetProcAddress(DWORD Module, LPCSTR FunName)
{
// 获取 Dos 头和 Nt 头
auto DosHeader = (PIMAGE_DOS_HEADER)Module;
auto NtHeader = (PIMAGE_NT_HEADERS)(Module + DosHeader->e_lfanew);
// 获取导出表结构
DWORD ExportRva = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(Module + ExportRva);
// 找到导出名称表、序号表、地址表
auto NameTable = (DWORD*)(ExportTable->AddressOfNames + Module);
auto FuncTable = (DWORD*)(ExportTable->AddressOfFunctions + Module);
    // [易错]序号表示WORD*不是DWORD*
auto OrdinalTable = (WORD*)(ExportTable->AddressOfNameOrdinals + Module);
// 遍历找名字
for (DWORD i = 0; i < ExportTable->NumberOfNames; ++i)
{
// 获取名字
char* Name = (char*)(NameTable[i] + Module);
if (!strcmp(Name, FunName))
return FuncTable[OrdinalTable[i]] + Module;
}
return -1;
}

// 跳转到 OEP
_declspec(naked) void JmpOep()
{
__asm
{
mov eax, StubData.OldOEP; 原始 OEP 的RVA
mov ebx, dword ptr FS : [0x30]; PEB
mov ebx, dword ptr[ebx + 0x08]; 当前加载基址
add eax, ebx; 计算出 OEP
jmp eax; 跳转 OEP
}
}

_declspec(naked) DWORD GetKernelBase()
{
__asm
{
// 按照加载顺序
mov eax, dword ptr fs : [0x30]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax + 0x18]
ret
}
}


// 解密数据
void XorData()
{
DWORD ImageBase = 0, OldProtect = 0;
__asm
{
mov ebx, dword ptr FS : [0x30]; PEB
mov ebx, dword ptr[ebx + 0x08]; 当前加载基址
mov ImageBase, ebx
}

// 获取到被加密区段的起始位置
BYTE* Data = (BYTE*)(ImageBase + StubData.XorRVA);

pVirtualProtect(Data, StubData.XorSize, PAGE_READWRITE, &OldProtect);

// 循环进行解密
for (DWORD i = 0; i < StubData.XorSize; ++i)
Data[i] ^= StubData.XorKey;

pVirtualProtect(Data, StubData.XorSize, OldProtect, &OldProtect);
}

// 获取所有想要使用的函数
void GetApis()
{
pVirtualProtect = (fnVirtualProtect)MyGetProcAddress(GetKernelBase(), "VirtualProtect");
}


// 导出且是一个裸函数,不会自动生成代码
_declspec(dllexport) _declspec(naked) void start()
{
GetApis();
XorData();
JmpOep();
}

header.h

#include <windows.h>

// 提供一个结构体,这里的数据应该由加壳器填充
struct ShareData
{
DWORD OldOEP = 0;
BYTE XorKey = 0;
DWORD XorRVA = 0;
DWORD XorSize = 0;
};

extern "C"
{
// 导出这个结构体的一个变量,提供给加壳器
_declspec(dllexport) ShareData StubData;

// 导出且是一个裸函数,不会自动生成代码
_declspec(dllexport) void start();
}

typedef BOOL(WINAPI* fnVirtualProtect)(
_In_  LPVOID lpAddress,
_In_  SIZE_T dwSize,
_In_  DWORD flNewProtect,
_Out_ PDWORD lpflOldProtect
);
fnVirtualProtect pVirtualProtect;

加载Stub

// 加载一个 PE 文件
void CMyPack::LoadStub(LPCSTR FileName)
{
// 以不调用 dllmain 的方式加载 dll 到当前的内存,会展开
StubBase = (DWORD)LoadLibraryExA(FileName, NULL, DONT_RESOLVE_DLL_REFERENCES);

// 获取 start 函数在 .text 的段内偏移
DWORD Start = (DWORD)GetProcAddress((HMODULE)StubBase, "start");
StartOffset = Start - StubBase - GetSection(StubBase, ".text")->VirtualAddress;

// 加载stub数据对象,向其中填充内容
StubData = (ShareData*)GetProcAddress((HMODULE)StubBase, "StubData");
}

为了方便调试,进行如下设置

  1. 设置 .exe(加壳器) 和 .dll(stub) 文件的输出路径为同一路径

    项目属性 -> 常规 -> 输出目录

    1566023396097

  2. 设置工作目录为刚才的输出路径

    项目属性 -> 调试 -> 工作目录

从stub拷贝到exe

// 拷贝区段,从 stub 拷贝 SrcName 区段到 exe 中并命名为 DestName
void CMyPack::CopySection(LPCSTR DestName, LPCSTR SrcName)
{
// 从 dll 中获取到需要拷贝的区段对应的区段头结构
auto SrcSection = GetSection(StubBase, ".text");

// 获取到最后一个区段的位置,下标从0开始,区段数量-1
auto LastSection = &IMAGE_FIRST_SECTION(NTHeader(FileBase))
[FileHeader(FileBase)->NumberOfSections - 1];

// 将文件头中的区段数量+1
FileHeader(FileBase)->NumberOfSections += 1;

// 获取新添加的区段头表的首地址
auto DestSection = LastSection + 1;

// 将源区段的属性直接拷贝到新的区段
memcpy(DestSection, SrcSection, sizeof(IMAGE_SECTION_HEADER));

// 设置区段的名称,可以是用 memcpy strcpy
memcpy(DestSection->Name, DestName, 7);

// 设置区段起始位置的 RVA = 上一个区段 RVA + 对齐的内存大小
DestSection->VirtualAddress = LastSection->VirtualAddress +
Aligment(LastSection->Misc.VirtualSize, OptHeader(FileBase)->SectionAlignment);

// 设置区段起始位置的 FOA = 上一个区段 FOA + 对齐的文件大小
DestSection->PointerToRawData = LastSection->PointerToRawData +
Aligment(LastSection->SizeOfRawData, OptHeader(FileBase)->FileAlignment);

// 重新的分配空间,大小是 最后一个区段的FOA + 最后一个区段的文件大小
FileSize = DestSection->PointerToRawData + DestSection->SizeOfRawData;
// 使用 realloc 的时候,它新的地址会作为返回值进行返回
FileBase = (DWORD)realloc((LPVOID)FileBase, FileSize);

// 重新设置映像大小 = 最后一个区段的 RVA + 最后一个区段的内存大小
OptHeader(FileBase)->SizeOfImage = DestSection->VirtualAddress + DestSection->Misc.VirtualSize;
}

添加区段的步骤

  1. 将文件头中的区段数量进行 +1

  2. 在区段头表中增加一项,直接覆盖后面的40字节数据

    1. 要设置区段表的名称,理论不能超过 7 字节

    2. 设置文件大小和内存大小,可以直接设置成一样

    3. 设置 RVA : RVA = 上一个区段的RVA + 上一个区段内存对齐后的内存大小

    4. 设置 FOA:FOA = 上一个区段的FOA + 上一个区段文件对齐后的文件大小

    5. 修改区段的属性,通常需要设置成 0xE00000E0,读写执行

  3. 填充 PE 文件,使其大小 = 最后一个区段的 FOA + 最后一个区段的文件大小

  4. 重新设置 SizeOfImage, 使其 = 最后一个区段的 RVA + 最后一个区段的内存大小

重新设置OEP

// 设置入口点为新区段中的 start 的位置(RVA)
void  CMyPack::SetOep()
{
// 保存原始的 OEP
StubData->OldOEP = OptHeader(FileBase)->AddressOfEntryPoint;

// 因为获取到的 start 函数的地址是在dll中的地址,现在这个区段被拷贝到了
// 被加壳程序中,所以需要重新计算 start 的 RVA 并设置为 OEP
OptHeader(FileBase)->AddressOfEntryPoint =
GetSection(FileBase, ".mypack")->VirtualAddress + StartOffset;
}

修复重定位

STUB 区域代码的重定位问题

起初代码是放在了 dll 中,需要重定位的内容,是以 dll 实际加载基址设置的

这里的重定位实际上是为了让 stub 区的代码跑起来,是 必然 要执行的操作

// 修复壳代码的重定位
void CMyPack::FixRealoc()
{
ULONG Size = 0, OldProtect = 0;

// 获取到程序的重定位表
auto RealocTable = (PIMAGE_BASE_RELOCATION)
ImageDirectoryEntryToData((PVOID)StubBase, TRUE, 5, &Size);

// 遍历重定位表,重定位表以一个空表结尾
while (RealocTable->SizeOfBlock)
{
// 获取重定位项,并依次修复重定位项
auto Item = (TypeOffset*)(RealocTable + 1);

// 获取到重定位项的个数
DWORD Count = (RealocTable->SizeOfBlock - 8) / 2;

// 遍历重定位项并修复
for (DWORD i = 0; i < Count; ++i)
{
// 修改整个分页的属性,使它能够被写入
VirtualProtect((LPVOID)(RealocTable->VirtualAddress + StubBase), 0x1000, PAGE_READWRITE, &OldProtect);

// 只需要关注 type == 3 的类型
if (Item[i].Type == 3)
{
// 需要重定位的数据所在的地址 Base + RVA + Offset
DWORD* ItemAddr = (DWORD*)(StubBase + RealocTable->VirtualAddress + Item[i].Offset);

// 计算出不会改变(会改变的有基址和段的RVA)的偏移
DWORD Offset = *ItemAddr - StubBase - GetSection(StubBase, ".text")->VirtualAddress;

// 计算出新的 VA
*ItemAddr = Offset + GetSection(FileBase, ".mypack")->VirtualAddress + OptHeader(FileBase)->ImageBase;
}

// 还原属性
VirtualProtect((LPVOID)(RealocTable->VirtualAddress + StubBase), 0x1000, OldProtect, &OldProtect);
}

// 找到下一个重定位块
RealocTable = (PIMAGE_BASE_RELOCATION)((DWORD)RealocTable + RealocTable->SizeOfBlock);
}

// 手动的关闭动态基址,否则程序无法运行
OptHeader(FileBase)->DllCharacteristics = 0;
}

加密

// 异或指定区段的数据
void CMyPack::XorFileSection(LPCSTR SectionName)
{
// 获取指定的区段
auto Section = GetSection(FileBase, SectionName);
// 保存区段的 RVA 以及 Size
StubData->XorRVA = Section->VirtualAddress;
StubData->XorSize = Section->SizeOfRawData;

// 计算出区段的位置
BYTE* Buffer = (BYTE*)(FileBase + Section->PointerToRawData);

// 随机生成一个加密的 key
srand((unsigned int)time(0));
StubData->XorKey = rand() % 0x100;

// 根据文件大小对它进行异或
for (DWORD i = 0; i < Section->SizeOfRawData; ++i)
Buffer[i] ^= StubData->XorKey;
}

拷贝数据

// 拷贝区段的内容,需要指定区段的名称
void CMyPack::CopySectionData(LPCSTR DestName, LPCSTR SrcName)
{
// 获取到源区段的区段头表结构并计算出偏移(DLL加载到了内存,使用的是内存偏移)
auto SrcSection = GetSection(StubBase, SrcName);
BYTE* SrcData = (BYTE*)(SrcSection->VirtualAddress + StubBase);

// 获取到目标区段的区段头表结构并计算出偏移(PE文件没有对齐,使用的是文件偏移)
auto DestSection = GetSection(FileBase, DestName);
BYTE* DestData = (BYTE*)(DestSection->PointerToRawData + FileBase);

// 以文件大小进行拷贝
memcpy(DestData, SrcData, SrcSection->SizeOfRawData);
}

保存文件

// 保存修改后的 PE 文件
void CMyPack::SaveFile(LPCSTR NewName)
{
// 打开一个文件,理论上应该对文件进行判断,是否是一个 PE 文件,位数是多少
HANDLE FileHandle = CreateFileA(NewName, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

// 写入 PE 文件的内容
DWORD BytesWrite = 0;
WriteFile(FileHandle, (LPVOID)FileBase, FileSize, &BytesWrite, NULL);

// 关闭句柄,防止句柄泄露
CloseHandle(FileHandle);
}

免责声明:文章转载自《写壳1》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SQLServer触发器asp.net 自定义控件下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Ubuntu 安装和卸载sublime

安装过程 输入以下命令安装:sudo add-apt-repository ppa:webupd8team/sublime-text-3sudo apt-get updatesudo apt-get install sublime-text-installer卸载过程完全卸载命令//sudo dpkg -r sublime-text(如果这条命令不能卸载则...

如何清除夜神模拟器的Pin密码

https://www.cnblogs.com/Zev_Fung/p/14192545.html 说明 上次临时设置了Pin密码,一段时间后,现在忘记了,必须要输入正确的Pin密码才能进入系统,郁闷 环境 夜神7.0.0.5 Android7 步骤 1.运行打开夜神模拟器 2.找到夜神安装目录:D:Program FilesNoxin 3.路径输入cmd,...

SourceTree使用教程详解(连接远程仓库,克隆,拉取,提交,推送,新建/切换/合并分支,冲突解决)

前言:   俗话说的好工欲善其事必先利其器,Git分布式版本控制系统是我们日常开发中不可或缺的。目前市面上比较流行的Git可视化管理工具有SourceTree、Github Desktop、TortoiseGit,综合网上的一些文章分析和自己的日常开发实践心得个人比较推荐开发者使用SourceTree,因为SourceTree同时支持Windows和Mac...

SV中的Interface和Program

Interface:SV中新定义的接口方式,用来简化接口连接,使用时注意在module或program之外定义interface,然后通过'include来添加进工程。         interface  arb_if(input bit clk);              //clk信号,一般单独拿出来                        ...

CentOS7安装python3.8.5

一、安装依赖包 yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make libffi-devel yum install gcc gcc-c++ openssl-devel libffi-dev...

网路编程(网站源码查看器)

一、网页源码查看器 1.shift+F2调用httpwatch(为网页抓包数据分析工具【需下载】): 2.HttpURLConnection:用于发送和接收数据 3.必须有联网权限:android.permission.INTERNET 4.异常处理方式: //若出现此种异常, W/System.err(5504): android.os.NetworkO...