| 本帖最后由 ofz 于 2014-10-5 23:02 编辑 
 看到那边 @漆黑之翼  同学用的 IDA 不错的样子,就去拖了一个下来玩。参考 IDA 的 Graphic View,然后结合之前用的 CheatEngine (CE) 和 OllyDbg (OD) 倒腾了一天,算是把这个问题解决了吧...下边简单记一下过程
 
 1. 使用 CE 查找内存
 因为要控制 SAI 窗口的缩放,所以先把缩放等级弄成一个比较奇怪的数字
 
   然后用 CE 在 SAI 的内存空间里搜索所有可能的值(整形2314,整形23140,浮点型231.4,双精度浮点型231.4等等所有可能的值都要试一下),这边发现这个值就是个 8 字节的双精度浮点
 
   
 之前的帖子提到用 CE 直接改这个值是没用的,所以现在的思路是:分析是哪个过程(函数)改变了这个值,然后尝试从外部调用这个函数来改变缩放比例。用 CE 调试还是有点不顺手...于是把地址 4D635E40 记下来,然后上 OD 啦ww
 
 2. 用 OD 下内存断点
 打开 OD 并 attach 到 SAI 上,在左下角内存视图里找到 4D635E40 这个地址,给那 8 个字节下内存断点(只断写入的就好
 
   
 回到 SAI 的导航器窗口里改一下缩放比例...砰!好像把什么东西拦下来了,赶紧去看看:
 
   
 这个 fstp 指令可以把 cpu 浮点数栈上的数弹到内存上。看起来这行代码想把 215.14 放到 4D635E40 这个地址里(目测 215.14 应该就是新的缩放比例
 
 
 3. 分析代码
 其实 OFz 这种水平的根本就不会分析啦,说是“猜测”更加合适一些吧...先看代码
 
   
 这行代码所在的函数 00A5ED30 挺短的,看起来至少有 3 个参数:第一个参数像是个指针(一开始把 arg.1 放到 ecx 里,然后通过 [ecx+38], [ecx+34] 这样子来使用),占4个字节;第二个参数可能是个双精度浮点(因为一直在对它作浮点数指令,那两个常数 2.0 和 1600.0 大概是用来限制它的范围的),很有可能就是缩放比例,占8个字节;然后因为前面占了8个字节,所以第三个参数应该会表示成 arg.4,不过具体用途还不清楚...
 
 
 然后看一下什么地方调用了这个函数...在函数首地址点右键 -> find references to -> selected command...好像只有 009C6190 这一个地方用到了...总之在调用的地方下个断点看看都传进去了些什么参数:
 
   
     
 发现点击一次导航器窗口的缩放滑块会调用两次 00A5ED30 。在右下角的堆栈视图里可以看到只有第三个参数不同(0x81/0x82)。推测这两次调用,一次来源于鼠标按下,一次来源于鼠标松开。后来发现这两个值 0x81,0x82 都是写死在代码里的,应该也不会出现其他值的可能性...然后第二个参数就是新的缩放比例,这一点也已经可以确认了
 所以接下来,如果想调用这个函数的话,问题就是参数1了...会是什么样的东西呢?
 
 4. 此处省略一千字...
 总之,你需要一边盯着内存窗口的变化,一边看着代码地在指令窗口里跳来跳去,记下一个个的 16 进制地址,然后通过 IDA 反编译出来的调用关系推测函数的作用,并脑补各个 16 进制数的联系...
 最后总结得到参数1的方法是:找到导航器的窗口句柄 hWnd -> GetProp(hWnd, "__SFLINFO__") 得到一个指针 -> 这个指针加上 0x8 字节的偏移再得到一个指针 -> 这个指针加上 0x10 字节的偏移再得到一个指针 -> 这个指针加上 0x44 字节的偏移再得到一个指针 -> 如果这个指针不是 0 的话,你就可以把它传给那个函数了
 
 感觉用代码说话会更简单些
 BYTE *pData = (BYTE *)GetProp(gSaiWnds.nav_zoom, SAI_PROP_WININFO);
 if (pData)
 pData = (BYTE *)(*((DWORD *)(pData + 0x8)));
 if (pData)
 pData = (BYTE *)(*((DWORD *)(pData + 0x10)));
 if (pData)
 pData = (BYTE *)(*((DWORD *)(pData + 0x44)));
 
 if (pData) {
 
 setZoomLevel(pData, dScale, 0x81);
 setZoomLevel(pData, dScale, 0x82);
 }
 
 5. 从外部调用
 要调用别的进程内部的方法,你需要把自己的代码注入到那个进程里,然后找到函数的地址并把它转化成一个函数指针来调用。注意在不同的系统 exe 可能会被映射到不同的基地址上,必须考虑这种情况。至于 __cdecl, __stdcall 和 __fastcall 这几个修饰符的差别就不多说了(弄错的话 vc 会直接把程序崩掉的...
 // 函数类型
 typedef void (__cdecl *setZoomLevelFunc)(BYTE *pData, double scale, DWORD);
 // 获取 exe 基地址
 DWORD pModule = (DWORD)GetModuleHandle(NULL);
 // 用基地址 + 偏移的方式获取函数指针
 setZoomLevelFunc setZoomLevel = (setZoomLevelFunc)(pModule + 0xAEB30);
 // 调用!
 setZoomLevel(pData, dScale, 0x81);
 setZoomLevel(pData, dScale, 0x82);
 
 进程注入的具体方法是很多的,这边因为之前给 SAI 下过钩子,所以可以直接在钩子的回调函数里执行,这样还能顺便拓展源程序的功能。比如原来在 SAI 里,PageUp/PageDown 有时候缩放幅度很大,而 shift+PageUP/PageDown 则是没反应的。利用这个按键组合和上面的技术可以实现任意的缩放控制(测试可行,代码就不贴了...
 
 6. 总结
 花了不少时间呢...虽然最后证实可行,不过感觉这个想法太 tricky 了...而且换个 SAI 的版本可能就不能用了...为了一点新功能牺牲稳定性感觉挺不值的呢...所以就这样玩玩就好啦,不会放到最终的代码里的...于是今天又白折腾了一整天呢...最后一段不小心打出来这么多省略号...大概真的是有点困了吧...嗯...晚安啦......
 
 
 
 |