There is one famous crackme. After studying it, it turns out that for its hacking, it is enough to patch two bytes at 40138B (score a conditional jump with NOPs) and two bytes at 401243 (register an unconditional jump there). I want to write a program that does this automatically.

 #include <iostream> #include <vector> #include <string> #include <Windows.h> enum {OK, CANT_OPEN}; int patch (TCHAR* fname) { char first_patch[2] = {0x90, 0x90}; // NOP NOP char second_patch[2] = {0xEB, 0x07}; // JMP 40124C HANDLE hFile = CreateFile(fname, FILE_ALL_ACCESS, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return CANT_OPEN; int SizeFile = GetFileSize(hFile, NULL); HANDLE MhFile = CreateFileMapping(hFile, 0, PAGE_READWRITE, 0, SizeFile, 0); HANDLE View = MapViewOfFile(MhFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); UnmapViewOfFile(View); CloseHandle(hFile); CloseHandle(MhFile); return OK; } int main (int argc, char *argv[]) { TCHAR* default_fname = "CRACKME.EXE"; if (argc > 1) { default_fname = argv[1]; } patch(default_fname); std::cout << default_fname << std::endl; return 0; } 

I projected the executable file into the address space of the process and received the address of this projection. Now you need to patch this image, after which the UnmapViewOfFile procedure will save the changes to disk.

How to find addresses like 40138B and 401243 in this projection? Where can I see the theory and examples of editing PE files?

Next, I write crap , you can not read. Successfully printed the MZ signature, which means that using View you can fill in header structures, possibly using Chris Kaspersky's macros, for alignment.

 char M = *((char *) View); char Z = *((char *) View + 1); std::cout << M << Z << std::endl; 

Verify that the DOS header is successfully loaded. Then add the code to catch possible errors.

 unsigned myRVAtoRAW(HANDLE pe_image, unsigned rva) { IMAGE_DOS_HEADER dos_header; std::memcpy(reinterpret_cast<char*>(&dos_header), pe_image, sizeof(dos_header)); std::cout << (dos_header.e_magic == 'ZM') << std::endl; return 0; } 
  • 2
    Learn the format of MZPE headers, especially the section tables. Then you write an RVA broadcast (and not even a VA like yours) to a physical offset. - Vladimir Martyanov

2 answers 2

You can go two ways:

  1. Understand and write yourself. Then I recommend reading the article: Developing RvaToRaw and RawToRva functions .
  2. Either use the functions from the famous dbghelp.dll .

I would advise to understand, the benefit is not difficult. Also worth noting is the famous article On Packers for the last time: Part One - Theoretical

  • Oh, good articles, thanks. - typemoon
  • To get ImageBase, I can use the pointer that returned MapViewOfFile and fill in the header structures? - typemoon
  • ImageBase is the value that is specified in the header. This is nothing more than a hypothesis that the compiler has put forward regarding the download address of the executable file. The compiler needs to somehow build code using addresses! From something it is necessary to make a start. This “something” is ImageBase . But in fact it may be that the system loader downloaded to a different address and then the table of relocatable elements takes effect. - sys_dev
  • And I do not understand this. If the absolute address in the RAM is made up of the relative address and the load address, then why the load address can be the same? Memory in Windows is one big segment, but programs must be located in it at different addresses. Why can be downloaded several programs with a download address of 400,000? Even in the article they write that this address can be changed by the loader. - typemoon
  • It is clear that one address is visible in HIEW, but it can change when loading, so ImageBase must be retrieved from the structure field after loading. Right? - typemoon

With regards to patching:

 int main() { HWND hWnd = FindWindow(0, "Calculator"); if(hWnd == 0){ MessageBox(0, "Error cannot find window.", "Error", MB_OK|MB_ICONERROR); } else { DWORD proccess_ID; GetWindowThreadProcessId(hWnd, &proccess_ID); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proccess_ID); if(!hProcess){ MessageBox(0, "Could not open the process!", "Error!", MB_OK|MB_ICONERROR); } else { DWORD newdatasize = sizeof(newdata); if(WriteProcessMemory(hProcess, (LPVOID)0x40138B , "0x90", newdatasize, NULL)){ MessageBox(NULL, "WriteProcessMemory worked.", "Success", MB_OK + MB_ICONINFORMATION); } else { MessageBox(NULL, "Error cannot WriteProcessMemory!", "Error", MB_OK + MB_ICONERROR); } CloseHandle(hProcess); } } return 0; } 

Addresses can be searched by pattern (unique signature), it can be done in any debugger.