Friday 8 November 2013

Crackmes [Reverse write-up] - Trojaner's Patch/InjectMe write-up (DLL injection)

Task from crackmes:
Patch or Inject it with a DLL, so that it will show the goodboy message :)
Happy Hacking!


As the task clearly says we have to use DLL injection in order to solve this task. Let's execute binary. The program asks to press Enter then it prints that it is not available and terminates after the enter key press.


Let's look at the binary internals. It is a good idea to start from strings. There is a string that we need to make printed ("Cracked: Yes, thank you for using my PatchMe, i hope you had fun :)"). You can determine a place in code to reach by finding usage of this string. To reach it there are two comparisons (calls to checkCountry) that had to be fixed. Look at the pictures to find out their locations.


Both of them compare return result of the checkCountry (called by me) function to 1. However this function never happens to return 1. It always returns 0.


Let's assume that we cannot patch the binary (it may have signature or some kind of digest value to check). Therefore DLL injection should be used to patch virtual memory image of the program's process to change execution flow. The idea of the DLL injection in our case is like that.

1) Our process Injector.exe creates another process with PatchMe.exe binary in a suspended state.
2) Our process Injector.exe allocates memory in virtual space of PatchMe.exe process
3) Our process Injector.exe makes an injection.dll loaded to the allocated space
4) injection.dll in DllMain function finds location of the places to patch
5) injection.dll in DllMain function patches binary and finishes execution
6) Our process Injector.exe resumes PatchMe.exe process and terminates
7) PatchMe.exe process continues execution of the patched code

Let's describe Injector.exe actions in detail.
Firstly suspended process is created by using CREATE_SUSPENDED flag in CreateProcess. Then our library is injected into that process. To do that we have to allocate memory using VirtualAllocEx function and write the name of our DLL there. Then function LoadLibrary should be passed to the CreateRemoteThread and will be called with the supplied DLL filename as an argument. Here will be executed DllMain function of the supplied DLL. This completes initialization of the PatchMe.exe process with injected DLL. Then process is resumed.

#include <windows.h>;
 
BOOL InjectLibrary(HANDLE hProcess, char *fnDll) {
 
    BOOL success = FALSE;
    HANDLE hThread = NULL;
    char *fnRemote = NULL;
    FARPROC procLoadLibraryA = NULL;
 
    size_t lenFilename = strlen(fnDll) + 1;
 
    /* Allocate space in the remote process */
    fnRemote = (char *)VirtualAllocEx(hProcess, NULL, lenFilename, MEM_COMMIT, PAGE_READWRITE);
     
    if (fnRemote) {
 
        /* Write the filename to the remote process. */
        if (WriteProcessMemory(hProcess, fnRemote, fnDll, lenFilename, NULL)) {
 
            /* Get the address of the LoadLibraryA function */
            procLoadLibraryA = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");
            hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)procLoadLibraryA, fnRemote, 0, NULL);
 
            if (hThread) {
                WaitForSingleObject(hThread, INFINITE);
                success = TRUE;
            }
        }
 
        VirtualFreeEx(hProcess, fnRemote, 0, MEM_RELEASE);
    }
 
    return success;
}
 
int main() { 
 STARTUPINFO info;
 PROCESS_INFORMATION process_info;
 ZeroMemory(&info, sizeof(STARTUPINFO));
 info.cb = sizeof(STARTUPINFO);
 
 if (CreateProcess("PatchMe.exe",NULL, NULL,NULL,NULL,NORMAL_PRIORITY_CLASS |CREATE_SUSPENDED,NULL,NULL,&info,&process_info) == TRUE)
 {
  InjectLibrary(process_info.hProcess, "injection.dll"); 
 }
 ResumeThread(process_info.hThread);
    
    return 0;
}
You can read more here.

Now it is time for injection.dll description. Its task is to patch 2 calls to the checkCountry function. These calls should be redirected to the returnOne function in order to pass comparisons mentioned earlier. Since there is an enabled address space randomization we cannot rely on any address, however we can rely on offsets. It is possible to obtain a module entry point. Thus all we need to do is calculate the offset from the module beginning to the place where the call to checkCountry occurs. To accomplish this let's find an entry point and addresses of calls in disassembler.

The offsets are : -0x949 and -0x8b3. We can use them to calculate runtime addresses of the calls to patch them. First module among the results (modules[0]) of the EnumProcessModules is happens to be PatchMe.exe, so an information about its entry point can be get using GetModuleInformation function. Then patching takes place. Relative offset should be calculated to jump from patched place to the returnOne function supplied by injected DLL. Finally instruction is written to the memory using WriteProcessMemory function.

injection.dll code:

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

#include <Psapi.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>

#define FIRST_USE_OFFSET -0x949
#define SECOND_USE_OFFSET -0x8b3

