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\)了。例如下面代码:

 1 #define MAX_LEN 256
 2 int Validation(char *py, char *px)
 3 {
 4     char azy[MAX_LEN] = {0};
 5     char azx[MAX_LEN] = {0};
 6     char *ptmp = NULL;
 7
 8     //这里加密算法实质是将数字转换为小写字母
 9     //但此处分别直接将待匹配密钥py和用户密码px转大写字符后对比
10     //而不是将px转小写字母后与py比较
11     ptmp = azy;
12     while(*py != ‘\0‘)
13     {
14         *ptmp++ = (*py++) & (~0x20); //这里是把所有字母转为大写
15     }
16
17     ptmp = azx;
18     while(*px != ‘\0‘)
19     {
20         *ptmp++ = (*px++) + 0x10;//这里把所有数字转大写字母
21     }
22
23     return strcmp(azy, azx);
24
25 }

  加密算法\(f\)是把数字映射为小写字母,但验证过程中,直接把用户输入密码映射到大写字母(即为\(s\)函数),同时将保存密码也转换到大写字母(\(g\)函数),再进行比较,这样就避免了加密算法\(f\)出现在程序中。当然这里算法很简单,也许能推导出\(f\),但随着算法复杂性增加就会非常难了。

  第二,尽量别用if…else判断验证结果。用了if…else结构判断,必然会有一个jmp指令,别人只要定位到该指令修改jmp条件,就彻底被爆破了。可以将验证结果作为索引去达到目的,比如上面的加密算法,若用户输入12345打印验证成功,否则失败。如下代码:

 1 int main()
 2 {
 3     char aKey[] = "abcde";
 4     char aPassword[MAX_LEN] = {0};
 5     printf("input password:\n");
 6     gets(aPassword);
 7
 8     int nRes = Validation(aKey, aPassword);
 9
10     //这里直接使用if...else判断
11     if(nRes != 0)
12     {
13         printf("validation failed!\n");
14         return 1;
15     }
16     printf("validation success!\n");
17
18     //这里讲验证结果作为索引
19     char aaPrintInfo[][MAX_LEN] = {"validation success!", "validation failed!"};
20     printf("%s\n",aaPrintInfo[nRes]);
21
22
23     return 0;
24 }

  如果不得不用if…else结构,可将if语句与验证函数分散开,对于静态分析代码的难度会有所增加。

  第三,就是一些关键提示信息不要放在堆内而放在栈内。OD有个查找字符串功能可以把程序堆内的字符串列出来,新手最喜欢用这个来定位跳转点爆破了。

1 void main()
2 {
3     char a[]= "this is in stack";
4     char *b = "this is in heap";
5
6     printf("%s\n%s\n", a, b, "also in heap");
7 }

  这段代码有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在运行的代码如下:

 1 #include "tlhelp32.h"
 2 bool IsODRuning()
 3 {
 4     HANDLE hwnd;
 5     PROCESSENTRY32 tp32; //结构体
 6     tp32.dwSize = sizeof(PROCESSENTRY32);
 7     TCHAR *str= _TEXT("OLLYDBG.EXE");
 8     bool bFindOD=false;
 9     hwnd=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
10     if(INVALID_HANDLE_VALUE!=hwnd)
11     {
12         Process32First(hwnd,&tp32);
13         do{
14
15             if(0==wcsicmp(str,tp32.szExeFile))
16             {
17                 bFindOD=true;
18                 break;
19             }
20         }while(Process32Next(hwnd,&tp32));
21     }
22     CloseHandle(hwnd);
23
24     return bFindOD;
25 } 

  最后,还有利用异常处理的方法。比如下面代码,通过人为故意产生一个中断异常,然后在异常处理中去验证,这样在调试的时候中断异常就是一个断点,从而程序不会进入异常处理。代码如下:

 1 long g_label = 0;
 2 LONG Handle(EXCEPTION_POINTERS *pExceptionInfo )
 3 {
 4     if(EXCEPTION_BREAKPOINT == pExceptionInfo->ExceptionRecord->ExceptionCode)
 5     {
 6         //validation
 7
 8         if(/*success*/)
 9         {
10             pExceptionInfo->ContextRecord->Eip = g_label;
11
12             return EXCEPTION_CONTINUE_EXECUTION;
13         }
14     }
15     return EXCEPTION_EXECUTE_HANDLER;
16 }
17
18 void main()
19 {
20
21     //===exception validation begin
22     LPTOP_LEVEL_EXCEPTION_FILTER lpOld;
23     lpOld = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)Handle);
24
25     __asm
26     {
27         push label_ok;
28         pop  g_label;
29         int 3;
30     }
31
32 label_ok:
33     SetUnhandledExceptionFilter(lpOld);
34     //===exception validation end
35     //do your things......
36 }

  当然也可以采用其他异常(如除0异常),但用异常的一个缺点就是自己写代码调试的时候也很不方便。

  以上就是我大概了解的反调试技术,不过加解密是具有强烈对抗性的,现在一些调试器都增加了反反调试手段,让程序的反调试失效。

