今天是我的18周岁生日,突然就想发表篇文章,而前几天刚好在做IopfCompleteRequest的分析,所以今天就赶赶急,把分析做做完,发表出来。 
     我在网上认识的一个前辈说过,看网上的文章,学网上已经有的技术,不是真技术;而只有自己研究才是自己所学的,所得的。而其中最为重要的是:开创自己的技术。而开创自己的技术,就需要自己先有研究的基础。
      本文自然不是什么开创之作,IopfCompleteRequest的伪代码早已漫天飞IopfCompleteRequest也早已没有什么秘密可言。但是我还是坚持一个人独立地看windows的二进制码,凭自己所学,分析那一个个指令。 
      鉴于本人水平有限,从接触编程到现在也才2个年头多,虽兢兢业业,勤奋学习,却也因为走过不少弯路而浪费了很多的时间。所以本文会有一些甚至许多错误,请前辈们斧正。
下面是总结的IoCompleteRequest流程:
 
总结流程:
;当调用IoCompleteRequest时,首先检查当前堆栈位置,和IRP类型;然后判断IRP堆栈里的Control,来完成设置IRPPendingReturn和
;判断是否成功,取消,错误,时调用完成例程,然后再边回卷堆栈,边调用完成例程,然后一直回转到堆栈位置比windows的自设的空堆栈还大的时候
;结束回转,进入对IRP事件,回调函数,IRP,MDL释放等的工作中去.
;结束回转后,首先判断是否有事件或回调函数,若无,则释放IRP和MDL,并返回、;若有,则先判断是否需要重解析,若需要,则将AuxiliaryBuffer释放掉,并清零,(大概就是清零后能达到重解析的目的,可以说是一个标志位吧)
;若不需要,则判断用户态设置的通信方法,是用事件的方法,还是用回调函数的方法。
;若为事件,则设置事件,然后无论IRP是Reserved还是正常的,都进行释放,释放后返回
;若为回调函数,则又分为三种情况,一是成功的情况下,二是取消的情况下,三是等待的情况下。
;成功和取消相仿,都是先初始化再插入队列
;只不过取消的情况下还和上面的AuxiliaryBuffer清零结合在一起,如果清零了,则直接返回。
;等待的状态下只初始化APC,并不插入队列。
;KeBugCheckEx 是错误报告,只有两处会产生错误报告,一是调用IoCompleteRequest时,堆栈CurrentLocation大于StackCount+1,而是IRP类型不为6
 
附件是IoCompleteRequest的二进制码,和我对二进制码的注释,还有总结的一些对IoCompleteRequest的结论。
 
 

代码:
nt!IopfCompleteRequest:
804f02c0 8bff            mov     edi,edi
804f02c2 55              push    ebp
804f02c3 8bec            mov     ebp,esp
804f02c5 83ec10          sub     esp,10h
804f02c8 53              push    ebx
804f02c9 56              push    esi
804f02ca 8bf1            mov     esi,ecx
804f02cc 8a4e23          mov     cl,byte ptr [esi+23h]      ;找到当前堆栈位置
804f02cf 8955f8          mov     dword ptr [ebp-8],edx      ;局部变量存储调整的优先级
804f02d2 8a5622          mov     dl,byte ptr [esi+22h]      ;堆栈数量
804f02d5 33db            xor     ebx,ebx        
804f02d7 fec2            inc     dl          ;堆栈数量自增1,从前辈那里得知,系统创建的第一个堆栈是空的,StackCount是会比当前堆栈栈底指针小1             
804f02d9 3aca            cmp     cl,dl          ;比较是否超过了最大数量          
804f02db 57              push    edi
804f02dc 895df4          mov     dword ptr [ebp-0Ch],ebx    ;清0,堆栈变量初始化
804f02df 0f8f91020000    jg      nt!IopfCompleteRequest+0x2b6 (804f0576);如果超过最大数目,就退出,并报告错误
804f02e5 66833e06        cmp     word ptr [esi],6      ;判断IRP类型号是否为6    关于TYPE微软没有公开
804f02e9 0f8587020000    jne     nt!IopfCompleteRequest+0x2b6 (804f0576);不是6则退出,并报告错误,估计IRP的TYPE号只能是6
804f02ef 8b7e60          mov     edi,dword ptr [esi+60h]    ;是CurrentStackLocation; 
804f02f2 fec1            inc     cl          ;让当前堆栈指针+1
  
      ;比较,结合上面,没有超过最大数量,这里应该是判断是否是最顶层的IRP堆栈  
      ;如果是最顶层的windows自建的IRP堆栈,则跳转
