include Barrier.asm
	include Apfn.asm
DS_LIMIT		equ 7FFDFH	; ..0x7FFDFFFF
TABLE_MASK	equ 100B
RPL_MASK 		equ 011B
DS_SELECTOR	equ (8H or RPL_MASK or TABLE_MASK)
UsSystemCall		equ 7FFE0300H
UsSystemCallRet	equ 7FFE0304H
MAXIMUM_INSTRUCTION_LENGTH	equ 15
%LOAD_REDUCED_DS macro
	push DS_SELECTOR
	pop ds
endm
%LOAD_DEFAULT_DS macro
	push KGDT_R3_DATA or RPL_MASK
	pop ds
endm
.data
CallCount		ULONG ?
.code
LeaveStub proc C
	%LOAD_REDUCED_DS
	ret
LeaveStub endp
EnterStub proc C
; [Esp]:	@Stub
;		@RefStub
;		p1
;		...
;		pN
	lea edx,[ecx*4 + 4]
	jecxz Stub
@@:
	push dword ptr [esp + edx]
	loop @B
Stub:
	push offset LeaveStub
	push dword ptr [esp + edx]	; @Stub
	push EFLAGS_TF
	%LOAD_REDUCED_DS	; Уже загружен диспетчером..
	popfd
	jmp dword ptr cs:[UsSystemCall]
; [Esp]:	@Stub
;		@LeaveStub
;		p1
;		...
;		pN
;		@Stub
;		@RefStub
;		p1
;		...
;		pN
EnterStub endp
$Message	CHAR "LOG #%p: %p", 13, 10, 0
; +
; VEH
; o Трассировочный баг не закрываем, это должен сделать первый обработчик в цепочке!
; o Если необходимо вызвать системный функционал из диспетчера, при дальнейшем развё
;   ртывании цепочки обработчиков, то восстановить на время вызова Ds в дефолтное зн
;   ачение, использую связку LOAD_DEFAULT_DS/LOAD_REDUCED_DS!
;
AccessDispatch proc uses esi edi ExceptionPointers:PEXCEPTION_POINTERS
Local BarrierEntry:PVOID, PageSize:ULONG, OldProtect:ULONG
	mov eax,ExceptionPointers
	mov esi,EXCEPTION_POINTERS.ExceptionRecord[eax]
	assume esi:PEXCEPTION_RECORD
	mov edi,EXCEPTION_POINTERS.ContextRecord[eax]
	assume edi:PCONTEXT
	cmp [esi].ExceptionFlags,NULL
	jne Chain
	cmp [esi].ExceptionCode,STATUS_ACCESS_VIOLATION
	je Access
	cmp [esi].ExceptionCode,STATUS_SINGLE_STEP
	jne Chain
	mov eax,dword ptr cs:[UsSystemCall]
	cmp [edi].regEip,eax
	je KiBreak
	jb StopTrace
	add eax,MAXIMUM_INSTRUCTION_LENGTH
	cmp [edi].regEip,eax
	jnb StopTrace
	or [edi].regEFlags,EFLAGS_TF
	jmp ReloadDs
