破解主要是解除软件中的各种限制,比如时间限制,序列号限制等,破解不应用于非法获取利润,而是用于学习研究。
文本源于学习《C++黑客编程揭秘与防范 第三版》的笔记,如有问题,还请指出。
CreakME编写
书上代码比较老,用的还是ANSI编码,我给改成UNICODE了,不过这都不重要。
void CFirstCreakMeDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
TCHAR szUser[MAXBYTE] = { 0 };
TCHAR szPassword[MAXBYTE] = { 0 };
TCHAR szTmpPassword[MAXBYTE] = { 0 };
// 获取输入的帐号和密码
GetDlgItemText(IDC_EDIT1, szUser, MAXBYTE);
GetDlgItemText(IDC_EDIT2, szPassword, MAXBYTE);
// 判断帐号是否为空
if (wcslen(szUser) == 0)
{
return;
}
// 判断密码是否为空
if (wcslen(szPassword) == 0)
{
return;
}
// 判断帐号长度是否小于4
if (wcslen(szUser) < 4)
{
return;
}
// 根据帐号生成密码
for (int i = 0; i < wcslen(szUser); i++)
{
if (szUser[i] == 'Z'
|| szUser[i] == 'z'
|| szUser[i] == '9')
{
szTmpPassword[i] = szUser[i];
}
else
{
szTmpPassword[i] = szUser[i] + 1;
}
}
// 把生成的密码和输入的密码进行匹配
if (wcscmp(szTmpPassword, szPassword) == 0)
{
MessageBox(L"密码正确");
}
else
{
MessageBox(L"密码错误");
}
}
用OD来破解
破解方法1:
查看调用的API:
右键,查找,当前模块中的名称,可以查到当前文件调用的API
右键点击下断点(这时应该是在函数里头,点击菜单栏调试执行到返回即可,在CALL调用的时候下断点):
wcscmp函数的功能是比较两个字符串,如果相同返回0,所以这里在调用完函数之后直接判断eax是不是0,这里应该返回值就存在eax里了,eax不为0的话,则跳转到下面00BD8410
这里跳转与不跳转的区别在于,push的文本不同:
最终都是调用MessageBox,所以这里只要把跳转条件修改掉即可完成破解:
双击反汇编窗口中跳转的这一行:
jnz short 00BD8410
然后把东西都删掉,用NOP填充,这样无论如何跳转都不会发送了,不管输入什么都会提示密码正确
IDA是在内存里加载PE文件来进行分析的,所以使用IDA分析的话,可以用OD找到程序关键部分,记住偏移量,在IDA里通过偏移找到目标位置:
这里可以很清晰的看出这里密码判断的过程
要将破解的程序发布出去,三种方法:
- 修改程序然后拿去替换源程序,版本问题可能导致无法使用
- 发布文件补丁,去修改源程序文件,需要对版本进行判断,但校验和检测可能导致无法使用,需要对校验和检测进行破解
- 发布内存补丁,通过补丁去打开程序(暂停线程模式),打开程序后修改内存,然后恢复主线程的运行
文件补丁
通过程序对目标二进制文件进行修改:先判断要修改的地方是不是对的,然后再进行修改
- 计算文件中的偏移量
- 打开文件获得文件句柄
- 设置文件指针,读取文件指定内容
- 判断读取的内容是否正确
- 正确则修改
源代码:
#include <windows.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
// RVA = 00E883FB
// FileOffset = 000177FB
DWORD dwFileOffset = 0x000077FB;
WORD bCode = 0;
DWORD dwReadNum = 0;
// 判断参数
if (argc != 2)
{
printf("Please input two argument \r\n");
//return -1;
}
TCHAR ExePath[MAX_PATH] = { 0 };
MultiByteToWideChar(CP_ACP,NULL,argv[1],-1,ExePath,strlen(argv[1]));
printf("%ws", ExePath);
// 打开文件
HANDLE hFile = CreateFile(ExePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("打开文件失败: %s\n",argv[1]);
printf("%d", GetLastError());
return -1;
}
SetFilePointer(hFile, dwFileOffset, 0, FILE_BEGIN);
ReadFile(hFile, (LPVOID)&bCode, sizeof(WORD), &dwReadNum, NULL);
printf("%X", bCode);
// 比较当前位置是否为JNZ那条语句
if (bCode != '\x75\x13')
{
printf("%08X \r\n", bCode);
CloseHandle(hFile);
return -1;
}
// NOP填充
bCode = '\x90\x90';
SetFilePointer(hFile, dwFileOffset, 0, FILE_BEGIN);
WriteFile(hFile, (LPVOID)&bCode, sizeof(WORD), &dwReadNum, NULL);
printf("Write NOP is Successfully ! \r\n");
CloseHandle(hFile);
// 运行
MessageBoxW(NULL, L"破解成功", L"提示", MB_OK);
return 0;
}
效果演示:
命令行运行:
> .\FilePatch.exe .\FilePatch1.exe
内存补丁
效果演示
源代码
2020年9月10日:书上给的代码思路我实现不出来,在读取进程内存的时候总是报错299,找不到解决方案,可能是现在知识量不足以解决这个问题:我感觉是读取的位置有问题,我不知道怎么确定我创建的进程是从哪个内存地址开始的
2020年9月11日:问题解决了!!详情参考下一篇笔记!
#include <Windows.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
char a[MAXBYTE] = "C:\\Users\\52925\\Desktop\\FirstCreakMe2.exe";
int err=0;
// RVA = 00E883FB
DWORD dwVAddress = 0x00400001;
BYTE bCode = 0;
SIZE_T dwReadNum = 0;
// 判断参数数量
if (argc != 2)
{
printf("Please input two argument \r\n");
//return -1;
}
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFO);
si.wShowWindow = SW_SHOW;
si.dwFlags = STARTF_USESHOWWINDOW;
PROCESS_INFORMATION pi = { 0 };
TCHAR ExePath[MAXBYTE] = { 0 };
MultiByteToWideChar(CP_ACP, NULL, a, -1, ExePath, MAXBYTE);
err = GetLastError();
BOOL bRet = CreateProcess(ExePath,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED, // 将子进程暂停
NULL,
NULL,
&si,
&pi);
err = GetLastError();
if (bRet == FALSE)
{
printf("CreateProcess Error ! \r\n");
return -1;
}
DWORD dwOldPro=0;
//VirtualProtectEx(pi.hProcess, (void*)dwVAddress,sizeof(BYTE), PAGE_READWRITE, &dwOldPro);
ReadProcessMemory(pi.hProcess,
(LPVOID)dwVAddress,
(LPVOID)&bCode,
sizeof(BYTE),
&dwReadNum);
err = GetLastError();
// 判断是否为JNZ
if (bCode != '\x75')
{
printf("%02X \r\n", bCode);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return -1;
}
// 将JNZ修改为JZ
bCode = '\x74';
WriteProcessMemory(pi.hProcess,
(LPVOID)dwVAddress,
(LPVOID)&bCode,
sizeof(BYTE),
&dwReadNum);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
printf("Write JZ is Successfully ! \r\n");
getchar();
return 0;
}