Thanks for the link, but now i'm kinda disappointed. The process described in that article is bullshit. While the conditions do probably often happen to come out the way they describe, there is nothing that makes it necessarily so. The fact that this appears to work some of the time is simply because olly takes up a lot of cpu cycles and thus alters the general pattern of thread readiness. NtYieldExecution will fail under two and only two conditions:
1. KiReadySummary is zero
2. KiFindReadyThread fails
I think there probably are ways to accurately manipulate NtYieldExecution into revealing a debugger in a meaningful (read: accurate) way, so not all is lost...but the authors of this article really should have at least attempted to discern why their supposed patterns were emerging. Since they didn't, I did, here's my analysis of NtYieldExecution, and the only other relevant function it calls KiFindReadyThread (KiSwapThread is not relevant because it cannot cause the function to fail):
Code:
.text:004240F3 ; NTSTATUS NtYieldExecution(void)
.text:004240F3 _NtYieldExecution@0 proc near ; DATA XREF: .text:0040BB00o
.text:004240F3
.text:004240F3 ; FUNCTION CHUNK AT .text:0043F4CF SIZE 0000000D BYTES
.text:004240F3
.text:004240F3 cmp ds:_KiReadySummary, 0
.text:004240FA push ebx ; This condition seems like it should be pretty much impossible.
.text:004240FB mov ebx, 40000024h
.text:00424100 jz loc_42419E
.text:00424106 push esi
.text:00424107 push edi
.text:00424108 mov eax, large fs:124h ; CurrentThread
.text:0042410E mov esi, eax
.text:00424110 call ds:__imp__KeRaiseIrqlToDpcLevel@0 ; KeRaiseIrqlToDpcLevel()
.text:00424116 mov [esi+58h], al ; CurrentKThread->WaitIrql = KeRaiseIrqlToDpcLevel()
.text:00424119 db 3Eh
.text:00424119 mov eax, ds:0FFDFF020h ; Kpcr->Prcb
.text:0042411F mov edi, eax
.text:00424121 cmp dword ptr [edi+8], 0 ; if(Kpcr->Prcb->NextThread == NULL)
.text:00424125 jnz short loc_424141 ; CurrentKThread->Priority
.text:00424127 movzx ecx, byte ptr [esi+12Bh] ; CurrentKThread->NextProcessor
.text:0042412E xor edx, edx
.text:00424130 inc edx ; Yield to priority 1 and above
.text:00424131 call @KiFindReadyThread@8 ; KiFindReadyThread(x,x)
.text:00424136 test eax, eax
.text:00424138 mov [edi+8], eax ; Kpcr->Kprcb->NextThread = KiFindReadyThread()
.text:0042413B jz loc_43F4CF
.text:00424141
.text:00424141 loc_424141: ; CODE XREF: NtYieldExecution()+32j
.text:00424141 movsx ecx, byte ptr [esi+33h] ; CurrentKThread->Priority
.text:00424145 cmp ecx, 10h
.text:00424148 mov eax, [esi+44h] ; CurrentKThread->ApcState->Process
.text:0042414B mov al, [eax+63h] ; CurrentKProcess->ThreadQuantum
.text:0042414E mov [esi+6Fh], al ; CurrentKThread->Quantum
.text:00424151 mov byte ptr [esi+2Dh], 1 ; CurrentKThread->State = 1
.text:00424155 jge short loc_424170 ; if(CurrentKThread->Priority >= 16)
.text:00424157 movsx eax, byte ptr [esi+6Eh] ; CurrentKThread->PriorityDecrement
.text:0042415B or edx, 0FFFFFFFFh
.text:0042415E sub edx, eax
.text:00424160 movsx eax, byte ptr [esi+6Ch] ; CurrentKThread->BasePriority
.text:00424164 add ecx, edx ; Perform the priority decrement
.text:00424166 cmp ecx, eax ; Check it against the base priority
.text:00424168 jge short loc_42416C ; CurrentKThread->PriorityDecrement = 0
.text:0042416A mov ecx, eax
.text:0042416C
.text:0042416C loc_42416C: ; CODE XREF: NtYieldExecution()+75j
.text:0042416C mov byte ptr [esi+6Eh], 0 ; CurrentKThread->PriorityDecrement = 0
.text:00424170
.text:00424170 loc_424170: ; CODE XREF: NtYieldExecution()+62j
.text:00424170 mov [esi+33h], cl ; CurrentKThread->Priority = previously calculated new priority
.text:00424173 lea eax, [esi+60h] ; CurrentKThread->SwapListEntry
.text:00424176 lea edx, _KiDispatcherReadyListHead[ecx*8] ; Get the dispatcher priority database we want
.text:0042417D mov esi, [edx+4] ; KiDispatcherReadyListHead.Blink
.text:00424180 mov [eax], edx ; CurrentKThread->SwapListEntry.Flink = KiDispatcherReadyListHead.Flink
.text:00424182 mov [eax+4], esi ; CurrentKThread->SwapListEntry.Blink = KiDispatcherReadyListHead.Blink
.text:00424185 mov [esi], eax ; KiDispatcherReadyListHead.Blink = CurrentKThread.SwapListEntry
.text:00424187 mov [edx+4], eax ; Duplicate operation...?
.text:0042418A xor eax, eax
.text:0042418C inc eax
.text:0042418D shl eax, cl
.text:0042418F or ds:_KiReadySummary, eax ; Insert priority mask for our thread into the bitmask so that the
.text:0042418F ; thread scheduler knows we're here
.text:00424195 call @KiSwapThread@0 ; KiSwapThread()
.text:0042419A xor ebx, ebx
.text:0042419C
.text:0042419C loc_42419C: ; CODE XREF: NtYieldExecution()+1B3E4j
.text:0042419C pop edi
.text:0042419D pop esi
.text:0042419E
.text:0042419E loc_42419E: ; CODE XREF: NtYieldExecution()+Dj
.text:0042419E mov eax, ebx
.text:004241A0 pop ebx
.text:004241A1 retn
.text:004241A1 _NtYieldExecution@0 endp ; sp-analysis failed
Code:
.text:0040503F ; START OF FUNCTION CHUNK FOR @KiFindReadyThread@8
.text:0040503F
.text:0040503F loc_40503F: ; CODE XREF: KiFindReadyThread(x,x)-9j
.text:0040503F ; KiFindReadyThread(x,x)+47j
.text:0040503F xor eax, eax
.text:00405041 jmp short loc_4050B8
.text:00405043 ; ---------------------------------------------------------------------------
.text:00405043
.text:00405043 loc_405043: ; CODE XREF: KiFindReadyThread(x,x)+4Bj
.text:00405043 dec edx ; Decrement the priority
.text:00405044 sub esi, 8 ; Drop down a priority level in KiDispatcherReadyListHead
.text:00405047 shl eax, 1 ; Left shift eax again
.text:00405049 jz short loc_40503F ; If *still* nothing, bail
.text:0040504B jmp short loc_40509B ; Check the 'sign bit' to make sure that the highest bit in eax is set, if not, jump
.text:0040504B ; END OF FUNCTION CHUNK FOR @KiFindReadyThread@8
.text:0040504B ; ---------------------------------------------------------------------------
.text:0040504D align 10h
.text:00405050 db 2 dup(90h)
.text:00405052
.text:00405052 ; =============== S U B R O U T I N E =======================================
.text:00405052
.text:00405052
.text:00405052 ; __fastcall KiFindReadyThread(x, x)
.text:00405052 @KiFindReadyThread@8 proc near ; CODE XREF: KiSwapThread()+23p
.text:00405052 ; KiAdjustQuantumThread(x)+10C4p ...
.text:00405052
.text:00405052 ; FUNCTION CHUNK AT .text:00401EF4 SIZE 00000011 BYTES
.text:00405052 ; FUNCTION CHUNK AT .text:0040503F SIZE 0000000E BYTES
.text:00405052
.text:00405052 xor eax, eax
.text:00405054 inc eax
.text:00405055 mov ecx, edx
.text:00405057 shl eax, cl ; 1 << Priority
.text:00405059 push 10h
.text:0040505B pop ecx ; Initialize minimum priority to the lowest real-time priority - 16
.text:0040505C dec eax
.text:0040505D not eax ; Turn eax into a mask for the specified priority and above
.text:0040505F and eax, ds:_KiReadySummary ; Pull the specified priority set out of the summary of ready threads
.text:00405065 mov edx, eax
.text:00405067 shr edx, 10h ; Shift out anything that isn't a real-time priority level at first
.text:0040506A jnz short loc_405070 ; See ifthere is anything in the upper quadrant of the priority half that we are examining
.text:0040506C xor ecx, ecx ; Minimum priority is zero since we're dealing with the lower half
.text:0040506E mov edx, eax ; If there are no real-time priority threads, then we can look at variable priority ones
.text:00405070
.text:00405070 loc_405070: ; CODE XREF: KiFindReadyThread(x,x)+18j
.text:00405070 test edx, 0FFFFFF00h ; See ifthere is anything in the upper quadrant of the priority half that we are examining
.text:00405076 jz short loc_40507B
.text:00405078 add ecx, 8 ; If so, up the minimum priority by 8 to mask out the lower quadrant
.text:0040507B
.text:0040507B loc_40507B: ; CODE XREF: KiFindReadyThread(x,x)+24j
.text:0040507B mov edx, eax
.text:0040507D shr edx, cl ; Down-shift KiReadySummary by the minimum priority
.text:0040507F push esi
.text:00405080 push 1Fh
.text:00405082 movsx edx, ds:_KiFindFirstSetLeft[edx] ; KiFindFirstSetLeft translates a quadrant bitmask into a value of 1-8, indicating
.text:00405082 ; the highest priority thread currently waiting
.text:00405089 add edx, ecx ; Add the minimum priority we calculated previously, and now we have our highest priority thread
.text:0040508B pop ecx
.text:0040508C sub ecx, edx ; 31 - HighestPriorityThread
.text:0040508E shl eax, cl ; Get rid of all the unset bits above the highest set bit
.text:00405090 lea esi, _KiDispatcherReadyListHead[edx*8] ; KiDispatcherReadyListHead[HighestPriority]
.text:00405097 test eax, eax
.text:00405099 jz short loc_40503F ; If eax is now zero, then our high priority thread has been pulled out from under us
.text:0040509B
.text:0040509B loc_40509B: ; CODE XREF: KiFindReadyThread(x,x)-7j
.text:0040509B test eax, eax ; Check the 'sign bit' to make sure that the highest bit in eax is set, if not, jump
.text:0040509D jge short loc_405043 ; Decrement the priority
.text:0040509F mov eax, [esi] ; NewThread
.text:004050A1 mov ecx, [eax] ; NewThread->Flink
.text:004050A3 sub eax, 60h ; Get pointer to the KTHREAD
.text:004050A6 push edi
.text:004050A7 mov edi, [eax+64h] ; NewThread->Blink
.text:004050AA mov [edi], ecx ; NewThread->Blink = NewThread->Flink
.text:004050AC mov [ecx+4], edi ; NewThread->Flink->Blink = NewThread->Blink
.text:004050AF cmp [esi], esi ; Check to see if the current entry is self-referential (if so, someone must have scheduled our thread while we were executing?)
.text:004050B1 pop edi
.text:004050B2 jz loc_401EF4 ; If this priority level is empty then set the priority mask to all the bits below this level
.text:004050B8
.text:004050B8 loc_4050B8: ; CODE XREF: KiFindReadyThread(x,x)-11j
.text:004050B8 pop esi
.text:004050B9 retn
.text:004050B9 @KiFindReadyThread@8 endp