KiBreak:
; Sysenter или Int 0x2e.
; При возврате из сервиса восстановим Ds. Восстановление происходит при трассировочном исключении
; (шлюз трассируется

, для большей надёжноти заменим арес возврата(аналогично и с KiUserCallbackD
; ispatcher, Pfn). Последние два механизма не обязательны, ибо калбэки вызываются с взведённым TF.
; Вызов может быть рекурсивным. Необходимо сохранить адрес возврата в стаб(из KiFastSystemCall) в 
; стеке, установив адрес возврата на LeaveStub(). Для этого исполним EnterStub().
	mov eax,[edi].regEsp
	xor ecx,ecx
	cmp dword ptr [eax + 4],offset LeaveStub
	mov edx,dword ptr [eax]
	jne @f
	or [edi].regEFlags,EFLAGS_TF
	jmp ReloadDs
@@:
; ooooooooooooooooooooooooooooooooooooooooooooooo
	pushad
; Логгируем вызов сервиса.
; Eip = @KiFastSystemCall/KiIntSystemCall
; Eax = Service ID
	%LOAD_DEFAULT_DS
	invoke DbgPrint, addr $Message, CallCount, [edi].regEax
	inc CallCount
	popad
; ooooooooooooooooooooooooooooooooooooooooooooooo
	cmp byte ptr [edx],0C3H	; Ret
	je GoStub
	cmp byte ptr [edx],0C2H	; Ret #
	jne StopTrace	; Число параметров не определено, не системный вызов.
	movzx ecx,word ptr [edx + 1]
GoStub:
	shr ecx,2
	mov [edi].regEip,offset EnterStub
	mov [edi].regEcx,ecx
StopTrace:
	and [edi].regEFlags,NOT(EFLAGS_TF)
	jmp ReloadDs
Access:
; [ExceptionInformation]:
; +0 R/W
; +4 Line address.
	cmp [esi].ExceptionInformation,ACCESS_TYPE_READ
	je @f	; Чтение или исполнение сегмента.
; Запись в сегмент. (-1 если смещение больше чем лимит сегмента

.
	cmp [esi + 4].ExceptionInformation,-1
	jne Chain	; Обращение в пределах сегмента, пропускаем исключение.
; Обращение за пределы сегмента. Проверяем сегмент данных.
	cmp [edi].regSegDs,DS_SELECTOR
	jne Chain	; Ds дефолтный(не DS_SELECTOR), обращение не к сегменту данных, пропускаем исключение.
	jmp Step	; Вероятно обращение к сегменту данных, восстанавливаем Ds в дефолтный и трассируем инструкцию.
@@:
	cmp [esi + 4].ExceptionInformation,-1
	jne IsCallout	; Обращение в пределах сегмента, возможно вызов InitRoutine().
	cmp [edi].regSegDs,DS_SELECTOR
	jne Chain	; Ds дефолтный, пропускаем исключение.
; Вероятно обращение к UsSharedData. Проверяем стаб.
IsBreak:
	cmp [edi].regEdx,UsSystemCall
	jne Step
	mov eax,[edi].regEip
	; ..IsValid
	cmp word ptr [eax],12FFH	; call dword ptr ds:[edx]
	jne Step	; Не стаб, восстанавливаем дефолтный Ds и трассируем инструкцию.
; Вызов из стаба(ZwXX()).
; При вызове калбэка будет сгенерировано исключение(APC и пр.), тогда перезагрузим Ds.
	;..
	jmp Step
IsCallout:
	cmp [edi].regEip,80000000H	; Исключенеи в пределах пользовательского ап(не Callout).
	jb Chain
; Возможно вызов InitRoutine(), проверяем.
	mov eax,fs:[TEB.Peb]
	mov eax,PEB.Ldr[eax]
	mov eax,PEB_LDR_DATA.InLoadOrderModuleList[eax]
	mov eax,LDR_DATA_TABLE_ENTRY.InLoadOrderModuleList.Flink[eax]
	mov eax,LDR_DATA_TABLE_ENTRY.EntryPoint[eax]
	cmp [esi].ExceptionAddress,eax
	jne Chain
	cmp [edi].regEip,eax
	jne Chain
; Вызов InitRoutine(). Корректируем адрес и возвращаемся.
	btr [edi].regEip,31
; Ds должен быть дефолтный, иначе возникнут рекурсивные вызовы!
	%LOAD_DEFAULT_DS
ReloadDs:
	mov [edi].regSegDs,DS_SELECTOR
Continue:
	mov eax,EXCEPTION_CONTINUE_EXECUTION
Exit:
	ret
Step:
	mov [edi].regSegDs,KGDT_R3_DATA or RPL_MASK
	or [edi].regEFlags,EFLAGS_TF
	jmp Continue
Chain:
	mov [edi].regSegDs,DS_SELECTOR
	%LOAD_REDUCED_DS
	xor eax,eax
	jmp Exit
AccessDispatch endp
; +
;
ApfnStub proc C
	%LOAD_REDUCED_DS
	ret
ApfnStub endp
; +
; Захват InitRoutine модуля ntdll.dll
; (Можно заменить указатель на заглушку, загружающую Ds).
;
%LDR_DILAPIDATE_DATABASE macro
	mov eax,fs:[TEB.Peb]
	mov eax,PEB.Ldr[eax]
	mov eax,PEB_LDR_DATA.InLoadOrderModuleList[eax]
	assume eax:PLDR_DATA_TABLE_ENTRY
	mov eax,[eax].InLoadOrderModuleList.Flink
	bts LDR_DATA_TABLE_ENTRY.EntryPoint[eax],31	; +0x80000000
endm
lpsz	db "..",0
Entry proc
Local ApfnInformation:APFN_INFORMATION
	invoke MessageBeep, 0	; For initialize Apfn.
	invoke ZwSetLdtEntries, DS_SELECTOR, 0FFDFH, 0C7F200H, 0, 0, 0
	test eax,eax
	mov gHandler,offset AccessDispatch
	jnz Exit
	invoke InitializeCalloutEntryListBarrier, addr gBarrier
	test eax,eax
	jnz Exit
	invoke ApfnRedirect, addr ApfnStub, addr ApfnInformation
	test eax,eax
	jnz Exit
	%LDR_DILAPIDATE_DATABASE
	invoke MessageBox, 0, addr lpsz, addr lpsz, MB_OK
	%LOAD_REDUCED_DS
	invoke ZwYieldExecution	; Break!
Exit:
	ret
Entry endp
end Entry