804f02f4 3aca            cmp     cl,dl          ;再次比较
      
         ;从这里可以看出,IRP堆栈是紧挨着排放的
804f02f6 8d4724          lea     eax,[edi+24h]        ;上一层堆栈的地址

      ;本层IRP堆栈调用了IofCompleteRequest,则向上层回卷
804f02f9 884e23          mov     byte ptr [esi+23h],cl      ;让堆栈指针+1存入堆栈位置
804f02fc 894660          mov     dword ptr [esi+60h],eax    ;把上层堆栈地址存进去
804f02ff 0f8fab000000    jg      nt!IopfCompleteRequest+0xf0 (804f03b0) ;如果当前是最顶层的IRP堆栈,则略过完成例程的调用,直接调后面对堆栈全部回卷后的处理,比较特殊的一种情况


           ;控制符内保存着四种信息,一是是否设置IRPPendingReturn,二是是否取消调用完成例程
           ;三是是否错误时调用完成例程,四是成功时是否调用完成例程
804f0305 83c703          add     edi,3          
     
     
804f0308 8a17            mov     dl,byte ptr [edi]      ;找到控制符
804f030a 80e201          and     dl,1          ;只保留最低位
     ;jl跳转表示STATUS标志位错误的时候  
804f030d 395e18          cmp     dword ptr [esi+18h],ebx    ;比较IRP是否成功
804f0310 8855ff          mov     byte ptr [ebp-1],dl      ;保留控制符最低位
804f0313 885621          mov     byte ptr [esi+21h],dl      ;当控制符为1的时候设置IRPPendingReturn
804f0316 8a17            mov     dl,byte ptr [edi]      ;找到控制符
804f0318 7c07            jl      nt!IopfCompleteRequest+0x61 (804f0321)  ;如果状态错误则跳
     
     ;成功时是否调用
804f031a f6c240          test    dl,40h          ;控制符比较
804f031d 7510            jne     nt!IopfCompleteRequest+0x6f (804f032f)  ;不为零则调用完成例程
804f031f eb04            jmp     nt!IopfCompleteRequest+0x65 (804f0325)
     ;测最高位--错误标志位,如果为1,则调用完成例程    
804f0321 84d2            test    dl,dl          
804f0323 780a            js      nt!IopfCompleteRequest+0x6f (804f032f)

804f0325 385e24          cmp     byte ptr [esi+24h],bl      ;比较看IRP是否取消
804f0328 7444            je      nt!IopfCompleteRequest+0xae (804f036e)  ;如果没取消则跳

    ;从上可以看出,如果IRP被取消了,则一定错误,查STATUS_CANCELLED ((NTSTATUS)0xC0000120L)也证实了这一点
    ;但是也可以看出,在没有错误的情况下,如果SL_INVOKE_ON_SUCCESS标志没设,而取消标志有设,也会调用完成例程
    ;因为微软的设置IRP为取消的函数会同时设置Status为错误,所以忽略了这种情况,如果采取自己设Cancel位,而不采用微软提供的函数
    ;那就会在这里出现问题,但是微软也不用负责,因为微软确实已经告诉你要用它提供的函数了

    ;如果有取消标志,则去调用
804f032a f6c220          test    dl,20h          ;控制符若为20,则表示调用完成例程        
804f032d 743f            je      nt!IopfCompleteRequest+0xae (804f036e)

    ;如果被取消,则清空本层IRP堆栈
