找回密码
 立即注册
首页 业界区 业界 记一次 .NET 某工控PCB巡检系统 崩溃分析

记一次 .NET 某工控PCB巡检系统 崩溃分析

辅箱肇 2025-9-28 18:03:05
一:背景

1. 讲故事

前些天训练营里的一位学员找到我,说他们的系统出现了崩溃,自己分析了遍也没找到是什么原因,让我帮忙看下怎么回事?dump拿到手后,接下来就上windbg分析。
二:崩溃分析

1. 为什么会崩溃

打开dump之后,windbg 会自动定位崩溃点,输出如下:
  1. ................................................................
  2. ................................................................
  3. .........................................
  4. Loading unloaded module list
  5. ...........................................
  6. This dump file has an exception of interest stored in it.
  7. The stored exception information can be accessed via .ecxr.
  8. (1cec.1984): Access violation - code c0000005 (first/second chance not available)
  9. +------------------------------------------------------------------------+
  10. | This target supports Hardware-enforced Stack Protection. A HW based    |
  11. | "Shadow Stack" may be available to assist in debugging and analysis.   |
  12. | See aka.ms/userhsp for more info.                                      |
  13. |                                                                        |
  14. | dps @ssp                                                               |
  15. |                                                                        |
  16. +------------------------------------------------------------------------+
  17. For analysis of this file, run !analyze -v
  18. clr!WKS::gc_heap::find_first_object+0xea:
  19. 00007ff9`9faea3eb 833800          cmp     dword ptr [rax],0 ds:00000461`0000085a=????????
复制代码
从卦中的 find_first_object 函数来看,这是GC在寻找需要标记的对象时出现了空地址,即经典的 托管堆损坏 问题。。。为了验证可以使用 !verifyheap 命令,输出如下:
  1. 0:016> !verifyheap
  2. Could not request method table data for object 00000296DB67CFC0 (MethodTable: 0000046100000858).
  3. Last good object: 00000296DB67CEF0.
复制代码
2. 为什么托管堆损坏了

从时间轴的角度来看,托管堆损坏 属于第二现场,第一现场是恶意的破坏现场,由于时间不能倒流,所以从dump中我们无法看到曾经发生过的事,那怎么办呢?有一个办法就是直接看 破坏现场,哈哈,这个是不是有点像法医学。。。 使用 dp 00000296DB67CFC0-0x80 L20 观察对象附近的破坏场所,输出如下:
  1. 0:016> dp 00000296DB67CFC0-0x80 L20
  2. 00000296`db67cf40  41816d40`414f1533 43202a65`41000000
  3. 00000296`db67cf50  414f1533`43202a65 41016d40`41016d40
  4. 00000296`db67cf60  40cf1533`40cf1533 414f1533`40cf1533
  5. 00000296`db67cf70  411fd70a`3f000000 43202a65`411fd70a
  6. 00000296`db67cf80  41016d40`43202a65 40800000`411b4fe6
  7. 00000296`db67cf90  41a00005`4247fffc 3f000000`41a00005
  8. 00000296`db67cfa0  4221c88f`41200000 00000000`411b4fe6
  9. 00000296`db67cfb0  000000be`00000000 00000523`000003ee
  10. 00000296`db67cfc0  00000461`0000085b 00000004`000007a6
  11. 00000296`db67cfd0  000003ee`000000be 0000085b`00000523
  12. 00000296`db67cfe0  000007a6`00000461 000000be`00000004
  13. 00000296`db67cff0  00000523`000003ee 00000461`0000085b
  14. 00000296`db67d000  00000004`000007a6 00000000`00000000
  15. 00000296`db67d010  00000000`00000000 00000000`00000000
  16. 00000296`db67d020  00000000`00000000 00000000`00000000
  17. 00000296`db67d030  00000000`00000000 00000000`00000000
