做过CARCK的都知道无论一个软件的注册码算法强度多高,但在验证时如果只是简单比较一下输入的注册码经过加密计算后与正确的注册码是否相等就判断用户是否注册,则这样的软件是非常容易被爆破的.无论软件编写者使用的是RSA或是DES之类的高强度算法,CARCKER都无需关心.短短的修改几个字节跳过验证(称之为爆破)就能让这个判断失效.
因此,需要从根本上解决这个问题则需要对功能核心代码也根据输入的注册码进行动态的解密执行,才能彻底防止爆破的可能性.
因为无论怎么样的程序语言写出来的代码,经过编译成可执行文件时,在机器中最终是一段内存数据,因此我们需要对在执行关键核心功能部分的内存数据进行事先加密,当用户输入正确的注册码时,这段内存数据解密后自然是正确的,能够运行无误,而如果是错误的呢,则这段内存数据自然会是无法执行,一般都会被操作系统报错并禁止运行,即使偶尔能够正常运行,它所执行的功能也自然不是原来的功能了.因此,我们不需要对注册码进行任何验证,只需要验证程序是否运行有错,就知道注册码是否正确了.
在下面一段示例代码中,核心功能执行代码为一字串数组,被事先经过了加密,然后被编译进EXE中,当需要使用它时,对它根据输入进的密码进行动态解密,这段代码能从根本上防止被爆破.
注:因为只是示例,加密算法是自己随便定义的,使每字节的值+1,当然如果在实际中应用时,可以根据喜好换用RSA或DES之类的高强度加密算法了.加密前的关键代码长度也不必和加密后的长度一样.
至于关键代码转换成机器码,可以参考一些SHELLCODE的编写文章,里面对于函数取址之类的介绍得比较详细,因为我主要是说明一种思路,具体技术细节在这也不多说了.
示例代码:
//By: 好人(QQ:46163851)
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <process.h>
using namespace std;
int g_nNumber;
void KernelProc( FARPROC fLbAddr, FARPROC fGtAddr );
int main(int argc, char* argv[])
{
cout << "请输入密码: ";
cin >> g_nNumber;
//取得LoadLibraryA和GetProcAddress函数的地址,以供核心代码调用
//因为是在自身进程里,取址方便多了.如果是外部进程插入SHELLCODE
//就得通过其他方法得到Kernel32.dll加载的地址再得到这些函数的地址了,这里就不过多介绍了
HMODULE hHandle = LoadLibrary( "Kernel32.dll" );
FARPROC fLbAddr = GetProcAddress( hHandle, "LoadLibraryA" );
FARPROC fGtAddr = GetProcAddress( hHandle, "GetProcAddress" );
//执行核心代码
KernelProc( fLbAddr, fGtAddr );
return 0;
}
void KernelProc( FARPROC fLbAddr, FARPROC fGtAddr )
{
try
{
//核心代码反汇编后并且经过加密的机器码
char szBuffer[200] = "/x8E/x96/xED/x00/x00/x00/xC7/x03/x56/xC7/x43/x02/x74/xC7/x43/x03/x66/xC7/x43/x04"
"/x73/xC7/x43/x05/x34/xC7/x43/x06/x33/xC7/x43/x07/x2F/xC7/x43/x08/x65/xC7/x43/x09"
"/x6D/xC7/x43/x0A/x6D/xC7/x43/x0B/x01/x53/x00/x56/x09/x8E/x96/xED/x00/x00/x00/xC7"
"/x03/x4E/xC7/x43/x02/x66/xC7/x43/x03/x74/xC7/x43/x04/x74/xC7/x43/x05/x62/xC7/x43"
"/x06/x68/xC7/x43/x07/x66/xC7/x43/x08/x43/xC7/x43/x09/x70/xC7/x43/x0A/x79/xC7/x43"
"/x0B/x42/xC7/x43/x0C/x01/x53/x51/x00/x56/x0D/x6B/x01/x6B/x01/x6B/x01/x6B/x01/x00"
"/xD1/xC4";
//核心代码解密还原成原始机器码(加密算法为每个字节值+1,解密为每字节值-1)
for( int i = 0; i<200; i++ )
{
szBuffer[i] -= g_nNumber;
}
//执行核心代码
__asm
{
lea edx,szBuffer
call edx
}
cout << "密码正确!" << endl;
system( "pause" );
/*
核心代码功能大致C代码:
typedef int (*MsgBox)( HWND, LPCTSTR, LPCTSTR, UINT );
typedef hHandle (*LoadLib)( LPCTSTR );
typedef FARPROC (*GetAddr)( HMODULE, LPCSTR );
FARPROC fLoadLibrary = (LoadLib) fLbAddr;
FARPROC fGetProcAddress = (GetAddr) fGtAddr;
HMODULE hHandle = fLoadLibrary( "User32.dll" );
FARPROC fMsgBox = (MsgBox) fGetProcAddress( hHandle, "MessageBoxA" );
fMsgBox( NULL, NULL, NULL, NULL );
//注:这里没有直接使用LoadLibrary和GetProcAddress函数,因为核心代码并不知道这个函数地址,
//这个地址在各台机器和系统平台下均有不同,不方便将之硬编码
//于是间接使用了传入的fLbAddr地址和fGtAddr地址调用.
//核心代码汇编码:
__asm
{
push offset string "User32.dll"
call fLbAddr
push offset string "MessageBoxA"
push eax
call fGtAddr
push 0
push 0
push 0
push 0
call eax
}
*/
}
//用SEH捕获错误以得知密码是否正确
catch( ... )
{
cout << "密码有误!" << endl;
system( "pause" );
}
}