804f032f 885ffe          mov     byte ptr [edi-2],bl
804f0332 885fff          mov     byte ptr [edi-1],bl
804f0335 881f            mov     byte ptr [edi],bl
804f0337 895f01          mov     dword ptr [edi+1],ebx
804f033a 895f05          mov     dword ptr [edi+5],ebx
804f033d 895f09          mov     dword ptr [edi+9],ebx
804f0340 895f0d          mov     dword ptr [edi+0Dh],ebx
804f0343 895f15          mov     dword ptr [edi+15h],ebx
    ;清空从MinjorFunction到FileObject的所有数据

804f0346 8a4622          mov     al,byte ptr [esi+22h]
804f0349 fec0            inc     al
804f034b 384623          cmp     byte ptr [esi+23h],al      ;和上面相仿  
804f034e 7504            jne     nt!IopfCompleteRequest+0x94 (804f0354)  ;判断是否是最顶层堆栈
804f0350 33c0            xor     eax,eax        ;当为最顶层堆栈,则让设备地址为0,最顶层堆栈是windows自己设立的
804f0352 eb06            jmp     nt!IopfCompleteRequest+0x9a (804f035a)

804f0354 8b4660          mov     eax,dword ptr [esi+60h]    ;把上一层的堆栈地址给eax
804f0357 8b4014          mov     eax,dword ptr [eax+14h]    ;找到设备地址    

      ;调用本层的完成例程,而堆栈设备指针和Context指针则是上层的
804f035a ff771d          push    dword ptr [edi+1Dh]      ;Context内容
804f035d 56              push    esi          ;IRP地址
804f035e 50              push    eax          ;设备地址  
804f035f ff5719          call    dword ptr [edi+19h]      ;调用完成例程      
804f0362 3d160000c0      cmp     eax,0C0000016h        ;比较运行后STATUS
    ;0C0000016h是STATUS_MORE_PROCESSING_REQUIRED。如果是,则不跳转,返回给本层堆栈代码,做继续处理  
    ;从这里可以看出,若返回本层堆栈代码,则IRP堆栈指针将指向上层堆栈,所以需要在后续的代码里调回来
804f0367 752a            jne     nt!IopfCompleteRequest+0xd3 (804f0393)  ;不是则跳转
804f0369 5f              pop     edi
804f036a 5e              pop     esi
804f036b 5b              pop     ebx
804f036c c9              leave
804f036d c3              ret



    ;如果不需要调用完成例程,则进行以下代码
804f036e 385dff          cmp     byte ptr [ebp-1],bl  
804f0371 7409            je      nt!IopfCompleteRequest+0xbc (804f037c)    ;控制符最低位若不设SL_PENDING_RETURNED则跳

804f0373 3a4e22          cmp     cl,byte ptr [esi+22h]        
804f0376 7f04            jg      nt!IopfCompleteRequest+0xbc (804f037c)    ;如果上层堆栈是顶层堆栈则跳,因为顶层堆栈是空的,没必要设置SL_PENDING_RETURNED
804f0378 80480301        or      byte ptr [eax+3],1                             ;设置最低位,和前面的代码连起来则应该是设置上层IRPPengReturn位
    ;合起来则是若本层IRP堆栈设置了SL_PENDING_RETURNED,则也设置上层SL_PENDING_RETURNED,再和上面的联系在一起,就是若本层有SL_PENDING_RETURNED
    ;则上层的IRPPengReturn也会被设置


    ;清空IRP堆栈
