✊Rot Front✊
-
Автор темы
- #1
Итак,поскольку мне было скучно и мне было интересно как античиты обнаруживают KM дебаггер,то я решил провести маленький анализ.
Начнём по порядку.
Я есть закон!
Первая функция,которая встречает нас в экспорте - KdEnableDebugger.
Я решил выбрать данную функцию т.к она экспортируется и вызывает другие интересные нас функции.
Сама функция выглядит следующем образом:
Поскольку для нас тут нет ничего интересного,то переходим к KdEnableDebuggerWithLock
Итак,здесь происходят главные моменты.
Во-первых,при вызове TakeLock с true функция присваивает KdpDebugRoutineSelect,KdDebuggerEnabled and
KERNEL_KUSER_SHARED_DATA ->KdDebuggerEnabled = TRUE;
Вызов KdpRestoreAllBreakpoints произойдёт ,если KdDisableCount != 0 && (KdDisableCountSaved == 1 && KdPreviouslyEnabled)
Мы немного отвлечёмся от последовательности и разберём KdpRestoreAllBreakpoints.
Я решил отвлечься т.к лучше понимать,как устроены bp в ядре.
Сама функция KdpRestoreAllBreakpoints вызывает KdpLowRestoreBreakpoint.
Я не буду показывать саму функцию т.к нас интересует только глобальные значения и что происходит.
Сама функция перезаписывает 2 bp на 0.
Если мы посмотрим на KdpLowRestoreBreakpoint,то увидим :
v1 = (char *)&KdpBreakpointTable + 0x28 * a1;
Размер структуры KdpBreakpointTable = 0x28.
Саму структуру я украду у
Структура ≈ выглядит так:
Вернёмся к функции KdEnableDebuggerWithLock.
Нас интересует вызов KdInitSystem.
Сама функция хоть и большая,но я попытался её разобрать с моей больной башкой, поэтому могут быть небольшие ошибки.
Сама функция выглядит ≈ так:
Тут много чего происходит вкусного.
Во-первых, инициализация завершается неудачно, если Phase == -1 и KeLoaderBlock->Extension->BootDebuggerActive не равен 0.
Во-вторых,строки "DBGPRINT_LOG_SIZE=", "DEBUGPORT=LOCAL" являются опциями при загрузке в windows( KeLoaderBlock->LoadOptions)
и они сохраняются в SystemStartOptions (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control),
благодаря этому можно сделать мемный детект дебаг мода и тест мода через реестр :D
Если KdDebuggerEnabled != 0 ,то функция перезаписывает KeLoaderBlock->Extension->KdEntropy на ноль и возвращает TRUE.
Я не буду всё расписывать, ибо я не извращенец, чтобы писать про 20+ глобальных переменных и анализировать их тип :D
Сразу говорю,что в KdInitialize( kdcom.dll) нет ничего интересного,поэтому не вижу смысла анализировать эту функцию.
Сама функция(KdInitSystem) будет вызвана во время инициализации системы,а точнее будет вызвана из KdInitSystem.
Мне уже лень её разбирать, поэтому можете самостоятельно проанализировать её.
Теперь проанализируем KdDisableDebugger т.е как выключается km дебаггер:
Сразу переходим к KdDisableDebuggerWithLock т.к ловить нечего
KERNEL_KUSER_SHARED_DATA ->KdDebuggerEnabled,KdpDebugRoutineSelect,KdDebuggerEnabled присваивается FALSE и KdDebuggerNotPresent присваивается TRUE.
Итак, небольшая статья подходит к концу т.к анализировать все переменные - скука смертная, то я оставлю небольшой подарок.
Вот переменные, которые можно использовать для обнаружения KM дебаггера/
Здесь написано с какой версии windows они присутствуют(я анализировал с windows 7 -11,но мне лень было устанавливать windows 8,поэтому мог ошибиться с KdLocalDebugEnabled):
Just bool:
KdLocalDebugEnabled windows 8.1(maybe windows 8)
(BOOLEAN)KdDebuggerNotPresent windows 7
(BOOLEAN)KdDebuggerEnabled windows 7
KdpBootedNodebug windows 7
KdpDebuggerStructuresInitialized windows 7
KdPageDebuggerSection windows 10
(BOOLEAN)KdIgnoreUmExceptions ? windows 7
(BOOLEAN)KdBlockEnable windows 7
(BOOLEAN)KUSER_SHARED_DATA->KdDebuggerEnabled windows 2000
Time(struct):
KdpTimeSlipDpc windows 7
KdpTimeSlipTimer windows 7
KdpTimeSlipWorkItem windows 7
Over stuff:
(xmmword)KdpContext windows 7
Сюрприз-сюрприз!
В winows 8.1 данная переменная не существует и она вычисляется следующем образом:KdpContext = (адрес)KdDebuggerNotPresent + 7
(struct)KdpBreakpointTable windows 7
Все кредиты идут colby57 т.к они мне не нужны :D.
Прости,что решил написать статью не потом,а сегодня.
Начнём по порядку.
Я есть закон!
Первая функция,которая встречает нас в экспорте - KdEnableDebugger.
Я решил выбрать данную функцию т.к она экспортируется и вызывает другие интересные нас функции.
Сама функция выглядит следующем образом:
C++:
NTSTATUS KdEnableDebugger()
{
int v0; // edi
NTSTATUS nt_status; // ebx
v0 = KeRelaxTimingConstraints(1);
nt_status = KdEnableDebuggerWithLock(TRUE);
KeRelaxTimingConstraints(v0);
return nt_status;
}
Поскольку для нас тут нет ничего интересного,то переходим к KdEnableDebuggerWithLock
C++:
NTSTATUS __fastcall KdEnableDebuggerWithLock(BOOLEAN TakeLock)
{
unsigned __int8 CurrentIrql; // bl
int v4; // eax
CurrentIrql = 0;
if ( KdPitchDebugger )
return STATUS_DEBUGGER_INACTIVE;
if ( KdBlockEnable )
return STATUS_ACCESS_DENIED;
if ( TakeLock )
{
CurrentIrql = KeGetCurrentIrql();
__writecr8(DISPATCH_LEVEL);
KxAcquireSpinLock(&KdDebuggerLock);
}
KdDisableCountSaved = KdDisableCount;
if ( KdDisableCount )
{
--KdDisableCount;
if ( KdDisableCountSaved == 1 && KdPreviouslyEnabled )
{
if ( TakeLock )
{
KdPowerTransitionEx(0x40000001i64, 0i64);
KdpDebugRoutineSelect = TRUE;
LOBYTE(KdDebuggerEnabled) = TRUE;
KERNEL_KUSER_SHARED_DATA ->KdDebuggerEnabled = TRUE; // MEMORY[0xFFFFF780000002D4] = 1;
KdpRestoreAllBreakpoints();
}
else
{
PoHiberInProgress = TRUE;
KdInitSystem(0, 0i64);
KdpRestoreAllBreakpoints();
PoHiberInProgress = 0;
}
}
if ( TakeLock )
{
KxReleaseSpinLock(&KdDebuggerLock);
__writecr8(CurrentIrql);
}
}
else
{
if ( TakeLock )
{
KxReleaseSpinLock(&KdDebuggerLock);
__writecr8(CurrentIrql);
return STATUS_INVALID_PARAMETER;
}
KdInitSystem(0, 0i64);
}
return STATUS_SUCCESS;
}
Итак,здесь происходят главные моменты.
Во-первых,при вызове TakeLock с true функция присваивает KdpDebugRoutineSelect,KdDebuggerEnabled and
KERNEL_KUSER_SHARED_DATA ->KdDebuggerEnabled = TRUE;
Вызов KdpRestoreAllBreakpoints произойдёт ,если KdDisableCount != 0 && (KdDisableCountSaved == 1 && KdPreviouslyEnabled)
Мы немного отвлечёмся от последовательности и разберём KdpRestoreAllBreakpoints.
Я решил отвлечься т.к лучше понимать,как устроены bp в ядре.
Сама функция KdpRestoreAllBreakpoints вызывает KdpLowRestoreBreakpoint.
Я не буду показывать саму функцию т.к нас интересует только глобальные значения и что происходит.
Сама функция перезаписывает 2 bp на 0.
Если мы посмотрим на KdpLowRestoreBreakpoint,то увидим :
v1 = (char *)&KdpBreakpointTable + 0x28 * a1;
Размер структуры KdpBreakpointTable = 0x28.
Саму структуру я украду у
Пожалуйста, авторизуйтесь для просмотра ссылки.
.Структура ≈ выглядит так:
C++:
typedef struct _BREAKPOINT_ENTRY
{
PVOID BreakPoint;
ULONG64 DirectoryTableBase;
DWORD32 InterruptByte_CC;
DWORD32 UnknownField_0;
DWORD32 OriginalByte;
DWORD32 UnknownField_1;
ULONG64 UnknownField_2;
}BREAKPOINT_ENTRY, *PBREAKPOINT_ENTRY;
Вернёмся к функции KdEnableDebuggerWithLock.
Нас интересует вызов KdInitSystem.
Сама функция хоть и большая,но я попытался её разобрать с моей больной башкой, поэтому могут быть небольшие ошибки.
Сама функция выглядит ≈ так:
C++:
BOOLEAN __fastcall KdInitSystem(ULONG Phase, PLOADER_PARAMETER_BLOCK KeLoaderBlock )
{
char v3; // r12
char v4; // r15
char v6; // r14
__int64 v7; // rcx
struct _KPRCB *CurrentPrcb; // rcx
__int64 v9; // rcx
char *LoadOptions; // rbp
char v11; // bl
char *v12; // rax
__int64 Extension; // rdi
unsigned int v14; // eax
const char *LoadOptions2; // rsi
char *v16; // rdx
unsigned __int64 v17; // rax
__int64 v18; // rcx
const char *j; // rcx
const char *v20; // rsi
__int64 v21; // rdx
unsigned int v22; // ebp
__int64 *k; // rbx
__int64 v24; // rdx
char *v25; // r9
__int64 v26; // r8
char v27; // al
__int64 v28; // rcx
PVOID PoolWithTag; // rax
PVOID v31; // rbx
int v32[8]; // [rsp+0h] [rbp-178h] BYREF
STRING DestinationString; // [rsp+20h] [rbp-158h] BYREF
char SourceString[256]; // [rsp+30h] [rbp-148h] BYREF
v3 = NULL;
v4 = NULL;
if ( Phase == -1 )
{
if(KeLoaderBlock->Extension-> BootDebuggerActive != 0)
__debugbreak();
}
else
{
if ( Phase )
{
KeQueryPerformanceCounter(&KdPerformanceCounterRate);
if ( !KdPitchDebugger )
{
for ( unsigned int i = 0; i < (unsigned int)KeNumberProcessors_0; ++i )
{
PoolWithTag = ExAllocatePoolWithTag(NonPagedPoolNx, 0x1000ui64, 'oIdK');
v31 = PoolWithTag;
if ( PoolWithTag )
{
memset(PoolWithTag, 0, 0x1000ui64);
_InterlockedOr(v32, FALSE);
KdLogBuffer[i] = v31;
}
}
}
KdpLoaderDebuggerBlock = FALSE;
return TRUE;
}
if ( (_BYTE)KdDebuggerEnabled )
goto LABEL_31;
KdpDebugRoutineSelect = FALSE;
BYTE1(KdDebuggerEnabled) = FALSE;
if ( !KdPitchDebugger || (v6 = TRUE, !KdLocalDebugEnabled) )
v6 = FALSE;
if ( KdDebugDevice && KdDebugDevice->TransportType == 3 )
KdTransportMaxPacketSize = 0x480;
if ( !KdpDebuggerDataListHead )
{
*((_QWORD *)&KdpContext + 1) = KdDebugDevice;
qword_140C00C90 = MmGetPagedPoolCommitPointer();
KdpPowerSpinLock = FALSE;
qword_140C40738 = &KdpPowerListHead;
KdpPowerListHead = &KdpPowerListHead;
qword_140C40748 = &KdpDebuggerDataListHead;
KdpDebuggerDataListHead = &KdpDebuggerDataListHead;
KdRegisterDebuggerDataBlock(v7, &KdDebuggerDataBlock);
WORD1(KdVersionBlock) = NtBuildNumber;
WORD3(KdVersionBlock) |= TRUE;
LOWORD(KdVersionBlock) = (unsigned int)NtBuildNumber >> 28;
*((_QWORD *)&xmmword_140C0F388 + 1) = &PsLoadedModuleList;
*(_WORD *)((char *)&KdVersionBlock + 11) = 0x3303;
qword_140C0F398 = (__int64)&KdpDebuggerDataListHead;
}
CurrentPrcb = KeGetCurrentPrcb();
if ( !CurrentPrcb->Context )
{
CurrentPrcb->ContextFlagsInit = CONTEXT_FULL;
CurrentPrcb->Context = &CurrentPrcb->ProcessorState.ContextFrame;
}
if ( KeLoaderBlock )
{
v9 = *(_QWORD *)(*(_QWORD *)(KeLoaderBlock + 16) + 48i64);
off_140C00DF8 = &KdpLoaderDebuggerBlock;
KdpLoaderDebuggerBlock = KeLoaderBlock + 16;
LoadOptions = KeLoaderBlock->LoadOptions
*(_QWORD *)&xmmword_140C0F388 = v9;
if ( LoadOptions )
{
strupr(LoadOptions);
LODWORD(KdPrintBufferAllocateSize) = FALSE;
v11 = FALSE;
v12 = strstr(LoadOptions, "DBGPRINT_LOG_SIZE=");
if ( v12 )
{
v14 = (atol(v12 + 18) + 4095) & 0xFFFFF000;
LODWORD(KdPrintBufferAllocateSize) = v14;
if ( v14 > 0x1000000 )
{
LODWORD(KdPrintBufferAllocateSize) = 0x1000000;
v14 = 0x1000000;
}
if ( v14 <= 0x1000 )
LODWORD(KdPrintBufferAllocateSize) = FALSE;
}
if ( strstr(LoadOptions, "NODEBUG") )
{
KdPitchDebugger = TRUE;
KdPageDebuggerSection = TRUE;
KdpBootedNodebug = TRUE;
}
else if ( strstr(LoadOptions, "DEBUGPORT=LOCAL") )
{
KdPitchDebugger = TRUE;
v6 = TRUE;
KdPageDebuggerSection = TRUE;
LOBYTE(KdDebuggerNotPresent) = TRUE;
KdLocalDebugEnabled = TRUE;
KdpBootedNodebug = FALSE;
}
else
{
LoadOptions2 = LoadOptions;
do
{
v16 = strstr(LoadOptions2, " DEBUG=");
if ( !v16 )
{
v16 = strstr(LoadOptions2, " DEBUG");
if ( !v16 )
break;
}
LoadOptions2 = v16 + 6;
v17 = v16[6];
if ( (unsigned __int8)v17 <= 0x3Du )
{
v18 = 0x2000000100000001i64;
if ( _bittest64(&v18, v17) )
{
KdpBootedNodebug = FALSE;
v11 = 1;
if ( v16[6] == 61 )
{
for ( j = v16 + 7; ; j = v20 + 1 )
{
LOBYTE(v17) = *j;
v20 = j;
while ( (_BYTE)v17 )
{
if ( (unsigned __int8)v17 <= 0x2Cu )
{
v21 = 0x100100000200i64;
if ( _bittest64(&v21, v17) )
break;
}
LOBYTE(v17) = *++v20;
}
v17 = (unsigned int)((_DWORD)v20 - (_DWORD)j);
if ( (_DWORD)v20 == (_DWORD)j )
break;
switch ( (_DWORD)v17 )
{
case 0xA:
LODWORD(v17) = strncmp(j, "AUTOENABLE", 0xAui64);
if ( !(_DWORD)v17 )
{
v3 = 1;
KdAutoEnableOnEvent = 1;
v4 = FALSE;
}
break;
case 7:
LODWORD(v17) = strncmp(j, "DISABLE", 7ui64);
if ( !(_DWORD)v17 )
{
v3 = 1;
KdAutoEnableOnEvent = FALSE;
v4 = 1;
}
break;
case 6:
LODWORD(v17) = strncmp(j, "NOUMEX", 6ui64);
if ( !(_DWORD)v17 )
KdIgnoreUmExceptions = TRUE;
break;
}
if ( *v20 != 44 )
break;
}
}
break;
}
}
}
while ( v16 != (char *)-6i64 );
}
if ( strstr(LoadOptions, "NOEVENT") )
{
KdEventLoggingEnabled = FALSE;
}
else if ( strstr(LoadOptions, "EVENT") )
{
KdEventLoggingEnabled = TRUE;
v11 = TRUE;
KdPageDebuggerSection = FALSE;
}
}
else
{
KdPitchDebugger = TRUE;
v11 = FALSE;
KdPageDebuggerSection = TRUE;
}
}
else
{
v11 = TRUE;
*(_QWORD *)&xmmword_140C0F388 = PsNtosImageBase;
}
qword_140C00B38 = xmmword_140C0F388;
if ( !v6 )
{
if ( KeLoaderBlock && KeLoaderBlock->OsLoaderSecurityVersion != TRUE )
v11 = FALSE;
if ( !v11 )
{
LOBYTE(KdDebuggerNotPresent) = TRUE;
goto LABEL_31;
}
if ( (int)KdInitialize(0i64, KeLoaderBlock, &KdpContext) < 0 )
{
KdPitchDebugger = FALSE;
v11 = FALSE;
LOBYTE(KdDebuggerNotPresent) = TRUE;
KdLocalDebugEnabled = TRUE;
}
else
{
KdpDebugRoutineSelect = TRUE;
}
}
if ( !KdpDebuggerStructuresInitialized )
{
BYTE4(KdpContext) = 0;
LODWORD(KdpContext) = 0x14;
KeInitializeDpc(&KdpTimeSlipDpc, (PKDEFERRED_ROUTINE)KdpTimeSlipDpcRoutine, 0i64);
KeInitializeTimerEx(&KdpTimeSlipTimer, NotificationTimer);
KdpTimeSlipWorkItem.Parameter = FALSE;
KdpTimeSlipWorkItem.WorkerRoutine = (void (__fastcall *)(void *))KdpTimeSlipWork;
KdpTimeSlipWorkItem.List.Flink = (_LIST_ENTRY *)FALSE;
KdpDebuggerStructuresInitialized = TRUE;
}
KdTimerStart = FALSE;
if ( KdEventLoggingEnabled && KdpBootedNodebug )
{
KdPitchDebugger = TRUE;
KdEventLoggingPresent = v11;
LOBYTE(KdDebuggerNotPresent) = TRUE;
KdLocalDebugEnabled = FALSE;
}
else
{
LOBYTE(KdDebuggerEnabled) = TRUE;
KERNEL_KUSER_SHARED_DATA->KdDebuggerEnabled = TRUE;
if ( KdLocalDebugEnabled )
goto LABEL_31;
}
if ( KdEventLoggingEnabled && !(_BYTE)KdDebuggerEnabled )
{
LABEL_31:
if ( KeLoaderBlock )
{
Extension = KeLoaderBlock->Extension
if ( Extension )
memset((void *)Extension->KdEntropy, 0, 0x20);
}
return TRUE;
}
KdPitchDebugger = FALSE;
if ( v3 )
{
KdDisableDebuggerWithLock();
KdBlockEnable = v4;
goto LABEL_31;
}
if ( KeLoaderBlock )
{
v22 = NULL;
for ( k = KeLoaderBlock->LoadOrderListHead; k != KeLoaderBlock -> LoadOrderListHead ; ++v22 )
{
if ( v22 >= 3 )
break;
DestinationString = 0i64;
LODWORD(v24) = FALSE;
v25 = (char *)k[10];
v26 = *((unsigned __int16 *)k + 36) >> 1;
if ( (unsigned int)v26 >= 0x100 )
v26 = 255i64;
do
{
v27 = *v25;
v25 += 2;
v28 = (unsigned int)v24;
v24 = (unsigned int)(v24 + 1);
SourceString[v28] = v27;
}
while ( (unsigned int)v24 < (unsigned int)v26 );
if ( (unsigned int)v24 >= 0x100ui64 )
_report_rangecheckfailure(v28, v24, v26, v25, *(_QWORD *)&DestinationString.Length, DestinationString.Buffer);
SourceString[v24] = FALSE;
RtlInitAnsiString(&DestinationString, SourceString);
DbgLoadImageSymbols(&DestinationString, k[6], -1);
k = (__int64 *)*k;
}
}
else
{
DbgLoadImageSymbols(0i64, qword_140C00B38, -1);
}
if ( KeLoaderBlock )
{
BYTE1(KdDebuggerEnabled) = KdPollBreakIn();
goto LABEL_31;
}
}
return TRUE;
}
Тут много чего происходит вкусного.
Во-первых, инициализация завершается неудачно, если Phase == -1 и KeLoaderBlock->Extension->BootDebuggerActive не равен 0.
Во-вторых,строки "DBGPRINT_LOG_SIZE=", "DEBUGPORT=LOCAL" являются опциями при загрузке в windows( KeLoaderBlock->LoadOptions)
и они сохраняются в SystemStartOptions (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control),
благодаря этому можно сделать мемный детект дебаг мода и тест мода через реестр :D
Если KdDebuggerEnabled != 0 ,то функция перезаписывает KeLoaderBlock->Extension->KdEntropy на ноль и возвращает TRUE.
Я не буду всё расписывать, ибо я не извращенец, чтобы писать про 20+ глобальных переменных и анализировать их тип :D
Сразу говорю,что в KdInitialize( kdcom.dll) нет ничего интересного,поэтому не вижу смысла анализировать эту функцию.
Сама функция(KdInitSystem) будет вызвана во время инициализации системы,а точнее будет вызвана из KdInitSystem.
Мне уже лень её разбирать, поэтому можете самостоятельно проанализировать её.
Теперь проанализируем KdDisableDebugger т.е как выключается km дебаггер:
C++:
NTSTATUS KdDisableDebugger()
{
return KdDisableDebuggerWithLock();
}
Сразу переходим к KdDisableDebuggerWithLock т.к ловить нечего
C++:
NTSTATUS __stdcall KdDisableDebuggerWithLock()
{
unsigned __int8 CurrentIrql; // bl
NTSTATUS nt_status; // edi
if ( KdPitchDebugger )
return STATUS_DEBUGGER_INACTIVE;
if ( KdBlockEnable )
return STATUS_ACCESS_DENIED;
CurrentIrql = KeGetCurrentIrql();
__writecr8(DISPATCH_LEVEL);
KxAcquireSpinLock(&KdDebuggerLock);
if ( KdDisableCount )
goto LABEL_11;
KdPreviouslyEnabled = (char)KdDebuggerEnabled;
if ( !(_BYTE)KdDebuggerEnabled || (nt_status = KdpAllowDisable(), nt_status >= 0) )
{
if ( (_BYTE)KdDebuggerEnabled )
{
KdpSuspendAllBreakpoints();
KERNEL_KUSER_SHARED_DATA ->KdDebuggerEnabled = FALSE; //MEMORY[0xFFFFF780000002D4] = FALSE;
KdpDebugRoutineSelect = FALSE;
LOBYTE(KdDebuggerNotPresent) = TRUE;
LOBYTE(KdDebuggerEnabled) = FALSE;
KdPowerTransitionEx(0x40000004i64, 0i64);
}
LABEL_11:
++KdDisableCount;
KxReleaseSpinLock(&KdDebuggerLock);
nt_status = STATUS_SUCCESS;
goto LABEL_12;
}
KxReleaseSpinLock(&KdDebuggerLock);
LABEL_12:
__writecr8(CurrentIrql);
return nt_status;
}
Итак, небольшая статья подходит к концу т.к анализировать все переменные - скука смертная, то я оставлю небольшой подарок.
Вот переменные, которые можно использовать для обнаружения KM дебаггера/
Здесь написано с какой версии windows они присутствуют(я анализировал с windows 7 -11,но мне лень было устанавливать windows 8,поэтому мог ошибиться с KdLocalDebugEnabled):
Just bool:
KdLocalDebugEnabled windows 8.1(maybe windows 8)
(BOOLEAN)KdDebuggerNotPresent windows 7
(BOOLEAN)KdDebuggerEnabled windows 7
KdpBootedNodebug windows 7
KdpDebuggerStructuresInitialized windows 7
KdPageDebuggerSection windows 10
(BOOLEAN)KdIgnoreUmExceptions ? windows 7
(BOOLEAN)KdBlockEnable windows 7
(BOOLEAN)KUSER_SHARED_DATA->KdDebuggerEnabled windows 2000
Time(struct):
KdpTimeSlipDpc windows 7
KdpTimeSlipTimer windows 7
KdpTimeSlipWorkItem windows 7
Over stuff:
(xmmword)KdpContext windows 7
Сюрприз-сюрприз!
В winows 8.1 данная переменная не существует и она вычисляется следующем образом:KdpContext = (адрес)KdDebuggerNotPresent + 7
(struct)KdpBreakpointTable windows 7
Все кредиты идут colby57 т.к они мне не нужны :D.
Прости,что решил написать статью не потом,а сегодня.
Пожалуйста, зарегистрируйтесь или авторизуйтесь, чтобы увидеть содержимое.
Последнее редактирование: