加密与解密之加壳程序

发布时间 2023-04-04 17:57:45作者: z5onk0

描述

  • 自己实现一个简单的加壳程序,能够对选取程序的代码段进行加密,并添加外壳部分,在运行时还原
  • 本文分为三个部分:外壳程序,加壳程序,和用户交互程序

外壳程序

  • 写到Stub.dll中,方便同加壳程序共享数据,获取原程序的PE文件信息
  • 将数据段合并到代码段,方便加壳程序读取并添加到原程序中
  • 由于只迁移代码段,不包含IAT表,所以外壳程序中无法直接调用api函数,需要自己实现一套api函数地址的获取机制
  • 具体来说,通过peb获取kernel32.dll的加载基址,对比其导出表中的字符串,查找到getprocaddress函数的地址,通过该函数获取loadlibrary函数地址,从而实现对其它dll的加载和函数调用
  • 实现一个简单的针对代码段逐字节的异或解密
  • 代码如下

#include "stdafx.h"
#include "Stub.h"


#pragma comment(linker, "/merge:.data=.text") 
#pragma comment(linker, "/merge:.rdata=.text")
#pragma comment(linker, "/section:.text,RWE")
extern "C"__declspec(dllexport) GLOBAL_PARAM g_stcParam = { (DWORD)(Start) };

typedef void(*FUN)();
FUN g_oep;

ULONGLONG GetKernel32Addr()
{
	ULONGLONG dwKernel32Addr = 0;

	// 获取TEB的地址
	_TEB* pTeb = NtCurrentTeb();
	// 获取PEB的地址
	PULONGLONG pPeb = (PULONGLONG)*(PULONGLONG)((ULONGLONG)pTeb + 0x60); 
	// 获取PEB_LDR_DATA结构的地址
	PULONGLONG pLdr = (PULONGLONG)*(PULONGLONG)((ULONGLONG)pPeb + 0x18); 
	//模块初始化链表的头指针InInitializationOrderModuleList
	PULONGLONG pInLoadOrderModuleList = (PULONGLONG)((ULONGLONG)pLdr + 0x10);
	// 获取链表中第一个模块信息,exe模块
	PULONGLONG pModuleExe = (PULONGLONG)*pInLoadOrderModuleList; 
	// 获取链表中第二个模块信息,ntdll模块
	PULONGLONG pModuleNtdll = (PULONGLONG)*pModuleExe;    
	// 获取链表中第三个模块信息,Kernel32模块
	PULONGLONG pModuleKernel32 = (PULONGLONG)*pModuleNtdll;   
	// 获取kernel32基址
	dwKernel32Addr = pModuleKernel32[6];                     
	return dwKernel32Addr;
}

ULONGLONG MyGetProcAddress()
{
	ULONGLONG dwBase = GetKernel32Addr();
	// 1. 获取DOS头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwBase;
	// 2. 获取NT头
	PIMAGE_NT_HEADERS64  pNt = (PIMAGE_NT_HEADERS64)(dwBase + pDos->e_lfanew);
	// 3. 获取数据目录表
	PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory;
	pExportDir = &(pExportDir[IMAGE_DIRECTORY_ENTRY_EXPORT]);
	DWORD dwOffset = pExportDir->VirtualAddress;
	// 4. 获取导出表信息结构
	PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwBase + dwOffset);
	DWORD dwFunCount = pExport->NumberOfFunctions;
	DWORD dwFunNameCount = pExport->NumberOfNames;
	DWORD dwModOffset = pExport->Name;

	// Get Export Address Table
	PDWORD pEAT = (PDWORD)(dwBase + pExport->AddressOfFunctions);
	// Get Export Name Table
	PDWORD pENT = (PDWORD)(dwBase + pExport->AddressOfNames);
	// Get Export Index Table
	PWORD  pEIT = (PWORD)(dwBase + pExport->AddressOfNameOrdinals);

	for (DWORD dwOrdinal = 0; dwOrdinal < dwFunCount; dwOrdinal++)
	{
		if (!pEAT[dwOrdinal]) // Export Address offset
			continue;

		// 1. 获取序号
		DWORD dwID = pExport->Base + dwOrdinal;
		// 2. 获取导出函数地址
		ULONGLONG dwFunAddrOffset = pEAT[dwOrdinal];

		for (DWORD dwIndex = 0; dwIndex < dwFunNameCount; dwIndex++)
		{
			// 在序号表中查找函数的序号
			if (pEIT[dwIndex] == dwOrdinal)
			{
				// 根据序号索引到函数名称表中的名字
				ULONGLONG dwNameOffset = pENT[dwIndex];
				char* pFunName = (char*)((ULONGLONG)dwBase + dwNameOffset);
				if (!strcmp(pFunName, "GetProcAddress"))
				{// 根据函数名称返回函数地址
					return dwBase + dwFunAddrOffset;
				}
			}
		}
	}
	return 0;
}