804f037c 885ffe          mov     byte ptr [edi-2],bl
804f037f 885fff          mov     byte ptr [edi-1],bl
804f0382 881f            mov     byte ptr [edi],bl
804f0384 895f01          mov     dword ptr [edi+1],ebx
804f0387 895f05          mov     dword ptr [edi+5],ebx
804f038a 895f09          mov     dword ptr [edi+9],ebx
804f038d 895f0d          mov     dword ptr [edi+0Dh],ebx
804f0390 895f15          mov     dword ptr [edi+15h],ebx
    ;从Minjor到DeviceObject


    ;这里的代码其实就是判断上层是否还有IRP堆栈,如果有再回卷,回卷之后再判断标志位,再调用完成例程
    ;直到IRP堆栈指针>堆栈数量 或者完成例程返回STATUS_MORE_PROCESSING_REQUIRED
804f0393 83466024        add     dword ptr [esi+60h],24h    ;IRP堆栈再上升一层
804f0397 8b4660          mov     eax,dword ptr [esi+60h]
804f039a 83c724          add     edi,24h        ;堆栈地址加24      
804f039d fe4623          inc     byte ptr [esi+23h]      ;再到上层
804f03a0 8a5622          mov     dl,byte ptr [esi+22h]
804f03a3 8a4e23          mov     cl,byte ptr [esi+23h]
804f03a6 fec2            inc     dl
804f03a8 3aca            cmp     cl,dl      
804f03aa 0f8e58ffffff    jle     nt!IopfCompleteRequest+0x48 (804f0308)  ;小于或者等于则回转
    ;最底层堆栈是从1为起始的
    ;联系前辈所说的,windows会建立一个空的堆栈,所以推测,这个空的堆栈应该是在最顶层,并且设置了一些特殊的数据来进行处理
    ;当jle不跳转时,则当前堆栈指针已经超出它的范围,后面的代码调用主要是收尾,并且最终会跳到804f0369 ,返回。

804f03b0 f6460808        test    byte ptr [esi+8],8
804f03b4 7428            je      nt!IopfCompleteRequest+0x11e (804f03de);如果没设置DO_EXCLUSIVE,则跳转
    ;DO_EXCLUSIVE文档中没有说明,应该是独立的话则没有事件和回调函数
    ;所以这里的独立的意思应当是说驱动和应用层相互独立,各自不影响运行的意思

804f03b6 8b7e0c          mov     edi,dword ptr [esi+0Ch]    ;MasterIrp地址
804f03b9 6a0a            push    0Ah
804f03bb 8d570c          lea     edx,[edi+0Ch]        
804f03be 59              pop     ecx
804f03bf e814310000      call    nt!IopInterlockedDecrementUlong (804f34d8);增加一个0ah
804f03c4 56              push    esi
804f03c5 8bd8            mov     ebx,eax              
804f03c7 e85c2d0000      call    nt!IopFreeIrpAndMdls (804f3128)  ;释放MDL和IRP空间
804f03cc 83fb01          cmp     ebx,1          
804f03cf 7598            jne     nt!IopfCompleteRequest+0xa9 (804f0369)  ;如果所有空间都已释放,则结束
804f03d1 8a55f8          mov     dl,byte ptr [ebp-8]
804f03d4 8bcf            mov     ecx,edi
804f03d6 ff1504b45480    call    dword ptr [nt!pIofCompleteRequest (8054b404)];不清楚这里是做什么用的,可能也是和下面的代码基本相同的功能
804f03dc eb8b            jmp     nt!IopfCompleteRequest+0xa9 (804f0369)  ;结束调用

    ;独立的IRP由IoCompleteRequest释放,不独立的由Io管理器释放,但是除有事件的以外
    ;以下都是不独立的情况下



804f03de 817e1804010000  cmp     dword ptr [esi+18h],104h      ;判断它是否是重解析
804f03e5 7521            jne     nt!IopfCompleteRequest+0x148 (804f0408)
804f03e7 8b461c          mov     eax,dword ptr [esi+1Ch]      ;得到Information
804f03ea 83f801          cmp     eax,1            
804f03ed 7619            jbe     nt!IopfCompleteRequest+0x148 (804f0408)  ;小于等于1时跳
     ;综合上面的则是,不是重解析,或information<=1时跳转下面的代码

     ;当Information不为0a0000003时,则设置Status为STATUS_IO_REPARSE_TAG_NOT_HANDLED 
