分析 - MTP操作文件阻塞卡死问题

一次项目,需要用到MTP协议存储文件数据,参考微软的WpdApiSample相关代码实现,下载地址如下:

https://github.com/microsoft/Windows-classic-samples/releases/download/MicrosoftDocs-Samples/wpd-sample.zip

功能代码完成后,在一次连续的通过MTP操作文件后,再次通过MTP操作文件时,出现调用阻塞较长时间的情况,这一情况只在MTP设备(手机)首次接入PC后出现,根据该情况进行分析。

通过多次跟踪调试发现,在调用IPortableDeviceContent::Delete方法删除文件后,后面再调用其它方法(如IPortableDevice::Open方法等)就会出现长时间阻塞卡死现象,并且通过exeplorer.exe浏览MTP设备(手机)中的文件也是浏览不了的,exeplorer.exe也是卡死。

猜测是不是和explorer.exe进程有关系?尝试Kill掉explorer.exe进程,然后查看是否出现阻塞卡死现象,在调用完Delete方法后,并没有出现,经过多次验证后发现确实和explorer.exe进程有关系。

那到底什么原因导致长时间的阻塞呢?一般阻塞情况的发生无外乎就是某一操作正在进行,还没有完成,如socket中的recv接收数据操作等。这里我们将关注WUDFHost.exe进程,通过调试发现,当调用完Delete方法后,WUDFHost.exe进程的CPU占用和内存占用都有所增加,并且其内存占用一直是持续增加。当WUDFHost.exe进程的内存不在增加时,之前MTP阻塞的调用也就返回了,所以根据我的判断,有如下的关系图:

当程序删除一个文件后,explorer.exe得到删除文件的消息,然后通过自己的一个判断后,通知WUDFHost.exe进程进行一个类似缓存的操作,由于缓存操作是一个耗时的操作,影响了后续的其它MTP请求,从而导致后续的MTP操作阻塞了。

为了验证这一猜测,通过Windbg来调试WUDFHost.exe进程。由于WUDFHost.exe进程的权限是Local Service,这里通过psexec工具来启动Windbg,使其具有更高的权限以调试WUDFHost进程,启动Windbg的命令如下:

psexec -d -s -i 1 "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe"

然后附加WUDFHost进程,操作MTP设备中的文件,当出现内存不断增长时,暂停调试,进行如下查看:

这里重点关注WpdMtpDr.dll模块 

根据 WpdMtpDr中相关调用的偏移量,在IDA中进行静态分析,分析出如下关键代码片段:

 这里CObjectLookupHandler::RecursiveSearchForUniqueId是一个递归调用,循环遍历MTP设备中的文件,添加到缓存。由此可知,WUDFHost.exe在进行一些文件信息的缓存操作,因而其内存占用一直处于增长状态。

追溯到上层调用,可以看到一个加锁的处理

由此可见,当缓存操作没有处理结束,其它MTP的处理操作都是无法进行的,因为锁的存在。

但在explorer.exe中删除文件操作不会导致因缓存带来的阻塞问题。

下面来看看explorer.exe的处理。 因为explorer.exe权限是普通用户,这里只需要以管理员权限启动Windbg来调试explorer.exe进程即可。

这里先介绍一个NirSoft工具集中的一个工具deviceioview.exe,该工具可以监控指定进程的DeviceIoControl调用,当删除一个MTP中的文件时,有如下DeviceIoControl调用

这里先不关心抓取到的具体数据信息,只需要知道此时explorer.exe调用了DeviceIoControl方法。

下面通过Windbg来动态调试explorer.exe进程,在DeviceIoControl处设置断点

bp kernel32!DeviceIoControl

 

可以看到,当删除文件时,explorer.exe发送一个消息到MTP设备,进行导致下层的WUDFHost进程进行文件列表缓存。

通过IDA静态分析上图中箭头所指方法 ,关键代码片段如下:

这里通过IPortableDeviceValues::GetStringValues方法获取WPD_CLIENT_EVENT_COOKIE的字符串值,如果获取成功,则比较是否与CLSID_WPD_NAMESPACE_EXTENSION({35786D3C-B075-49B9-88DD-029876E11C01})的字符串值相同,如果相同,则_OnWpdEventMessage方法结束,不在进行后续操作,如下图为跳转后的位置: 

 如果WPD_CLIENT_EVENT_COOKIE的字符串的值和CLSID_WPD_NAMESPACE_EXTENSION字符串的值不相同,则会走到下面的位置,从而最终会发送消息到MTP设备,最终导致WUDFHost进程缓存文件列表信息,导致后续MTP操作阻塞。

我们再来看看explorer.exe是否设置了 WPD_CLIENT_EVENT_COOKIE的值为“{35786D3C-B075-49B9-88DD-029876E11C01}”。

因为Windows访问MTP设备时需要调用IPortableDevice::Open方法,该方法最终实现是在模块PortableDeviceApi.dll中,在WinDbg中下断点

bp PortableDeviceApi!CPortableDevice::Open

触发断点后的调用堆栈

 

这里查看wpdshext!GlobalInterfaceThreadProc+0x201反汇编

在调用CPortableDevice::Open前,通过GetClientInfo方法获取了Open所需的PortableDeviceValues的值,在call wpdshext!GetClientInfo (000007fe`e13621c0)处下断点,进入GetClientInfo方法查看具体的实现

浏览GetClientInfo反汇编代码后,可以看到这里通过WPD_CLIENT_EVENT_COOKIE设置了CLSID_DeviceFolder的GUID字符串,该字符串如下

该值正是 上文提到的CLSID_WPD_NAMESPACE_EXTENSION(“{35786D3C-B075-49B9-88DD-029876E11C01}”)的值,反汇编代码对应的伪代码如下:

因此,在WpdApiSample中,只需要在调用IPortableDevice::Open前,在GetClientInformation函数中,设置WPD_CLIENT_EVENT_COOKIE的值为CLSID_WPD_NAMESPACE_EXTENSION表示的GUID字符串的值,即可绕过WUDFHost进程进行缓存操作,避免阻塞的发生,这也就是为什么在explorer.exe中删除文件时不会发生因缓存而阻塞的情况。

在DeviceEnumeration.cpp中的GetClientInformation方法中加入如下代码:

(完)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值