世上最完整的软件安全解释以及实例.docx
《世上最完整的软件安全解释以及实例.docx》由会员分享,可在线阅读,更多相关《世上最完整的软件安全解释以及实例.docx(81页珍藏版)》请在冰豆网上搜索。
世上最完整的软件安全解释以及实例
世上最完整的软件安全解释以及实例
第2章软件安全
本章介绍如何对应用软件进行保护:
●简单实现软件的功能、时间、日期次数、路径限制;
●软件的防拷贝方法;
●软件的注册;
●软件的补丁、汉化;
●软件的防篡改、加密方法;
●软件的加密。
这是本章涉及的问题。
2.1软件安全概述
软件安全(SoftwareSecurity)是指在软件在受到恶意攻击的情形下依然能够继续正确运行及确保软件被在授权范围内合法使用的思想。
软件安全在于保护软件中的智力成果、知识产权不被非法使用,包括篡改及盗用等。
研究的内容主要包括防止软件盗版、软件逆向工程、授权加密以及非法篡改等。
采用的技术包括防篡改技术、授权加密技术等方法。
共享软件是以“先试后买”的方式销售的具有版权的软件,根据软件开发者的授权,可以先免费下载试用共享软件的试用版本,认为满意后再通过本站向软件开发者付费成为注册用户,享用完整的功能和服务,共享软件的保护尤其涉及到本章的内容。
2.2共享软件功能限制
共享软件常用的功能保护包括日期限制、按钮或菜单功能限制、连续运行时间限制、运行次数限制、设置水印等。
1、日期限制
通常的做法是安装程序预先将终止日期设置在某个位置,可以是注册表、磁盘受保护扇区、某个文件中。
程序每次运行时,先检测日期。
检测日期可以通过调用函数或取网络时间。
1)取本机时间
voidGetLocalTime(
LPSYSTEMTIMElpSystemTime
);
lpSystemTime是一个SYSTEMTIME类型结构,可以得到本机的时间。
2)获取网络时间
用前面的方法提取时间,但用户可以修改时间,虽然我们可以HOOK函数SetLocalTime来禁止修改时间,但还是可以通过BIOS时间。
下面的代码使用套接字向网络时间服务器发送请求,获取时间。
structNTP_Packet
{
intControl_Word;
introot_delay;
introot_dispersion;
intreference_identifier;
__int64reference_timestamp;
__int64originate_timestamp;
__int64receive_timestamp;
inttransmit_timestamp_seconds;
inttransmit_timestamp_fractions;
};
BOOLGetNetTime(SYSTEMTIME&newtime)
{
WORDwVersionRequested;
WSADATAwsaData;
//初始化版本
wVersionRequested=MAKEWORD(1,1);
if(0!
=WSAStartup(wVersionRequested,&wsaData))
{
WSACleanup();
returnFALSE;
}
if(LOBYTE(wsaData.wVersion)!
=1||HIBYTE(wsaData.wVersion)!
=1)
{
WSACleanup();
returnFALSE;
}
//这个IP是中国大陆时间同步服务器地址,可自行修改
SOCKETsoc=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
structsockaddr_inaddrSrv;
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(123);
NTP_PacketNTP_Send,NTP_Recv;
NTP_Send.Control_Word=htonl(0x0B000000);
NTP_Send.root_delay=0;
NTP_Send.root_dispersion=0;
NTP_Send.reference_identifier=0;
NTP_Send.reference_timestamp=0;
NTP_Send.originate_timestamp=0;
NTP_Send.receive_timestamp=0;
NTP_Send.transmit_timestamp_seconds=0;
NTP_Send.transmit_timestamp_fractions=0;
if(SOCKET_ERROR==sendto(soc,(constchar*)&NTP_Send,sizeof(NTP_Send),
0,(structsockaddr*)&addrSrv,sizeof(addrSrv)))
{
closesocket(soc);
returnFALSE;
}
intsockaddr_Size=sizeof(addrSrv);
if(SOCKET_ERROR==recvfrom(soc,(char*)&NTP_Recv,sizeof(NTP_Recv),
0,(structsockaddr*)&addrSrv,&sockaddr_Size))
{
closesocket(soc);
returnFALSE;
}
closesocket(soc);
WSACleanup();
floatSplitseconds;
structtm*lpLocalTime;
time_tntp_time;
//获取时间服务器的时间
lpLocalTime=localtime(&ntp_time);
if(lpLocalTime==NULL)returnFALSE;
//获取换算后时间
newtime.wYear=lpLocalTime->tm_year+1900;
newtime.wMonth=lpLocalTime->tm_mon+1;
newtime.wDayOfWeek=lpLocalTime->tm_wday;
newtime.wDay=lpLocalTime->tm_mday;
newtime.wHour=lpLocalTime->tm_hour;
newtime.wMinute=lpLocalTime->tm_min;
newtime.wSecond=lpLocalTime->tm_sec;
//设置时间精度
Splitseconds=(float)ntohl(NTP_Recv.transmit_timestamp_fractions);
Splitseconds=(float)0.000000000200*Splitseconds;
Splitseconds=(float)1000.0*Splitseconds;
newtime.wMilliseconds=(unsignedshort)Splitseconds;
returnTRUE;
}
套接字的知识可以在网路安全一章读到。
3)读本机上某个文件的最后访问时间
如果用户不上网,就不好取这个日期。
先用CreateFile打开一个文件,可以获取某个文件的建立时间、最后访问时间、最后写的时间。
由于文件时间格式的问题,需要调用FileTimeToLocalFileTime将文件时间转成本地时间。
BOOLGetFileTime(
HANDLEhFile,
LPFILETIMElpCreationTime,
LPFILETIMElpLastAccessTime,
LPFILETIMElpLastWriteTime
);
BOOLFileTimeToLocalFileTime(
constFILETIME*lpFileTime,LPFILETIMElpLocalFileTime);
比如在QQ的聊天留下的图片的文件夹下C:
\ProgramFiles\Tencent\QQ\Users\****\Image下,一般有个叫Thumbs.db的文件如果发现了有文件的建立时间,比现在本机的本机时间早,说明用户更改了本地时间。
使用BOOLCFile:
:
GetStatus(LPCTSTRlpszFileName,CFileStatus&rStatus)获取时间更方便。
可以写成如下的函数:
voidGetAFileTime(SYSTEMTIME&newtime)
{
CFileStatusr;
CStringfile="C:
\\ProgramFiles\\Tencent\\QQ\\Users\\**********\\Image\\
Thumbs.db";
CFile:
:
GetStatus(file,r);
r.m_atime.GetAsSystemTime(newtime);//最后访问时间
}
4)完整实现
找到应用程序的初始化函数InitInstance(),在AfxSocketInit()运行后添加如下代码:
SYSTEMTIMEt1,t2,t3;
:
:
GetLocalTime(&t1);//取本地时间
GetNetTime(t2);//取网络时间
GetAFileTime(t3);//取文件时间
if(t1.wYear>2012&&t1.wMonth>3&&t1.wDay>20)return0;
if(t2.wYear>2012&&t2.wMonth>3&&t2.wDay>20)return0;
if(t3.wYear>2012&&t3.wMonth>3&&t3.wDay>20)return0;
注意:
在程序初始化是要选中套接字。
完整程序见“日期限制演示程序”。
2、连续运行时间限制
连续运行时间限制是指从程序开始运行,运行到一定的时间后,可能程序退出或限制某些功能的使用。
连续运行时间限制一般通过定时器实现。
1)添加定时器消息函数
如图2-1所示。
2)建立定时器
BOOLC连续运行时间限制Dlg:
:
OnInitDialog()
{
CDialog:
:
OnInitDialog();
SetIcon(m_hIcon,TRUE);//设置大图标
SetIcon(m_hIcon,FALSE);//设置小图标
//TODO:
在此添加额外的初始化代码
SetTimer(1,60000,NULL);//定时器60000毫秒运行一次
returnTRUE;//除非将焦点设置到控件,否则返回TRUE
}
图2-1添加定时器消息函数
3)添加时间检查代码
voidC连续运行时间限制Dlg:
:
OnTimer(UINT_PTRnIDEvent)
{
//TODO:
在此添加消息处理程序代码和/或调用默认值
staticinttimeout=30;
if(timeout==0):
:
ExitProcess(0);
timeout--;
CDialog:
:
OnTimer(nIDEvent);
}
上面的程序在连续运行半小时后自动退出。
3、按钮或菜单功能限制
下面的两个API函数可以实现按钮或菜单的使能和变灰。
BOOLEnableWindow(HWNDhWnd,BOOLbEnable);
//hWnd是窗口句柄,bEnable=FALSE,按钮变灰,否则变亮
BOOLEnableMenuItem(
HMENUhMenu,
UINTuIDEnableItem,
UINTuEnable
);
hMenu是菜单句柄,uIDEnableItem是菜单子项的ID,uEnable决定是否使能。
4、运行次数限制
只允许运行若干次。
次数可以先由安装程序写在磁盘的某个位置。
比如某文件中,磁盘扇区或注册表。
下面是将次数放在注册表的例子,主要代码如下。
BOOLCTimesApp:
:
InitInstance()
{
HKEYkey;
longre=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\
Windows\\CurrentVersion",0,KEY_ALL_ACCESS,&key);//打开注册表
if(re!
=ERROR_SUCCESS){
:
:
MessageBox(0,"打开键失败","提示错误",MB_OK);
return0;}
DWORDtimes=4,type=0,len;
//查询注册表SOFTWARE\\Microsoft\\Windows\\CurrentVersion下的times
re=RegQueryValueEx(key,"times",0,&type,(LPBYTE)(×),&len);
if(re!
=ERROR_SUCCESS){//若不存在,说明是首次运行,设置次数为4
:
:
MessageBox(0,"欢迎使用,最多5次","提示",MB_OK);
:
:
RegSetValueEx(key,"times",0,REG_DWORD,(unsignedchar*)(×),
sizeof(DWORD));
}
else{
//次数减1,判断是否为0
times--;
if(times==0){
:
:
MessageBox(0,"次数已到,不能使用","提示",MB_OK);
:
:
RegCloseKey(key);//到0了,直接退出
return0;}
:
:
RegSetValueEx(key,"times",0,REG_DWORD,(unsignedchar*)(×),4);
CStringinf;
inf.Format("剩余次数%d",times);
:
:
MessageBox(0,inf,"提示",MB_OK);//提示剩余次数。
}
:
:
RegCloseKey(key);
这段代码要放在应用程序初始化的方法InitInstance()中。
运行时界面如图2-2。
图2-2次数限制
5、设置水印
比如某扫描控件,为IE所调用,控制扫描仪的扫描。
如果没有注册,在扫描10次后,将在扫描的图片中显示水印。
添加水印可以在函数OnPaint()中通过调用下面的API实现。
BOOLExtTextOut(
HDChdc,//handletoDC
intX,//x-coordinateofreferencepoint
intY,//y-coordinateofreferencepoint
UINTfuOptions,//text-outputoptions
CONSTRECT*lprc,//optionaldimensions
LPCTSTRlpString,//string
UINTcbCount,//numberofcharactersinstring
CONSTINT*lpDx//arrayofspacingvalues
);
整个过程如下:
1)设置全局变量,分别为整形num,用来表示已经打开的图片数,初始化为0,打
开的文件名为CString类型fname。
2)添加画图函数
voidC添加水印Dlg:
:
ShowPicture(char*lpstrFile,HWNDhWnd)
{
//显示图片,此文的重点之所在了,lpstrFile为图片文件名,hWnd为窗口句柄
HDChDC_Temp=:
:
GetDC(hWnd);
IPicture*pPic;
IStream*pStm;
BOOLbResult;
HANDLEhFile=NULL;
DWORDdwFileSize,dwByteRead;//打开图形文件
hFile=CreateFile(lpstrFile,GENERIC_READ,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile!
=INVALID_HANDLE_VALUE){
dwFileSize=GetFileSize(hFile,NULL);//获取文件字节数
if(dwFileSize==0xFFFFFFFF)return;
}
elsereturn;
//分配全局存储空间
HGLOBALhGlobal=GlobalAlloc(GMEM_MOVEABLE,dwFileSize);
LPVOIDpvData=NULL;
if(hGlobal==NULL)return;
if((pvData=GlobalLock(hGlobal))==NULL)//锁定分配内存块
return;
ReadFile(hFile,pvData,dwFileSize,&dwByteRead,NULL);//把文件读入内存缓冲区
GlobalUnlock(hGlobal);
CreateStreamOnHGlobal(hGlobal,TRUE,&pStm);//装入图形文件
bResult=OleLoadPicture(pStm,dwFileSize,TRUE,IID_IPicture,(LPVOID*)&pPic);
if(FAILED(bResult))return;
OLE_XSIZE_HIMETRIChmWidth;//图片的真实宽度,单位为英寸
OLE_YSIZE_HIMETRIChmHeight;//图片的真实高度,单位为英寸
pPic->get_Width(&hmWidth);
pPic->get_Height(&hmHeight);//转换hmWidth和hmHeight为pixels距离
intnWidth=MulDiv(hmWidth,GetDeviceCaps(hDC_Temp,LOGPIXELSX),2540);
intnHeight=MulDiv(hmHeight,GetDeviceCaps(hDC_Temp,LOGPIXELSY),2540);
//将图形输出到屏幕上(有点像BitBlt)
bResult=pPic->Render(hDC_Temp,0,0,nWidth/4,nHeight/4,0,hmHeight,hmWidth,-hmHeight,NULL);
pPic->Release();
CloseHandle(hFile);//关闭打开的文件
if(SUCCEEDED(bResult)){
return;}
else{
return;}
}
3)添加选取图片文件的函数,如下
voidC添加水印Dlg:
:
OnBnClickedButton1()
{
CFileDialogdlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_ALLOWMULTISELECT,NULL,this);
dlg.m_ofn.lpstrInitialDir="E:
\\docs\\photo\\fa";
dlg.m_ofn.lpstrFilter="jpgFiles(*.jpg)\0*.jpg\0bmpFiles(*.bmp)\0*.bmp\0AllFiles(*.*)\0*.*\0\0";
if(dlg.DoModal()!
=IDOK)return;
fname=dlg.GetPathName();
num++;
this->Invalidate();
}
图2-3水印演示
4)在OnPaint()中添加显示图片和水印的代码
if(fname!
="")ShowPicture((char*)fname.GetBuffer(0),this->m_hWnd);
if(num>=5){
HDChdc;
hdc=:
:
GetDC(this->m_hWnd);//获得设备句柄
:
:
SetTextColor(hdc,RGB(255,0,0));
:
:
ExtTextOut(hdc,50,140,ETO_CLIPPED,NULL,inf,strlen(inf),NULL);
}
完整的程序见“添加水印”。
程序显示如图2-3。
2.3软件注册
经常遇到有软件需要注册才能安装。
一般的是每个软件有一个注册码。
软件的注册码由另一个程序生成。
软件安装时,需要拿输入的注册码与软件中已经保存的注册码或注册码经过加密的值进行比较。
我们先模拟生成注册码,然后得到注册码的Md5值。
安装程序中保留了这个Md5值。
当用户安装时,要输入注册码,如果输入错误,则提示错误并不再安装。
1、生成注册码模拟
界面如图2-4。
图2-4生成注册码
要运用到第一章的MD5算法。
第一列控件是5个编辑框,名字分别为m_e1~m_e5,第二列的编辑框为5个编辑框中字符串的Md5值。
序列号用随机数生成。
voidC模拟生成序列号Dlg:
:
OnBnClickedButton1()
{
BYTEkey[5];
CStrings1,s2,s3,s4,s5;
srand((unsigned)time(NULL));
for(inti=0;i<5;i++)key[i]=(BYTE)rand()%16;
s1.Format("%01X%01X%01X%01X%01X",key[0],key[1],key[2],key[3],key[4]);
m_e1.SetWindowTextA(s1);//生成第1个编辑框中字符
for(inti=0;i<5;i++)key[i]=(BYTE)rand()%16;
s2.Format("%01X%01X%01X%01X%01X",key[0],key[1],key[2],key[3],key[4]);
m_e2.SetWindowTextA(s2);//生成第2个编辑框中字符
for(inti=0;i<5;i++)key[i]=(BYTE)rand()%16;
s3.Format("%01X%01X%01X%01X%01X",key[0],key[1],key[2],key[3],key[4]);
m_e3.SetWindowTextA(s3);//生成第3个编辑框中字符
for(inti=0;i<5;i++)key[i]=(BYTE)rand()%16;
s4.Format("%01X%01X%01X%01X%01X",key