804f03ef 3d030000a0      cmp     eax,0A0000003h
      
804f03f4 750b            jne     nt!IopfCompleteRequest+0x141 (804f0401)
804f03f6 8b4654          mov     eax,dword ptr [esi+54h]
804f03f9 8945f4          mov     dword ptr [ebp-0Ch],eax      ;把AuxiliaryBuffer保存起来
     ;AuxiliaryBuffer文档中的说明是保存不在正常的缓冲区中的信息
  
804f03fc 895e54          mov     dword ptr [esi+54h],ebx      ;清空
804f03ff eb07            jmp     nt!IopfCompleteRequest+0x148 (804f0408)

804f0401 c74618790200c0  mov     dword ptr [esi+18h],0C0000279h      ;STATUS_IO_REPARSE_TAG_NOT_HANDLED 


804f0408 8b4654          mov     eax,dword ptr [esi+54h]      ;得到AuxiliaryBuffer地址  
804f040b 3bc3            cmp     eax,ebx          ;看看是否为零
804f040d 740a            je      nt!IopfCompleteRequest+0x159 (804f0419)
    ;联合上面的结果是:如果eax=0A0000003h,则不释放,如果不等,则释放掉内存
804f040f 53              push    ebx
804f0410 50              push    eax
804f0411 e8d0350500      call    nt!ExFreePoolWithTag (805439e6)    ;如果存在则释放掉
804f0416 895e54          mov     dword ptr [esi+54h],ebx      ;清空

804f0419 8b4608          mov     eax,dword ptr [esi+8]
804f041c 66a90204        test    ax,402h          ;判断是不是 DO_VERIFY_VOLUME 和DO_NEVER_LAST_DEVICE                
804f0420 747b            je      nt!IopfCompleteRequest+0x1dd (804f049d)  ;不是则跳
804f0422 66a94004        test    ax,440h          ;判断是不是DO_DEVICE_HAS_NAME          
804f0426 53              push    ebx          
804f0427 7449            je      nt!IopfCompleteRequest+0x1b2 (804f0472)  ;没有名字则跳,结合跳转下面的代码,则这里的意思应该是判断是否有回调函数,若没有,则设置事件
    ;虽然以上的标志在windows里的解释是 DO_VERIFY_VOLUME,DO_DEVICE_HAS_NAME,DO_NEVER_LAST_DEVICE  ,但是个人认为这些标志是跟是否有回调函数相关的   
    ;另外has_Name可理解为有事件的名字,所以应该是跟事件相关
804f0429 8b4e18          mov     ecx,dword ptr [esi+18h]
804f042c 83e042          and     eax,42h          ;eax赋值为42
804f042f 8bf8            mov     edi,eax
804f0431 8b4628          mov     eax,dword ptr [esi+28h]      ;存UserIosb
804f0434 8908            mov     dword ptr [eax],ecx        ;存状态
804f0436 8b4e1c          mov     ecx,dword ptr [esi+1Ch]      ;存信息
804f0439 894804          mov     dword ptr [eax+4],ecx

804f043c 0fbe45f8        movsx   eax,byte ptr [ebp-8]        ;优先级
804f0440 50              push    eax
804f0441 ff762c          push    dword ptr [esi+2Ch]
804f0444 e8bf870000      call    nt!KeSetEvent (804f8c08)      ;设置用户事件
804f0449 3bfb            cmp     edi,ebx          
804f044b 0f8418ffffff    je      nt!IopfCompleteRequest+0xa9 (804f0369)    ;完成IRP,这里不会跳转,因为上面edi刚赋值成42h

804f0451 3b3580095580    cmp     esi,dword ptr [nt!IopReserveIrpAllocator (80550980)];看它是否是保留IRP,无论是保留的,还是不保留的,都释放掉
804f0457 750e            jne     nt!IopfCompleteRequest+0x1a7 (804f0467)

