首页
论坛
专栏
课程

[调试逆向] [系统底层] [原创]调试陷阱ThreadHideFromDebugger的另一种对抗方法

2019-2-27 10:55 2319

[调试逆向] [系统底层] [原创]调试陷阱ThreadHideFromDebugger的另一种对抗方法

2019-2-27 10:55
2319
测试环境: windows 7 x64
炒个冷饭,说说ThreadHideFromDebugger的另一种对抗方法,之所以说是另一种是因为第一种方法 无论是r3还是r0都可以用hook解决。
接下来说说另一种方法 ,先来看一个函数定义:
NTSTATUS
NTAPI
NtSetInformationThread(
    _In_ HANDLE ThreadHandle,
    _In_ THREADINFOCLASS ThreadInformationClass,
    _In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation,
    _In_ ULONG ThreadInformationLength
    );
它是这么用的:
NtSetInformationThread(handle.get(), ThreadHideFromDebugger, nullptr, 0);

再来看一个结构定义:
typedef enum _THREADINFOCLASS {
    ThreadBasicInformation          = 0,
    ThreadTimes                     = 1,
    ThreadPriority                  = 2,
    ThreadBasePriority              = 3,
    ThreadAffinityMask              = 4,
    ThreadImpersonationToken        = 5,
    ThreadDescriptorTableEntry      = 6,
    ThreadEnableAlignmentFaultFixup = 7,
    ThreadEventPair_Reusable        = 8,
    ThreadQuerySetWin32StartAddress = 9,
    ThreadZeroTlsCell               = 10,
    ThreadPerformanceCount          = 11,
    ThreadAmILastThread             = 12,
    ThreadIdealProcessor            = 13,
    ThreadPriorityBoost             = 14,
    ThreadSetTlsArrayAddress        = 15,   // Obsolete
    ThreadIsIoPending               = 16,
    ThreadHideFromDebugger          = 17,
    ThreadBreakOnTermination        = 18,
    ThreadSwitchLegacyState         = 19,
    ThreadIsTerminated              = 20,
    ThreadLastSystemCall            = 21,
    ThreadIoPriority                = 22,
    ThreadCycleTime                 = 23,
    ThreadPagePriority              = 24,
    ThreadActualBasePriority        = 25,
    ThreadTebInformation            = 26,
    ThreadCSwitchMon                = 27,   // Obsolete
    ThreadCSwitchPmu                = 28,
    ThreadWow64Context              = 29,
    ThreadGroupInformation          = 30,
    ThreadUmsInformation            = 31,   // UMS
    ThreadCounterProfiling          = 32,
    ThreadIdealProcessorEx          = 33,
    ThreadCpuAccountingInformation  = 34,
    ThreadSuspendCount              = 35,
    ThreadActualGroupAffinity       = 41,
    ThreadDynamicCodePolicyInfo     = 42,
    ThreadSubsystemInformation      = 45,

    MaxThreadInfoClass              = 51,
} THREADINFOCLASS;

今天我们只关心 ThreadHideFromDebugger,由上面的结构可以看出它的值是:0x11。
好,那我们看下对于这个参数,NtSetInformationThread是怎么实现的:
    case ThreadHideFromDebugger:
        if (ThreadInformationLength != 0) {
            return STATUS_INFO_LENGTH_MISMATCH;
        }

        st = ObReferenceObjectByHandle (ThreadHandle,
                                        THREAD_SET_INFORMATION,
                                        PsThreadType,
                                        PreviousMode,
                                        &Thread,
                                        NULL);

        if (!NT_SUCCESS (st)) {
            return st;
        }

        PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_HIDEFROMDBG);

        ObDereferenceObject (Thread);

        return st;
        break;
接下来,来看看调试子系统对于有“PS_CROSS_THREAD_FLAGS_HIDEFROMDBG”这个标志的线程是怎么处理的:
    Process = PsGetCurrentProcess();
    if (DebugException) {
        if (PsGetCurrentThread()->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_HIDEFROMDBG) {
            Port = NULL;
        } else {
            Port = Process->DebugPort;
        }
        LpcPort = FALSE;
    } else {
        Port = Process->ExceptionPort;
        m.h.u2.ZeroInit = LPC_EXCEPTION;
        LpcPort = TRUE;
    }
