加密与解密之补丁开发

开头

  • 看完《加密与解密第四版》,有些心得体会,收获主要集中在补丁程序开发、Dll注入、函数hook、程序二次开发、加壳5个方面,可以统称为PEDIY
  • 按照自己的理解和思考,上述5个方面各进行了一次实践,分4篇内容写出来,其中Dll注入一篇贯穿在二次开发和函数hook中,不单独写了

描述

  • 本篇补丁程序分为文件补丁和内存补丁
  • 写了个messagebox的例程,目标是通过补丁程序修改弹窗显示的内容

原程序

  • 代码
#include <windows.h>

int main(int argc, char* argv[])
{
    MessageBox(NULL, "恭喜你成功进入游戏!", "成功", MB_OK);
    return 0;
}

文件补丁

原理

  • 关键在于查找到要修改的位置的文件偏移地址

步骤

  • 首先在IDA打开程序,查找到title和text两个字符串的地址

  • 由于IDA中看到的是默认加载的映像地址,还需要转化为文件地址才能使用,方法是减掉.rdata段的起始地址,得到段内偏移地址,再用PE Tools打开文件,查找到.rdata的文件地址,再加上段内偏移地址,即可定位到目标字符串在文件中的偏移地址0x89b0
  • 接下来,编写程序打开目标文件,在指定偏移处填入字符串,需要注意一个中文字符占两个字节,多余的字节用\x00填充

代码

#include <windows.h>
#include <cstdio>

int main(int argc, char* argv[])
{
    HANDLE hFile = CreateFile("Encoding.exe", 0xC0000000, 3, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Error opening file. Error code: %d\n", GetLastError());
        return 1;
    }

    DWORD dwBytesWritten;
    char title[] = "失败";
    DWORD title_offset = 0x89b0;
    SetFilePointer(hFile, title_offset, NULL, FILE_BEGIN);
    if (!WriteFile(hFile, title, sizeof(title)-1, &dwBytesWritten, NULL)) {
        printf("Error writing to file. Error code: %d\n", GetLastError());
        CloseHandle(hFile);
        return 1;
    }

    char text[] = "进入游戏失败!\x00\x00\x00\x00\x00\x00";
    DWORD text_offset = 0x89b8;
    SetFilePointer(hFile, text_offset, NULL, FILE_BEGIN);
    if (!WriteFile(hFile, text, sizeof(text)-1, &dwBytesWritten, NULL)) {
        printf("Error writing to file. Error code: %d\n", GetLastError());
        CloseHandle(hFile);
        return 1;
    }

    CloseHandle(hFile);
}

效果展示

  • 打补丁前运行程序提示成功进入游戏,打补丁后,运行程序提示失败

内存补丁

原理

  • 设想场景是文件被加密处理,无法直接打上文件补丁,需要程序运行到特定处后,在内存中修改数据,改变程序运行结果
  • 按照判断程序运行到特定处的方法,可以划分为多种方法,包括
    • 创建进程后挂起线程,隔一段时间查询程序目标位置是否被解密还原
    • 创建调试进程,在目标位置处设置硬件断点或者写入int 3断点,程序运行到目标位置处触发异常,被父进程捕获
  • 本文尝试了很多遍第二种方法,在64位程序上不管如何用waitfordebugevent都捕捉不到程序异常。不光捕捉不到异常,目标程序还卡在了奇怪的位置不能继续运行,故放弃,采用了第一种比较笨的方法可行

步骤

  • 创建进程后立即挂起线程,读取目标地址处的值(这里选用的是call messagebox处的值),判断是否已被还原为指定的值,如果没有还原,则让线程继续执行10秒,重复进行判断
  • 如果已被还原,则修改目标地址的值,恢复线程运行,很奇怪的是,我这里必须要调用两次ResumeThread才能目标程序才能正常运行,难以解释...

代码

#include <windows.h>
#include <cstdio>
#include "psapi.h"

#define BREAK_POINT1     0x00007FF7FA171796       //需要中断的地址
#define BREAK_SIZE   6
#define SZFILENAME       ".\\Encoding.exe"  //目标文件名
#define TITLE_ADDR 0x00007FF7FA179BB0
#define TEXT_ADDR 0x00007FF7FA179BB8

BYTE TarGetData[] = { 0xFF,0x15, 0xB4, 0xE9, 0x00, 0x00 }; 			//补丁前的代码

char title[] = "失败";
char text[] = "进入游戏失败!\x00\x00\x00\x00\x00\x00";

int main(int argc, char* argv[])
{
	STARTUPINFOA     	si;
	PROCESS_INFORMATION pi;

	BYTE      ReadBuffer[MAX_PATH] = { 0 };
	BOOL      bContinueRun = TRUE;
	DWORD	  Oldpp;


	ZeroMemory(&si, sizeof(STARTUPINFO));
	ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

	if (!CreateProcess(SZFILENAME,
		NULL,
		NULL,
		NULL,
		FALSE,
		CREATE_SUSPENDED,
		NULL,
		NULL,
		&si,
		&pi)
		)
	{
		MessageBox(NULL, "CreateProcess Failed.", "ERROR", MB_OK);
		return FALSE;
	}


	while (bContinueRun) {
		SuspendThread(pi.hThread);
		ReadProcessMemory(pi.hProcess, (LPVOID)BREAK_POINT1, &ReadBuffer, BREAK_SIZE, NULL);
		//判断是不是完全解码出来了
		if (!memcmp(TarGetData, ReadBuffer, BREAK_SIZE)) {
			MessageBox(NULL, "已运行至目标代码处,准备打补丁", "提示", MB_OK);
			VirtualProtectEx(pi.hProcess, (LPVOID)TITLE_ADDR, sizeof(title) - 1, PAGE_EXECUTE_READWRITE, &Oldpp);
			VirtualProtectEx(pi.hProcess, (LPVOID)TEXT_ADDR, sizeof(text) - 1, PAGE_EXECUTE_READWRITE, &Oldpp);
			WriteProcessMemory(pi.hProcess, (LPVOID)TITLE_ADDR, title, sizeof(title) - 1, NULL);
			WriteProcessMemory(pi.hProcess, (LPVOID)TEXT_ADDR, text, sizeof(text) - 1, NULL);

			ResumeThread(pi.hThread);
			ResumeThread(pi.hThread);
			break;
		}
		ResumeThread(pi.hThread);
		Sleep(10);
	}
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	return 0;
}

效果

  • 直接运行目标程序
  • 通过补丁程序运行目标程序
posted @ 2023-04-04 15:43  z5onk0  阅读(94)  评论(0编辑  收藏  举报