804f0459 ff75f8          push    dword ptr [ebp-8]
804f045c 56              push    esi
804f045d e886310000      call    nt!IopFreeReserveIrp (804f35e8)    ;释放IRP
804f0462 e902ffffff      jmp     nt!IopfCompleteRequest+0xa9 (804f0369)    ;完成IRP

804f0467 56              push    esi  
804f0468 e867dcffff      call    nt!IoFreeIrp (804ee0d4)      ;释放IRP
804f046d e9f7feffff      jmp     nt!IopfCompleteRequest+0xa9 (804f0369)


    ;有名字的情况下设置调用回调函数,然后完成,并且不释放IRP,应该是由其他的负责释放IRP(可能是IO管理器,可能是APC例程(不清楚APC例程是在用户态执行还是))
804f0472 0fbe4626        movsx   eax,byte ptr [esi+26h]        ;ApcEnvironment
804f0476 53              push    ebx
804f0477 53              push    ebx
804f0478 53              push    ebx
804f0479 68702f4f80      push    offset nt!IopCompletePageWrite (804f2f70)
804f047e 50              push    eax
804f047f ff7650          push    dword ptr [esi+50h]        ;线程地址
804f0482 8d7e40          lea     edi,[esi+40h]          ;DeviceQueueEntry
804f0485 57              push    edi
804f0486 e817a20000      call    nt!KeInitializeApc (804fa6a2)      ;初始化APC例程
804f048b 0fbe45f8        movsx   eax,byte ptr [ebp-8]
804f048f 50              push    eax
804f0490 53              push    ebx
804f0491 53              push    ebx
804f0492 57              push    edi
804f0493 e86ca20000      call    nt!KeInsertQueueApc (804fa704)      ;插入APC队列
804f0498 e9ccfeffff      jmp     nt!IopfCompleteRequest+0xa9 (804f0369)    ;完成函数

804f049d 8b7e04          mov     edi,dword ptr [esi+4]        ;MDLAddress地址
804f04a0 eb08            jmp     nt!IopfCompleteRequest+0x1ea (804f04aa)  
804f04a2 57              push    edi
804f04a3 e872590100      call    nt!MmUnlockPages (80505e1a)      ;解锁
804f04a8 8b3f            mov     edi,dword ptr [edi]        ;得到主版本号

804f04aa 3bfb            cmp     edi,ebx          ;查0
804f04ac 75f4            jne     nt!IopfCompleteRequest+0x1e2 (804f04a2)  ;不为0则解锁
804f04ae f6460908        test    byte ptr [esi+9],8        ;DO_SHUTDOWN_REGISTERED
804f04b2 742a            je      nt!IopfCompleteRequest+0x21e (804f04de)

804f04b4 385e21          cmp     byte ptr [esi+21h],bl        ;查PendingReturn
804f04b7 7525            jne     nt!IopfCompleteRequest+0x21e (804f04de)  ;返回等待的情况下跳转

804f04b9 817e1804010000  cmp     dword ptr [esi+18h],104h      ;查是否重解析
804f04c0 0f85a3feffff    jne     nt!IopfCompleteRequest+0xa9 (804f0369)
804f04c6 817e1c030000a0  cmp     dword ptr [esi+1Ch],0A0000003h      ;查Information
804f04cd 0f8596feffff    jne     nt!IopfCompleteRequest+0xa9 (804f0369)
804f04d3 8b45f4          mov     eax,dword ptr [ebp-0Ch]
804f04d6 894654          mov     dword ptr [esi+54h],eax      ;恢复原来的值
804f04d9 e98bfeffff      jmp     nt!IopfCompleteRequest+0xa9 (804f0369)

