ida怎么看伪代码_调试实战通过转储⽂件分析程序⽆响应之使
⽤windbg+IDA逆向篇...
缘起
最近,接连在项⽬中遇到了两个界⾯⽆响应的问题。都只发⽣在客户特定机器上,不⽅便直接调试,只能抓取 dump 进⾏事后分析了。
抓取 dump
远程连上可以重现问题的机器,使⽤ process explorer 初步观察卡死的进程,发现 CPU 占⽤率很低,
经过⼀段时间的观察,基本确定是⼀个死锁问题。 在卡死的进程上右键,保存完整转储,压缩,发回本地进⾏分析。
使⽤ windbg 进⾏分析
双击抓取的 dump ⽂件,因为之前已经执⾏过 -IA,所以默认会通过 windbg 打开 dump ⽂件。先使⽤ ~*kvn 粗略浏览⼀下每个线程的调⽤栈(因为⽐较长,这⾥就不截图了)。经过观察,很快锁定了两个值得进⼀步查看的线程:⼀个是主线程(界⾯线程),因为是界⾯⽆响应,肯定要关注界⾯线程。另外⼀个是 7 号⼯作线程。分别看⼀下这两个线程的调⽤栈。
主线程的调⽤栈如下图所⽰:
注意上图红⾊⾼亮部分,主线程通过 SleepConditionVariableCS() 进⼊等待。
看完主线程,再看 7 号⼯作线程的调⽤栈,如下图所⽰:
7 号线程对 SendMessage() 的调⽤⾮常值得怀疑。
猜测整个流程是这样的:主线程不知由于什么原因进⼊等待状态,⽽⼯作线程由于各种各样的原因也进⼊了等待状态。其中 7 号线程最明显,因为它正在发消息,⽽主线程此时是⽆论如何也不会响应这个消息的。
于是,典型的死锁再⼀次发⽣了。
加载
加载 AssemblyDesign_Tools.dll 的符号后,查看对应的源码,消除对 SendMessage() 的调⽤,问题解
决!so easy!
说明:⼯作线程中并没有直接调⽤ SendMessage(),⽽是调⽤了操作界⾯的相关 API,间接调⽤了 SendMessage() 给界⾯线程发消息。
死锁的问题解决了,但是为什么向主线程发个消息就死锁了呢?秉着打破砂锅问到底的原则,我⼜开始折腾了。下⾯的内容适合喜欢调试逆向的极客阅读。
深⼊调查
最开始的思路是:查看主线程在等待的条件变量,然后再调查哪个⼯作线程会唤醒这个条件变量。奈何 64 位下,前四个参数通过寄存器RCX, RDX, r8, r9 进⾏传递,如果这些寄存器没有在栈上存储⼀份的话,很难查看具体的值。折腾⼀番后,确实没到有⽤的信息,⽽且就算到了,也很难出是哪个线程会执⾏唤醒操作。
这个死锁问题不像关键段死锁解决起来那么直接。不能直接通过命令(!cs -l),或者查看调⽤栈就能直接理出头绪。看来只能硬着头⽪逆向分析相关代码了。
0 号线程和 7 号线程最值得怀疑,其它线程基本可以排除。先看看主线程为什么会等待吧。
主线程逻辑
variable怎么记到调⽤ BentleyG!Bentley::BeConditionVariable::WaitOnCondition() 的地⽅,也就是 5 号栈帧。
在 IDA 中打开 MobileDgn.dll,并到这个函数,然后按下神奇的 F5:
可以看到,主线程在陷⼊等待之前,向⼯作线程发送了⼀个任务,也就是 sub_7FEDAC749A0,传递的参数是 v5。v5 偏移 88 的位置保存了 BeConditonVariable 类型的变量,也就是 WaitOnCondition() 所等待的变量。猜测:sub_7FEDAC749A0 内部会唤醒这
个BeConditionVariable, 如果 sub_7FEDAC749A0 被顺利执⾏,那么主线程的等待⾃然就结束了。
先看看 sub_7FEDAC749A0 的反汇编代码,当然是直接看 F5 后的伪代码了。
可以很明显的看到 sub_7FEDAC749A0 内部调⽤了 Bentley::BeConditionVariable::Wake((CMFCRibbonInfo::XElementButtonUndo *) ((char *)v1 + 88), 1); 。
从函数名就可以猜到是⽤来唤醒 BeConditionVariable 的。
如此看来,sub_7FEDAC749A0 很有可能还没有被执⾏,⼯作线程就挂起了。⼀起来看看 SendToWorkThread 是怎么把任务发送到⼯作线程的。
SendToWorkThread() 会先判断是否在⼯作线程运⾏,如果是则直接执⾏对应的函数,否则就根据参数⽣成⼀个 RpcMessage ,然后发送这个新⽣成的 RpcMessage 到⼯作线程的任务队列中。
再来看⼀下⽣成 RpcMessage 的函数,我把这个函数命名为 MakeMobileDgnRPCMessage()。
⼀定要记住这⾥的关键信息,后⾯会根据这⾥的关键信息验证。 HandlePaint() 传过来的函数地址是 0x7FEDAC749A0,保存在了 rpcMsg 偏移 128 的位置。MobileDgnRPCGenericMessage 类的虚表地址是 000007FEDAD19658。
继续追踪 SendAsynToWorkThread(),如下图:
继续追踪 HandleRpcMessage()(这个名字是我命名的,不是 IDA 识别的),传给它的第⼀个参数是⼀个全局变量(姑且命名为
g_taskQueueManager),第⼆个参数是要发送的 rpcMsg,第三个参数 v3 的值是 2,第四个参数是 1。
这个函数⽐较长,我只逆了个⼤概,⼤体思路是先检查是否存在,不存在则插⼊。如果队列已经满了,需要等待⼯作线程从队列中取⾛⼀些任务才返回。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论