emmm...破案了,原来清空DebugPort不是某P首创,人家巨硬的内核里本来就是这么做的,只不过一个是暴力清空而另一个是“假装没有”。
这也就非常符合微软对“ ThreadHideFromDebugger ”这个参数的描述:设置此参数将使这条线程对调试器“隐藏”。即调试器收不到调试信息,因而就会出现当调试器对被调试进程下断点,线程执行到被下断点代码的时候就会卡死这种现象。
OK,接下来我们考虑一下怎么干掉它。
看以下定义:
#define RtlInterlockedSetBitsDiscardReturn(Flags, Flag) \
    (VOID) RtlInterlockedSetBits(Flags, Flag)

#define PS_SET_BITS(Flags, Flag) \
    RtlInterlockedSetBitsDiscardReturn (Flags, Flag)
还记得这句代码吗?
PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_HIDEFROMDBG);
从这句代码我们得到了关键点:CrossThreadFlags
看一下 ETHREAD的结构:
dt _ETHREAD
kd> dt _ETHREAD
ntdll!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x360 CreateTime       : _LARGE_INTEGER
   +0x368 ExitTime         : _LARGE_INTEGER
   +0x368 KeyedWaitChain   : _LIST_ENTRY
   +0x378 ExitStatus       : Int4B
   +0x380 PostBlockList    : _LIST_ENTRY
   +0x380 ForwardLinkShadow : Ptr64 Void
   +0x388 StartAddress     : Ptr64 Void
   +0x390 TerminationPort  : Ptr64 _TERMINATION_PORT
   +0x390 ReaperLink       : Ptr64 _ETHREAD
   +0x390 KeyedWaitValue   : Ptr64 Void
   +0x398 ActiveTimerListLock : Uint8B
   +0x3a0 ActiveTimerListHead : _LIST_ENTRY
   +0x3b0 Cid              : _CLIENT_ID
   +0x3c0 KeyedWaitSemaphore : _KSEMAPHORE
   +0x3c0 AlpcWaitSemaphore : _KSEMAPHORE
   +0x3e0 ClientSecurity   : _PS_CLIENT_SECURITY_CONTEXT
   +0x3e8 IrpList          : _LIST_ENTRY
   +0x3f8 TopLevelIrp      : Uint8B
   +0x400 DeviceToVerify   : Ptr64 _DEVICE_OBJECT
   +0x408 CpuQuotaApc      : Ptr64 _PSP_CPU_QUOTA_APC
   +0x410 Win32StartAddress : Ptr64 Void
   +0x418 LegacyPowerObject : Ptr64 Void
   +0x420 ThreadListEntry  : _LIST_ENTRY
   +0x430 RundownProtect   : _EX_RUNDOWN_REF
   +0x438 ThreadLock       : _EX_PUSH_LOCK
   +0x440 ReadClusterSize  : Uint4B
   +0x444 MmLockOrdering   : Int4B
   +0x448 CrossThreadFlags : Uint4B
   +0x448 Terminated       : Pos 0, 1 Bit
   +0x448 ThreadInserted   : Pos 1, 1 Bit
   +0x448 HideFromDebugger : Pos 2, 1 Bit
   +0x448 ActiveImpersonationInfo : Pos 3, 1 Bit
   +0x448 Reserved         : Pos 4, 1 Bit
   +0x448 HardErrorsAreDisabled : Pos 5, 1 Bit
   +0x448 BreakOnTermination : Pos 6, 1 Bit
   +0x448 SkipCreationMsg  : Pos 7, 1 Bit
   +0x448 SkipTerminationMsg : Pos 8, 1 Bit
   +0x448 CopyTokenOnOpen  : Pos 9, 1 Bit
   +0x448 ThreadIoPriority : Pos 10, 3 Bits
   +0x448 ThreadPagePriority : Pos 13, 3 Bits
   +0x448 RundownFail      : Pos 16, 1 Bit
   +0x448 NeedsWorkingSetAging : Pos 17, 1 Bit
   +0x44c SameThreadPassiveFlags : Uint4B
   +0x44c ActiveExWorker   : Pos 0, 1 Bit
   +0x44c ExWorkerCanWaitUser : Pos 1, 1 Bit
   +0x44c MemoryMaker      : Pos 2, 1 Bit
   +0x44c ClonedThread     : Pos 3, 1 Bit
   +0x44c KeyedEventInUse  : Pos 4, 1 Bit
   +0x44c RateApcState     : Pos 5, 2 Bits
   +0x44c SelfTerminate    : Pos 7, 1 Bit
   +0x450 SameThreadApcFlags : Uint4B
   +0x450 Spare            : Pos 0, 1 Bit
   +0x450 StartAddressInvalid : Pos 1, 1 Bit
   +0x450 EtwPageFaultCalloutActive : Pos 2, 1 Bit
   +0x450 OwnsProcessWorkingSetExclusive : Pos 3, 1 Bit
   +0x450 OwnsProcessWorkingSetShared : Pos 4, 1 Bit
   +0x450 OwnsSystemCacheWorkingSetExclusive : Pos 5, 1 Bit
   +0x450 OwnsSystemCacheWorkingSetShared : Pos 6, 1 Bit
   +0x450 OwnsSessionWorkingSetExclusive : Pos 7, 1 Bit
   +0x451 OwnsSessionWorkingSetShared : Pos 0, 1 Bit
   +0x451 OwnsProcessAddressSpaceExclusive : Pos 1, 1 Bit
   +0x451 OwnsProcessAddressSpaceShared : Pos 2, 1 Bit
   +0x451 SuppressSymbolLoad : Pos 3, 1 Bit
   +0x451 Prefetching      : Pos 4, 1 Bit
   +0x451 OwnsDynamicMemoryShared : Pos 5, 1 Bit
   +0x451 OwnsChangeControlAreaExclusive : Pos 6, 1 Bit
   +0x451 OwnsChangeControlAreaShared : Pos 7, 1 Bit
   +0x452 OwnsPagedPoolWorkingSetExclusive : Pos 0, 1 Bit
   +0x452 OwnsPagedPoolWorkingSetShared : Pos 1, 1 Bit
   +0x452 OwnsSystemPtesWorkingSetExclusive : Pos 2, 1 Bit
   +0x452 OwnsSystemPtesWorkingSetShared : Pos 3, 1 Bit
   +0x452 TrimTrigger      : Pos 4, 2 Bits
   +0x452 Spare1           : Pos 6, 2 Bits
   +0x453 PriorityRegionActive : UChar
   +0x454 CacheManagerActive : UChar
   +0x455 DisablePageFaultClustering : UChar
   +0x456 ActiveFaultCount : UChar
   +0x457 LockOrderState   : UChar
   +0x458 AlpcMessageId    : Uint8B
   +0x460 AlpcMessage      : Ptr64 Void
   +0x460 AlpcReceiveAttributeSet : Uint4B
   +0x468 AlpcWaitListEntry : _LIST_ENTRY
   +0x478 CacheManagerCount : Uint4B
   +0x47c IoBoostCount     : Uint4B
   +0x480 IrpListLock      : Uint8B
   +0x488 ReservedForSynchTracking : Ptr64 Void
   +0x490 CmCallbackListHead : _SINGLE_LIST_ENTRY
