首页
社区
课程
招聘
[原创]记一次复杂的debug过程
发表于: 2016-12-6 14:17 5868

[原创]记一次复杂的debug过程

2016-12-6 14:17
5868

记一次复杂的debug过程

笔者一个tdi驱动,造成部分应用层程序卡死,抓包发现在服务器无响应或失去连接时可能重现。
review代码发现发送和接受逻辑里都加了超时判断,如果超时会置错误值并完成该IRP,看似没有问题。

内核调试查看三层挂起的线程信息,用到的命令如下:

!process 0 0
.process /i /p 8154d020
g
!process
.reload
!cs -l
!thread 814a7020

   0  Id: 55c.520 Suspend: 1 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr  Args to Child              
0012f194 7c92da2c 719ce0d8 000004c0 0012f1e0 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0012f198 719ce0d8 000004c0 0012f1e0 0012f1d4 ntdll!NtRemoveIoCompletion+0xc (FPO: [5,0,0])
0012f1d8 719d80b4 719cd65f 518ec7c8 0021fa98 mswsock!SockIsSocketConnected+0x60 (FPO: [Non-Fpo])
0012f21c 71a309e4 000008b0 000301da 00000373 mswsock!WSPAsyncSelect+0x145 (FPO: [Non-Fpo])
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for Common.dll - 
0012f244 51708a38 000008b0 000301da 00000373 ws2_32!WSAAsyncSelect+0x53 (FPO: [Non-Fpo])
......

可见挂在了NtRemoveIoCompletion上,NtRemoveIoCompletion是完成端口相关的函数,在《windows内和原理与实现》中对完成端口有描述。
可这里为什么挂起了,是个问题。

出错程序网络操作比较多分析不便,于是试图手写一个用到WSAAsyncSelect的demo.
MSDN查看该函数,搜得示例代码,然后纠结于一个问题,connect与WSAAsyncSelect谁先谁后;
测试先WSAAsyncSelect后connect的话,并不会重现卡死的问题,
IDA mswsock发现*(_DWORD *)(CompletionKey + 0x8C)为真的时候才会去NtRemoveIoCompletion;
CompletionKey + 0x8C是什么?
结合NT4源码和WRK以及调试,跟踪socket的创建过程,
发现CompletionKey + 0x8C就是 SOCKET_INFORMATION 里的 AsyncConnectContext;
而 AsyncConnectContext 是在 connect 里赋值的;
说明要重现卡死的问题,connect 要在 WSAAsyncSelect 前面,至此问题可以重现了。

NtRemoveIoCompletion为什么没有返回?
查阅《windows内和原理与实现》可知,完成端口在消息来时插入一个KQUEUE,而NtRemoveIoCompletion是取KQUEUE里消息的函数,
NtRemoveIoCompletion未返回说明KQUEUE是空的,那么就需要追踪插入KQUEUE的过程,
插入 KQUEUE 是在完成 IRP 时做的,这里为什么没有做?

    if ( FileObject )
    {
      CompletionContext = FileObject->CompletionContext;
      if ( CompletionContext )
      {
        Port = (int)CompletionContext->Port;
        Key = CompletionContext->Key;
      }
    }
    
     if ( UserApcRoutine )
    {
      KeInitializeApc(
        Apc,
        thread,
        2,
        IopUserCompletion,
        IopUserRundown,
        UserApcRoutine,
        (char)Apc[-1].ApcListEntry.Blink,
        Apc[-1].SystemArgument1);
      KeInsertQueueApc(Apc, Apc[-1].RundownRoutine, 0, 2);
    }
    else if ( Port && pIrp->Overlay.AllocationSize._s0.HighPart )
    {
      *(_DWORD *)&Apc->Type = Key;
      pIrp->Tail.Overlay.PacketType = 0;
      KeInsertQueue(Port, (int)&pIrp->Tail.Overlay.ListEntry);
    }
    
 在代码里判断超时后调用IoCompleteRequest的地方下断,跟进去,
 IoCompleteRequest->IopfCompleteRequest->IopCompleteRequest,
 需要断在IopCompleteRequest,可是IopCompleteRequest操作很频繁,
 这里需要IopCompleteRequest的参数,是我们的代码调用的时候则断,于是用条件断点:
 
bu MySys!MyFunc+0x666 "r @$t0=poi(ecx+0x64);gc;"     ---这里是调用IoCompleteRequest的地方!
bp IopCompleteRequest ".if(@$t0==poi(poi(esp+0x10))){}.else{gc;}"

断下后,跟IopCompleteRequest,最后发现会检查Irp-Flags,决定是否会往完成端口投递消息。
解决方案之一是在完成IRP之前往 flags 置 pending,就是一句话IoMarkIrpPending(pIrp);

-End-


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (2)
雪    币: 18
活跃值: (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
过滤封包干什么好事
2016-12-6 15:27
0
雪    币: 1040
活跃值: (1293)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
IoMarkIrpPending确实很坑爹……经常会有莫名其妙的卡死问题……就是因为缺这玩意儿~
2016-12-6 17:24
0
游客
登录 | 注册 方可回帖
返回
//