void XorCode()
{
	// 获取代码基址
	PBYTE pBase = (PBYTE)((ULONGLONG)g_stcParam.dwImageBase + g_stcParam.lpStartVA);
    // 异或操作
	for (DWORD i = 0; i < g_stcParam.dwCodeSize; i++)
	{
		pBase[i] ^= g_stcParam.byXor;
	}
}

void  Start()
{
	// 获取kernel32基址
	fnGetProcAddress pfnGetProcAddress = (fnGetProcAddress)MyGetProcAddress();
	ULONGLONG dwBase = GetKernel32Addr();
	// 获取API地址
	fnLoadLibraryA pfnLoadLibraryA = (fnLoadLibraryA)pfnGetProcAddress((HMODULE)dwBase, "LoadLibraryA");
	fnGetModuleHandleA pfnGetModuleHandleA = (fnGetModuleHandleA)pfnGetProcAddress((HMODULE)dwBase, "GetModuleHandleA");
	fnVirtualProtect pfnVirtualProtect = (fnVirtualProtect)pfnGetProcAddress((HMODULE)dwBase, "VirtualProtect");
	HMODULE hUser32 = (HMODULE)pfnGetModuleHandleA("user32.dll");
	fnMessageBox pfnMessageBoxA = (fnMessageBox)pfnGetProcAddress(hUser32, "MessageBoxA");
	HMODULE hKernel32 = (HMODULE)pfnGetModuleHandleA("kernel32.dll");
	fnExitProcess pfnExitProcess = (fnExitProcess)pfnGetProcAddress(hKernel32, "ExitProcess");

    // 弹出信息框
	int nRet = pfnMessageBoxA(NULL, "欢迎使用免费64位加壳程序,是否运行主程序?", "Hello PEDIY", MB_YESNO);
	if (nRet == IDYES)
	{
	    // 修改代码段属性
	    ULONGLONG dwCodeBase = g_stcParam.dwImageBase + (DWORD)g_stcParam.lpStartVA;
	    DWORD dwOldProtect = 0;
	    pfnVirtualProtect((LPBYTE)dwCodeBase, g_stcParam.dwCodeSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	    XorCode(); // 解密代码
	    pfnVirtualProtect((LPBYTE)dwCodeBase, g_stcParam.dwCodeSize, dwOldProtect, &dwOldProtect);
		g_oep = (FUN)(g_stcParam.dwImageBase + g_stcParam.dwOEP);
		g_oep(); // 跳回原始OEP
	}
	// 退出程序
	pfnExitProcess(0);
}

加壳程序

  • 分为两部分,外层调用和里层实现,里层实现封装到c++类中
  • 加壳代码也编译为Pack.dll,供用户交互程序调用

外层调用

  • 读取目标程序,记录PE文件信息,实现针对代码段的逐字节异或加密
  • 加载外壳程序Stub.dll,将PE文件信息传递给壳程序
  • 修复壳程序中的重定位表,将OEP设置为壳程序的入口
  • 将壳程序的区段添加到原PE文件中,修正PE的区块表,生成新程序

#include "stdafx.h"
#include "Pack.h"

// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 Pack.h
CPack::CPack()
{
	return;
}

#include <Psapi.h>
#pragma comment(lib,"psapi.lib")
#include "../Stub/Stub.h"
#ifdef DEBUG
#pragma comment(lib,"../x64/Debug/Stub.lib")
#endif // DEBUG
#ifdef NDEBUG
#pragma comment(lib,"../x64/release/Stub.lib")
#endif // NDEBUG

#include "PE.h"
BOOL Pack(CString strPath, BYTE byXor)
{
	// 1.获取目标文件PE信息
	CPE objPE;
	objPE.InitPE(strPath);
	BOOL bRet = FALSE;
	DWORD dwVirtualAddr = objPE.XorCode(byXor);

	//2. 获取Stub文件PE信息,将必要的信息设置到Stub中
	HMODULE hMod = LoadLibrary(L"Stub.dll");
	PGLOBAL_PARAM pstcParam = (PGLOBAL_PARAM)GetProcAddress(hMod, "g_stcParam");
	pstcParam->dwImageBase = objPE.m_dwImageBase;
	pstcParam->dwCodeSize = objPE.m_dwCodeSize;
	pstcParam->dwOEP = objPE.m_dwOEP;
	pstcParam->byXor = byXor;
	pstcParam->lpStartVA = (PBYTE)dwVirtualAddr;

	// 3. 添加Stub代码段到被加壳程序中
	// 3.1 读取Stub代码段
	MODULEINFO modinfo = { 0 };
	GetModuleInformation(GetCurrentProcess(), hMod, &modinfo, sizeof(MODULEINFO));
	PBYTE  lpMod = new BYTE[modinfo.SizeOfImage];
	memcpy_s(lpMod, modinfo.SizeOfImage, hMod, modinfo.SizeOfImage);
	PBYTE pCodeSection = NULL;
	DWORD dwCodeBaseRVA = 0;
	DWORD dwSize = objPE.GetSectionData(lpMod, 0, pCodeSection, dwCodeBaseRVA);

	// 3.2 修复Stub段中的代码
	objPE.FixReloc(lpMod, pCodeSection, objPE.m_dwNewSectionRVA);

	// 3.3 修改被加壳程序的OEP,指向stub
	DWORD dwStubOEPRVA = pstcParam->dwStart - (DWORD)hMod;
	DWORD dwNewOEP = dwStubOEPRVA - dwCodeBaseRVA;
	//StubOEP = dwStubOEPRVA - 原RVA + 新区段的RVA;
	objPE.SetNewOEP(dwNewOEP);
	objPE.ClearRandBase();
	objPE.ClearBundleImport();//兼容有绑定输入表项的程序

	// 3.4 读取重定位的信息,修复代码
	if (objPE.AddSection(pCodeSection, dwSize, "15pack"))
	{
		bRet = TRUE;
	}
	// 释放资源
	delete lpMod;
	lpMod = NULL;
	return bRet;
}

里层实现

#include "stdafx.h"
#include "PE.h"


CPE::CPE() :m_dwFileSize(0), m_pFileBase(NULL),
m_dwFileAlign(0), m_dwMemAlign(0)
{
}


CPE::~CPE()
{
}

DWORD CPE::RVA2OffSet(DWORD dwRVA, PIMAGE_NT_HEADERS64  pNt)
{
	DWORD dwOffset = 0;
	// 1. 获取第一个区段结构体
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
	// 2. 获取区段数量
	DWORD dwSectionCount = pNt->FileHeader.NumberOfSections;
	// 3. 遍历区段信息表
	for (DWORD i = 0; i < dwSectionCount; i++)
	{
		// 4. 匹配RVA所在的区段
		if (dwRVA >= pSection[i].VirtualAddress &&
			dwRVA < (pSection[i].VirtualAddress + pSection[i].Misc.VirtualSize)
			)
		{   // 计算对应的文件偏移
			dwOffset = dwRVA - pSection[i].VirtualAddress +
				pSection[i].PointerToRawData;
			return dwOffset;
		}
	}
	return dwOffset;
}


BOOL CPE::InitPE(CString strPath)
{
	if (m_objFile.m_hFile == INVALID_HANDLE_VALUE && m_pFileBase) {
		m_objFile.Close(); delete m_pFileBase; m_pFileBase = NULL;
	}

	m_objFile.Open(strPath, CFile::modeRead);
	m_dwFileSize = (DWORD)m_objFile.GetLength();
	m_pFileBase = new BYTE[m_dwFileSize];
	if (m_objFile.Read(m_pFileBase, (DWORD)m_dwFileSize))
	{
		// 1. 获取DOS头
		PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_pFileBase;
		// 2. 获取NT头
		m_pNT = (PIMAGE_NT_HEADERS64)((ULONGLONG)m_pFileBase + pDos->e_lfanew);
		// 3. 获取文件对齐、内存对齐等信息
		m_dwFileAlign = m_pNT->OptionalHeader.FileAlignment;
		m_dwMemAlign = m_pNT->OptionalHeader.SectionAlignment;
		m_dwImageBase = m_pNT->OptionalHeader.ImageBase;
		m_dwOEP = m_pNT->OptionalHeader.AddressOfEntryPoint;
		m_dwCodeBase = m_pNT->OptionalHeader.BaseOfCode;
		m_dwCodeSize = m_pNT->OptionalHeader.SizeOfCode;


		// 3. 获取最后一个区段后的地址
		PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(m_pNT);
		m_pLastSection = &pSection[m_pNT->FileHeader.NumberOfSections];

		// 4. 获取新加区段的起始RVA,  内存4K对齐
		DWORD dwVirtualSize = m_pLastSection[-1].Misc.VirtualSize;
		if (dwVirtualSize%m_dwMemAlign)
			dwVirtualSize = (dwVirtualSize / m_dwMemAlign + 1) * m_dwMemAlign;
		else
			dwVirtualSize = (dwVirtualSize / m_dwMemAlign)* m_dwMemAlign;
		m_dwNewSectionRVA = m_pLastSection[-1].VirtualAddress + dwVirtualSize;

		return TRUE;
	}
	return FALSE;
}

ULONGLONG CPE::AddSection(LPBYTE pBuffer, DWORD dwSize, PCHAR pszSectionName)
{
	// 1 修改文件头中的区段数量
	m_pNT->FileHeader.NumberOfSections++;

	// 2 增加区段表项
	memset(m_pLastSection, 0, sizeof(IMAGE_SECTION_HEADER));
	strcpy_s((char*)m_pLastSection->Name, IMAGE_SIZEOF_SHORT_NAME, pszSectionName);

	DWORD dwVirtualSize = 0; // 区段虚拟大小
	DWORD dwSizeOfRawData = 0; // 区段文件大小
	DWORD dwSizeOfImage = m_pNT->OptionalHeader.SizeOfImage;
	{
		if (dwSizeOfImage%m_dwMemAlign)
			dwSizeOfImage = (dwSizeOfImage / m_dwMemAlign + 1) * m_dwMemAlign;
		else
			dwSizeOfImage = (dwSizeOfImage / m_dwMemAlign) * m_dwMemAlign;

		if (dwSize%m_dwMemAlign)
			dwVirtualSize = (dwSize / m_dwMemAlign + 1) * m_dwMemAlign;
		else
			dwVirtualSize = (dwSize / m_dwMemAlign) * m_dwMemAlign;

		if (dwSize%m_dwFileAlign)
			dwSizeOfRawData = (dwSize / m_dwFileAlign + 1) * m_dwFileAlign;
		else
			dwSizeOfRawData = (dwSize / m_dwFileAlign) * m_dwFileAlign;
	}

	m_pLastSection->VirtualAddress = dwSizeOfImage;
	m_pLastSection->PointerToRawData = m_dwFileSize;
	m_pLastSection->SizeOfRawData = dwSizeOfRawData;
	m_pLastSection->Misc.VirtualSize = dwVirtualSize;
	m_pLastSection->Characteristics = 0XE0000040;

	// 3 增加文件大小,创建文件,添加代码
	m_pNT->OptionalHeader.SizeOfImage = dwSizeOfImage + dwVirtualSize;
	m_pNT->OptionalHeader.AddressOfEntryPoint = m_dwNewOEP + m_pLastSection->VirtualAddress;

	// 3.1 生成输出文件路径
	CString strPath = m_objFile.GetFilePath();
	TCHAR szOutPath[MAX_PATH] = { 0 };
	LPWSTR strSuffix = PathFindExtension(strPath);                     // 获取文件的后缀名
	wcsncpy_s(szOutPath, MAX_PATH, strPath, wcslen(strPath)); // 备份目标文件路径到szOutPath
	PathRemoveExtension(szOutPath);                                         // 将szOutPath中保存路径的后缀名去掉
	wcscat_s(szOutPath, MAX_PATH, L"_15Pack");                       // 在路径最后附加“_15Pack”
	wcscat_s(szOutPath, MAX_PATH, strSuffix);                           // 在路径最后附加刚刚保存的后缀名

	// 3.2 创建文件
	CFile objFile(szOutPath, CFile::modeCreate | CFile::modeReadWrite);
	objFile.Write(m_pFileBase, m_dwFileSize);
	objFile.SeekToEnd();
	objFile.Write(pBuffer, dwSize);

	return m_pLastSection->VirtualAddress;
}

// 在内存中重定位Stub
void CPE::FixReloc(PBYTE lpImage, PBYTE lpCode, DWORD dwCodeRVA)
{
	// 1. 获取DOS头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
	// 2. 获取NT头
	PIMAGE_NT_HEADERS64  pNt = (PIMAGE_NT_HEADERS64)((ULONGLONG)lpImage + pDos->e_lfanew);
	// 3. 获取数据目录表
	PIMAGE_DATA_DIRECTORY pRelocDir = pNt->OptionalHeader.DataDirectory;
	pRelocDir = &(pRelocDir[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
	// 4. 获取重定位目录
	PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)lpImage + pRelocDir->VirtualAddress);
	typedef struct {
		WORD Offset : 12;          // (1) 大小为12Bit的重定位偏移
		WORD Type : 4;             // (2) 大小为4Bit的重定位信息类型值
	}TypeOffset, *PTypeOffset;	   // 这个结构体是A1Pass总结的

	// 循环获取每一个MAGE_BASE_RELOCATION结构的重定位信息
	while (pReloc->VirtualAddress)
	{
		PTypeOffset pTypeOffset = (PTypeOffset)(pReloc + 1);
		ULONGLONG dwSize = sizeof(IMAGE_BASE_RELOCATION);
		ULONGLONG dwCount = (pReloc->SizeOfBlock - dwSize) / 2;
		for (ULONGLONG i = 0; i < dwCount; i++)
		{
			if (*(PULONGLONG)(&pTypeOffset[i]) == NULL)
				break;
			ULONGLONG dwRVA = pReloc->VirtualAddress + pTypeOffset[i].Offset;
			PULONGLONG pRelocAddr = (PULONGLONG)((ULONGLONG)lpImage + dwRVA);
			// 修复重定位信息   公式:需要修复的地址-原映像基址-原区段基址+现区段基址+现映像基址
			ULONGLONG dwRelocCode = *pRelocAddr - pNt->OptionalHeader.ImageBase - pNt->OptionalHeader.BaseOfCode +
				                                           dwCodeRVA + m_dwImageBase;
			*pRelocAddr = dwRelocCode;
		}
		pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)pReloc + pReloc->SizeOfBlock);
	}
}