所以 CrossThreadFlags的偏移是  0x448。
然后就可以敲代码了:
auto RemoveThreadFlagByClientId(PCLIENT_ID a_cid) -> NTSTATUS
{
	auto v_ret_status { STATUS_SUCCESS };
	PETHREAD v_thread;
	CLIENT_ID v_cid;

	PAGED_CODE();
	__try
	{
		ProbeForRead(a_cid, sizeof(CLIENT_ID), sizeof(ULONG));
		v_cid = *a_cid;
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return GetExceptionCode();
	}
	
	if (nullptr != v_cid.UniqueThread)
	{
		//获得ETHREAD
		v_ret_status = PsLookupThreadByThreadId(v_cid.UniqueThread,&v_thread);
		if (!NT_SUCCESS(v_ret_status))
		{
			return v_ret_status;
		}
		//&v_thread->CrossThreadFlags & ThreadHideFromDebugger;
		//win7 sp1 x64
		#define cross_thread_flags_offest 0x448
		if(*reinterpret_cast<PULONG>(reinterpret_cast<ULONG64>(v_thread)) + cross_thread_flags_offest & ThreadHideFromDebugger)
		{
			
			*reinterpret_cast<PULONG>(reinterpret_cast<ULONG64>(v_thread) + cross_thread_flags_offest) &= ~ThreadHideFromDebugger;
			
		}
	}
	return v_ret_status;
}
这样,就可以从线程的ETHREAD摘掉这个标志了。
什么, 你问有啥用?SSDT就不说了,R3的内存加载一份调用就把你的hook绕过了。

