前一段时间在设计项目的软件时,出于对软件保护的考虑,想把主程序和其所需的支持库隐藏起来。经过一番讨论和思考,决定另做一个程序作为外壳程序,以资源文件的形式把主程序文件和支持库加载到外壳程序里,在外壳程序运行时实现对主程序和其所需支持库的自动释放和删除。这样不仅实现了对软件的保护,而且在程序发布时只需给用户一个可执行文件(外壳程序),用户使用和管理起来也就更方便。下面以Windows自带程序write.exe(主程序)为例说明具体的实现方法。
一、资源文件的建立和使用
打开纯文本编辑软件(如notepad),按照格式
资源名 资源类型 资源的文件名
输入以下内容:
ding WAV ding.wav
1stboot BITMAP 1stboot.bmp
write EXE c:\write.exe
保存为资源文件mysrc.rc。这样就生成了一个资源文件。需要注意的是,如果路径中含有中文,那么要将文件名放在双引号里面。
启动C++ Builder,打开新工程,将资源文件mysrc.rc添加进项目中。你也可以通过BIN目录下的
brcc32.exe将RC文件编绎为RES文件,然后用 #program resource "*.res" 语句将其联编进项目中。我们采用更简单的前一种方法。编译运行后外壳程序里就含有了主程序和其所需的支持库。
二、资源的释放及调用
因为我们用的资源是可执行文件和支持库,所以不能只在内存中操作,要释放成文件。其他诸如声音、位图等资源只要在内存中操作既可,这里就不再赘述了。把资源释放成文件的方法也非常简单。下面给出源代码:
void __fastcall TForm1::ReleaseExefile()
{
//TODO: Add your source code here
HRSRC hMyRes;
HGLOBAL hgpt;
LPVOID lpBuff;
DWORD rcSize=20480; //20480为write.exe的大小
HANDLE hFile;
LPDWORD dwByte;
dwByte=&rcSize;
hMyRes=FindResource(HInstance,"write","exe");
//HInstance指示资源所在地为执行文件
if(hMyRes==NULL)
ShowMessage(SysErrorMessage(GetLastError()));
hgpt=LoadResource(NULL,hMyRes);
if(hgpt==NULL)
ShowMessage(SysErrorMessage(GetLastError()));
lpBuff=LockResource(hgpt);
//写入文件
try
{
hFile=CreateFile("e:\\mywrite.exe",GENERIC_WRITE,0,NULL, CREATE _ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
WriteFile(hFile,lpBuff,rcSize,dwByte,NULL);
if(*dwByte!=20480)
ShowMessage("无法写入文件!");
}
__finally
{
CloseHandle(hFile);
}
}
这样就在硬盘的E盘根目录下生成了一个mywrite.exe文件,在外壳程序中调用它既可。
Windows为在程序中调用其它程序提供了许多API函数如已经过时的LoadModule()、为16位Windows程序兼容性设立的WinExec()和基于32位Windows程序的CreateProcess()、ShellExecute()/ShellExecuteEx()。
ShellExcute()只有6个参数,使用起来要简单得多。函数的声明如下:
HISTANCE ShellExecute (
HWND hwnd, //父窗体句柄
LPCTSTR lpOperation, //指向文件处理方式字符串的指针
LPCTSTR lpFile, //指向文件名字符串的指针
LPCTSTR lpParameters, //指向可执行文件运行参数字符串的指针
LPCTSTR lpDirectory, //指向默认路径字符串的指针
INT nShowCmd //标志程序被显示的方式
);例如:
if(FileExists("e:\\mywrite.exe"))
{
ShellExecute(NULL,"Open","e:\\mywrite.exe",NULL,NULL,SW_SHOWNORMAL);
}
三、自动删除文件及消息的使用
在用户点击关闭后,我们要删除硬盘上的主程序文件。但由于管理权限不够,不能让主程序删除自身,所以还要借助外壳程序来实现。这样的话就用到了Windows的消息机制。
消息(Message)指的就是Windows操作系统发给应用程序的一个通告,它告诉应用程序某个特定的事件发生了。从数据结构的角度来说消息是一个结构体,结构如下:
typedef struct
{
HWND hwnd; //检索消息的窗口句柄
UINT message; //代表一个消息的消息质
WPARAM wParam; //消息附加信息的字参数
LPARAM lParam; //消息附加信息的长字参数
DWORD time; //消息入队时间
POINT pt; //消息发送时鼠标的位置point.x,point.y
}tagMSG
Windows操作系统中的消息从其发生到被处理完成一般有5个步骤:
(1)系统发生了一个事件
(2)Windows系统把事件翻译为对应的消息并把它放到消息队列中。
(3)应用程序从消息队列中获取消息,然后把它封装在TMSG结构中。
(4)应用程序通过消息循环分派给对应的窗口函数。
(5)窗口函数最终处理这个消息。
应用程序也可以像Windows系统一样在窗口或者是组件之间发消息。C++ Builder为此提供了几种途径:使用函数TControl::Perform()或者API函数SendMessage()和PostMessage()向特定窗体发送消息,或者是使用函数TWinControl::Broadcast()和API函数
BroadcastSystemMessage()广播消息。
我们采用了使用API函数PostMessage()向特定窗体发送消息 WM_CLOSE的方法。PostMessage()的原型声明如下:
Bool PostMessage(HWND hwnd,UINT Msg,WPARAM wParam,LPARAM lParam);
例如:PostMessage(Handle,WM_CLOSE,0,0); //Handle为接收消息的窗体(外壳程序)句柄。
主程序关闭后向外壳程序发送一个关闭消息,外壳程序接收到主程序发送的WM_CLOSE消息后,响应关闭事件,我们就可以在外壳程序的FormClose事件中实现文件的动态删除。
四、隐藏程序
这种保护方法的关键是不能显示出有两个应用程序在运行,所以应该将外壳程序隐藏起来。这要考虑窗体、任务栏图标和CTRL-ALT-DEL对话框等方面。
1、隐藏窗体和任务栏图标
想隐藏窗体要将窗体的Visible属性设为false,想隐藏程序的任务栏图标可以用ShowWindow函数并传给它窗口句柄 ShowWindow(Application->Handle,SW_HIDE);但通过ShowWindow函数来隐藏窗口的任务栏图标图标是不持久的。某些动作会使任务栏图标重现。可以将隐藏的应用程序窗口设为Tool Window来移走程序的任务栏图标而避免它再次出现。Tool Window永远不会有任务栏图标。但把应用程序窗口设为Tool Window有一个副作用:当用户按下Alt+Tab时它会出现在程序列表中。可以调用API函数GetWindowLong()和SetWindowLong()来使应用程序窗口成为一个Tool Window,源代码如下:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD dwExStyle=GetWindowLong(Application->Handle,GWL_EXSTYLE);
dwExStyle|=WS_EX_TOOLWINDOW;
SetWindowLong(Application->Handle,GWL_EXSTYLE,dwExStyle);
try
{
Application->Initialize();
Application->Title = "动态释放";
Application->CreateForm(__classid(TForm1), &Form1);
Application->ShowMainForm=false;
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
这样就隐藏了程序的任务栏图标,但它还会显示在CTRL-ALT-DEL对话框中。
2、不显示在CTRL-ALT-DEL对话框中
最常用的方法是调用RegisterServiceProcess API 函数将程序注册成为一个服务模式程序。 RegisterServiceProcess是一个在Kernel32.dll里相关但无正式文件的函数。这个函数的主要目的是创建一个服务模式程序。但由于windows NT下没有RegisterServiceProcess函数。所以这种方法只能在Windows95/98下实现。源代码如下:
//------------Header file------------------------------
typedef DWORD (__stdcall *pRegFunction)(DWORD, DWORD);
class TForm1 : public TForm
{
__published:
TButton *Button1;
private:
HINSTANCE hKernelLib;
pRegFunction RegisterServiceProcess;
public:
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();
};
//-----------CPP file------------------------------
#include "Unit1.h"
#define RSP_SIMPLE_SERVICE 1
#define RSP_UNREGISTER_SERVICE 0
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
hKernelLib = LoadLibrary("kernel32.dll");
if(hKernelLib)
{
RegisterServiceProcess =(pRegFunction)GetProcAddress(hKernelLib,"RegisterServiceProcess");
if(RegisterServiceProcess)
RegisterServiceProcess(GetCurrentProcessId(),RSP_SIMPLE_SERVICE);
}
}
__fastcall TForm1::~TForm1()
{
if(hKernelLib)
{
if(RegisterServiceProcess)
RegisterServiceProcess(GetCurrentProcessId(),RSP_UNREGISTER_SERVICE);
FreeLibrary(hKernelLib);
}
}
经过这样处理后我们就在系统中看不见外壳程序的身影,从而给人一种只有一个程序(主程序)的错觉。
在WinNT下只要我们的程序是以进程内核的形式运行,都不可能逃离CTRL+ALT+DEL的法眼。当然也有瞒天过海的方法,但在这里不适用,故不再赘述。
结束语
这种把主程序作为资源文件使用并在外壳程序运行过程中动态生成和删除主程序的方法只是提供了一种软件保护的思路,并不能从根本上解决问题。但如果把主程序和外壳程序用不同的加壳软件(如AsPack,Upx等)加壳,会使程序的破解变得更加困难。