804f04de 385e24          cmp     byte ptr [esi+24h],bl        ;查取消
804f04e1 8b7e64          mov     edi,dword ptr [esi+64h]
804f04e4 897df0          mov     dword ptr [ebp-10h],edi      ;存堆栈地址
804f04e7 752e            jne     nt!IopfCompleteRequest+0x257 (804f0517)
804f04e9 0fbe4626        movsx   eax,byte ptr [esi+26h]        ;ApcEnvironment
804f04ed 53              push    ebx
804f04ee 53              push    ebx
804f04ef 53              push    ebx
804f04f0 68624e5780      push    offset nt!IopAbortRequest (80574e62)
804f04f5 6898374f80      push    offset nt!IopCompleteRequest (804f3798)
804f04fa 50              push    eax
804f04fb ff7650          push    dword ptr [esi+50h]
804f04fe 8d7e40          lea     edi,[esi+40h]
804f0501 57              push    edi
804f0502 e89ba10000      call    nt!KeInitializeApc (804fa6a2)      ;等待的情况,先做初始化APC,并不插入APC队列
804f0507 0fbe45f8        movsx   eax,byte ptr [ebp-8]
804f050b 50              push    eax
804f050c ff75f4          push    dword ptr [ebp-0Ch]
804f050f ff75f0          push    dword ptr [ebp-10h]
804f0512 e97bffffff      jmp     nt!IopfCompleteRequest+0x1d2 (804f0492)  ;有取消例程则重新调用插入和IoCompleteReques

    ;在取消的情况下,也插入APC队列,调用回调函数,只是参数不一样
804f0517 ff1514774d80    call    dword ptr [nt!_imp__KeRaiseIrqlToDpcLevel (804d7714)]
804f051d 8ac8            mov     cl,al
804f051f 8b4650          mov     eax,dword ptr [esi+50h]      ;AuxiliaryBuffer地址
804f0522 3bc3            cmp     eax,ebx
804f0524 884dff          mov     byte ptr [ebp-1],cl
804f0527 743b            je      nt!IopfCompleteRequest+0x2a4 (804f0564)
804f0529 0fbe4e26        movsx   ecx,byte ptr [esi+26h]
804f052d 53              push    ebx
804f052e 53              push    ebx
804f052f 53              push    ebx
804f0530 68624e5780      push    offset nt!IopAbortRequest (80574e62)
804f0535 6898374f80      push    offset nt!IopCompleteRequest (804f3798)
804f053a 51              push    ecx
804f053b 50              push    eax
804f053c 8d7e40          lea     edi,[esi+40h]
804f053f 57              push    edi
804f0540 e85da10000      call    nt!KeInitializeApc (804fa6a2)
804f0545 0fbe45f8        movsx   eax,byte ptr [ebp-8]
804f0549 50              push    eax
804f054a ff75f4          push    dword ptr [ebp-0Ch]
804f054d ff75f0          push    dword ptr [ebp-10h]
804f0550 57              push    edi
804f0551 e8aea10000      call    nt!KeInsertQueueApc (804fa704)
804f0556 8a4dff          mov     cl,byte ptr [ebp-1]
804f0559 ff151c774d80    call    dword ptr [nt!_imp_KfLowerIrql (804d771c)]
804f055f e905feffff      jmp     nt!IopfCompleteRequest+0xa9 (804f0369)

    ;如果AuBuffer地址没有,则直接返回
804f0564 ff151c774d80    call    dword ptr [nt!_imp_KfLowerIrql (804d771c)]
804f056a 57              push    edi
804f056b 56              push    esi
804f056c e80b2b0000      call    nt!IopDropIrp (804f307c)
804f0571 e9f3fdffff      jmp     nt!IopfCompleteRequest+0xa9 (804f0369)    ;还是调完成例程
    