驱动保护与硬件加密狗



  程序做好上面的保护,基本上已经具有一定的自我保护能力了,一般个人写的软件已经足够。如果你写的是商业软件,需要高度防范破解,那可以采用驱动保护或硬件加密狗。具体采用何种根据软件来定。

  如果软件是一些专业性较强的,可以采用硬件加密狗来保护;如果软件是像网络游戏那样面向广泛大众群体的,采用加密狗就不现实了,一般都采用驱动保护,企鹅的游戏基本都有TenProtect的驱动保护,盛大的GPK保护等都是比较典型的例子。

  加密狗我没仔细研究过,就不好多说了。上面那些反调试的手段都运行在ring3级别,而驱动则运行ring0级别,驱动保护的做法主要是hook系统底层的一些API,通过检验调用者来区分外部调试修改还是程序自己的操作。比如打开进程的操作,所有调试器都需要调用,通过驱动层hook该函数来防止调试器打开或附加到程序进程。

结语



  自从接触了这些东西,才知道“涉密不上网,上网不涉密”的真正意义。要知道这一行高手很多,即便用尽各种手段,也不可能保证软件绝对安全,只要软件运行就会留下痕迹,就有被破解的可能。

  现在已一年多没搞这些了,以后估计也没时间去搞,当初学习的时候虽然很累,但却感觉很充实很有兴趣,甚至还想换那方向的工作,谨以此作为对那段时间学习的总结。

  虽然写了这么些加密的东西,我个人还是更崇尚开源,如果不是那么必要,还是希望大家能多把源码与人分享,共同进步。

时间: 2024-08-16 20:58:08

windows程序防狼术入门的相关文章

第三章—Windows程序

这一章我都不知道该如何写了,呵呵~~ 毕竟,Win32是一个非常深奥的系统,目前还容不得我这种 小辈在这儿说三道四,不过,我既然是要写给那些入门阶段的朋友们看的,又不是写给那些搞程序设计老鸟看的,所以,我也犯不着怕被人背后指着骂 本章的名字就叫<Windows程序>而不是<Windows程序设计>所以,我只是讲一些关于Windows程序运作的原理: Windows 为什么叫Windows,相信所有用过的朋友都可以明白,那桌面上一个一个的窗口,就是它名字的由来.也就是这一个又一个窗口

Spark+ECLIPSE+JAVA+MAVEN windows开发环境搭建及入门实例【附详细代码】

http://blog.csdn.net/xiefu5hh/article/details/51707529 Spark+ECLIPSE+JAVA+MAVEN windows开发环境搭建及入门实例[附详细代码] 标签: SparkECLIPSEJAVAMAVENwindows 2016-06-18 22:35 405人阅读 评论(0) 收藏 举报  分类: spark(5)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 前言 本文旨在记录初学Spark时,根据官网快速

