——此文曾作为连载刊登于《电脑报》2003年41、42期,如要转载,请注明出自《电脑报》
——本人仅是一名初学者,如有疏漏之处,还请列位前辈们指教,谢谢!crossbow@citiz.net
共享软件是软件业目前世界上比较热门的话题,国内更是如此。成千上万的中国程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动来获得丰厚的回报;但,实际并非如此,绝大多数的人都弑羽而归。值得注意的是:除了选题和技术上的原因外,最大的原因就是共享软件被破解(Crack)了。 { 利用RSA进行注册码的数字签名验证 } if RSAVerify(md5(Key), MD5(Code), e, n) then ShowMessage('注册成功!') else ShowMessage('注册失败!'); { 这里Key是用户输入的注册码,是由你发送给注册用户的 } { Code是根据用户输入的用户名自动计算出来的注册码 } { e是RSA算法的公匙,而n是RSA算法的模数。 }
这个注册函数即使使用了强劲的RSA算法进行注册码验证,可是依然很容易被破解,我们只要把这里修改为: { 将逻辑判断改为否 } if not RSAVerify(MD5(Key), MD5(Code), e, n) then ShowMessage('注册成功!') else ShowMessage('注册失败!');
就可以了。这时戏剧性的结果会产生:随便输入任何注册码都可以注册通过,相反输入正确的注册码却无法通过注册。:) 其具体操作是先反汇编或者跟踪你的程序,找到判断注册码的cmp、test等汇编指令后的关键跳转指令处,通常是je、jz之类的汇编指令,把它们修改为jne或jnz即可,这样常常只需要修改一个字节就可以完美破解之。:) { 探测FileMon } function DetectFileMon: Boolean; begin if CreateFile(PChar('//./FILEVXD'), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True //如果有,就Down机! else Result := False; end;
当然,你可以保护得更好一些:可以不采用临时Dll,而把解密后的关键代码用WriteProcessMemory这个API函数写入到主可执行文件自己进程被提交(Committed)的内存页面的指定位置去。这样由于磁盘上没有解密后的临时文件,破解更加困难。事实上,目前世界上最强劲的专业保护软件Armadillo就是用的这种方法。而且这种方法可以充分防止被调试器Dump。但实现起来比较困难,尤其是在WinNT 5以后的操作系统中。 EncryptedCode = Blowfish(MD5(UserName), MD5(Key)); //你的加密算法,使用了Blowfish(对称算法)和MD5(Hash算法)
虽然我不了解Blowfish和MD5算法的原理,也不会逆向它们,但我了解你的效验算法的流程和算法名,我马上就可以从网上找到类似的Blowfish和MD5算法包,从而模拟你的软件仿造出注册机,啊?!真是……$&*&($#%@! 0167:005B9F70 MOV EAX,[EBP-10] 0167:005B9F73 CALL 00404000 0167:005B9F78 PUSH EAX 0167:005B9F79 MOV EAX,[EBP-10] 0167:005B9F7C CALL 004041C4 0167:005B9F81 LEA ECX,[EBP-14] 0167:005B9F84 POP EDX 0167:005B9F85 CALL 004B860C
当然,最好把Hash算法也全部改名,给会给他们制造更多的困难。但注意,MD5和SHA之类的Hash的初始值会被Cracker从内存中找到,这样他就知道了你用的Hash了。所有建议同时使用MD5的变形算法Ripe-MD(RMD)128或160和其它的Hash,如Tiger, Haval等算法。 { 检查自己的进程的父进程是否为Explorer.exe,否则是被调试器加载了 } { 不过注意,控制台程序的父进程在WinNT下是Cmd.exe哦!} { 注意加载TlHelp32.pas单元 } procedure CheckParentProc; var //检查自己的进程的父进程 Pn: TProcesseNtry32; sHandle: THandle; H, ExplProc, ParentProc: Hwnd; Found: Boolean; Buffer: array[0..1023] of Char; Path: string; begin H := 0; ExplProc := 0; ParentProc := 0; //得到Windows的目录 SetString(Path, Buffer, GetWindowsDirectory(Buffer, Sizeof(Buffer) - 1)); Path := UpperCase(Path) + '/EXPLORER.EXE'; //得到Explorer的路径 //得到所有进程的列表快照 sHandle := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0); Found := Process32First(sHandle, Pn); //查找进程 while Found do //遍历所有进程 begin if Pn.szExeFile = ParamStr(0) then //自己的进程 begin ParentProc := Pn.th32ParentProcessID; //得到父进程的进程ID //父进程的句柄 H := OpenProcess(PROCESS_ALL_access, True, Pn.th32ParentProcessID); end else if UpperCase(Pn.szExeFile) = Path then ExplProc := Pn.th32ProcessID; //Explorer的PID Found := Process32Next(sHandle, Pn); //查找下一个 end; //嗯,父进程不是Explorer,是调试器…… if ParentProc <> ExplProc then begin TerminateProcess(H, 0); //杀之!除之而后快耶! :) //你还可以加上其它什么死机代码来消遣消遣这位可爱的Cracker :) end; end;
你可以在Delphi或者VC中试试,呵呵,是不是把Delphi和VC杀掉了,因为你现在用的是Delphi和VC的内置调试器来运行你的程序的,当然它会六亲不认了,呵呵!调试的时候你还是把它注释掉吧,发布时别忘记激活哟!新闻热点
疑难解答