/** * @brief Checks whether injected code is in good shape and injects if not yet injected. * * If injected code is not found injection is attempted unless 3rd party overwrote * original code at injection location. */ void HardHook::check() { if (memcmp(baseptr, replace, 6) != 0) { if (memcmp(baseptr, orig, 6) == 0) { fods("HH: Restoring function %p", baseptr); inject(true); } else { fods("HH: Function %p replaced by third party. Lost."); } } }
/** * @brief Checks whether injected code is in good shape and injects if not yet injected. * * If injected code is not found injection is attempted unless 3rd party overwrote * original code at injection location. */ void HardHook::check() { if (memcmp(baseptr, replace, CODEREPLACESIZE) != 0) { // The instructions do not match our replacement instructions // If they match the original code, inject our hook. if (memcmp(baseptr, orig, CODEREPLACESIZE) == 0) { fods("HardHook: Reinjecting hook into function %p", baseptr); //TODO: Why do we want to force, even without a trampoline?!? inject(true); } else { fods("HardHook: Function %p replaced by third party. Lost injected hook."); } } }
void checkD3D9Hook(bool preonly) { if (bChaining) { ods("D3D9: Causing a chain"); return; } bChaining = true; HMODULE hD3D = GetModuleHandle("D3D9.DLL"); if (hD3D != NULL) { if (! bHooked) { char procname[2048]; GetModuleFileName(NULL, procname, 2048); fods("D3D9: CreateWnd in unhooked D3D App %s", procname); bHooked = true; // Add a ref to ourselves; we do NOT want to get unloaded directly from this process. GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char *>(&HookCreate), &hSelf); // Can we use the prepatch data? GetModuleFileName(hD3D, procname, 2048); if (_stricmp(d3dd->cFileName, procname) == 0) { unsigned char *raw = (unsigned char *) hD3D; HookCreateRaw((voidFunc)(raw + d3dd->iOffsetCreate)); if (d3dd->iOffsetCreateEx) HookCreateRawEx((voidFunc)(raw + d3dd->iOffsetCreateEx)); } else if (! preonly) { fods("D3D9 Interface changed, can't rawpatch"); pDirect3DCreate9 d3dc9 = reinterpret_cast<pDirect3DCreate9>(GetProcAddress(hD3D, "Direct3DCreate9")); ods("Got %p", d3dc9); if (d3dc9) { IDirect3D9 *id3d9 = d3dc9(D3D_SDK_VERSION); if (id3d9) { HookCreate(id3d9); id3d9->Release(); } else { ods("Failed Direct3DCreate9"); } } else { ods("D3D Library without Direct3DCreate9?"); } } else { bHooked = false; } } } bChaining = false; }
/** * @brief Injects redirection code into the target function. * * Replaces the first 6 Bytes of the function indicated by baseptr * with the replacement code previously generated (usually a jump * to mumble code). If a trampoline is available this injection is not needed * as control flow was already permanently redirected by HardHook::setup . * * @param force Perform injection even when trampoline is available. */ void HardHook::inject(bool force) { if (! baseptr) return; if (! force && bTrampoline) return; DWORD origProtect; if (VirtualProtect(baseptr, CODEREPLACESIZE, PAGE_EXECUTE_READWRITE, &origProtect)) { for (int i = 0; i < CODEREPLACESIZE; ++i) { baseptr[i] = replace[i]; // Replace with jump to new code } DWORD tempProtect; VirtualProtect(baseptr, CODEREPLACESIZE, origProtect, &tempProtect); FlushInstructionCache(GetCurrentProcess(), baseptr, CODEREPLACESIZE); } // Verify that the injection was successful for (int i = 0; i < CODEREPLACESIZE; ++i) { if (baseptr[i] != replace[i]) { fods("HardHook: Injection failure noticed at byte %d", i); } } }
void checkDXGIHook(bool preonly) { if (bChaining) { ods("DXGI: Causing a chain"); return; } if (! dxgi->iOffsetPresent || ! dxgi->iOffsetResize) return; bChaining = true; HMODULE hDXGI = GetModuleHandleW(L"DXGI.DLL"); HMODULE hD3D10 = GetModuleHandleW(L"D3D10CORE.DLL"); if (hDXGI && hD3D10) { if (! bHooked) { wchar_t procname[2048]; GetModuleFileNameW(NULL, procname, 2048); fods("DXGI: Hookcheck '%ls'", procname); bHooked = true; // Add a ref to ourselves; we do NOT want to get unloaded directly from this process. GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char *>(&checkDXGIHook), &hSelf); // Can we use the prepatch data? GetModuleFileNameW(hDXGI, procname, 2048); if (_wcsicmp(dxgi->wcDXGIFileName, procname) == 0) { unsigned char *raw = (unsigned char *) hDXGI; HookPresentRaw((voidFunc)(raw + dxgi->iOffsetPresent)); HookResizeRaw((voidFunc)(raw + dxgi->iOffsetResize)); GetModuleFileNameW(hD3D10, procname, 2048); if (_wcsicmp(dxgi->wcD3D10FileName, procname) == 0) { unsigned char *raw = (unsigned char *) hD3D10; HookAddRelease((voidFunc)(raw + dxgi->iOffsetAddRef), (voidFunc)(raw + dxgi->iOffsetRelease)); } } else if (! preonly) { fods("DXGI Interface changed, can't rawpatch"); } else { bHooked = false; } } } bChaining = false; }
// EnsureMinHookInitialized ensures that the MinHook // library is initialized. If MinHook is already // initialized, calling this function is a no-op. static void EnsureMinHookInitialized() { // Ensure MH_Initialize is only called once. if (InterlockedCompareExchange(&minhook_init_once, 1, 0) == 0) { MH_STATUS status = MH_Initialize(); if (status != MH_OK && status != MH_ERROR_ALREADY_INITIALIZED) { fods("HardHook: Failed to initialize minhook; MH_Initialize returned %s", MH_StatusToString(status)); } } }
/** * @brief Makes sure the given replacement function is run once func is called. * * Tries to construct a trampoline for the given function (@see HardHook::cloneCode) * and then injects replacement function calling code into the first 6 bytes of the * original function (@see HardHook::inject). * * @param func Pointer to function to redirect. * @param replacement Pointer to code to redirect to. */ void HardHook::setup(voidFunc func, voidFunc replacement) { if (baseptr) return; fods("HardHook: Setup: Asked to replace %p with %p", func, replacement); unsigned char *fptr = reinterpret_cast<unsigned char *>(func); unsigned char *nptr = reinterpret_cast<unsigned char *>(replacement); call = (voidFunc) cloneCode((void **) &fptr); if (call) { bTrampoline = true; } else { // Could not create a trampoline. Use alternative method instead. bTrampoline = false; call = func; } DWORD origProtect; if (VirtualProtect(fptr, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) { unsigned char **iptr = reinterpret_cast<unsigned char **>(&replace[1]); replace[0] = 0x68; // PUSH immediate 1 Byte *iptr = nptr; // (imm. value = nptr) 4 Byte replace[5] = 0xc3; // RETN 1 Byte // Save original 6 bytes at start of original function for (int i = 0; i < CODEREPLACESIZE; ++i) orig[i] = fptr[i]; baseptr = fptr; //TODO: Why do we want to force, even without a trampoline?!? We may // break the x86 instructions. // This is only safe as long as we always restore before calling .call. inject(true); DWORD tempProtect; VirtualProtect(fptr, CODEPROTECTSIZE, origProtect, &tempProtect); } else { fods("HardHook: setup failed; failed to make original code read and executable"); } }
/** * @brief Makes sure the given replacement function is run once func is called. * * Uses MinHook to put the hook in place. * * @param func Pointer to function to redirect. * @param replacement Pointer to code to redirect to. */ void HardHook::setup(voidFunc func, voidFunc replacement) { m_func = func; m_replacement = replacement; MH_STATUS status = MH_CreateHook((LPVOID) func, (LPVOID)replacement, (LPVOID *)&call); if (status != MH_OK) { fods("HardHook: setup failed, MH_CreateHook returned %s", MH_StatusToString(status)); } inject(true); }
/** * @brief Injects redirection code into the target function. * * @param force No-op in the MinHook-based HardHook implementation. */ void HardHook::inject(bool force) { if (!force) { // MinHook guarantees the presence of a trampoline, so // inject() and restore() are no-ops unless force is // set to true. return; } MH_STATUS status = MH_EnableHook((LPVOID)m_func); if (status != MH_OK) { fods("HardHook: inject() failed: MH_EnableHook returned %s", MH_StatusToString(status)); } }
/** * @brief Makes sure the given replacement function is run once func is called. * * Tries to construct a trampoline for the given function (@see HardHook::cloneCode) * and then injects replacement function calling code into the first 6 bytes of the * original function (@see HardHook::inject). * * @param func Pointer to function to redirect. * @param replacement Pointer to code to redirect to. */ void HardHook::setup(voidFunc func, voidFunc replacement) { if (baseptr) return; unsigned char *fptr = reinterpret_cast<unsigned char *>(func); unsigned char *nptr = reinterpret_cast<unsigned char *>(replacement); fods("HardHook: Asked to replace %p with %p", func, replacement); call = (voidFunc) cloneCode((void **) &fptr); if (call) { bTrampoline = true; } else { // Could not create a trampoline. Use alternative method instead. bTrampoline = false; call = func; } DWORD oldProtect; if (VirtualProtect(fptr, 16, PAGE_EXECUTE_READ, &oldProtect)) { unsigned char **iptr = reinterpret_cast<unsigned char **>(&replace[1]); replace[0] = 0x68; // PUSH immediate 1 Byte *iptr = nptr; // (imm. value = nptr) 4 Byte replace[5] = 0xc3; // RETN 1 Byte for (int i=0;i<6;i++) // Save original 6 bytes at start of original function orig[i] = fptr[i]; baseptr = fptr; inject(true); DWORD restoreProtect; VirtualProtect(fptr, 16, oldProtect, &restoreProtect); } else { fods("HardHook: Failed vprotect"); } }
/** * @brief Injects redirection code into the target function. * * Replaces the first 6 Bytes of the function indicated by baseptr * with the replacement code previously generated (usually a jump * to mumble code). If a trampoline is available this injection is not needed * as control flow was already permanently redirected by HardHook::setup . * * @param force If true injection will be performed even when trampoline is available. */ void HardHook::inject(bool force) { DWORD oldProtect, restoreProtect; int i; if (! baseptr) return; if (! force && bTrampoline) return; if (VirtualProtect(baseptr, 6, PAGE_EXECUTE_READWRITE, &oldProtect)) { for (i=0;i<6;i++) baseptr[i] = replace[i]; // Replace with jump to new code VirtualProtect(baseptr, 6, oldProtect, &restoreProtect); FlushInstructionCache(GetCurrentProcess(),baseptr, 6); } for (i=0;i<6;i++) if (baseptr[i] != replace[i]) fods("HH: Injection failure at byte %d", i); }
/** * @brief No-op in MinHook-based HardHook implementation. */ void HardHook::check() { fods("HardHook: unused 'check' method called for MinHook-based HardHook"); }
void HardHook::print() { fods("HardHook: unused 'print' method called for MinHook-based HardHook"); }
void readConfig( const char* filename ) { /* States: * 0 : reading new entities ( to: 1, 3, 4, 5 ) * 1 : reading machine * 2 : reading role * 3 : reading core * 4 : reading file * 5 : reading test ( to: 2 ) */ int state = 0; struct machine m; struct role r; struct file f; struct core c; struct test t; xmlTextReaderPtr xmlRead = xmlReaderForFile( filename, NULL, XML_PARSE_NONET | XML_PARSE_NOENT | XML_PARSE_NOCDATA | XML_PARSE_NOXINCNODE | XML_PARSE_COMPACT ); if( !xmlRead ) quit( "Could not open %s for reading as XML config\n", filename ); xmlTextReaderSetErrorHandler( xmlRead, xmlErrorHandler, 0 ); int nodeType, ret; xmlChar* nodeName; while( 1 ) { if( ( ret = xmlTextReaderRead( xmlRead ) ) != 1 ) { if( ret < 0 ) quit( "Error occurred\n" ); if( state ) quit( "No more nodes to read, but state is not 0. Incomplete elements.\n" ); xmlTextReaderClose( xmlRead ); return; } nodeType = xmlTextReaderNodeType( xmlRead ); nodeName = NULL; switch( nodeType ) { case XmlNodeType.Element : nodeName = xmlTextReaderLocalName( xmlRead ); switch( state ) { case 0 : if( !strcmp( nodeName, "machine" ) ) { state = 1; bzero( m, sizeof( struct machine ) ); readValidRequiredAttribute( m.name, "machine", "name" ); readValidRequiredAttribute( m.address, "machine", "address" ); if( !strcmp( m.address, "DAS4" ) ) { readValidRequiredAttribute( m.count, "machine", "DAS4 node count" ); char* end = NULL; m.icount = strtol( m.count, &end, 10 ); if( end == m.count || *end || m.icount < 1 ) quit( "Invalid DAS4 node count for machine %s\n", m.name ); } break; } if( !strcmp( nodeName, "core" ) ) { state = 3; bzero( c, sizeof( struct core ) ); readValidRequiredAttribute( c.name, "core", "name" ); break; } if( !strcmp( nodeName, "file" ) ) { state = 4; bzero( f, sizeof( struct file ) ); readValidRequiredAttribute( f.name, "file", "name" ); readValidRequiredAttribute( f.size, "file", "size" ); char* end = NULL; f.isize = strtol( f.size, &end, 10 ); if( end == f.size || f.isize < 1 ) quit( "Invalid size specifier for file %s\n", f.name ); if( *end && *(end+1) ) quit( "Invalid size specifier for file %s\n", f.name ); if( *end ) { switch( *end ) { case 'b' : case 'B' : break; case 'k' : case 'K' : f.isize *= 1024; break; case 'm' : case 'M' : f.isize *= 1024 * 1024; break; case 'g' : case 'G' : f.isize *= 1024 * 1024 * 1024; break; case 't' : case 'T' : f.isize *= 1024 * 1024 * 1024 * 1024; break; default : quit( "Invalid size specifier for file %s\n", f.name ); } } if( f.isize > 512 * 1024 * 1024 ) { int p = 512*1024*1024; while( p < f.isize ) p <<= 1; if( p != f.isize ) quit( "Invalid size specifier for file %s: sizes above 512M should be powers of 2\n", f.name ); } if( f.isize & 0x3 ) quit( "Invalid size specifier for file %s: sizes should be a multiple of 4\n", f.name ); break; } if( !strcmp( nodeName, "test" ) ) { state = 5; bzero( t, sizeof( struct test ) ); readValidRequiredAttribute( t.name, "test", "name" ); break; } break; case 1 : if( !strcmp( nodeName, "tmpDir" ) ) { onlyOne( m.tmpdir, "machine", "tmpDir" ); readValidString( m.tmpdir, "temporary directory location" ); skipToEndElement( "tmpDir" ); break; } if( !strcmp( nodeName, "params" ) ) { onlyOne( m.params, "machine", "params" ); readValidString( m.params, "params" ); skipToEndElement( "params" ); break; } printf( "Unexpected element %s in machine %s\n", nodeName, m.name ); break; case 2 : if( !strcmp( nodeName, "machine" ) ) { if( r.machineCount > 255 ) quit( "Maximum of 256 machines per role passed on line %d\n", xmlTextReaderGetParserLineNumber( xmlRead ) ); readValid( r.machine[r.machineCount], "machine name" ); r.machineCount++; skipToEndElement( "machine" ); break; } if( !strcmp( nodeName, "user" ) ) { onlyOne( r.user, "role", "user" ); readValidString( r.user, "user" ); skipToEndElement( "user" ); break; } if( !strcmp( nodeName, "core" ) ) { onlyOne( r.core, "role", "core" ); readValidString( r.core, "core name" ); skipToEndElement( "core" ); break; } if( !strcmp( nodeName, "file" ) ) { onlyOne( r.file, "role", "file" ); readValidString( r.file, "file name" ); skipToEndElement( "file" ); break; } if( !strcmp( nodeName, "params" ) ) { onlyOne( r.params, "role", "params" ); readValidString( r.params, "params" ); skipToEndElement( "params" ); break; } printf( "Unexpected element %s in role on line %d\n", nodeName, xmlTextReaderGetParserLineNumber( xmlRead ) ); break; case 3 : if( !strcmp( nodeName, "params" ) ) { onlyOne( c.params, "core", "params" ); readValidString( c.params, "params" ); skipToEndElement( "params" ); break; } if( !strcmp( nodeName, "localDir" ) ) { onlyOne( c.localdir, "core", "localDir" ); readValidString( c.localdir, "local core directory" ); skipToEndElement( "localDir" ); break; } if( !strcmp( nodeName, "compilationDir" ) ) { onlyOne( c.compdir, "core", "compilationDir" ); readValidString( c.compdir, "relative compilation directory" ); skipToEndElement( "compilationDir" ); break; } if( !strcmp( nodeName, "program" ) ) { onlyOne( c.program, "core", "program" ); readValidString( c.program, "program name" ); skipToEndElement( "program" ); break; } printf( "Unexpected element %s in core %s\n", nodeName, c.name ); skipToEndElement( nodeName ); break; case 4 : if( !strcmp( nodeName, "offset" ) ) { onlyOne( c.offset, "file", "offset" ); readValidString( c.offset, "offset" ); char* end = NULL; f.ioffset = strtol( f.offset, &end, 10 ); if( end == f.offset || *end || f.ioffset < 0 || f >= f.isize ) quit( "Invalid file offset for file %s\n", f.name ); skipToEndElement( "offset" ); break; } printf( "Unexpected element %s in file %s\n", nodeName, f.name ); skipToEndElement( nodeName ); break; case 5 : if( !strcmp( nodeName, "role" ) ) { state = 2; bzero( r, sizeof( struct role ) ); readValidRequiredAttribute( r.type, "role", "type" ); if( strcmp( r.type, "seed" ) && strcmp( r.type, "leech" ) ) quit( "Invalid value for attribute 'type' for role on line %d, expected 'seed' or 'leech'\n", xmlTextReaderGetParserLineNumber( xmlRead ) ); break; } quit( "Unexpected element %s in test on line %d\n", nodeName, xmlTextReaderGetParserLineNumber( xmlRead ) ); default : quit( "State sanity\n", nodeName ); } break; case XmlNodeType.EndElement : nodeName = xmlTextReaderLocalName( xmlRead ); switch( state ) { case 0 : quit( "End element %s found while not in entity\n", nodeName ); case 1 : if( !strcmp( nodeName, "machine" ) ) { memcpy( machines + machineCount, &m, sizeof( struct machine ) ); machineCount++; state = 0; } break; case 2 : if( !strcmp( nodeName, "role" ) ) { mempcy( t.roles + r.roleCount, &r, sizeof( struct role ) ); t.roleCount++; state = 5; } break; case 3 : if( !strcmp( nodeName, "core" ) ) { memcpy( cores + coreCount, &c, sizeof( struct core ) ); coreCount++; state = 0; } break; case 4 : if( !strcmp( nodeName, "file" ) ) { memcpy( files + fileCount, &f, sizeof( struct file ) ); fileCount++; state = 0; } break; case 5 : if( !strcmp( nodeName, "test" ) ) { memcpy( tests + testCount, &t, sizeof( struct test ) ); testCount++; state = 0; } break; default : quit( "State sanity\n" ); } default : } if( nodeName ) free( nodeName ); } } void validateConfigAndGenerateScripts( ) { int i, j, k, l; bool found; // == Validate test-role references to machines, cores and files // == Also register which machine uses which cores and which files for( i = 0; i < testCount; i++ ) { for( j = 0; j < tests[i].roleCount; j++ ) { // Check for validity of each role's core found = false; for( k = 0; k < coreCount; k++ ) { if( !strcmp( cores[k].name, tests[i].roles[j].core ) ) { found = true; break; } } if( !found ) quit( "Test %s role %i refers to core %s which does not exist\n", tests[i].name, j, tests[i].roles[j].core ); // Check for validity of each role's file found = false; for( k = 0; k < fileCount; k++ ) { if( !strcmp( files[k].name, tests[i].roles[j].file ) ) { found = true; break; } } if( !found ) quit( "Test %s role %i refers to file %s which does not exist\n", tests[i].name, j, tests[i].roles[j].file ); // Check for validity of each role's machines for( l = 0; l < tests[i].roles[j].machineCount; l++ ) { found = false; for( k = 0; k < machineCount; k++ ) { if( !strcmp( machines[k].name, tests[i].roles[j].machines[l] ) ) { // Machine found: register used core with the machine if needed for( m = 0; m < machines[k].coreCount; m++ ) { if( !strcmp( machines[k].cores[m], tests[i].roles[j].core ) ) { found = true; break; } } if( !found ) machines[k].cores[machines[k].coreCount++] = tests[i].roles[j].core; // Machine found: register used file with the machine if needed, only if seeding if( !strcmp( tests[i].roles[j].type, "seed" ) ) { found = false; for( m = 0; m < machines[k].fileCount; m++ ) { if( !strcmp( machines[k].files[m], tests[i].roles[j].file ) ) { found = true; break; } } if( !found ) machines[k].files[machines[k].fileCount++] = tests[i].roles[j].file; } // Machine found found = true; break; } } if( !found ) quit( "Test %s role %i refers to machine %s which does not exist\n", tests[i].name, j, tests[i].roles[j].machines[l] ); } } } // Create temporary script FILE* script = tmpfile(); if( !script ) quit( "Can't create temporary script\n" ); fprintf( script, "#!/bin/bash\n" ); // == Check validity of machines and users: can each machine be accessed? for( i = 0; i < machineCount; i++ ) { // Creates a function in the script for sending command to the machine using ssh // Call using // ssh_machine_%i "commands" || cleanup -1 // where %i is the index of the machine in machines. Also be sure to return non-zero from your commands on error. fprintf( script, "function ssh_machine_%i {\n", i ); if( strcmp( machines[i].address, "DAS4" ) ) fprintf( script, " ssh -T -n -o BatchMode=yes -h \"%s\"", machines[i].address ); else fprintf( script, " ssh -T -n -o BatchMode=yes -h fs3.das4.tudelft.nl" ); if( machines[i].user ) fprintf( script, " -l \"%s\"", machines[i].user ); if( machines[i].params ) fprintf( script, " %s", machines[i].params ); fprintf( script, " $1 || return -1;\n" ); fprintf( script, "}\n" ); // Creates a function in the script for sending files to the machine using scp // Call using // scp_to_machine_%i localfile remotefile fprintf( script, "function scp_to_machine_%i {\n", i ); fprintf( script, "scp -o BatchMode=yes " ); if( machines[i].params ) fprintf( script, "%s ", machines[i].params ); fprintf( script, "$1 ", l ); if( machines[i].user ) fprintf( script, "\"%s\"@", machines[i].user ); if( strcmp( machines[i].address, "DAS4" ) ) fprintf( script, "\"%s\"", machines[i].address ); else fprintf( script, "fs3.das4.tudelft.nl" ); fprintf( script, ":$2 || cleanup -1\n", i, l ) fprintf( script, "}\n" ); // Creates a function in the script for retrieving files from the machine using scp // Call using // scp_from_machine_%i remotefile localfile fprintf( script, "function scp_from_machine_%i {\n", i ); fprintf( script, "scp -o BatchMode=yes " ); if( machines[i].params ) fprintf( script, "%s ", machines[i].params ); if( machines[i].user ) fprintf( script, "\"%s\"@", machines[i].user ); if( strcmp( machines[i].address, "DAS4" ) ) fprintf( script, "\"%s\"", machines[i].address ); else fprintf( script, "fs3.das4.tudelft.nl" ); fprintf( script, ":$1 $2 || cleanup -1\n", i, l ) fprintf( script, "}\n" ); // Checks reachability of machine fprintf( script, "ssh_machine_%i || exit -1\n", i ); } // == Check validity of each core: can each core be packaged? Can it be compiled locally and does the program then exist? // Create a cleanup file. This file should have code appended to cleanup things when errors occur or testing has finished. See existing code for examples of concatenating to it. // The cleanup function is available after this as well. Call it with an exit argument to end the script cleanly. fprintf( script, "CLEANUPFILE=`mktemp`\n" ); fprintf( script, "chmod +x CLEANUPFILE\n" ); fprintf( script, "[ -x CLEANUPFILE ] || exit -1\n" ); fprintf( script, "function cleanup {\n" ); fprintf( script, " (\ncat <<EOL\nrm $CLEANUPFILE\nEOL\n) >> $CLEANUPFILE\n" ); fprintf( script, " . $CLEANUPFILE\n" ); fprintf( script " exit $1\n" ); fprintf( script, "}\n" ); // Create a local temporary directory for storage fprintf( script, "TARDIR=`mktemp -d`\n[ \"X${TARDIR}X\" == \"XX\" ] && exit -1\n" ); fprintf( script, "(\ncat <<EOL\n#!/bin/bash\nrm -rf $TARDIR\nEOL\n) >> $CLEANUPFILE\n" ); fprintf( script, "CURDIR=`pwd`\n" ); // Create a tarball for the testenvironment in the temporary local storage fprintf( script, "make clean || cleanup -1\n" ); fprintf( script, "tar cf ${TARDIR}/testenvironment.tar . || cleanup -1\n" ); fprintf( script, "bzip2 ${TARDIR}/testenvironment.tar || cleanup -1\n" ); // Check whether the needed tools of the testenvironment compile locally fprintf( script, "make genfakedata || cleanup -1\n" ); for( i = 0; i < coreCount; i++ ) { if( !cores[i].localdir ) cores[i].localdir = strdup( "../" ); // Create a tarball for the core in the temporary local storage fprintf( script, "cd %s || cleanup -1\n", cores[i].localdir ); fprintf( script, "make clean\n" ); // Not checked: SHOULD be available, but... fprintf( script, "tar cf ${TARDIR}/core_%i.tar . || cleanup -1\n", i ); fprintf( script, "bzip2 ${TARDIR}/core_%i.tar || cleanup -1\n", i ); if( !cores[i].compdir ) cores[i].compdir = strdup( "testenvironment/" ); // Check whether the core compiles locally and the program exists after fprintf( script, "cd %s || cleanup -1\n", cores[i].compdir ); fprintf( script, "make || cleanup -1\n" ); if( !cores[i].program ) cores[i].program = strdup( "swift" ); fprintf( script, "[ -x %s ] || cleanup -1\n", cores[i].program ); fprintf( script, "cd ${CURDIR}\n" ); } // For each file, precalculate the hash for( i = 0; i < fileCount; i++ ) { // Create some temporary file to write fake data to. These fake data files will be regenerated at each machine since generating is faster than copying. size_t size = files[i].isize; FILE* data = tmpfile(); if( !data ) quit( "can't create temporary data file\n" ); int datan = fileno( data ); int filesize; if( size > 512*1024*1024 ) filesize = 512*1024*1024; else filesize = size; if( generateFakeData( datan, filesize ) ) quit( "could not write fake data\n" ); MemoryHashStorage mhs; FileOffsetDataStorage fods( datan, files[i].ioffset ); if( !fods.valid() ) quit( "can't read back from temporary data file\n" ); HashTree ht( fods, Sha1Hash::ZERO, mhs ); Sha1Hash hash = ht.root_hash(); if( size > filesize ) { MemoryHashStorage mhs2; int max = size/filesize; for( j = 0; j < max; j++ ) mhs2.setHash( bin64(0,j), hash ); int lvl = 0; do { max >>= 1; lvl++; for( j = 0; j < max; j++ ) mhs2.hashLeftRight( bin64_t(lvl, j) ); } while( max > 1 ); files[i].hash = mhs.getHash( bin64_t(lvl, 0) ); } else
/** * @brief Tries to construct a trampoline from original code. * * A trampoline is called by an injected mumble function to return * control flow to the original code. * * For this to work we have to save all commands overlapping the first 6 bytes * of code in the original function. This is needed so the first redirection, * to our mumble code, can be inserted in their place. * * As commands must not be destroyed they have to be disassembled to get their length. * All encountered commands will be part of the trampoline and stored in pCode (shared * for all trampolines). * * If code is encountered that can not be moved into the trampoline (conditionals etc.) * construction fails and and NULL is returned. If enough commands can be saved the * trampoline is finalized by appending a jump back to the original code. The return value * in this case will be the address of the newly constructed trampoline. * * pCode + offset to trampoline: * [SAVED CODE FROM ORIGINAL > 6 bytes][JUMP BACK TO ORIGINAL CODE] * * @param porig Original code * @return Pointer to trampoline on success. NULL if trampoline construction failed. */ void *HardHook::cloneCode(void **porig) { DWORD oldProtect, restoreProtect; if (! pCode || uiCode > 4000) { uiCode = 0; pCode = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); } unsigned char *o = (unsigned char *) *porig; unsigned char *n = (unsigned char *) pCode; n += uiCode; unsigned int idx = 0; if (!VirtualProtect(o, 16, PAGE_EXECUTE_READ, &oldProtect)) { fods("HardHook: Failed vprotect (1)"); return NULL; } while (*o == 0xe9) { // JMP unsigned char *tmp = o; int *iptr = reinterpret_cast<int *>(o+1); // Follow jmp relative to next command. It doesn't make a difference // if we actually perform all the jumps or directly jump to the end of // the chain. Hence these jumps need not be part of the trampoline. o += *iptr + 5; fods("HardHook: Chaining from %p to %p", *porig, o); *porig = o; // Assume jump took us out of our read enabled zone, get rights for the new one VirtualProtect(tmp, 16, oldProtect, &restoreProtect); if (!VirtualProtect(o, 16, PAGE_EXECUTE_READ, &oldProtect)) { fods("HardHook: Failed vprotect (2)"); return NULL; } } do { unsigned char opcode = o[idx]; unsigned char a = o[idx+1]; unsigned char b = o[idx+2]; unsigned int extra = 0; n[idx] = opcode; idx++; switch (opcode) { case 0x50: // PUSH case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: // POP case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: break; case 0x68: // PUSH immediate extra = 4; break; case 0x81: // CMP immediate extra = modrmbytes(a,b) + 5; break; case 0x83: // CMP extra = modrmbytes(a,b) + 2; break; case 0x8b: // MOV extra = modrmbytes(a,b) + 1; break; default: fods("HardHook: Unknown opcode at %d: %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x", idx-1, o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7], o[8], o[9], o[10], o[11]); VirtualProtect(o, 16, oldProtect, &restoreProtect); return NULL; break; } for (unsigned int i=0;i<extra;++i) n[idx+i] = o[idx+i]; idx += extra; } while (idx < 6); VirtualProtect(o, 16, oldProtect, &restoreProtect); n[idx++] = 0xe9; // Add a relative jmp back to the original code int offs = o - n - 5; int *iptr = reinterpret_cast<int *>(&n[idx]); *iptr = offs; idx += 4; uiCode += idx; FlushInstructionCache(GetCurrentProcess(), n, idx); return n; }
void HardHook::print() { fods("HardHook: code replacement: %02x %02x %02x %02x %02x => %02x %02x %02x %02x %02x (currently effective: %02x %02x %02x %02x %02x)", orig[0], orig[1], orig[2], orig[3], orig[4], replace[0], replace[1], replace[2], replace[3], replace[4], baseptr[0], baseptr[1], baseptr[2], baseptr[3], baseptr[4]); }
void HardHook::setupInterface(IUnknown *unkn, LONG funcoffset, voidFunc replacement) { fods("HardHook: setupInterface: Replacing %p function #%ld", unkn, funcoffset); void **ptr = reinterpret_cast<void **>(unkn); ptr = reinterpret_cast<void **>(ptr[0]); setup(reinterpret_cast<voidFunc>(ptr[funcoffset]), replacement); }
void HardHook::print() { fods("HardHook: %02x %02x %02x %02x %02x => %02x %02x %02x %02x %02x (%02x %02x %02x %02x %02x)", orig[0], orig[1], orig[2], orig[3], orig[4], replace[0], replace[1], replace[2], replace[3], replace[4], baseptr[0], baseptr[1], baseptr[2], baseptr[3], baseptr[4]); }
void checkOpenGLHook() { if (bChaining) { return; ods("Causing a chain"); } bChaining = true; HMODULE hGL = GetModuleHandle("OpenGL32.DLL"); if (hGL != NULL) { if (! bHooked) { char procname[1024]; GetModuleFileName(NULL, procname, 1024); fods("OpenGL: Unhooked OpenGL App %s", procname); bHooked = true; // Add a ref to ourselves; we do NOT want to get unloaded directly from this process. GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char *>(&checkOpenGLHook), &hSelf); INJECT(wglSwapBuffers); // INJECT(wglSwapLayerBuffers); GLDEF(wglCreateContext); GLDEF(glGenTextures); GLDEF(glDeleteTextures); GLDEF(glEnable); GLDEF(glDisable); GLDEF(glBlendFunc); GLDEF(glColorMaterial); GLDEF(glViewport); GLDEF(glMatrixMode); GLDEF(glLoadIdentity); GLDEF(glOrtho); GLDEF(glBindTexture); GLDEF(glPushMatrix); GLDEF(glColor4ub); GLDEF(glTranslatef); GLDEF(glBegin); GLDEF(glEnd); GLDEF(glTexCoord2f); GLDEF(glVertex2f); GLDEF(glPopMatrix); GLDEF(glTexParameteri); GLDEF(glTexEnvi); GLDEF(glTexImage2D); GLDEF(glTexSubImage2D); GLDEF(glPixelStorei); GLDEF(wglMakeCurrent); GLDEF(wglGetCurrentContext); GLDEF(wglGetCurrentDC); hGL = GetModuleHandle("GDI32.DLL"); if (hGL) { // INJECT(SwapBuffers); GLDEF(GetDeviceCaps); } else { ods("OpenGL: Failed to find GDI32"); } } else { hhwglSwapBuffers.check(); } } bChaining = false; }
/** * @brief Tries to construct a trampoline from original code. * * A trampoline is the replacement code that features the original code plus * a jump back to the original instructions that follow. * It is called to execute the original behavior. As it is a replacement for * the original, the original can then be overwritten. * The size of the trampoline is at least CODEREPLACESIZE. Thus, CODEREPLACESIZE * bytes of the original code can afterwards be overwritten (and the trampoline * called after those instructions for the original logic). * CODEREPLACESIZE has to be smaller than CODEPROTECTSIZE. * * As commands must not be destroyed they have to be disassembled to get their length. * All encountered commands will be part of the trampoline and stored in pCode (shared * for all trampolines). * * If code is encountered that can not be moved into the trampoline (conditionals etc.) * construction fails and NULL is returned. If enough commands can be saved the * trampoline is finalized by appending a jump back to the original code. The return value * in this case will be the address of the newly constructed trampoline. * * pCode + offset to trampoline: * [SAVED CODE FROM ORIGINAL which is >= CODEREPLACESIZE bytes][JUMP BACK TO ORIGINAL CODE] * * @param porig Original code * @return Pointer to trampoline on success. NULL if trampoline construction failed. */ void *HardHook::cloneCode(void **porig) { if (! pCode || uiCode > 4000) { pCode = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); uiCode = 0; } // If we have no memory to clone to, return. if (! pCode) { return NULL; } unsigned char *o = (unsigned char *) *porig; unsigned char *n = (unsigned char *) pCode; n += uiCode; unsigned int idx = 0; DWORD origProtect; if (!VirtualProtect(o, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) { fods("HardHook: CloneCode failed; failed to make original code read and executable"); return NULL; } // Follow relative jumps to next instruction. On execution it doesn't make // a difference if we actually perform all the jumps or directly jump to the // end of the chain. Hence these jumps need not be part of the trampoline. while (*o == 0xe9) { // JMP unsigned char *tmp = o; int *iptr = reinterpret_cast<int *>(o+1); o += *iptr + 5; fods("HardHook: CloneCode: Skipping jump from %p to %p", *porig, o); *porig = o; // Assume jump took us out of our read enabled zone, get rights for the new one DWORD tempProtect; VirtualProtect(tmp, CODEPROTECTSIZE, origProtect, &tempProtect); if (!VirtualProtect(o, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) { fods("HardHook: CloneCode failed; failed to make jump target code read and executable"); return NULL; } } do { unsigned char opcode = o[idx]; unsigned char a = o[idx+1]; unsigned char b = o[idx+2]; unsigned int extra = 0; n[idx] = opcode; ++idx; switch (opcode) { case 0x50: // PUSH case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: // POP case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: break; case 0x6a: // PUSH immediate extra = 1; break; case 0x68: // PUSH immediate extra = 4; break; case 0x81: // CMP immediate extra = modrmbytes(a,b) + 5; break; case 0x83: // CMP extra = modrmbytes(a,b) + 2; break; case 0x8b: // MOV extra = modrmbytes(a,b) + 1; break; default: { int rmop = ((a>>3) & 7); if (opcode == 0xff && rmop == 6) { // PUSH memory extra = modrmbytes(a,b) + 1; break; } fods("HardHook: CloneCode failed; Unknown opcode at %d: %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x", idx-1, o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7], o[8], o[9], o[10], o[11]); DWORD tempProtect; VirtualProtect(o, CODEPROTECTSIZE, origProtect, &tempProtect); return NULL; break; } } for (unsigned int i = 0; i < extra; ++i) n[idx+i] = o[idx+i]; idx += extra; } while (idx < CODEREPLACESIZE); DWORD tempProtect; VirtualProtect(o, CODEPROTECTSIZE, origProtect, &tempProtect); // Add a relative jmp back to the original code n[idx++] = 0xe9; int *iptr = reinterpret_cast<int *>(&n[idx]); int offs = o - n - 5; *iptr = offs; idx += 4; uiCode += idx; FlushInstructionCache(GetCurrentProcess(), n, idx); fods("HardHook: trampoline creation successful at %p", n); return n; }