Jak napisać skomplikowany patch?

Nawiązując do artykułu Jak napisać prosty patch? i komentarzy @ img (hi mate), prezentuję bardziej rozbudowaną wersję patchera z całą gamą sprawdzania potencjalnych błędów. Jeśli zauważycie więcej potencjalnych kłopotów – piszcie w komentarzach (sam jestem ciekaw, co jeszcze może pójść nie tak w jak się wydaje prostej operacji).

#define UNICODE

#include <windows.h>
#include <Sfc.h>

// zalacz biblioteke SFC.lib (mechanizm ochrony plikow systemowych WFP / WRP)
#pragma comment(lib, "sfc")

int main()
{
	// pelna sciezka pliku do spatchowania
	const wchar_t * wszFilePath = L"C:\\plik.exe";

	// bajty, ktore zostana zapisane do pliku
	const BYTE cPatch[] = { 0xAA, 0xBB, 0xCC };

	// offset, pod ktorym zostana zapisane powyzsze bajty
	const DWORD dwPatchRawOffset = 0x1234;

	// rozmiar pliku
	DWORD dwFileSize = 0;

	// ilosc zapisanych bajtow w pliku (parametr dla funkcji WriteFile)
	DWORD dwWritten = 0;

	// kod bledu z funkcji zapisu do pliku WriteFile
	BOOL bWriteFileResult = FALSE;

	// sprawdz czy plik ma atrybuty read only lub znajduje sie na nosniku jak np. DVD
	DWORD dwFileAttributes = GetFileAttributes(wszFilePath);

	// czy udalo sie odczytac atrybuty?
	if (dwFileAttributes == INVALID_FILE_ATTRIBUTES)
	{
		MessageBox(NULL, L"Nie mozna odczytac atrybutow pliku (plik nie istnieje?)!", L"Blad", MB_ICONERROR);
		return 1;
	}

	// czy plik ma atrybuty tylko do odczytu?
	if ( (dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0)
	{
		MessageBox(NULL, L"Wybrany plik nie ma praw do zapisu (lub znajduje sie na takim nosniku jak np. DVD)!", L"Blad", MB_ICONERROR);
		return 2;
	}

	// czy plik jest chronionym plikiem systemowym?
	if (SfcIsFileProtected(NULL, wszFilePath) == TRUE)
	{
		MessageBox(NULL, L"Wybrany plik jest chronionym plikiem systemowym i automatycznie zostanie odtworzony po zmianie przez system Windows!", L"Blad", MB_ICONERROR);
		return 3;
	}

	// otworz plik wejsciowy w trybie do zapisu (flaga GENERIC_WRITE)
	HANDLE hFile = CreateFile(wszFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	// jesli nie udalo sie otworzyc pliku - wyswietl komunikat i zakoncz
	if (hFile == INVALID_HANDLE_VALUE)
	{
		MessageBox(NULL, L"Nie udalo sie otworzyc pliku wejsciowego!", L"Blad", MB_ICONERROR);
		return 4;
	}

	// pobierz rozmiar pliku
	dwFileSize = GetFileSize(hFile, NULL);

	// czy plik jest pusty?
	if (dwFileSize == 0)
	{
		CloseHandle(hFile);

		MessageBox(NULL, L"Plik jest pusty (0 bajtow)!", L"Blad", MB_ICONERROR);
		return 5;
	}

	// czy wskaznik pliku, gdzie ma byc spatchowany przekracza rozmiar pliku?
	if ( (dwPatchRawOffset + sizeof(cPatch)) > dwFileSize)
	{
		CloseHandle(hFile);

		MessageBox(NULL, L"Wskaznik do spatchowania znajduje sie poza rozmiarem pliku!", L"Blad", MB_ICONERROR);
		return 6;
	}

	// ustaw wskaznik pliku na offsecie (raw offset),
	// gdzie zostana zapisane zmodyfikowane bajty
	if (SetFilePointer(hFile, dwPatchRawOffset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		CloseHandle(hFile);

		MessageBox(NULL, L"Nie udalo sie ustawic wskaznika do spatchowania w wybranym pliku!", L"Blad", MB_ICONERROR);
		return 7;
	}

	// pod wskazanym adresem zapisz bajty z tablicy cPatch
	bWriteFileResult = WriteFile(hFile, cPatch, sizeof(cPatch), &dwWritten, NULL);

	// czy udalo sie zapisac do pliku?
	if (bWriteFileResult == FALSE)
	{
		CloseHandle(hFile);

		MessageBox(NULL, L"Wystapil blad zapisu do pliku!", L"Blad", MB_ICONERROR);
		return 8;
	}

	// czy zapisane zostaly wszystkie bajty patcha?
	if (dwWritten != sizeof(cPatch))
	{
		CloseHandle(hFile);

		MessageBox(NULL, L"Nie udalo sie zapisac wszystkich bajtow!", L"Blad", MB_ICONERROR);
		return 9;
	}

	// zapisz wyniki na dysk
	if (FlushFileBuffers(hFile) == FALSE)
	{
		CloseHandle(hFile);

		MessageBox(NULL, L"Nie udalo sie zapisac zmian na dysk!", L"Blad", MB_ICONERROR);
		return 10;
	}

	// zamknij plik
	CloseHandle(hFile);

	MessageBox(NULL, L"Plik zostal pomyslnie zmodyfikowany", L"Informacja", MB_ICONINFORMATION);

	return 0;
}

Źródła w języku angielskim zostały także opublikowane na https://github.com/PELock/Patch-File-Example

Jak odczytać pamięć klucza sprzętowego ROCKEY2?

ROCKEY2 jest prostym kluczem sprzętowym (tzw. dongle), wykorzystywanym Klucz sprzętowy ROCKEY2m.in. w systemach licencyjnych oprogramowania.

Całe zabezpieczenie opiera się także na envelope (czyli exe-protectorze), jednak ze względu na prostotę tego konkretnego modelu klucza nie polecałbym jego stosowania w zabezpieczeniu oprogramowania.

ROCKEY2 posiada wbudowaną pamięć, składającą się z 5 bloków po 512 bajtów każdy.

Poniżej znajdziecie kod pozwalający odczytać wszystkie banki pamięci klucza. Wymagana jest do tego znajomość poprawnego identyfikatora użytkownika (uid), a do kompilacji potrzebne Wam będzie SDK tegoż klucza.

#include <windows.h>
#include <stdio.h>
#include "Rockey2.h"

// prosta funkcja zapisujaca zawartosc bufora do pliku
void save_file(char *file_name, char *buffer_bytes, size_t buffer_size)
{
	FILE *file_handle = NULL;

	file_handle = fopen(file_name, "wb+");

	fwrite(buffer_bytes, 1, buffer_size, file_handle);

	fclose(file_handle);
}

// zapisuje wybrany blok pamieci klucza Rockey2 do pliku
void dump_rockey2_block(int key_handle, int block_index, char *file_name)
{
	BYTE buffer_bytes[512] = { 0 };

	int result = 0;

	result = RY2_Read(key_handle, block_index, buffer_bytes);

	save_file(file_name, buffer_bytes, sizeof(buffer_bytes));
}

int main(int argc, char **argv)
{
	// liczba znalezionych kluczy
	int keys_count = 0;

	// uchwyt otworzonego klucza
	int key_handle = 0;

	int i = 0;

	// identyfikator klucza potrzebny do jego otwarcia
	DWORD uid = 0x12345678;

	DWORD hid = 0;

	printf("[i] memory dumper dla kluczy Rockey2 - www.secnews.pl\n");

	// znajdz aktualnie dostepne klucze Rockey2
	keys_count = RY2_Find();

	if (keys_count == 0)
	{
		printf("[!] nie znaleziono zadnych kluczy Rockey!");
		return 1;
	}

	printf("[i] znalezionych kluczy Rockey2 = %i\n", keys_count);

	// otworz klucz korzystajac z parametru UID
	key_handle = RY2_Open(1, uid, &hid);

	if (key_handle < 0)
	{
		printf("[!] nie udalo sie uzyskac dostepu do klucza (bledny uid?)!");
		return 2;
	}

	printf("[i] uchwyt klucza = %08X, HardwareId = %08X\n", key_handle, hid);

	// odczytaj wybrane bloki pamieci klucza
	// Rockey2 posiada 5 blokow po 512 bajtow kazdy
	dump_rockey2_block(key_handle, 0, "block_dump_0.bin");
	//dump_rockey2_block(key_handle, 1, "block_dump_1.bin");
	//dump_rockey2_block(key_handle, 2, "block_dump_2.bin");
	//dump_rockey2_block(key_handle, 3, "block_dump_3.bin");
	//dump_rockey2_block(key_handle, 4, "block_dump_4.bin");

	// zamknij klucz
	RY2_Close(0);

	return 0;
}

 

Jak napisać prosty patch?

Dzisiaj coś dla początkujących, czyli krótki program w C/C++, prezentujący jak używając funkcji WinApi napisać prosty patch, który otworzy wskazany plik i zmodyfikuje kilka bajtów pod konkretnym adresem.

#include <windows.h>

int main()
{
	// bajty, ktore zostana zapisane do pliku
	BYTE cPatch[] = { 0xAA, 0xBB, 0xCC };

	// offset, pod ktorym zostana zapisane powyzsze bajty
	const DWORD dwPatchRawOffset = 0x1234;

	// ilosc zapisanych bajtow w pliku (parametr dla funkcji WriteFile)
	DWORD dwWritten = 0;

	// uchwyt pliku
	HANDLE hFile = INVALID_HANDLE_VALUE;

	// otworz plik wejsciowy w trybie do zapisu (flaga GENERIC_WRITE)
	hFile = CreateFile("plik.exe", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	// jesli nie udalo sie otworzyc pliku - wyswietl komunikat i zakoncz
	if (hFile == INVALID_HANDLE_VALUE)
	{
		MessageBox(NULL, "Nie udalo sie otworzyc pliku wejsciowego!", "Blad", MB_ICONERROR);
		return 1;
	}

	// ustaw wskaznik pliku na offsecie (raw offset),
	// gdzie zostana zapisane zmodyfikowane bajty
	SetFilePointer(hFile, dwPatchRawOffset, 0, FILE_BEGIN);

	// pod wskazanym adresem zapisz bajty z tablicy cPatch
	WriteFile(hFile, cPatch, sizeof(cPatch), &dwWritten, NULL);

	// zamknij plik
	CloseHandle(hFile);

	MessageBox(NULL, "Plik zostal pomyslnie zmodyfikowany", "Informacja", MB_ICONINFORMATION);

	return 0;
}

Źródła w języku angielskim zostały opublikowane na https://github.com/PELock/Simple-Patch-File-Example

PS. Być może ktoś zechce w komentarzach zaprezentować jak taki prosty patch wyglądałby w innych językach programowania. Jestem ciekaw waszych interpretacji oraz usprawnień.

Patcher dla C#

Czekałem na koniec świata, ale jakoś go nie było, tymczasem mała klasa w C# do patchowania plików z paroma przydatnymi opcjami jak np. patchowanie stringow Delphi.

using System;
////////////////////////////////////////////////////////////////////////////////
//
// Simple C# Patcher Class for Windows (Complex)
//
// You can patch:
//
// - byte arrays
// - single bytes
// - integers
// - strings
// - delphi strings
//
// Language : C#
// Author   : Bartosz Wójcik
// Website  : https://www.pelock.com
//
////////////////////////////////////////////////////////////////////////////////

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace NetPatcher
{
    class Patcher
    {
        public FileStream inputFile;

        public Patcher()
        {
        }

        ~Patcher()
        {
            CloseFile();
        }

        public bool OpenFile(string filePath)
        {
            try
            {
                inputFile = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite);

                return inputFile == null ? false : true;
            }
            catch (Exception e)
            {
                return false;
            }

            return false;
        }

        public void CloseFile()
        {
            if (inputFile != null)
            {
                inputFile.Close();
            }

            inputFile = null;
        }

        public long PatchArray(long fileOffset, byte[] patchBytes)
        {
            inputFile.Seek(fileOffset, SeekOrigin.Begin);
            inputFile.Write(patchBytes, 0, patchBytes.Length);

            return inputFile.Position;
        }

        public long PatchByte(long fileOffset, byte patchByte)
        {
            inputFile.Seek(fileOffset, SeekOrigin.Begin);
            inputFile.WriteByte(patchByte);

            return inputFile.Position;
        }

        public long PatchString(long fileOffset, string patchString)
        {
            System.Text.Encoding asciiEncoding = System.Text.Encoding.ASCII;
            byte[] encodedPatchString = asciiEncoding.GetBytes(patchString);

            return PatchArray(fileOffset, encodedPatchString);
        }

        public long PatchDelphiString(long fileOffset, string patchString)
        {
            System.Text.Encoding asciiEncoding = System.Text.Encoding.GetEncoding(1250);
            
            byte[] encodedPatchString = asciiEncoding.GetBytes(patchString);

            PatchByte(fileOffset, (byte)encodedPatchString.Length);
            PatchArray(fileOffset + 1, encodedPatchString);

            return inputFile.Position;
        }

        public long PatchInt32(long fileOffset, Int32 patchInt32)
        {
            byte[] encodedInt32 = BitConverter.GetBytes(patchInt32);
            PatchArray(fileOffset, encodedInt32);

            return inputFile.Position;
        }

        public long PatchFill(long fileOffset, long Length, byte patchByteFill)
        {
            while (Length-- != 0)
            {
                PatchByte(fileOffset++, patchByteFill);
            }

            return inputFile.Position;
        }

    }
}

Źródła dostępne także na https://github.com/PELock/Simple-DotNet-Patcher-Class