void CPE::SetNewOEP(DWORD dwOEP)
{
	m_dwNewOEP = dwOEP;
}

void CPE::ClearBundleImport()
{
	// 1. 获取DOS头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_pFileBase;
	// 2. 获取NT头
	PIMAGE_NT_HEADERS64  pNt = (PIMAGE_NT_HEADERS64)((ULONGLONG)m_pFileBase + pDos->e_lfanew);
	// 3. 获取数据目录表
	PIMAGE_DATA_DIRECTORY pDir = pNt->OptionalHeader.DataDirectory;
	// 4. 清空绑定输入表项
	ZeroMemory(&(pDir[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]), sizeof(IMAGE_DATA_DIRECTORY));
}

void CPE::ClearRandBase()
{
	m_pNT->OptionalHeader.DllCharacteristics = 0;
}

DWORD CPE::GetSectionData(PBYTE lpImage, DWORD dwSectionIndex, PBYTE& lpBuffer, DWORD& dwCodeBaseRVA)
{
	// 1. 获取DOS头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
	// 2. 获取NT头
	PIMAGE_NT_HEADERS64  pNt = (PIMAGE_NT_HEADERS64)((ULONGLONG)lpImage + pDos->e_lfanew);
	// 3. 获取第一个区段结构体
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
	DWORD dwSize = pSection[0].SizeOfRawData;

	ULONGLONG dwCodeAddr = (ULONGLONG)lpImage + pSection[0].VirtualAddress;
	lpBuffer = (PBYTE)dwCodeAddr;
	dwCodeBaseRVA = pSection[0].VirtualAddress;
	return dwSize;
}


