#include "Thread.h"
#include "../ProcessCore.h"
#include "../../Misc/DynImport.h"
#include "../../Include/Macro.h"
namespace blackbone
{
Thread::Thread( DWORD id, ProcessCore* core, DWORD access /*= DEFAULT_ACCESS*/ )
: _core( core )
, _id( id )
, _handle( OpenThread( access, FALSE, id ) )
{
}
Thread::Thread( HANDLE handle, ProcessCore* core )
: _core( core )
, _handle( handle )
{
_id = (handle ? GetThreadIdT( handle ) : 0);
}
Thread::~Thread()
{
Close();
}
///
/// Get WOW64 TEB
///
/// Process TEB
/// TEB pointer
blackbone::ptr_t Thread::teb( _TEB32* pteb ) const
{
return _core->native()->getTEB( _handle, pteb );
}
///
/// Get Native TEB
///
/// Process TEB
/// TEB pointer
blackbone::ptr_t Thread::teb( _TEB64* pteb ) const
{
return _core->native()->getTEB( _handle, pteb );
}
///
/// Suspend thread
///
/// true on success
bool Thread::Suspend()
{
// Prevent deadlock
if (_id == GetCurrentThreadId())
return true;
// Target process is x86 and not running on x86 OS
const auto& barrier = _core->native()->GetWow64Barrier();
if (barrier.type == wow_64_32 && !barrier.x86OS)
return (SAFE_CALL(Wow64SuspendThread, _handle ) != -1);
else
return (SuspendThread( _handle ) != -1);
}
///
/// Resumes thread.
///
/// true on success
bool Thread::Resume()
{
if (_id == GetCurrentThreadId())
return true;
return (ResumeThread( _handle ) != -1);
}
///
/// Check if thread is suspended
///
/// true if suspended
bool Thread::Suspended()
{
if (_id == GetCurrentThreadId())
return false;
auto count = (_core->isWow64() && !_core->native()->GetWow64Barrier().x86OS)
? SAFE_CALL( Wow64SuspendThread, _handle )
: SuspendThread( _handle );
ResumeThread( _handle );
return count > 0;
}
///
/// Get WOW64 thread context
///
/// Returned context
/// Context flags.
/// true if thread shouldn't be suspended before retrieving context
/// Status code
NTSTATUS Thread::GetContext( _CONTEXT32& ctx, DWORD flags /*= CONTEXT_ALL*/, bool dontSuspend /*= false*/ )
{
NTSTATUS status = STATUS_INVALID_THREAD;
memset( &ctx, 0x00, sizeof( ctx ) );
ctx.ContextFlags = flags;
if (dontSuspend || Suspend())
{
status = _core->native()->GetThreadContextT( _handle, ctx );
if (!dontSuspend)
Resume();
}
return status;
}
///
/// Get native thread context
///
/// Returned context
/// Context flags.
/// true if thread shouldn't be suspended before retrieving context
/// Status code
NTSTATUS Thread::GetContext( _CONTEXT64& ctx, DWORD flags /*= CONTEXT64_ALL*/, bool dontSuspend /*= false*/ )
{
NTSTATUS status = STATUS_INVALID_THREAD;
memset( &ctx, 0x00, sizeof(ctx) );
ctx.ContextFlags = flags;
if (dontSuspend || Suspend())
{
status = _core->native()->GetThreadContextT( _handle, ctx );
if (!dontSuspend)
Resume();
}
return status;
}
///
/// Set WOW64 thread context
///
/// Context to set
/// true if thread shouldn't be suspended before retrieving context
/// Status code
NTSTATUS Thread::SetContext( _CONTEXT32& ctx, bool dontSuspend /*= false */ )
{
NTSTATUS status = STATUS_INVALID_THREAD;
if (dontSuspend || Suspend())
{
status = _core->native()->SetThreadContextT( _handle, ctx );
if (!dontSuspend)
Resume();
}
return status;
}
///
/// Set native thread context
///
/// Context to set
/// true if thread shouldn't be suspended before retrieving context
/// Status code
NTSTATUS Thread::SetContext( _CONTEXT64& ctx, bool dontSuspend /*= false*/ )
{
NTSTATUS status = STATUS_INVALID_THREAD;
if(dontSuspend || Suspend())
{
status = _core->native()->SetThreadContextT( _handle, ctx );
if(!dontSuspend)
Resume();
}
return status;
}
///
/// Terminate thread
///
/// Exit code
/// Status code
NTSTATUS Thread::Terminate( DWORD code /*= 0*/ )
{
SetLastNtStatus( STATUS_SUCCESS );
TerminateThread( _handle, code );
return LastNtStatus();
}
///
/// Join thread
///
/// Join timeout
/// true on success
bool Thread::Join( int timeout /*= INFINITE*/ )
{
return (WaitForSingleObject( _handle, timeout ) == WAIT_OBJECT_0);
}
///
/// Add hardware breakpoint to thread
///
/// Breakpoint address
/// Breakpoint type(read/write/execute)
/// Number of bytes to include into breakpoint
/// Index of used breakpoint; -1 if failed
call_result_t Thread::AddHWBP( ptr_t addr, HWBPType type, HWBPLength length )
{
_CONTEXT64 context64 = { 0 };
_CONTEXT32 context32 = { 0 };
bool use64 = !_core->native()->GetWow64Barrier().x86OS;
// CONTEXT_DEBUG_REGISTERS can be operated without thread suspension
auto status = use64 ? GetContext( context64, CONTEXT64_DEBUG_REGISTERS, true ) : GetContext( context32, CONTEXT_DEBUG_REGISTERS, true );
auto pDR7 = use64 ? reinterpret_cast(&context64.Dr7) : reinterpret_cast(&context32.Dr7);
if (!NT_SUCCESS( status ))
return status;
// Check if HWBP is already present
for (int i = 0; i < 4; i++)
{
if ( (use64 && *(&context64.Dr0 + i) == addr && context64.Dr7 & (1ll << 2 * i)) ||
(!use64 && *(&context32.Dr0 + i) == addr && context32.Dr7 & (1ll << 2 * i)))
return i;
}
// Get free DR
int freeIdx = pDR7->getFreeIndex();
// If all 4 registers are occupied - error
if (freeIdx < 0)
return STATUS_NO_MORE_ENTRIES;
// Enable corresponding HWBP and local BP flag
pDR7->l_enable = 1;
pDR7->setLocal( freeIdx, 1 );
pDR7->setRW( freeIdx, static_cast(type) );
pDR7->setLen( freeIdx, static_cast(length) );
use64 ? *(&context64.Dr0 + freeIdx) = addr : *(&context32.Dr0 + freeIdx) = static_cast(addr);
// Write values to registers
status = use64 ? SetContext( context64, true ) : SetContext( context32, true );
return call_result_t( freeIdx, status );
}
///
/// Remove existing hardware breakpoint
///
/// Breakpoint index
/// true on success
NTSTATUS Thread::RemoveHWBP( int idx )
{
if (idx < 0 || idx > 4)
return false;
_CONTEXT64 context64 = { 0 };
_CONTEXT32 context32 = { 0 };
bool use64 = !_core->native()->GetWow64Barrier().x86OS;
auto status = use64 ? GetContext( context64, CONTEXT64_DEBUG_REGISTERS, true ) : GetContext( context32, CONTEXT_DEBUG_REGISTERS, true );
auto pDR7 = use64 ? reinterpret_cast(&context64.Dr7) : reinterpret_cast(&context32.Dr7);
if (!NT_SUCCESS( status ))
return status;
pDR7->setLocal( idx, 0 );
pDR7->setLen( idx, 0 );
pDR7->setRW( idx, 0 );
if (pDR7->empty())
pDR7->l_enable = 0;
return use64 ? SetContext( context64 ) : SetContext( context32 );
}
///
/// Remove existing hardware breakpoint
///
/// Breakpoint address
/// true on success
NTSTATUS Thread::RemoveHWBP( ptr_t ptr )
{
_CONTEXT64 context64 = { 0 };
_CONTEXT32 context32 = { 0 };
bool use64 = !_core->native()->GetWow64Barrier().x86OS;
auto status = use64 ? GetContext( context64, CONTEXT64_DEBUG_REGISTERS, true ) : GetContext( context32, CONTEXT_DEBUG_REGISTERS, true );
auto pDR7 = use64 ? reinterpret_cast(&context64.Dr7) : reinterpret_cast(&context32.Dr7);
if (!NT_SUCCESS( status ))
return false;
// Search for breakpoint
for (int i = 0; i < 4; i++)
{
if ((&context64.Dr0)[i] == ptr || (&context32.Dr0)[i] == static_cast(ptr))
{
use64 ? *(&context64.Dr0 + i) = 0 : *(&context32.Dr0 + i) = 0;
pDR7->setLocal( i, 0 );
pDR7->setLen( i, 0 );
pDR7->setRW( i, 0 );
if (pDR7->empty())
pDR7->l_enable = 0;
return use64 ? SetContext( context64 ) : SetContext( context32 );
}
}
return STATUS_NOT_FOUND;
}
///
/// Get thread exit code
///
/// Thread exit code
DWORD Thread::ExitCode() const
{
DWORD code = MAXULONG32_2;
GetExitCodeThread( _handle, &code );
return code;
}
///
/// Get thread creation time
///
/// Thread creation time
uint64_t Thread::startTime() const
{
FILETIME times[4] = { };
if (GetThreadTimes( _handle, ×[0], ×[1], ×[2], ×[3] ))
return (static_cast(times[0].dwHighDateTime) << 32) | times[0].dwLowDateTime;
return MAXULONG64_2;
}
///
/// Get total execution time(user mode and kernel mode)
///
/// Total execution time
uint64_t Thread::execTime() const
{
FILETIME times[4] = { };
if (GetThreadTimes( _handle, ×[0], ×[1], ×[2], ×[3] ))
return ((static_cast(times[2].dwHighDateTime) << 32) | times[2].dwLowDateTime)
+ ((static_cast(times[3].dwHighDateTime) << 32) | times[3].dwLowDateTime);
return MAXULONG64_2;
}
///
/// Close handle
///
void Thread::Close()
{
_handle.reset();
_id = 0;
}
///
/// GetThreadId support for XP
///
/// Thread handle
/// Thread ID
DWORD Thread::GetThreadIdT( HANDLE hThread )
{
static auto pGetThreadId = (decltype(&GetThreadId))GetProcAddress( GetModuleHandleW( L"kernel32.dll" ), "GetThreadId" );
if (pGetThreadId != nullptr)
{
return pGetThreadId( hThread );
}
// XP version
else
{
_THREAD_BASIC_INFORMATION_T tbi = { 0 };
ULONG bytes = 0;
if (NT_SUCCESS( SAFE_NATIVE_CALL( NtQueryInformationThread, hThread, (THREADINFOCLASS)0, &tbi, (ULONG)sizeof( tbi ), &bytes ) ))
return tbi.ClientID.UniqueThread;
return 0;
}
}
}