804f0576 53              push    ebx
804f0577 53              push    ebx
804f0578 68620d0000      push    0D62h
804f057d 56              push    esi
804f057e 6a44            push    44h
804f0580 e885830000      call    nt!KeBugCheckEx (804f890a)      ;错误处理

    结论1:
    ;804f0308 8a17            mov     dl,byte ptr [edi]      ;找到控制符
    ;804f030a 80e201          and     dl,1          ;只保留最低位
    ;804f0313 885621          mov     byte ptr [esi+21h],dl      ;当控制符为1的时候设置IRPPendingReturn
    ;每次设完成例程的时候都要先判断PendingReturn,然后再IoMarkPendingReturn,就是这里的原因,如果不IoMarkPendingReturn,则IRPPendingReturn就不会被设置
    ;即使IRP的IRPPendingReturn已经被设置过了,在这里也会被取消掉
    
    ;如果不调用完成例程,则为了传递这个标志,也会调用和IoMarkPendingReturn类似的代码,去设置这个标志,如下:
    ;804f036e 385dff          cmp     byte ptr [ebp-1],bl  
    ;804f0371 7409            je      nt!IopfCompleteRequest+0xbc (804f037c)    ;控制符最低位若不设SL_PENDING_RETURNED则跳
    ;804f0373 3a4e22          cmp     cl,byte ptr [esi+22h]        
    ;804f0376 7f04            jg      nt!IopfCompleteRequest+0xbc (804f037c)    ;如果上层堆栈是顶层堆栈则跳,因为顶层堆栈是空的,没必要设置SL_PENDING_RETURNED
    ;804f0378 80480301        or      byte ptr [eax+3],1                               ;设置最低位,和前面的代码连起来则应该是设置上层IRPPengReturn位
    
    结论2:
    ;返回状态为STATUS_MORE_PROCESSING_REQUIRED,则堆栈不进行回卷,但是底层的IRP堆栈数据也已经被破坏
    
    结论3:
    ;从以上可以看出,调用完成例程返回的STATUS如果不是错误和STATUS_MORE_PROCESSING_REQUIRED的情况下,基本没什么用。
    
    结论4:
    ;如果IRP被取消了,则一定错误,查STATUS_CANCELLED ((NTSTATUS)0xC0000120L)也证实了这一点
    ;但是也可以看出,在没有错误的情况下,如果SL_INVOKE_ON_SUCCESS标志没设,而取消标志有设,也会调用完成例程
    ;因为微软的设置IRP为取消的函数会同时设置Status为错误,所以忽略了这种情况,如果采取自己设Cancel位,而不采用微软提供的函数
    ;那就会在这里出现问题,但是微软也不用负责,因为微软确实已经告诉你要用它提供的函数了

    

    总结流程:
    ;综合上述所有,当调用IoCompleteRequest时,首先检查当前堆栈位置,和IRP类型;然后判断IRP堆栈里的Control,来完成设置IRPPendingReturn和
    ;判断是否成功,取消,错误,时调用完成例程,然后再边回卷堆栈,边调用完成例程,然后一直回转到堆栈位置比windows的自设的空堆栈还大的时候
    ;结束回转,进入对IRP事件,回调函数,IRP,MDL释放等的工作中去.
                
                ;结束回转后,首先判断是否有事件或回调函数,若无,则释放IRP和MDL,并返回、
                ;若有,则先判断是否需要重解析,若需要,则将AuxiliaryBuffer释放掉,并清零,(大概就是清零后能达到重解析的目的,可以说是一个标志位吧)
    ;若不需要,则判断用户态设置的通信方法,是用事件的方法,还是用回调函数的方法。
                ;若为事件,则设置事件,然后无论IRP是Reserved还是正常的,都进行释放,释放后返回
    ;若为回调函数,则又分为三种情况,一是成功的情况下,二是取消的情况下,三是等待的情况下。
    ;成功和取消相仿,都是先初始化再插入队列
    ;只不过取消的情况下还和上面的AuxiliaryBuffer清零结合在一起,如果清零了,则直接返回。
    ;等待的状态下只初始化APC,并不插入队列。
    ;KeBugCheckEx 是错误报告,只有两处会产生错误报告,一是调用IoCompleteRequest时,堆栈CurrentLocation大于StackCount+1,而是IRP类型不为6
上传的附件 对IoCompleteRequest的分析.txt