最近摆弄OD,试了一下Shub-Nigurrath的xADT 1.3,没想到又被
ProcessHeap逮到了,实在有点火大! 想起Themida玩这个也很起
劲,干脆让被调试程序使用标准堆, 彻底了断;-)
下面的代码从W2K源码里找的,我自己也稀里糊涂,乱解释的地方
请指教.
看看heap.c:
RtlCreateHeap (
IN ULONG Flags,
IN PVOID HeapBase OPTIONAL,
IN SIZE_T ReserveSize OPTIONAL,
IN SIZE_T CommitSize OPTIONAL,
IN PVOID Lock OPTIONAL,
IN PRTL_HEAP_PARAMETERS Parameters OPTIONAL
)
......
PVOID NextHeapHeaderAddress;
PHEAP_UNCOMMMTTED_RANGE UnCommittedRange, *pp;
RTL_HEAP_PARAMETERS TempParameters;
ULONG NtGlobalFlag = RtlGetNtGlobalFlags();
/* RtlCreateHeap在ntoskrnl和ntdll各有1个, 从下面的
条件编译指令看,似乎是同一个文件编译出来的. 内核的
RtlGetNtGlobalFlags直接取的全局变量, ntdll内
的实现则是从PEB取的
*/
#ifndef NTOS_KERNEL_RUNTIME
PPEB Peb;
#else // NTOS_KERNEL_RUNTIME
extern SIZE_T MmHeapSegmentReserve;
extern SIZE_T MmHeapSegmentCommit;
extern SIZE_T MmHeapDeCommitTotalFreeThreshold;
extern SIZE_T MmHeapDeCommitFreeBlockThreshold;
#endif // NTOS_KERNEL_RUNTIME
......
//
// If the caller does not want to skip heap validiation checks then we
// need to validate the rest of the flags but simply masking out only
// those flags that want on a create heap call
//
if (!(Flags & HEAP_SKIP_VALIDATION_CHECKS)) {
if (Flags & ~HEAP_CREATE_VALID_MASK) {
HeapDebugPrint(( "Invalid flags (%08x) specified to RtlCreateHeap\n", Flags ));
HeapDebugBreak( NULL );
Flags &= HEAP_CREATE_VALID_MASK;
}
}
/*
这个标记HEAP_SKIP_VALIDATION_CHECKS=0x10000000,多次使用,貌似可以不用
调试堆,不过是从调用参数来的,不大好改,而且Heap相关函数不少 */
//
// If nt global flags tells us to always do tail or free checking
// or to disable coalescing then force those bits set in the user
// specified flags
//
if (NtGlobalFlag & FLG_HEAP_ENABLE_TAIL_CHECK) {
Flags |= HEAP_TAIL_CHECKING_ENABLED;
}
if (NtGlobalFlag & FLG_HEAP_ENABLE_FREE_CHECK) {
Flags |= HEAP_FREE_CHECKING_ENABLED;
}
if (NtGlobalFlag & FLG_HEAP_DISABLE_COALESCING) {
Flags |= HEAP_DISABLE_COALESCE_ON_FREE;
}
//
// In the non kernel case we also check if we should
// validate parameters, validate all, or do stack backtraces
//
Peb = NtCurrentPeb();
if (NtGlobalFlag & FLG_HEAP_VALIDATE_PARAMETERS) {
Flags |= HEAP_VALIDATE_PARAMETERS_ENABLED;
}
if (NtGlobalFlag & FLG_HEAP_VALIDATE_ALL) {
Flags |= HEAP_VALIDATE_ALL_ENABLED;
}
if (NtGlobalFlag & FLG_USER_STACK_TRACE_DB) {
Flags |= HEAP_CAPTURE_STACK_BACKTRACES;
}
/* 测试NtGlobalFlag */
........
#ifndef NTOS_KERNEL_RUNTIME
//
// In the non kernel case check if we are creating a debug heap
// the test checks that skip validation checks is false.
//
if (DEBUG_HEAP( Flags )) {
return RtlDebugCreateHeap( Flags,
HeapBase,
ReserveSize,
CommitSize,
Lock,
Parameters );
}
#endif // NTOS_KERNEL_RUNTIME
/* 这里是我们感兴趣的了,DEBUG_HEAP(Flags)为TRUE则
创建调试堆(注意内核模式永远不会用调试堆),这个宏
定义在heappriv.h内:
#define HEAP_DEBUG_FLAGS (HEAP_VALIDATE_PARAMETERS_ENABLED | \
HEAP_VALIDATE_ALL_ENABLED | \
HEAP_CAPTURE_STACK_BACKTRACES | \
HEAP_CREATE_ENABLE_TRACING | \
HEAP_FLAG_PAGE_ALLOCS)
#define DEBUG_HEAP(F) ((F & HEAP_DEBUG_FLAGS) && !(F & HEAP_SKIP_VALIDATION_CHECKS))
另外几个宏在heap.h内:
#define HEAP_SIGNATURE (ULONG)0xEEFFEEFF
#define HEAP_LOCK_USER_ALLOCATED (ULONG)0x80000000
#define HEAP_VALIDATE_PARAMETERS_ENABLED (ULONG)0x40000000
#define HEAP_VALIDATE_ALL_ENABLED (ULONG)0x20000000
#define HEAP_SKIP_VALIDATION_CHECKS (ULONG)0x10000000
#define HEAP_CAPTURE_STACK_BACKTRACES (ULONG)0x08000000
#define CHECK_HEAP_TAIL_SIZE HEAP_GRANULARITY
#define CHECK_HEAP_TAIL_FILL 0xAB
#define FREE_HEAP_FILL 0xFEEEFEEE <------- MAGIC ;-)
#define ALLOC_HEAP_FILL 0xBAADF00D <------- MAGIC
*/
看起来只要PEB内的NtGlobalFlag为0,就可以避开调试堆,下面是XP SP2下的代码:
PAGE:004AC2B0 __stdcall MmCreatePeb(x, x, x) proc near
......
PAGE:004AC38A mov ecx, [ebp+var_1C]
PAGE:004AC38D mov [ecx+2], al
PAGE:004AC390 mov eax, [ebp+var_1C]
PAGE:004AC393 mov ecx, dword ptr _NtGlobalFlag <- 这里
PAGE:004AC399 mov [eax+68h], ecx
PAGE:004AC39C mov eax, _MmCriticalSectionTimeout
PAGE:004AC3A1 mov ecx, [ebp+var_1C]
PAGE:004AC3A4 mov [ecx+70h], eax
创建PEB时用的是内核全局变量,用WinDbg可以看到,全局NtGlobalFlag为0,
但被调试时PEB内的NtGlobalFlag却是70h(如果不做手脚).
下面是ntdll内的代码:
VOID
LdrpInitialize (
IN PCONTEXT Context,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
Routine Description:
This function is called as a User-Mode APC routine as the first
user-mode code executed by a new thread. It's function is to initialize
loader context, perform module initialization callouts...
......
//#if DBG
if (TRUE)
//#else
// if (Peb->BeingDebugged || Peb->ReadImageFileExecOptions)
//#endif
{
PWSTR pw;
pw = (PWSTR)Peb->ProcessParameters->ImagePathName.Buffer;
if (!(Peb->ProcessParameters->Flags & RTL_USER_PROC_PARAMS_NORMALIZED)) {
pw = (PWSTR)((PCHAR)pw + (ULONG_PTR)(Peb->ProcessParameters));
}
UnicodeImageName.Buffer = pw;
UnicodeImageName.Length = Peb->ProcessParameters->ImagePathName.Length;
UnicodeImageName.MaximumLength = UnicodeImageName.Length;
//
// Hack for NT4 SP4. So we don't overload another GlobalFlag
// bit that we have to be "compatible" with for NT5, look for
// another value named "DisableHeapLookaside".
//
LdrQueryImageFileExecutionOptions( &UnicodeImageName,
L"DisableHeapLookaside",
REG_DWORD,
&RtlpDisableHeapLookaside,
sizeof( RtlpDisableHeapLookaside ),
NULL
);
st = LdrQueryImageFileExecutionOptions( &UnicodeImageName,
L"GlobalFlag",
REG_DWORD,
&Peb->NtGlobalFlag,
sizeof( Peb->NtGlobalFlag ),
NULL
);
if (!NT_SUCCESS( st )) {
if (Peb->BeingDebugged) {
/*** PEB.NtGlobalFlag被改写了 ***/
Peb->NtGlobalFlag |= FLG_HEAP_ENABLE_FREE_CHECK |
FLG_HEAP_ENABLE_TAIL_CHECK |
FLG_HEAP_VALIDATE_PARAMETERS;
}
}
#if defined(_WIN64)
NtHeader = RtlImageNtHeader(Peb->ImageBaseAddress);
if (NtHeader && NtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
UseWOW64 = TRUE;
}
#endif
}
if ( Peb->NtGlobalFlag & FLG_HEAP_PAGE_ALLOCS ) {
//
// We will enable page heap (RtlpDebugPageHeap) only after
// all other initializations for page heap are finished.
//
//
// If page heap is enabled we need to disable any flag that
// might force creation of debug heaps for normal NT heaps.
// This is due to a dependency between page heap and NT heap
// where the page heap within PageHeapCreate tries to create
// a normal NT heap to accomodate some of the allocations.
// If we do not disable these flags we will get an infinite
// recursion between RtlpDebugPageHeapCreate and RtlCreateHeap.
//
/*** PEB.NtGlobalFlag被改写了 ***/
Peb->NtGlobalFlag &= ~( FLG_HEAP_ENABLE_TAGGING |
FLG_HEAP_ENABLE_TAG_BY_DLL |
FLG_HEAP_ENABLE_TAIL_CHECK |
FLG_HEAP_ENABLE_FREE_CHECK |
FLG_HEAP_VALIDATE_PARAMETERS |
FLG_HEAP_VALIDATE_ALL |
FLG_USER_STACK_TRACE_DB );
大致就是这么个意思, 如果PEB.BeginDebugged为TRUE,就要使用调试堆. 至于
BeingDebugged在哪里被置1(或者能否不让其被置1,怀疑会影响调试)就没有管
了,在SoftICE里试了一下,在这里patch就行.
WinXP SP1/SP2,都在LdrpInitializeExecutionOptions内,下面是SP2的:
.text:7C942D56 __stdcall LdrpInitializeExecutionOptions(x, x) proc near
.text:7C942D56
.text:7C942D56 mov edi, edi
.text:7C942D58 push ebp
.text:7C942D59 mov ebp, esp
.text:7C942D5B sub esp, 40h
.text:7C942D5E mov eax, ___security_cookie
.text:7C942D63 push ebx
.text:7C942D64 push esi
.text:7C942D65 mov esi, [ebp+arg_4]
.text:7C942D68 push edi
.text:7C942D69 mov edi, [ebp+arg_0]
.text:7C942D6C mov [ebp+var_4], eax
.text:7C942D6F lea eax, [ebp+var_2C]
.text:7C942D72 push eax
.text:7C942D73 xor ebx, ebx
.text:7C942D75 push edi
.text:7C942D76 mov [ebp+var_38], edi
.text:7C942D79 mov [ebp+var_25], bl
.text:7C942D7C call LdrpOpenImageFileOptionsKey(x,x)
.text:7C942D81 test eax, eax
.text:7C942D83 jge sub_7C95DE3C
.text:7C942D89 test dword ptr [esi+68h], 2000100h
.text:7C942D90 jnz sub_7C95DF14
.text:7C942D96 cmp [esi+2], bl <----- BeginDebugged
.text:7C942D99 jnz loc_7C95DF23 <----- NOP这6字节
.text:7C942D9F
.text:7C942D9F loc_7C942D9F:
.text:7C942D9F mov ecx, [ebp+var_4]
.text:7C942DA2 mov al, [ebp+var_25]
.text:7C942DA5 pop edi
.text:7C942DA6 pop esi
.text:7C942DA7 pop ebx
.text:7C942DA8 call __security_check_cookie(x)
.text:7C942DAD leave
.text:7C942DAE retn 8
Patch位置在SP1和SP2下不大相同,似乎没有合适的函数作搜索跳板,我是直接在
text区段找的,只有1处满足类似下面的代码序列:
.text:7C942D89 test dword ptr [esi+68h], 2000100h
.text:7C942D90 jnz sub_7C95DF14
.text:7C942D96 cmp [esi+2], bl
.text:7C942D99 jnz loc_7C95DF23
我这里是Hook了OD自己的WaitForDebugEvent的, 这样patch就行:
BOOL __stdcall COllyDbg::NewWaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent,
DWORD dwMilliseconds
)
{
DWORD dwOldProt;
DWORD dwNewProt = PAGE_EXECUTE_READWRITE;
BYTE nops[6] = {0x90,0x90,0x90,0x90,0x90,0x90};
........
switch(lpDebugEvent->dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
if(m_PatchDebugHeap)
{
VirtualProtectEx(lpDebugEvent->u.CreateProcessInfo.hProcess,
(PVOID)m_PatchDebugHeap,
6,
dwNewProt,
&dwOldProt)
);
WriteProcessMemory(lpDebugEvent->u.CreateProcessInfo.hProcess,
(PVOID)m_PatchDebugHeap,
nops,
6,
NULL
);
VirtualProtectEx(lpDebugEvent->u.CreateProcessInfo.hProcess,
(PVOID)m_PatchDebugHeap,
6,
dwOldProt,
&dwNewProt
);
}
break;
......
m_PatchDebugHeap就是搜索出的patch地址.终于清静了;-)
[课程]Android-CTF解题方法汇总!