/* Return true if frame appears to be a legitimate, readable stack frame. */ bool IsValidFrame( const StackFrame *frame ) { if( !IsReadableFrame( frame ) ) return false; /* The frame link should only go upwards. */ if( frame->link <= frame ) return false; /* The link should be on the stack. */ if( !IsOnStack( frame->link ) ) return false; /* The return address should be in a readable, executable page. */ if( !IsExecutableAddress( frame->return_address ) ) return false; /* The return address should follow a CALL opcode. */ if( !PointsToValidCall(frame->return_address) ) return false; return true; }
bool CreateTrampolineFunction(CREATE_TREMPOLINE_T& ct) { assert(("CreateTrampolineFunction", ct.pTarget != NULL)); #if defined _M_X64 CALL_ABS call = { 0x15FF, 0x00000000 }; JMP_ABS jmp = { 0x25FF, 0x00000000 }; JCC_ABS jcc = { 0x70, 0x02, 0xEB, 0x06, 0x25FF, 0x00000000 }; #elif defined _M_IX86 CALL_REL call = { 0xE8, 0x00000000 }; JMP_REL jmp = { 0xE9, 0x00000000 }; JCC_REL jcc = { 0x800F, 0x00000000 }; #endif size_t oldPos = 0; size_t newPos = 0; uintptr_t jmpDest = 0; // 関数内ジャンプの飛び先アドレス(分岐中判定に使用) bool finished = false; // 関数終了フラグ while (!finished) { uint8_t *pInst = reinterpret_cast<uint8_t*>(ct.pTarget) + oldPos; hde_t hs; hde_disasm(pInst, &hs); if ((hs.flags & F_ERROR) == F_ERROR) { return false; } void* pCopySrc = pInst; size_t copySize = hs.len; if (pInst - reinterpret_cast<uint8_t*>(ct.pTarget) >= sizeof(JMP_REL)) { // ターゲット関数へのジャンプを書き込み、関数を終了 AppendTempAddress(reinterpret_cast<uintptr_t>(pInst), newPos, jmp, ct); pCopySrc = &jmp; copySize = sizeof(jmp); finished = true; } #if defined _M_X64 // RIP相対アドレッシングを使用している命令 (ModR/M = 00???101B) else if ((hs.modrm & 0xC7) == 0x05) { // RIP相対アドレスのみ書き換え AppendRipRelativeAddress(pInst, newPos, hs, ct); // JMP (FF /4)なら関数を終了 if (hs.opcode == 0xFF && hs.modrm_reg == 4) { finished = true; } } #endif // 相対直接CALL else if (hs.opcode == 0xE8) { AppendTempAddress(GetRelativeBranchDestination(pInst, hs, false), newPos, call, ct); pCopySrc = &call; copySize = sizeof(call); } // 相対直接JMP (EB or E9) else if ((hs.opcode & 0xFD) == 0xE9) { uintptr_t dest = GetRelativeBranchDestination(pInst, hs, hs.opcode == 0xEB); // 関数内へのジャンプはそのままコピー(ジャンプ中は命令長が変わるような操作は不可) if (IsInternalJump(ct.pTarget, dest)) { jmpDest = std::max<uintptr_t>(jmpDest, dest); } else { AppendTempAddress(dest, newPos, jmp, ct); pCopySrc = &jmp; copySize = sizeof(jmp); // 分岐中でなければ関数を終了 finished = (reinterpret_cast<uintptr_t>(pInst) >= jmpDest); } } // 相対直接Jcc else if ((hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0 || (hs.opcode2 & 0xF0) == 0x80) { uintptr_t dest = GetRelativeBranchDestination(pInst, hs, (hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0); // 関数内へのジャンプはそのままコピー(分岐中は命令長が変わるような操作は不可) if (IsInternalJump(ct.pTarget, dest)) { jmpDest = std::max<uintptr_t>(jmpDest, dest); } else if ((hs.opcode & 0xFC) == 0xE0) // 関数外へのJCXZ, JECXZ には対応しない { return false; } else { AppendTempAddress(dest, newPos, jcc, ct); SetJccOpcode(hs, jcc); pCopySrc = &jcc; copySize = sizeof(jcc); } } // RET (C2 or C3) else if ((hs.opcode & 0xFE) == 0xC2) { // 分岐中でなければトランポリン関数を終了 finished = (reinterpret_cast<uintptr_t>(pInst) >= jmpDest); } // 分岐中は命令長が変わるような操作は不可 if (reinterpret_cast<uintptr_t>(pInst) < jmpDest && copySize != hs.len) { return false; } ct.trampoline.resize(newPos + copySize); memcpy(&ct.trampoline[ newPos ], pCopySrc, copySize); ct.oldIPs.push_back(oldPos); oldPos += hs.len; ct.newIPs.push_back(newPos); newPos += copySize; } // Is there enough place for a long jump? if (oldPos < sizeof(JMP_REL) && !IsCodePadding(reinterpret_cast<uint8_t*>(ct.pTarget) + oldPos, sizeof(JMP_REL) - oldPos)) { // Is there enough place for a short jump? if (oldPos < sizeof(JMP_REL_SHORT) && !IsCodePadding(reinterpret_cast<uint8_t*>(ct.pTarget) + oldPos, sizeof(JMP_REL_SHORT) - oldPos)) { return false; } // Can we place the long jump above the function? if (!IsExecutableAddress(reinterpret_cast<uint8_t*>(ct.pTarget) - sizeof(JMP_REL))) { return false; } if (!IsCodePadding(reinterpret_cast<uint8_t*>(ct.pTarget) - sizeof(JMP_REL), sizeof(JMP_REL))) { return false; } ct.patchAbove = true; } return true; }
/* This x86 backtracer attempts to walk the stack frames. If we come to a * place that doesn't look like a valid frame, we'll look forward and try * to find one again. */ static void do_backtrace( const void **buf, size_t size, const BacktraceContext *ctx ) { /* Read /proc/pid/maps to find the address range of the stack. */ get_readable_ranges( g_ReadableBegin, g_ReadableEnd, 1024 ); get_readable_ranges( g_ExecutableBegin, g_ExecutableEnd, 1024, READABLE_ONLY|EXECUTABLE_ONLY ); /* Find the stack memory blocks. */ g_StackBlock1 = find_address( ctx->sp, g_ReadableBegin, g_ReadableEnd ); g_StackBlock2 = find_address( SavedStackPointer, g_ReadableBegin, g_ReadableEnd ); /* Put eip at the top of the backtrace. */ /* XXX: We want EIP even if it's not valid, but we can't put NULL on the * list, since it's NULL-terminated. Hmm. */ unsigned i=0; if( i < size-1 && ctx->ip ) // -1 for NULL buf[i++] = ctx->ip; /* If we did a CALL to an invalid address (eg. call a NULL callback), then * we won't have a stack frame for the function that called it (since the * stack frame is set up by the called function), but if esp hasn't been * changed after the CALL, the return address will be esp[0]. Grab it. */ if( IsOnStack( ctx->sp ) ) { const void *p = ((const void **) ctx->sp)[0]; if( IsExecutableAddress( p ) && PointsToValidCall( p ) && i < size-1 ) // -1 for NULL buf[i++] = p; } #if 0 /* ebp is usually the frame pointer. */ const StackFrame *frame = (StackFrame *) ctx->bp; /* If ebp doesn't point to a valid stack frame, we're probably in * -fomit-frame-pointer code. Ignore it; use esp instead. It probably * won't point to a stack frame, but it should at least give us a starting * point in the stack. */ if( !IsValidFrame( frame ) ) frame = (StackFrame *) ctx->sp; #endif /* Actually, let's just use esp. Even if ebp points to a valid stack frame, there might be * -fomit-frame-pointer calls in front of it, and we want to get those. */ const StackFrame *frame = (StackFrame *) ctx->sp; while( i < size-1 ) // -1 for NULL { /* Make sure that this frame address is readable, and is on the stack. */ if( !IsReadableFrame( frame ) ) break; if( !IsValidFrame( frame ) ) { /* We've lost the frame. We might have crashed while in a call in -fomit-frame-pointer * code. Iterate through the stack word by word. If a word is possibly a valid return * address, record it. This is important; if we don't do this, we'll lose too many * stack frames at the top of the trace. This can have false positives, and introduce * garbage into the trace, but we should eventually find a real stack frame. */ void **p = (void **) frame; if( IsExecutableAddress( *p ) && PointsToValidCall( *p ) ) buf[i++] = *p; /* The frame pointer is invalid. Just move forward one word. */ frame = (StackFrame *) (((char *)frame)+4); continue; } /* Valid frame. Store the return address, and hop forward. */ buf[i++] = frame->return_address; frame = frame->link; } buf[i] = NULL; }
//------------------------------------------------------------------------- MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) { MH_STATUS status = MH_OK; EnterSpinLock(); if (g_hHeap != NULL) { if ((g_Flags & MH_FLAGS_SKIP_EXEC_CHECK) || (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour)) ) { UINT pos = FindHookEntry(pTarget); if (pos == INVALID_HOOK_POS) { LPVOID pBuffer = AllocateBuffer(pTarget); if (pBuffer != NULL) { TRAMPOLINE ct; ct.pTarget = pTarget; ct.pDetour = pDetour; ct.pTrampoline = pBuffer; if (CreateTrampolineFunction(&ct)) { PHOOK_ENTRY pHook = AddHookEntry(); if (pHook != NULL) { pHook->pTarget = ct.pTarget; #ifdef _M_X64 pHook->pDetour = ct.pRelay; #else pHook->pDetour = ct.pDetour; #endif pHook->pTrampoline = ct.pTrampoline; pHook->patchAbove = ct.patchAbove; pHook->isEnabled = FALSE; pHook->queueEnable = FALSE; pHook->nIP = ct.nIP; memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs)); memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs)); // Back up the target function. if (ct.patchAbove) { memcpy( pHook->backup, (LPBYTE)pTarget - sizeof(JMP_REL), sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); } else { memcpy(pHook->backup, pTarget, sizeof(JMP_REL)); } if (ppOriginal != NULL) *ppOriginal = pHook->pTrampoline; } else { status = MH_ERROR_MEMORY_ALLOC; } } else { status = MH_ERROR_UNSUPPORTED_FUNCTION; } if (status != MH_OK) { FreeBuffer(pBuffer); } } else { status = MH_ERROR_MEMORY_ALLOC; } } else { status = MH_ERROR_ALREADY_CREATED; } } else { status = MH_ERROR_NOT_EXECUTABLE; } } else { status = MH_ERROR_NOT_INITIALIZED; } LeaveSpinLock(); return status; }
MH_STATUS CreateHook(void* pTarget, void* const pDetour, void** ppOriginal) { CriticalSection::ScopedLock lock(*gCS); if (!gIsInitialized) { return MH_ERROR_NOT_INITIALIZED; } HOOK_ENTRY *pHook = FindHook(pTarget); if (pHook != NULL) { return MH_ERROR_ALREADY_CREATED; } if (!IsExecutableAddress(pTarget) || !IsExecutableAddress(pDetour)) { return MH_ERROR_NOT_EXECUTABLE; } { bool committed = false; RollbackIfNotCommitted scopedRollback(&committed); // トランポリン関数を作成する CREATE_TREMPOLINE_T ct = { 0 }; ct.pTarget = pTarget; if (!CreateTrampolineFunction(ct)) { return MH_ERROR_UNSUPPORTED_FUNCTION; } void* pJmpPtr = pTarget; if (ct.patchAbove) { pJmpPtr = reinterpret_cast<char*>(pJmpPtr) - sizeof(JMP_REL); } void* pTrampoline = AllocateCodeBuffer(pJmpPtr, ct.trampoline.size()); if (pTrampoline == NULL) { return MH_ERROR_MEMORY_ALLOC; } #if defined _M_X64 void* pTable = AllocateDataBuffer(pTrampoline, (ct.table.size() + 1) * sizeof(uintptr_t)); if (pTable == NULL) { return MH_ERROR_MEMORY_ALLOC; } #endif ct.pTrampoline = pTrampoline; #if defined _M_X64 ct.pTable = pTable; #endif if (!ResolveTemporaryAddresses(ct)) { return MH_ERROR_UNSUPPORTED_FUNCTION; } memcpy(pTrampoline, &ct.trampoline[ 0 ], ct.trampoline.size()); #if defined _M_X64 if (ct.table.size() != 0) { memcpy(pTable, &ct.table[ 0 ], ct.table.size() * sizeof(uintptr_t)); } #endif // ターゲット関数のバックアップをとる size_t backupSize = sizeof(JMP_REL); if (ct.patchAbove) { backupSize += sizeof(JMP_REL_SHORT); } void* pBackup = AllocateDataBuffer(NULL, backupSize); if (pBackup == NULL) { return MH_ERROR_MEMORY_ALLOC; } memcpy(pBackup, pJmpPtr, backupSize); // 中継関数を作成する #if defined _M_X64 void* pRelay = AllocateCodeBuffer(pJmpPtr, sizeof(JMP_ABS)); if (pRelay == NULL) { return MH_ERROR_MEMORY_ALLOC; } WriteAbsoluteJump(pRelay, pDetour, reinterpret_cast<uintptr_t*>(pTable) + ct.table.size()); #endif CommitBuffer(); committed = true; // フック情報の登録 HOOK_ENTRY hook = { 0 }; hook.pTarget = pTarget; hook.pDetour = pDetour; #if defined _M_X64 hook.pTable = pTable; hook.pRelay = pRelay; #endif hook.pTrampoline = pTrampoline; hook.pBackup = pBackup; hook.patchAbove = ct.patchAbove; hook.isEnabled = false; hook.queueEnable = false; hook.oldIPs = ct.oldIPs; hook.newIPs = ct.newIPs; std::vector<HOOK_ENTRY>::iterator i = std::lower_bound(gHooks.begin(), gHooks.end(), hook); i = gHooks.insert(i, hook); pHook = &(*i); } // OUT引数の処理 *ppOriginal = pHook->pTrampoline; return MH_OK; }