DWORD CPE::XorCode(BYTE byXOR)
{
	DWORD dwVirtualAddr = m_dwCodeBase;
	DWORD dwOffset = RVA2OffSet(m_dwCodeBase, m_pNT);
	if (!dwOffset)
	{// 兼容 debug版本,第一个区段没有大小
		PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(m_pNT);
		dwOffset = RVA2OffSet(pSection[1].VirtualAddress, m_pNT);
		dwVirtualAddr = pSection[1].VirtualAddress;
	}
	PBYTE pBase = (PBYTE)((ULONGLONG)m_pFileBase + dwOffset);

	for (DWORD i = 0; i < m_dwCodeSize; i++)
	{
		pBase[i] ^= byXOR;
	}

	return dwVirtualAddr;
}

用户交互程序

  • 从命令行中读取目标程序路径和用于异或加密的十六进制字节
  • 用loadlibrary加载Pack.dll,调用pack函数,实现对目标程序的加密

#include <windows.h>
#include <iostream>
#include <atlstr.h>

typedef bool (*Pack)(CString, BYTE);

int main(int argc, char* argv[]) {
    // Get the command line arguments
    LPWSTR* szArgList;
    int argCount;
    szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount);

    // Check if there are enough arguments
    if (argCount < 3) {
        MessageBox(NULL, TEXT("Not enough arguments provided"), TEXT("Error"), MB_OK);
        return 0;
    }

    // Get the file path and number from the command line arguments
    CString filePath(szArgList[1]);
    BYTE hexNumber = (BYTE)_wtoi(szArgList[2]);

    // Print the file path and number
    std::cout << "加密文件路径: " << filePath.GetString() << std::endl;
    std::cout << "加密密钥: " << std::hex << (int)hexNumber << std::endl;
    std::cout << "开始加密" << std::endl;

    // Load the Pack.dll library
    HINSTANCE hinstLib = LoadLibrary(TEXT("Pack.dll"));
    if (hinstLib == NULL) {
        MessageBox(NULL, TEXT("Unable to load library"), TEXT("Error"), MB_OK);
        return 0;
    }

    // Get the Pach function from the library
    Pack pack = (Pack)GetProcAddress(hinstLib, "Pack");
    if (pack == NULL) {
        MessageBox(NULL, TEXT("Unable to find pack function"), TEXT("Error"), MB_OK);
        return 0;
    }

    // Call the Pach function with the hex number
    bool result = pack(filePath, hexNumber);
    if (result) {
        std::cout << "加密成功!" << std::endl;
    }
    else {
        std::cout << "加密失败!" << std::endl;
    }

    FreeLibrary(hinstLib);

    // Free the memory allocated for the command line arguments
    LocalFree(szArgList);

    return 0;
}

效果

  • 加密前,运行原程序Msg.exe
  • 将壳程序Stub.dll,加壳程序Pack.dll和用户交互程序Shell.exe放到Msg.exe同目录下,命令行中运行Shell.exe,生成加壳后的程序Msg_15Pack.exe
  • 运行加壳后生成的程序Msg_15Pack.exe,功能正常