void ErrorExit(LPTSTR lpszFunction);
void patchJump(LPVOID moduleEntryPoint, DWORD patchOffset, LPVOID targetToJump, BOOL printBeforeAndAfter = TRUE);

void returnOne()
{
 printf("returnOne called\n");
 __asm {
  mov eax, 1
 }

}
void printCurrentProcessMemoryAtAddress(LPVOID address,SIZE_T dwLen)
{
 unsigned char* mem = new unsigned char[dwLen];
 SIZE_T read;

 if (ReadProcessMemory(GetCurrentProcess(), address,mem,dwLen,&read) == FALSE) 
 {
  ErrorExit(TEXT("ReadProcessMemory"));
 }
 else 
 {
  int lineCount = read / 16;
  for(int i = 0; i < lineCount; i++)
  {
   printf("line %d :",i);
   int columnCount = ((i == lineCount - 1) ? ((read % 16 == 0)? 16 : read % 16) : 16 );
   for ( int j = 0; j < columnCount; ++j)
   {
    printf("%02hhX ", mem[i * 16 + j]);
   }
   printf("\n");
  }
 }
 delete[] mem;
}
bool APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {

 if (dwReason == DLL_PROCESS_ATTACH) {
  HMODULE modules[1024];
  DWORD cbNeeded;
  MODULEINFO module_info;

  printf("DLL injection by Digital\n");
  printf("Pointer to returnOne:%08x\n", returnOne);
  returnOne();
  if (EnumProcessModules(GetCurrentProcess(),modules,sizeof(modules),&cbNeeded))
  {
   for (DWORD i = 0; i < cbNeeded / sizeof(HMODULE); ++i)
   {
    TCHAR szModName[MAX_PATH];
    if ( GetModuleFileNameEx( GetCurrentProcess(), modules[i], szModName,sizeof(szModName) / sizeof(TCHAR)))
    {
     _tprintf( TEXT("\t%s (0x%08X)\n"), szModName, modules[i] );
    }
   }
  }

  if (GetModuleInformation(GetCurrentProcess(),modules[0],&module_info, sizeof(module_info)) == FALSE)
  {
   ErrorExit(TEXT("GetModuleInformation"));
  }
  printf("EntryPoint:%08x\n",module_info.EntryPoint);

  patchJump(module_info.EntryPoint,FIRST_USE_OFFSET,returnOne);
  patchJump(module_info.EntryPoint,SECOND_USE_OFFSET,returnOne);
 }

 return true;
}

void patchJump(LPVOID moduleEntryPoint, DWORD patchOffset, LPVOID targetToJump, BOOL printBeforeAndAfter)
{
 LPCH address = (LPCH)moduleEntryPoint + patchOffset;
 DWORD offsetForJump = 0;
 DWORD written = 0;
 UCHAR patch[5];

 if (printBeforeAndAfter)
 {
  printCurrentProcessMemoryAtAddress(address ,30);
 }

 offsetForJump =  (DWORD)returnOne - (DWORD)address - 5;
 printf("Jump offset : %08x\n", offsetForJump);

 patch[0] = (unsigned char)0xe8;
 *(PDWORD)&patch[1] = offsetForJump;

 if (WriteProcessMemory(GetCurrentProcess(),address,patch, sizeof(patch), &written) == FALSE || written != sizeof(patch))
 {
  ErrorExit(TEXT("WriteProcessMemory"));
 }
 printf("After:\n");
 if (printBeforeAndAfter)
 {
  printCurrentProcessMemoryAtAddress(address,30);
 }
}

void ErrorExit(LPTSTR lpszFunction) 
{ 
 LPVOID lpMsgBuf;
 LPVOID lpDisplayBuf;
 DWORD dw = GetLastError(); 

 FormatMessage(
  FORMAT_MESSAGE_ALLOCATE_BUFFER | 
  FORMAT_MESSAGE_FROM_SYSTEM |
  FORMAT_MESSAGE_IGNORE_INSERTS,
  NULL,
  dw,
  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  (LPTSTR) &lpMsgBuf,
  0, NULL );

 lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
  (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 
 StringCchPrintf((LPTSTR)lpDisplayBuf, 
  LocalSize(lpDisplayBuf) / sizeof(TCHAR),
  TEXT("%s failed with error %d: %s"), 
  lpszFunction, dw, lpMsgBuf); 
 MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

 LocalFree(lpMsgBuf);
 LocalFree(lpDisplayBuf);
 ExitProcess(dw); 
}

Let's run Injector.exe to ensure that it works. 


3 comments:

  1. Thanks for solving my crackme :)
    You are the first person who did this by injecting

    ReplyDelete
    Replies
    1. Thank you for the task. It was interesting to practice DLL injection :)

      Delete