Windows程序----初识Windows程序

先来看一些励志名言来激励一下自己吧!  励志名言:每一发奋发奋的背后,必有加倍的赏赐 1.有无目标是成功者与平庸者的根本差别. 2.成功不是将来才有的,而是从决定去做的那一刻起,持续累积而成. 3.当一个人先从自我的内心开始奋斗,他就是个有价值的人. 4.时间给勤勉的人留下智慧的力量,给懒惰的人留下空虚和悔恨. 5.如果可以重新活一次,每个人都将是成功者. 1.创建一个windows应用程序,一共包括四步 (1)打Visual Studio 开发工具1 (2)选择”文件”→”新建”→”项目”命令

windows phone开发-windows azure mobile service使用入门

在使用azure之前,我一直只能做本地app,或者使用第三方提供的api,尽管大多数情况下够用,但是仍不能随心所欲操纵数据,这种感觉不是特别好.于是在azure发布后,我就尝试使用azure来做为个人数据中心,可选的方式有很多,但今天我给大家介绍的是azure mobile service. 1.创建Mobile Service Azure中创建Mobile Service很简单,与创建其他项目类似,流程如下: i. ii. iii. ii 这里我使用的是Windows Azure国际版,经过简

Java程序员的Golang入门指南(上)

Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵.如果你是某种语言老手,你就是那门语言的"传道者",下意识地会保护它.无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的.<肖申克的救赎>对此有很好的注脚: [Red] These walls are funny.

Windows程序代码重构

代码重构:在程序功能实现之后,对代码进行一定规模的整理,使之符合"高内聚.低耦合"的软件设计原则,便于维护和使用. ①用函数封装消息处理代码--对Windows程序窗口函数中的每一个case程序段进行封装以形成一个消息处理函数,而在case中调用这个函数. ②利用数组或链表实现消息映射表进一步实现代码的隔离--因为窗口函数switch-case结构实质上实现的就是一个根据消息标识来查找消息处理代码的功能,故可以用消息映射表和一段查表程序来替代它,表中的每一项可以使用一个函数指针来指向消

编写的windows程序,崩溃时产生crash dump文件的办法

一.引言 dump文件是C++程序发生异常时,保存当时程序运行状态的文件,是调试异常程序重要的方法,所以程序崩溃时,除了日志文件,dump文件便成了我们查找错误的最后一根救命的稻草.windows程序产生dump文件和linux程序产生dump文件的方式不一样,linux默认是不让产生core dump文件,只要在用户自己的~/.bash_profile文件中增加 ulimit -S -c unlimited > /dev/null 2>&1 这样程序崩溃就可以产生可调试的core d

盘点那些快速打开windows程序的快捷键

很多时候,快捷键带给我们的不仅仅是快速,还能带给我们一种心灵上的满足感.试想一下,别人都在中规中矩的用鼠标点,一步步慢慢调出所需要程序,而你却可以直接通过一个快捷键就完成了,是不是感觉特别有成就呢?今天就给大家整理了打开windows程序的常用快捷命令,windows系统拥护者的朋友们赶紧来转走收藏吧. 注:开始菜单中的"运行"是通向程序的快捷途径,输入特定的命令后,即可快速的打开Windows的大部分程序. winver检查Windows版本 wmimgmt.msc 打开Window

windows程序如何实现精准定时

有朋友问我,"小兵以太网测试仪"这个软件,是如何控制发包频率的. 我想想,干脆写一篇文章来聊聊这个问题吧. windows下,每当一个程序设定的定时器时间到了,windows会给程序发送一个WM_TIMER消息. 根据著名的windows编程书籍<Windows程序设计>的描述(在第8章), 我们知道,windows的定时器有如下的缺陷: 1. 精度不高 windows98大概是55ms,Windows NT大概是10ms. 2. WM_TIMER消息可能无法及时处理 WM