示例代码见:https://github.com/luciouskami/RemoveFlagTest
瞎写的,见谅。



[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 2019-2-27 23:49 被黑洛编辑 ,原因:
上一主题 下一主题
最新回复 (7)
开花的水管 2019-2-27 13:53
2
0
大只狼 2019-2-27 16:16
3
0
又出bug了 2 2019-3-5 21:54
4
0
要是优雅的干掉Hidefromdebugger
需要重写掉调试流程的某些函数,干脆不用这个标志位就好了
3环枚举符号发送到0环,0环对着ida和wrk的源代码抄,论坛也有类似的代码
自己创建新的DebugPort类型,顺便也过了句柄表检测

最后于 2019-3-5 21:55 被又出bug了编辑 ,原因:
uvbs 2019-3-5 21:59
5
0
又出bug了 要是优雅的干掉Hidefromdebugger需要重写掉调试流程的某些函数,干脆不用这个标志位就好了3环枚举符号发送到0环,0环对着ida和wrk的源代码抄,论坛也有类似的代码自己创建新的DebugP ...
求教win7 和win10 如何新建DebugPort
又出bug了 2 2019-3-5 22:04
6
0
uvbs 求教win7 和win10 如何新建DebugPort
DbgkInitialize (
    VOID
    )
/*++

Routine Description:

    Initialize the debug system

Arguments:

    None

Return Value:

    NTSTATUS - Status of operation

--*/
{
    NTSTATUS Status;
    UNICODE_STRING Name;
    OBJECT_TYPE_INITIALIZER oti = {0};
    GENERIC_MAPPING GenericMapping = {STANDARD_RIGHTS_READ | DEBUG_READ_EVENT,
                                      STANDARD_RIGHTS_WRITE | DEBUG_PROCESS_ASSIGN,
                                      STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
                                      DEBUG_ALL_ACCESS};


    PAGED_CODE ();

    ExInitializeFastMutex (&DbgkpProcessDebugPortMutex);

    RtlInitUnicodeString (&Name, L"DebugObject");

    oti.Length                    = sizeof (oti);
    oti.SecurityRequired          = TRUE;
    oti.InvalidAttributes         = 0;
    oti.PoolType                  = NonPagedPool;
    oti.DeleteProcedure           = DbgkpDeleteObject;
    oti.CloseProcedure            = DbgkpCloseObject;
    oti.ValidAccessMask           = DEBUG_ALL_ACCESS;
    oti.GenericMapping            = GenericMapping;
    oti.DefaultPagedPoolCharge    = 0;
    oti.DefaultNonPagedPoolCharge = 0;

    Status = ObCreateObjectType (&Name, &oti, NULL, &DbgkDebugObjectType);
    if (!NT_SUCCESS (Status)) {
        return Status;
    }
    return Status;
}
--
直接抄,wrk提供了源代码,把其余函数的符号位置找到就好了,这个是楼主的帖子,写多不好(win7和win10也有这个函数,你打开ida逆下基本没怎么改)
最后于 2019-3-5 22:05 被又出bug了编辑 ,原因:
黑洛 1 2019-3-6 00:59
7
0
又出bug了 uvbs 求教win7 和win10 如何新建DebugPort DbgkInitialize&nbsp;(&nbsp;&nbs ...
实际上,你说的这个方法是最好的,也是能根本解决问题的。还准备啥时候再写写骗个回复什么的。
晨曦风海 2019-3-8 16:53
8
0
这饭好冷
游客
登录 | 注册 方可回帖
返回