diff --git a/Rakefile b/Rakefile index 91fdc55..e1276be 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,40 @@ desc 'Build' task :build do + puts "\n=== Building ===" system('mkdir build') - system('gcc -o build/test_gcc_i386 -framework CoreServices *.c *.cp') + + puts "\n*** Building GCC i386 ***" + system('gcc -o build/test_gcc_i386 -arch i386 -framework CoreServices *.c *.cp') + + puts "\n*** Building GCC x86_64 ***" + system('gcc -o build/test_gcc_x86_64 -arch x86_64 -framework CoreServices *.c *.cp') + + puts "\n*** Building Clang i386 ***" + system('clang -o build/test_clang_i386 -arch i386 -framework CoreServices *.c *.cp') + + puts "\n*** Building Clang x86_64 ***" + system('clang -o build/test_clang_x86_64 -arch x86_64 -framework CoreServices *.c *.cp') end desc 'Test' -task :test do +task :test => [:build] do + puts "\n=== Testing ===" + + puts "\n*** Testing GCC i386 ***" system('build/test_gcc_i386') + puts '!!! FAILED !!!' if $?.exitstatus != 0 + + puts "\n*** Testing GCC x86_64 ***" + system('build/test_gcc_x86_64') + puts '!!! FAILED !!!' if $?.exitstatus != 0 + + puts "\n*** Testing Clang i386 ***" + system('build/test_clang_i386') + puts '!!! FAILED !!!' if $?.exitstatus != 0 + + puts "\n*** Testing Clang x86_64 ***" + system('build/test_clang_x86_64') + puts '!!! FAILED !!!' if $?.exitstatus != 0 end desc 'Clean up' @@ -14,4 +42,4 @@ task :clean do system('rm -rf build') end -task :default => [:build, :test] \ No newline at end of file +task :default => [:clean, :test, :clean] \ No newline at end of file diff --git a/mach_override.c b/mach_override.c index c1d60ef..b6f7143 100644 --- a/mach_override.c +++ b/mach_override.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -21,6 +22,7 @@ #pragma mark - #pragma mark (Constants) +#define kPageSize 4096 #if defined(__ppc__) || defined(__POWERPC__) long kIslandTemplate[] = { @@ -41,8 +43,11 @@ long kIslandTemplate[] = { #elif defined(__i386__) #define kOriginalInstructionsSize 16 +// On X86 we migh need to instert an add with a 32 bit immediate after the +// original instructions. +#define kMaxFixupSizeIncrease 5 -char kIslandTemplate[] = { +unsigned char kIslandTemplate[] = { // kOriginalInstructionsSize nop instructions so that we // should have enough space to host original instructions 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, @@ -56,10 +61,12 @@ char kIslandTemplate[] = { #elif defined(__x86_64__) #define kOriginalInstructionsSize 32 +// On X86-64 we never need to instert a new instruction. +#define kMaxFixupSizeIncrease 0 #define kJumpAddress kOriginalInstructionsSize + 6 -char kIslandTemplate[] = { +unsigned char kIslandTemplate[] = { // kOriginalInstructionsSize nop instructions so that we // should have enough space to host original instructions 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, @@ -74,9 +81,6 @@ char kIslandTemplate[] = { #endif -#define kAllocateHigh 1 -#define kAllocateNormal 0 - /************************** * * Data Types @@ -87,7 +91,6 @@ char kIslandTemplate[] = { typedef struct { char instructions[sizeof(kIslandTemplate)]; - int allocatedHigh; } BranchIsland; /************************** @@ -98,10 +101,9 @@ typedef struct { #pragma mark - #pragma mark (Funky Protos) - mach_error_t +static mach_error_t allocateBranchIsland( BranchIsland **island, - int allocateHigh, void *originalFunctionAddress); mach_error_t @@ -138,8 +140,7 @@ eatKnownInstructions( static void fixupInstructions( - void *originalFunction, - void *escapeIsland, + uint32_t offset, void *instructionsToFix, int instructionCount, uint8_t *instructionSizes ); @@ -156,12 +157,10 @@ fixupInstructions( #if defined(__i386__) || defined(__x86_64__) mach_error_t makeIslandExecutable(void *address) { mach_error_t err = err_none; - vm_size_t pageSize; - host_page_size( mach_host_self(), &pageSize ); - uintptr_t page = (uintptr_t)address & ~(uintptr_t)(pageSize-1); + uintptr_t page = (uintptr_t)address & ~(uintptr_t)(kPageSize-1); int e = err_none; - e |= mprotect((void *)page, pageSize, PROT_EXEC | PROT_READ | PROT_WRITE); - e |= msync((void *)page, pageSize, MS_INVALIDATE ); + e |= mprotect((void *)page, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE); + e |= msync((void *)page, kPageSize, MS_INVALIDATE ); if (e) { err = err_cannot_override; } @@ -216,7 +215,7 @@ mach_override_ptr( &jumpRelativeInstruction, &eatenCount, originalInstructions, &originalInstructionCount, originalInstructionSizes ); - if (eatenCount > kOriginalInstructionsSize) { + if (eatenCount + kMaxFixupSizeIncrease > kOriginalInstructionsSize) { //printf ("Too many instructions eaten\n"); overridePossible = false; } @@ -239,7 +238,7 @@ mach_override_ptr( // Allocate and target the escape island to the overriding function. BranchIsland *escapeIsland = NULL; if( !err ) - err = allocateBranchIsland( &escapeIsland, kAllocateHigh, originalFunctionAddress ); + err = allocateBranchIsland( &escapeIsland, originalFunctionAddress ); if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); @@ -281,7 +280,7 @@ mach_override_ptr( // technically our original function. BranchIsland *reentryIsland = NULL; if( !err && originalFunctionReentryIsland ) { - err = allocateBranchIsland( &reentryIsland, kAllocateHigh, escapeIsland); + err = allocateBranchIsland( &reentryIsland, escapeIsland); if( !err ) *originalFunctionReentryIsland = reentryIsland; } @@ -324,7 +323,8 @@ mach_override_ptr( // // Note that on i386, we do not support someone else changing the code under our feet if ( !err ) { - fixupInstructions(originalFunctionPtr, reentryIsland, originalInstructions, + uint32_t offset = (uintptr_t)originalFunctionPtr - (uintptr_t)reentryIsland; + fixupInstructions(offset, originalInstructions, originalInstructionCount, originalInstructionSizes ); if( reentryIsland ) @@ -361,79 +361,88 @@ mach_override_ptr( #pragma mark - #pragma mark (Implementation) +static bool jump_in_range(intptr_t from, intptr_t to) { + intptr_t field_value = to - from - 5; + int32_t field_value_32 = field_value; + return field_value == field_value_32; +} + /******************************************************************************* Implementation: Allocates memory for a branch island. @param island <- The allocated island. - @param allocateHigh -> Whether to allocate the island at the end of the - address space (for use with the branch absolute - instruction). @result <- mach_error_t ***************************************************************************/ - mach_error_t -allocateBranchIsland( +static mach_error_t +allocateBranchIslandAux( BranchIsland **island, - int allocateHigh, - void *originalFunctionAddress) + void *originalFunctionAddress, + bool forward) { assert( island ); - - mach_error_t err = err_none; - - if( allocateHigh ) { - vm_size_t pageSize; - err = host_page_size( mach_host_self(), &pageSize ); - if( !err ) { - assert( sizeof( BranchIsland ) <= pageSize ); -#if defined(__ppc__) || defined(__POWERPC__) - vm_address_t first = 0xfeffffff; - vm_address_t last = 0xfe000000 + pageSize; -#elif defined(__x86_64__) - vm_address_t first = ((uint64_t)originalFunctionAddress & ~(uint64_t)(((uint64_t)1 << 31) - 1)) | ((uint64_t)1 << 31); // start in the middle of the page? - vm_address_t last = 0x0; + assert( sizeof( BranchIsland ) <= kPageSize ); + + vm_map_t task_self = mach_task_self(); + vm_address_t original_address = (vm_address_t) originalFunctionAddress; + vm_address_t address = original_address; + + for (;;) { + vm_size_t vmsize = 0; + memory_object_name_t object = 0; + kern_return_t kr = 0; + vm_region_flavor_t flavor = VM_REGION_BASIC_INFO; + // Find the region the address is in. +#if __WORDSIZE == 32 + vm_region_basic_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; + kr = vm_region(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); #else - vm_address_t first = 0xffc00000; - vm_address_t last = 0xfffe0000; + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = vm_region_64(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); #endif - - vm_address_t page = first; - int allocated = 0; - vm_map_t task_self = mach_task_self(); - - while( !err && !allocated && page != last ) { - - err = vm_allocate( task_self, &page, pageSize, 0 ); - if( err == err_none ) - allocated = 1; - else if( err == KERN_NO_SPACE ) { -#if defined(__x86_64__) - page -= pageSize; -#else - page += pageSize; + if (kr != KERN_SUCCESS) + return kr; + assert((address & (kPageSize - 1)) == 0); + + // Go to the first page before or after this region + vm_address_t new_address = forward ? address + vmsize : address - kPageSize; +#if __WORDSIZE == 64 + if(!jump_in_range(original_address, new_address)) + break; #endif - err = err_none; - } - } - if( allocated ) - *island = (BranchIsland*) page; - else if( !allocated && !err ) - err = KERN_NO_SPACE; + address = new_address; + + // Try to allocate this page. + kr = vm_allocate(task_self, &address, kPageSize, 0); + if (kr == KERN_SUCCESS) { + *island = (BranchIsland*) address; + return err_none; } - } else { - void *block = malloc( sizeof( BranchIsland ) ); - if( block ) - *island = block; - else - err = KERN_NO_SPACE; + if (kr != KERN_NO_SPACE) + return kr; } - if( !err ) - (**island).allocatedHigh = allocateHigh; - - return err; + + return KERN_NO_SPACE; +} + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress) +{ + mach_error_t err = + allocateBranchIslandAux(island, originalFunctionAddress, true); + if (!err) + return err; + return allocateBranchIslandAux(island, originalFunctionAddress, false); } + /******************************************************************************* Implementation: Deallocates memory for a branch island. @@ -448,24 +457,9 @@ freeBranchIsland( { assert( island ); assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] ); - assert( island->allocatedHigh ); - - mach_error_t err = err_none; - - if( island->allocatedHigh ) { - vm_size_t pageSize; - err = host_page_size( mach_host_self(), &pageSize ); - if( !err ) { - assert( sizeof( BranchIsland ) <= pageSize ); - err = vm_deallocate( - mach_task_self(), - (vm_address_t) island, pageSize ); - } - } else { - free( island ); - } - - return err; + assert( sizeof( BranchIsland ) <= kPageSize ); + return vm_deallocate( mach_task_self(), (vm_address_t) island, + kPageSize ); } /******************************************************************************* @@ -582,6 +576,7 @@ static AsmInstructionMatch possibleInstructions[] = { { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax + { 0x6, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xE8, 0x00, 0x00, 0x00, 0x00, 0x58} }, // call $imm; pop %eax { 0x0 } }; #elif defined(__x86_64__) @@ -598,7 +593,11 @@ static AsmInstructionMatch possibleInstructions[] = { { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax - { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax + { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax + + //leaq offset(%rip),%rax + { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00} }, + { 0x0 } }; #endif @@ -620,7 +619,6 @@ static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* return match; } -#if defined(__i386__) || defined(__x86_64__) static Boolean eatKnownInstructions( unsigned char *code, @@ -697,70 +695,62 @@ eatKnownInstructions( static void fixupInstructions( - void *originalFunction, - void *escapeIsland, + uint32_t offset, void *instructionsToFix, int instructionCount, uint8_t *instructionSizes ) { + // The start of "leaq offset(%rip),%rax" + static const uint8_t LeaqHeader[] = {0x48, 0x8d, 0x05}; + int index; for (index = 0;index < instructionCount;index += 1) { if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative { - uint32_t offset = (uintptr_t)originalFunction - (uintptr_t)escapeIsland; uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1); *jumpOffsetPtr += offset; } - - originalFunction = (void*)((uintptr_t)originalFunction + instructionSizes[index]); - escapeIsland = (void*)((uintptr_t)escapeIsland + instructionSizes[index]); + + // leaq offset(%rip),%rax + if (memcmp(instructionsToFix, LeaqHeader, 3) == 0) { + uint32_t *LeaqOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 3); + *LeaqOffsetPtr += offset; + } + + // 32-bit call relative to the next addr; pop %eax + if (*(uint8_t*)instructionsToFix == 0xE8) + { + // Just this call is larger than the jump we use, so we + // know this is the last instruction. + assert(index == (instructionCount - 1)); + assert(instructionSizes[index] == 6); + + // Insert "addl $offset, %eax" in the end so that when + // we jump to the rest of the function %eax has the + // value it would have if eip had been pushed by the + // call in its original position. + uint8_t *op = (uint8_t*)instructionsToFix; + op += 6; + *op = 0x05; // addl + uint32_t *addImmPtr = (uint32_t*)(op + 1); + *addImmPtr = offset; + } + instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]); } } -#endif -#if defined(__i386__) -__asm( - ".text;" - ".align 2, 0x90;" - "_atomic_mov64:;" - " pushl %ebp;" - " movl %esp, %ebp;" - " pushl %esi;" - " pushl %ebx;" - " pushl %ecx;" - " pushl %eax;" - " pushl %edx;" - - // atomic push of value to an address - // we use cmpxchg8b, which compares content of an address with - // edx:eax. If they are equal, it atomically puts 64bit value - // ecx:ebx in address. - // We thus put contents of address in edx:eax to force ecx:ebx - // in address - " mov 8(%ebp), %esi;" // esi contains target address - " mov 12(%ebp), %ebx;" - " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address - " mov (%esi), %eax;" - " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address - " lock; cmpxchg8b (%esi);" // atomic move. - - // restore registers - " popl %edx;" - " popl %eax;" - " popl %ecx;" - " popl %ebx;" - " popl %esi;" - " popl %ebp;" - " ret" -); -#elif defined(__x86_64__) void atomic_mov64( uint64_t *targetAddress, uint64_t value ) { - *targetAddress = value; + bool swapOk = false; + while (!swapOk) + { + uint64_t oldValue = *targetAddress; + swapOk = OSAtomicCompareAndSwap64Barrier(oldValue, value, (int64_t*)targetAddress); + } } -#endif -#endif + +#endif // defined(__i386__) || defined(__x86_64__) diff --git a/test_mach_override.cp b/test_mach_override.cp index 77ad748..5f64111 100644 --- a/test_mach_override.cp +++ b/test_mach_override.cp @@ -5,8 +5,36 @@ #include #include "mach_override.h" -#define assertStrEqual( EXPECTED, ACTUAL ) if( strcmp( (EXPECTED), (ACTUAL) ) != 0 ) { printf( "EXPECTED: %s\nACTUAL: %s\n", (EXPECTED), (ACTUAL)); assert( strcmp( (EXPECTED), (ACTUAL) ) == 0 ); } -#define assertIntEqual( EXPECTED, ACTUAL ) if( (EXPECTED) != (ACTUAL) ) { printf( "EXPECTED: %d\nACTUAL: %d\n", (EXPECTED), (ACTUAL)); assert( (EXPECTED) == (ACTUAL) ); } +// A simple extension to assert that can print expectation information: +#define assertf(CONDITION, FORMAT, ...) \ + if (!(CONDITION)) { \ + printf((FORMAT), ##__VA_ARGS__); \ + assert(CONDITION); \ + } + +#define string_equals(s1, s2) \ + ({ \ + strcmp(s1, s2) == 0; \ + }) + +#define assertStrEqual(EXPECTED, ACTUAL) \ + assertf(string_equals(EXPECTED, ACTUAL), \ + "EXPECTED: %s\nACTUAL: %s\n", \ + EXPECTED, \ + ACTUAL); + +#define assertStr2Equal(EXPECTED1, EXPECTED2, ACTUAL) \ + assertf(string_equals(EXPECTED1, ACTUAL) || string_equals(EXPECTED2, ACTUAL), \ + "EXPECTED: %s OR %s\nACTUAL: %s\n", \ + EXPECTED1, \ + EXPECTED2, \ + ACTUAL); + +#define assertIntEqual(EXPECTED, ACTUAL) \ + assertf(EXPECTED == ACTUAL, \ + "EXPECTED: %d\nACTUAL: %d\n", \ + EXPECTED, \ + ACTUAL); //------------------------------------------------------------------------------ #pragma mark Test Local Override by Pointer @@ -41,29 +69,30 @@ void testLocalFunctionOverrideByPointer() { #pragma mark Test System Override by Pointer char* (*strerrorPtr)(int) = strerror; -const char* strerrReturnValue = "Unknown error: 0"; +const char* strerrReturnValueOn10_6 = "Unknown error: 0"; +const char* strerrReturnValueOn10_7 = "Undefined error: 0"; void testSystemFunctionOverrideByPointer() { - SInt32 sysv; - if (Gestalt( gestaltSystemVersion, &sysv ) == noErr && sysv >= 0x1070) - strerrReturnValue = "Undefined error: 0"; - // Test original. - assertStrEqual( strerrReturnValue, strerrorPtr( 0 ) ); + assertStr2Equal(strerrReturnValueOn10_6, + strerrReturnValueOn10_7, + strerrorPtr(0)); // Override system function by pointer. kern_return_t err; - MACH_OVERRIDE( char*, strerror, (int errnum), err ) { + MACH_OVERRIDE(char*, strerror, (int errnum), err) { // Test calling through the reentry island back into the original // implementation. - assertStrEqual( strerrReturnValue, strerror_reenter( 0 ) ); + assertStr2Equal(strerrReturnValueOn10_6, + strerrReturnValueOn10_7, + strerror_reenter(0)); - return (char *)"strerrorOverride"; + return (char*)"strerrorOverride"; } END_MACH_OVERRIDE(strerror); - assert( !err ); + assert(!err); // Test override took effect. - assertStrEqual( "strerrorOverride", strerrorPtr( 0 ) ); + assertStrEqual("strerrorOverride", strerrorPtr(0)); } //------------------------------------------------------------------------------