几年前,公司买了台国产贴片机,里面的主程序是QT编写,运行在WINDOW XP系统上。主程序打开的界面,如图:
我来简单介绍下程序界面,各位读者不需要搞明白功能,只要知道大体的流程即可。
分析主界面:
一、左边的列表:
贴片生产文件,里面包括了贴片时元器件的坐标、飞达安装的元器件类型、Mark定位点等信息的贴片机工艺文件(文件的格式为CSV),使用和编辑工艺文件时,需先点击选中。
二、右边的三个按钮:
- 用打开自带的WPS,以便直接编辑CSV工艺文件,但是因为CSV文件格式比较复杂,一般不使用此功能;
- 中间的编辑是在程序中详细设定CSV
- 最后的加工按钮,就是进入贴片加工工艺,也是我们今天需要分析的重点项目。
分析加工界面:
注意: 因为逆向不能影响生产,我是在虚拟机下运行的主程序,所以下面的6个摄像头控件是不工作的,实际是显示了6个吸嘴取料的放大照片。同时因为我运行的是贴片机的默认工艺文件,所以左边的列表也是不显示内容的
一、左侧部分:
- 最左侧的列表,显示了MARK点信息(MARK点是PCB板上的一个标记,因为每次进板时位置的不同,所以需要MARK点,来定位每块PCB板的实际坐标,方便程序换算实际贴片坐标用的)。
- 中间的列表显示的贴片元器件的详细参数列表。
二、右侧部分:
- 加工日志,显示了PCB加工的结果信息。
- 功能按钮区域:
- 启动:开始贴片
- 单步(Enter):贴片每步细化,按此按钮一次即操作一步,比如:进板-->识别板边-->识别Mark点-->吸嘴取料-->贴装-->吸嘴再次取料-->再次贴装-->吸嘴N次取料-->N次贴装-->结束贴装
- 停止:在贴装时的暂停操作
- 配置:这个我们在下面会详细说说,里面包括了我们需要修改的同取功能
- 退出加工:顾名思义退出贴片
- PCB退板:把贴好的或者不贴的PCB板退出贴片机
三、下部:
- 六个贴装头取料时的照片(飞行相机拍摄)
- MARK点图片(MARK相机识别,在图1,复用贴装头1)
因为这台贴片机是多个贴装头的,所以取料时可以并排同时取多个料栈的元器件(需要满足连续的料栈才能同取),但是让人费解的是,这个主程序的贴片配置中(功能按钮中的"配置"按钮),关于同取的设置,每次退出加工程序都会重置,如果没有设置,就会变成没有同取,如下图所示:
逆向目标:
所以我们需要实现的目的已经明确:"取料支持同取"自动勾选(不用每次去按"配置"按钮,自动设置同取操作)
逆向思路:
在这里我想分享我逆向程序的一些经验给大家:
- 针对一个未知源代码程序时,我们需要大体知道一个程序的界面流程,可以在心里构建出一个大概的分析目标,不能没有目的。
- 特别是定位关键的代码时,我们至少需要大体知道代码所在的区域在哪里。
- 使用IDA找到函数调用堆栈链。
- 还有这个关键代码一定是修改了某些变量,我们只要定位到这个变量。
- 使用我们的二分算法,在这个区域内的中间函数下断点,观察定位到的变量是否改变成理想值。
- 如变量改变,则在前半段代码区域使用相同的方法,不停缩小范围,最终定位到关键函数。
- 如变量不改变,则在后半段区域再次使用二分法不停缩小范围,最终定位到关键函数。
- 在关键函数内,通过同样的二分法,定位到关键代码跳转即可。
接下来,我就为大家演示下分析流程。
逆向分析流程:
根据上面的逆向思路,我们已经有了第一条的分析目标--"取料支持同取"自动勾选;所以我们看看第二条的代码所在区域,根据我们之前分析的主界面和加工界面相关信息,我们知道,点击主界面的"加工"按钮会进入加工界面,再点击"配置"按钮可以设置"取料支持同取"。
流程如下:"加工"-->"配置"-->"取料支持同取"
因为我们需要勾选"取料支持同取"(下面简称:"同取"),所以不免要从程序初始化CheckBox控件代码入手,我们暂时还不知道QT里是怎么初始化CheckBox控件(因为CheckBox控件有选中和未选中两种状态,所以这个初始化的过程包括:修改并保存一个类似布尔值的公共变量,然后通过读取这个公共变量的值,来修改CheckBox的属性。PS:因为代码设计的关系,点击退出加工后,这个布尔值会重置),我们现在还不知道这个变量的位置,但是可以确定的是,初始化这个控件时,一定会初始化"同取"字符串,如果我们能定位到初始化"同取"的代码处,我们就可以通过IDA的Xref grap to确定初始化CheckBox的函数调用链。
如果大家还不是太明白,我们实际来操作一下吧!
打开我们的IDA8.3,加载贴片机主程序,按Shift+F5打开字符串窗口(怎么使用IDA8.3搜索中文字符串的方法请看以下链接。)
最新IDA8.3安装后需要做的一些完善工作(包括IDAPython报错、ChatGPT的模块安装、中文字符串的显示,各种问题解决方法合集)https://blog.csdn.net/donglxd/article/details/135243027如下图所示,在String窗口中,我们搜索下"取料支持同取"字符串,可以看到有一个结果,点击后,我们很方便的定位到调用它的代码处。
点击上面的搜索到的字符串来到字符串的定义位置,如下图所示:
点击上图中字符串后面的引用代码地址,我们来到了如下图的地址处:
选中如上图中的引用代码位置,然后按一下空格,切换到如下图的方框格模式,方便我们找到函数头,我们只要点击下图中红框处的函数规模总体预览图的最上面,就可以快速定位到函数头部。
如下图:来到函数头部后,我们右击第一行代码,在弹出的菜单中选择Xrefs graph to来显示函数的调用链
如下图就是刚刚引用"同取"字符串之前运行的所有调用函数链。
知道了函数调用链,我们接下去分析下,因为我们要找到设置CheckBox的公共变量,而设置CheckBox的操作,QT一定有相应的API函数,我们打开IDA搜索下导入表,如下图:
至于那个API,查询微软的Copilot AI后,给出了下面的链接(QT系统学习系列:1.1 QAbstractButton(按钮抽象基类)),这个链接中说明了AbstractButton是checkbox的基类,CheckBox有两个函数setChecked和isChecked,分别是设置CheckBox和读取CheckBox状态。
然后我们再次使用Copilot查询setChecked函数的名称修饰,如下图:
所以,我们IDA查询到的第一个函数"_ZN15QABstractButton10setCheckedEb"就是setChecked,同样的方法查询到"_ZNK15QAbstractButton9isCheckedEv" 就是isChecked,既然知道了setChecked这个API函数,我们查找下引用他的地址有哪些,方法如下图:
点击上图中的setChecked名称修饰,定位到下图中的函数输入表位置,右击后打开函数调用链图
如下图所示,setChecked的调用链,咋一看很复杂是不是?是不是想放弃不接着看了,其实不用想的太复杂,有时答案就在眼前,只要你耐心查找。请接着读下去。
你们还记得,我们刚刚也查找过一个函数调用链吗? 对了,就是调用同取函数那个调用链,我们把两张图放一起对比下,如下图所示:
我用红笔圈出的两张图的相同之处,显然这个函数调用链(sub_4A2500-->sub_4A23E0-->sub_4362F0-->sub_491660-->sub_4CBB40)中的最后一链sub_491660-->sub_4CBB40中包含了sub_491230-->"_ZN15QABstractButton10setCheckedEb"(setChecked),所以我们只需找到sub_491230中的setChecked,以方便之后,我们定位到之前提到的公共变量位置。
我们先用IDA定位到sub_491230函数看看,因为里面有setChecked函数调用,具体的分析请看下图:
因为程序是X86的,所以我们用X32DBG调试下,如下图:
可以看到内存地址74D0C8就是setChecked的公共变量参数(根据call esi得知,esp就是第一参数,而mov dword ptr ss:[esp],eax这句设置了esp,所以eax的赋值语句就是这个第一参数,也就是公共变量),打开主程序后运行启动,打开加工窗口后这个变量会一直存在,我们可以尝试设置下复选框,看看这个公共变量会改变吗?如下图:
可以看到公共变量74D0C8变成了00 00 01 01的格式,因为内存中是从高到低读取,所以我们需要的是从右往左数第二个字节的01 。
既然我们知道了公共变量,让我们再来分析下程序的流程,根据之前的程序流程分析,在主程序页面点击加工后,公共变量就建立了(这点从退出加工页面就会重置"同取"设置就可以判断出来),所以我们从主程序页面的"启动"这个字符串"开刀",Strings搜索下"启动",如下图:
下图是函数头部:
右击这个sub_433830,点击Xrefs graph to,查看函数调用链:
函数sub_433830的调用链如下图右半部分,可以看到sub_433830的调用链在setChecked调用链的前面,和我们分析的一样:
如上图,我们需要判断设定公共变量74D0C8是否在蓝色部分的这些函数里? 所以我们先把蓝色部分的这些函数头部都下断点,使用之前文章中提到的二分法,逐步缩小区域即可。我们在x32dbg中使用bp命令快速下断点,如bp 4352C0,下断之后的效果如下:
然后重新运行程序,把下面的内存也设置到公共变量74D0C8的地址观察变化。程序启动后,点击主界面的"启动"按钮,遗憾的是程序直接运行到了加工界面,断点并没有断下,而是需要点击加工界面里的配置才会断下,观察公共变量74D0C8发现,在进入加工界面后,就已经被设置,所以我们可以尝试sub_433830函数的另一边调用链,如下图:
我为了调试时,观察方便,把红框中下断的每个函数都注释了一个简称,如sub1、sub2这样的,方便辨认,并在X32dbg中也对应的注释了函数简称,如下图:
然后,我们用x32dbg重新运行程序,我们一边观察公共变量74D0C8的变化,一边在中断时按运行按钮,跳过不需要的断点,如果x32dbg不中断时,我们再切会贴片机程序正常操作下,如点击项目名、点击启动等,经过几次中断后,我发现公共变量74D0C8变化成我们需要的00 00 00 01了,如下图:
如上图,因为中断在sub5函数头部,所以设置公共变量的代码一定在sub4函数,然后我们可以使用,如下图的二分法,逐步缩小查找范围,定位到关键代码(记住每次都要重启程序调试,我们可以按空格键把不需要的断点关掉,加快调试速度。还有别忘了重启后每次都要在内存1中定位到公共变量74D0C8)。
通过上面的方法,我们很快定位到了关键函数,如下图:
如上图,可以看到运行过注释的这句关键点,下面的公共变量74D0C8变成了00 00 00 01,显然设置代码在这个函数(关键点)中,在这个关键函数上下断。
把不需要的断点取消,重新运行程序,按F7进这个函数看看,如图:
如上图,进关键函数后是一个大跳转,此时单步下去,并注意下面的公共变量74D0C8的值。
我们跟进这个大跳转,如图:
如上图,我们来到了设置公共变量74D0C8的关键函数处,粗略看了下都是mov的赋值操作,看来地方来对了。这个函数不长,我们一行行单步观察公共变量的值变化就好。
如上图可知,我们运行了上面4行mov的赋值代码后,74D0C8变量成我们需要的值,但是这个00 00 00 01是不设置同取的变量。不知道大家还记得之前我们调试时,尝试点击"同取"复选框改变的值吗?
就是上图第3个字节位,而我们再看这4句mov赋值,显然赋值的地址也是按ecx的寄存器累加上去,一位位的设置的,显然mov byte ptr ds:[ecx+0x02], 0x00,这句就是我们需要修改的,把后面的0x00改成0x01即可,如下图:
然后右击修改的代码,点击补丁保存修改后的文件,我们可以另起一个名称,如下图:
然后我们运行下这个修改后的主程序看看。
可以看到,修改成功了,教程到此也写完了,感谢大家的耐心观看,本人不才,但是希望大家有学到新知识,谢谢!