windows程序防狼术入门
当初由于一些原因以及兴趣,学习了一段时间软件逆向,对于软件加密解密有了点粗略的了解。而后看到某些同学辛辛苦苦的搞出个软件,自己费心费力去加密,但搞出来后往往能被秒破,实不忍心。今天大概总结下一些基本的软件加密手段,以供参考,高手勿喷。
关于解密
软件解密主要有2个层次,一个俗称爆破,就是不分析加密算法,只修改一些与验证相关的跳转指令来使得软件正常运行,另一个就是能真正破解加密算法,进而写出注册机。破解手段通常有静态分析和动态分析两种方式,目前二者的代表工具是IDA和OllyDbg(OD)。
加密算法与代码
加密首先必须设计一套加密算法,这个可以用现成的如MD5,SHA之类的算法,也可以自己设计个稍微简单点的算法。一般情况,作为一个开发者,设计一个简单的加密算法应该问题不大的,但是算法设计必须要严密,不能出现漏网之鱼。比如一个时间限制的算法,如果只记录开始结束时间,然后用当前时间去判断,这样的算法通过修改系统时间就给绕过去了,就不够严密,需要改善;例如可再记录一个最近一次运行时间,这样就可以处理修改系统时间的漏洞了。
有了一个完善的加密算法,最直接也最容易想到的做法就是把用户输入的密码用算法转换后与保存的密钥对比,一致则验证通过,不一致则验证失败。这样的加密程序估计新手也能快速爆破了。那么在代码编写时,需要注意下面几点
首先,加密算法尽量不出现在程序中。比如你的加密算法是\(f\),用户输入密码\(x\),程序保存的秘钥为\(y\),那么只有在\(y==f(x)\)时才能验证通过。避免\(f\)的具体实现出现在程序中,可以防止破解者分析你的加密算法从而写出注册机,那么可以设计另外一组算法\(g\)和\(h\)使得\(y==f(x)\;\Leftrightarrow \; g(y)==h(f(x))\),记\(s=hf\),这样在程序里就只会出现\(g\)和\(s\)而不会出现\(f\)了。例如下面代码:
#define MAX_LEN 256
int Validation(char *py, char *px)
{
char azy[MAX_LEN] = {};
char azx[MAX_LEN] = {};
char *ptmp = NULL; //这里加密算法实质是将数字转换为小写字母
//但此处分别直接将待匹配密钥py和用户密码px转大写字符后对比
//而不是将px转小写字母后与py比较
ptmp = azy;
while(*py != '\0')
{
*ptmp++ = (*py++) & (~0x20); //这里是把所有字母转为大写
} ptmp = azx;
while(*px != '\0')
{
*ptmp++ = (*px++) + 0x10;//这里把所有数字转大写字母
} return strcmp(azy, azx); }
加密算法\(f\)是把数字映射为小写字母,但验证过程中,直接把用户输入密码映射到大写字母(即为\(s\)函数),同时将保存密码也转换到大写字母(\(g\)函数),再进行比较,这样就避免了加密算法\(f\)出现在程序中。当然这里算法很简单,也许能推导出\(f\),但随着算法复杂性增加就会非常难了。
第二,尽量别用if…else判断验证结果。用了if…else结构判断,必然会有一个jmp指令,别人只要定位到该指令修改jmp条件,就彻底被爆破了。可以将验证结果作为索引去达到目的,比如上面的加密算法,若用户输入12345打印验证成功,否则失败。如下代码:
int main()
{
char aKey[] = "abcde";
char aPassword[MAX_LEN] = {};
printf("input password:\n");
gets(aPassword); int nRes = Validation(aKey, aPassword); //这里直接使用if...else判断
if(nRes != )
{
printf("validation failed!\n");
return ;
}
printf("validation success!\n"); //这里讲验证结果作为索引
char aaPrintInfo[][MAX_LEN] = {"validation success!", "validation failed!"};
printf("%s\n",aaPrintInfo[nRes]); return ;
}
如果不得不用if…else结构,可将if语句与验证函数分散开,对于静态分析代码的难度会有所增加。
第三,就是一些关键提示信息不要放在堆内而放在栈内。OD有个查找字符串功能可以把程序堆内的字符串列出来,新手最喜欢用这个来定位跳转点爆破了。
void main()
{
char a[]= "this is in stack";
char *b = "this is in heap"; printf("%s\n%s\n", a, b, "also in heap");
}
这段代码有3个字符串(a,b和“also in heap”),编译后通过OD加载并查找字符串,如下图:
可以看到存放在堆中的字符串被搜索出来了,从而可以快速定位到对应代码位置:
上图中选中行的edx存放的就是字符串a,但却不会被搜索出来。
加壳
不得不说,虽然上面做了那么些工作,对于破解来说也仅仅增加了一点点的难度,一般的新手努力点也不难搞定。那么通过软件加壳的方式可以把那些不会脱壳的新手们挡在门外。
对于普通的PE文件,将其按二进制打开可以直接解析其内部的数据或者指令,壳就相当于一个加锁的箱子,让人不能直接看到PE文件的真正内容而只能看到加密后的内容,在程序运行时在将其解密到内存从而运行。也就是说,对于加壳的程序,静态分析是不可行的,必须要脱壳后才能分析,即便是动态调试也可能会很所难度。
软件壳有压缩壳和加密壳,一般压缩壳主要是减小PE文件的大小,而加密壳则是为了防止PE文件被反编译、调试和修改等。常用的一些壳如UPX,ASP等等都有专门的加壳与脱壳工具,目前据说最难搞定的还是vmprotect,在看雪网站有多种加壳工具,大家可自行参考。
加壳后PE文件的大小以及程序入口点都会发生变化,可以使用PEID来查看相关加壳信息。下图是程序加壳前后的信息,可以看到PE文件的很多信息都不一样了:
反调试
如果通过加壳保护了程序,固然不错。但目前大多数的壳都有了脱壳机,有很大风险被脱掉,那我们还得要加强防范,这就是程序反调试。反调试的基本思想是检测程序当前是否在被调试,若是则做一些保护措施,如退出、崩溃等手段。
运行一个程序,其进程内有很多地方会标识当前进程是否在被调试,通过检测这些变量就可以简单地判断出来从而进行处理。Windows系统还提供了一个IsDebuggerPresent的API来供调用,不过该函数名声太大,很多调试器都会绕过它。这个博客列得比较详细,值得参考。
另外,若在程序某个位置打了软件断点,此处会被调试器修改为0xCC,当执行到该处时才会修改回去,因此还有一类方法就是程序校验,如CRC校验或MD5校验。基本做法是将当前PE文件做为输入,生成一个字符串,通过判断字符串是否改变可判断程序是否被调试或修改。
还有一种比较粗暴的做法。目前windows程序调试器用得较多时OD和SoftIce,可以通过枚举系统当前进程来判断这两款调试器是否在运行,若运行则认为程序在被调试。也许别人在调试别的程序呢,不管那么多了,为了安全起见,不得不“宁肯错杀三千也绝不漏网一人”。判断系统是否有OD在运行的代码如下:
#include "tlhelp32.h"
bool IsODRuning()
{
HANDLE hwnd;
PROCESSENTRY32 tp32; //结构体
tp32.dwSize = sizeof(PROCESSENTRY32);
TCHAR *str= _TEXT("OLLYDBG.EXE");
bool bFindOD=false;
hwnd=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
if(INVALID_HANDLE_VALUE!=hwnd)
{
Process32First(hwnd,&tp32);
do{ if(==wcsicmp(str,tp32.szExeFile))
{
bFindOD=true;
break;
}
}while(Process32Next(hwnd,&tp32));
}
CloseHandle(hwnd); return bFindOD;
}
最后,还有利用异常处理的方法。比如下面代码,通过人为故意产生一个中断异常,然后在异常处理中去验证,这样在调试的时候中断异常就是一个断点,从而程序不会进入异常处理。代码如下:
long g_label = ;
LONG Handle(EXCEPTION_POINTERS *pExceptionInfo )
{
if(EXCEPTION_BREAKPOINT == pExceptionInfo->ExceptionRecord->ExceptionCode)
{
//validation if(/*success*/)
{
pExceptionInfo->ContextRecord->Eip = g_label; return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_EXECUTE_HANDLER;
} void main()
{ //===exception validation begin
LPTOP_LEVEL_EXCEPTION_FILTER lpOld;
lpOld = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)Handle); __asm
{
push label_ok;
pop g_label;
int ;
} label_ok:
SetUnhandledExceptionFilter(lpOld);
//===exception validation end
//do your things......
}
当然也可以采用其他异常(如除0异常),但用异常的一个缺点就是自己写代码调试的时候也很不方便。
以上就是我大概了解的反调试技术,不过加解密是具有强烈对抗性的,现在一些调试器都增加了反反调试手段,让程序的反调试失效。
驱动保护与硬件加密狗
程序做好上面的保护,基本上已经具有一定的自我保护能力了,一般个人写的软件已经足够。如果你写的是商业软件,需要高度防范破解,那可以采用驱动保护或硬件加密狗。具体采用何种根据软件来定。
如果软件是一些专业性较强的,可以采用硬件加密狗来保护;如果软件是像网络游戏那样面向广泛大众群体的,采用加密狗就不现实了,一般都采用驱动保护,企鹅的游戏基本都有TenProtect的驱动保护,盛大的GPK保护等都是比较典型的例子。
加密狗我没仔细研究过,就不好多说了。上面那些反调试的手段都运行在ring3级别,而驱动则运行ring0级别,驱动保护的做法主要是hook系统底层的一些API,通过检验调用者来区分外部调试修改还是程序自己的操作。比如打开进程的操作,所有调试器都需要调用,通过驱动层hook该函数来防止调试器打开或附加到程序进程。
结语
自从接触了这些东西,才知道“涉密不上网,上网不涉密”的真正意义。要知道这一行高手很多,即便用尽各种手段,也不可能保证软件绝对安全,只要软件运行就会留下痕迹,就有被破解的可能。
现在已一年多没搞这些了,以后估计也没时间去搞,当初学习的时候虽然很累,但却感觉很充实很有兴趣,甚至还想换那方向的工作,谨以此作为对那段时间学习的总结。
虽然写了这么些加密的东西,我个人还是更崇尚开源,如果不是那么必要,还是希望大家能多把源码与人分享,共同进步。
windows程序防狼术入门的更多相关文章
- Windows程序----初识Windows程序
先来看一些励志名言来激励一下自己吧! 励志名言:每一发奋发奋的背后,必有加倍的赏赐 1.有无目标是成功者与平庸者的根本差别. 2.成功不是将来才有的,而是从决定去做的那一刻起,持续累积而成. 3.当 ...
- 第三章—Windows程序
这一章我都不知道该如何写了,呵呵~~ 毕竟,Win32是一个非常深奥的系统,目前还容不得我这种 小辈在这儿说三道四,不过,我既然是要写给那些入门阶段的朋友们看的,又不是写给那些搞程序设计老鸟看的,所以 ...
- 第一个Windows程序
今天,我们的任务就是和大家一起开发第一个Windows程序,这个程序的功能非常简单,就是弹出一个对话框,但是简单的程序可以帮助大家建立信心. 例1 第一个Windows程序 /* ********** ...
- Directx11学习笔记【一】 最简单的windows程序HelloWin
声明:本系列教程代码有部分来自dx11龙书及dx11游戏编程入门两本书,后面不再说明 首先,在vs2013中创建一个空的解决方案Dx11Demo,以后的工程都会放在这个解决方案下面.然后创建一个win ...
- Spark+ECLIPSE+JAVA+MAVEN windows开发环境搭建及入门实例【附详细代码】
http://blog.csdn.net/xiefu5hh/article/details/51707529 Spark+ECLIPSE+JAVA+MAVEN windows开发环境搭建及入门实例[附 ...
- 窗体应用程序防腾讯QQ源码
窗体应用程序防腾讯QQ源码 using System; using System.Collections.Generic; using System.ComponentModel; using Sys ...
- 无责任Windows Azure SDK .NET开发入门(二):使用Azure AD 进行身份验证
<編者按>本篇为系列文章,带领读者轻松进入Windows Azure SDK .NET开发平台.本文为第二篇,将教导读者使用Azure AD进行身分验证.也推荐读者阅读无责任Windows ...
- 初识Windows程序
首先,我们创建第一个Windows程序,一共分为4个步骤: 1.打开Visual Studio开发工具 2.选择"文件"→"新建"→"项目" ...
- Windows程序内部运行机制 转自http://www.cnblogs.com/zhili/p/WinMain.html
一.引言 要想熟练掌握Windows应用程序的开发,首先需要理解Windows平台下程序运行的内部机制,然而在.NET平台下,创建一个Windows桌面程序,只需要简单地选择Windows窗体应用程序 ...
随机推荐
- mysql插入数据与删除重复记录的几个例子(收藏)
mysql插入数据与删除重复记录的几个例子 12-26shell脚本实现mysql数据的批量插入 12-26mysql循环语句插入数据的例子 12-26mysql批量插入数据(insert into ...
- cocos2d-x之利用富文本控件遍历xml
1. #ifndef SuperRichText_hpp #define SuperRichText_hpp #include <stdio.h> #include "cocos ...
- 设计模式C#实现(四)——迭代器模式
迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示. UML类图: 煎饼屋和餐厅合并了!但是有个小问题,虽然两家都同意实现相同的菜单项MenuItem,但是煎饼屋想使用A ...
- windows内核编程之常用数据结构
1.返回状态 绝大部分的内核api返回值都是一个返回状态,也就是一个错误代码.这个类型为NTSTATUS.我们自己写的函数也大部分这样做. NTSTATUS MyFunction() { NTSTAT ...
- selenium如何解决IE自动填充表单问题
有时候用selenium会碰到自动填充表单的问题,如输入用户名后,密码自动填充,此时再填充密码会导致登录失败,解决办法:每个输入框都调用clear()方法
- 【温故而知新-Javascript】对象
1 创建对象 Javascript 支持对象的概率.有多种方法可以用来创建对象. <!DOCTYPE html> <html lang="en"> < ...
- Mecanim的Avater
角色共用同一套动作原理 先说说为什么不同的角色可以共用同一套动作:因为导入之后,我们需要为它们每一个模型都创建一个Avater,而Avater里存储了骨骼的蒙皮信息(创建Avater时把三维软件里的蒙 ...
- 并发用户数与TPS之间的关系
1. 背景 在做性能测试的时候,很多人都用并发用户数来衡量系统的性能,觉得系统能支撑的并发用户数越多,系统的性能就越好:对TPS不是非常理解,也根本不知道它们之间的关系,因此非常有必要进行解释. 2 ...
- java11-6 String类的其它功能
String类的其他功能: 替换功能: String replace(char old,char new) String replace(String old,String new) 去除字符串两空格 ...
- HASHKILL
6ac66ed89ef9654cf25eb88c21f4ecd0是flag的MD5码,(格式为ctf{XXX_XXXXXXXXXXX_XXXXX})由一个0-1000的数字,下划线,纽约的一个区,下划 ...