今天是我的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
我在网上认识的一个前辈说过,看网上的文章,学网上已经有的技术,不是真技术;而只有自己研究才是自己所学的,所得的。而其中最为重要的是:开创自己的技术。而开创自己的技术,就需要自己先有研究的基础。
本文自然不是什么开创之作,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