初见破解-我的第一个CreakMe

selph
selph
发布于 2020-09-22 / 581 阅读
0
0

初见破解-我的第一个CreakMe

破解主要是解除软件中的各种限制,比如时间限制,序列号限制等,破解不应用于非法获取利润,而是用于学习研究。

文本源于学习《C++黑客编程揭秘与防范 第三版》的笔记,如有问题,还请指出。

CreakME编写

书上代码比较老,用的还是ANSI编码,我给改成UNICODE了,不过这都不重要。

image-20200910155625207

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:

image-20200910161944550

右键,查找,当前模块中的名称,可以查到当前文件调用的API

右键点击下断点(这时应该是在函数里头,点击菜单栏调试执行到返回即可,在CALL调用的时候下断点):

image-20200910163352640

wcscmp函数的功能是比较两个字符串,如果相同返回0,所以这里在调用完函数之后直接判断eax是不是0,这里应该返回值就存在eax里了,eax不为0的话,则跳转到下面00BD8410

这里跳转与不跳转的区别在于,push的文本不同:

image-20200910163900252

最终都是调用MessageBox,所以这里只要把跳转条件修改掉即可完成破解:

双击反汇编窗口中跳转的这一行:

jnz short 00BD8410

image-20200910164057106

然后把东西都删掉,用NOP填充,这样无论如何跳转都不会发送了,不管输入什么都会提示密码正确

image-20200910164148571

IDA是在内存里加载PE文件来进行分析的,所以使用IDA分析的话,可以用OD找到程序关键部分,记住偏移量,在IDA里通过偏移找到目标位置:

image-20200910165807537

这里可以很清晰的看出这里密码判断的过程


要将破解的程序发布出去,三种方法:

  1. 修改程序然后拿去替换源程序,版本问题可能导致无法使用
  2. 发布文件补丁,去修改源程序文件,需要对版本进行判断,但校验和检测可能导致无法使用,需要对校验和检测进行破解
  3. 发布内存补丁,通过补丁去打开程序(暂停线程模式),打开程序后修改内存,然后恢复主线程的运行

文件补丁

通过程序对目标二进制文件进行修改:先判断要修改的地方是不是对的,然后再进行修改

  • 计算文件中的偏移量
  • 打开文件获得文件句柄
  • 设置文件指针,读取文件指定内容
  • 判断读取的内容是否正确
  • 正确则修改

源代码:

#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

image-20200910192041082image-20200910192121653

内存补丁

效果演示

image-20200911193626463

源代码

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;
}


评论