复制代码
再回头看下错误信息,说 00000296DB67CFC0 处应该是方法表,结果变成了现在的很多数字,看起来像是 C++ 写入的数组,为了防止误判,我让朋友又继续抓崩溃dump,看看dump是不是具有随机性,防止南辕北辙,朋友也顺利的抓到了第二个dump。
  1. For analysis of this file, run !analyze -v
  2. clr!WKS::gc_heap::find_first_object+0x83:
  3. 00007ff9`a077a388 833900          cmp     dword ptr [rcx],0 ds:000001cc`00000666=????????
  4. 0:094> !verifyheap
  5. Could not request method table data for object 000001C64B541738 (MethodTable: 000001CC00000664).
  6. Last good object: 000001C64B53F758.
  7. 0:094> dp 000001C64B541738-0x80 L20
  8. 000001c6`4b5416b8  00000000`00000000 00000000`00000000
  9. 000001c6`4b5416c8  00000000`00000000 00000000`00000000
  10. 000001c6`4b5416d8  00000000`00000000 00000000`00000000
  11. 000001c6`4b5416e8  00000000`00000000 00000000`00000000
  12. 000001c6`4b5416f8  00000000`00000000 00000000`00000000
  13. 000001c6`4b541708  00000000`00000000 00000000`00000000
  14. 000001c6`4b541718  00000000`00001fe0 00000000`00000000
  15. 000001c6`4b541728  000000be`00000000 00000261`00000556
  16. 000001c6`4b541738  000001cc`00000666 00000004`000005c7
  17. 000001c6`4b541748  00000556`000000be 00000666`00000261
  18. 000001c6`4b541758  000005c7`000001cc 000000be`00000004
  19. 000001c6`4b541768  00000261`00000556 000001cc`00000666
  20. 000001c6`4b541778  00000004`000005c7 00000000`00000000
  21. 000001c6`4b541788  00000000`00000000 00000000`00000000
  22. 000001c6`4b541798  00000000`00000000 00000000`00000000
  23. 000001c6`4b5417a8  00000000`00000000 00000000`00000000
复制代码
从卦中看,第二个dump也是出现了类似 C++ 数组的内容,到这里基本就能断定有人有意或者无意的往托管堆写入数组内容,导致托管堆对象破坏,让朋友关注下代码中的 fixed,pinvoke 之类,截图如下:
1.png

3. 后续花絮

几天之后,朋友给我带来了一个好消息,说它通过 assert 断言一步一步的试,最终还真给找到了。。。大概就是 C++ 写托管堆的时候越界了,参考代码如下:
  1. int cadx = measure_info[i].posx;       
  2. int cady = measure_info[i].posy;       
  3. int samplex = cadx - nSamplePosInCADx;       
  4. int sampley = cady - nSamplePosInCADy;       
  5. if (samplex < 0 || samplex >= nWidthSample || sampley < 0 || sampley >= nHeightSample)       
  6. {       
  7.     continue;
  8. }       
  9. int offpos = sampley / grid_ver * 5 + samplex / grid_her;   //错误的 int offpos = cady / grid_ver * 5 + cadx / grid_her       
  10. assert(offpos >= 0 && offpos < 25);       
  11. int cad_offx = pnSubOffset[offpos * 2 + 0], cad_offy = pnSubOffset[offpos * 2 + 1];       
复制代码
找到当然是开心的,也确实这种问题比较难搞,不过不知道朋友为什么没有使用我推荐的 ttd 方式,毕竟它的程序有一个重要的特征,即启动后1分钟之内必崩,完全可以尝试ttd,参考如下:
  1. 0:094> vertarget
  2. Windows 10 Version 19044 MP (32 procs) Free x64
  3. Product: WinNt, suite: SingleUserTS
  4. Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
  5. Debug session time: Wed Mar 12 15:12:41.000 2025 (UTC + 8:00)
  6. System Uptime: 32 days 4:35:52.688
  7. Process Uptime: 0 days 0:00:41.000
  8.   Kernel time: 0 days 0:01:21.000
  9.   User time: 0 days 0:08:01.000
复制代码
三:总结

这次事故是 C++ 操控 C# 托管对象时,C++这边数组越界导致的托管堆损坏引发崩溃,这种仅凭第二现场就能寻找蛛丝马迹的案例,真的少之又少。。。也算是不幸中的万幸吧,当然也在于朋友的不抛弃不放弃,终见曙光,调试难!
2.jpeg

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册