设为首页收藏本站喵玉殿官方微博

 找回密码
 少女注册中
搜索
查看: 5066|回复: 3

[编程算法] 有人做过 PaintTool SAI 的逆向吗?

[复制链接]
发表于 2014-9-7 14:45:07 | 显示全部楼层 |阅读模式
本帖最后由 ofz 于 2014-12-26 00:13 编辑

中午好。看到 OllyDbg 的那个帖子,OFz 想起了以前的一个坑...觉得这边应该会有这方面的触手,就试着提问看看了

OFz 想对 SAI 做逆向,并不是为了破解(那个早有大大做过了),而是想解决某“SAI 触屏外挂”存在的一些问题。这个外挂的基本功能之一是获取用户的触摸操作(点击,双指点击,移动,缩放,旋转等),然后转换为 SAI 的快捷键命令。但是,单靠快捷键是没办法完成所有事情的,有一些操作还是要直接在 SAI 的内部进行。CheatEngine 和 OllyDbg 我还是知道一点的,因此现在这个外挂可以读取出 SAI 的笔刷尺寸,选取颜色等参数。不过不像一般的游戏,我发现直接改写 SAI 内存里的数值没办法让它如想象中工作(大概具有多窗口的程序本身就比较复杂,而且 SAI 又是个比较奇葩的多窗口程序,后面会补充

这就是我想通过逆向 SAI 解决的问题了。具体地讲,就是用外部程序让 SAI 进行下面的操作:
1. 无级缩放和旋转。SAI 里面 PgUp 和 PgDn 是缩放,Del 和 End 是旋转,不过都是一级一级的。注意到在导航器里是可以拖滚动条来实现无级缩放和旋转的,而且也有 Ctrl+Space+鼠标框选(缩放)和 Alt+Space+鼠标拖动(旋转)这两个操作,所以 SAI 内部应该是支持的。怎样把这段用于缩放的代码找出来?
2. 控制它选取特定的颜色。那个外挂的主体是个 hta 程序,可以载入图片并浮动在 SAI 顶层(我只是想从图片上取色而已

然后这边再提供一些之前探索的时候总结出的 SAI 的几个特(qi)别(pa)之处
1. SAI 的窗口里主要只有两种 Class:普通的 sfl_window_class 和 用于菜单栏的 sfl_menubar_class。因此你无法通过 Class 来判断这个控件是按钮还是滚动条,但是你可以用 GetDlgCtrlID 辨别出想要的窗口
2. 一般的控件(输入框,按钮等)是通过消息与父窗口通信的(所以只要拦截或发送这些消息就能控制它们了),SAI 似乎不是。看起来更像是子窗口直接调用父窗口的方法这样子来的
3. 多数 SAI 的窗口,可以通过调用 GetProp(hWnd, "_SFLWININFO_") 得到一个地址,这个地址里面存储了很多信息(包括窗口句柄,窗口尺寸等)。有些特殊窗口可能还会在那里保存笔刷尺寸,缩放等级这样的有用数据。不过你直接往里面写入新的数值是没有用的
4. 直接创建 sfl_window_class 窗口会导致 SAI 崩溃(也许跟 3 有关
5. 一般 Win32 程序会在消息处理程序的最后调用 DefWndProc,我在 SAI 里面没找到这个调用(所以一直不清楚主消息处理在哪里

恩,暂时就这些。欢迎触手们点拨。如果 SAI 确实过于奇葩无法驯服的话,我就只好尝试模拟点击这种奇技淫巧去了...

ps: 那个触屏外挂我会考虑发布的(等功能更加稳定一些
pps: 之前给它加功能的时候,每次总是想“解决这个问题就开始好好学画画!”,不过每次总是画不了几笔就又想到新问题了...所以到现在还是完全不会画的说(所以赶紧来个大触救我吧www



发表于 2014-9-7 17:29:53 来自手机 | 显示全部楼层
这个。。。ofz是用的触摸屏么。。。
听说绘图触是用的板子啊。。。
或许你可以寄希望于十二大神。。。
我们这种只会正向。。。

点评

ofz
tpt2 有压感的哦,实际上是把板子直接集成到触屏后面啦(surface 也是这样)。然后小心眼的 wacom 总担心这些机器会跟自家的 新帝 系列打起来,所以只愿意集成入门档次的板子进去...  发表于 2014-9-8 11:28
我是yoga2 13,但是没压感。。。 SAI通常用板子。。。  发表于 2014-9-7 20:32
ofz
ofz 有一只 thinkpad tablet 2(win8+触屏+wacom电磁笔),能随手涂涂画画感觉还是不错的w  发表于 2014-9-7 18:00
回复

使用道具 举报

发表于 2014-9-7 20:11:17 | 显示全部楼层
illuststudio低端玩家表示毫不知情

点评

ofz
感觉还是 sai 的压感用着更顺手ww,还有个 sketchbook pro 也挺喜欢的  发表于 2014-9-8 11:18
回复

使用道具 举报

 楼主| 发表于 2014-10-5 22:57:28 | 显示全部楼层
本帖最后由 ofz 于 2014-10-5 23:02 编辑

看到那边 @漆黑之翼  同学用的 IDA 不错的样子,就去拖了一个下来玩。参考 IDA 的 Graphic View,然后结合之前用的 CheatEngine (CE) 和 OllyDbg (OD) 倒腾了一天,算是把这个问题解决了吧...下边简单记一下过程

1. 使用 CE 查找内存
因为要控制 SAI 窗口的缩放,所以先把缩放等级弄成一个比较奇怪的数字
f1.PNG
然后用 CE 在 SAI 的内存空间里搜索所有可能的值(整形2314,整形23140,浮点型231.4,双精度浮点型231.4等等所有可能的值都要试一下),这边发现这个值就是个 8 字节的双精度浮点
f2.PNG

之前的帖子提到用 CE 直接改这个值是没用的,所以现在的思路是:分析是哪个过程(函数)改变了这个值,然后尝试从外部调用这个函数来改变缩放比例。用 CE 调试还是有点不顺手...于是把地址 4D635E40 记下来,然后上 OD 啦ww

2. 用 OD 下内存断点
打开 OD 并 attach 到 SAI 上,在左下角内存视图里找到 4D635E40 这个地址,给那 8 个字节下内存断点(只断写入的就好
f3.PNG

回到 SAI 的导航器窗口里改一下缩放比例...砰!好像把什么东西拦下来了,赶紧去看看:
f4.PNG

这个 fstp 指令可以把 cpu 浮点数栈上的数弹到内存上。看起来这行代码想把 215.14 放到 4D635E40 这个地址里(目测 215.14 应该就是新的缩放比例


3. 分析代码
其实 OFz 这种水平的根本就不会分析啦,说是“猜测”更加合适一些吧...先看代码
f5.PNG

这行代码所在的函数 00A5ED30 挺短的,看起来至少有 3 个参数:第一个参数像是个指针(一开始把 arg.1 放到 ecx 里,然后通过 [ecx+38], [ecx+34] 这样子来使用),占4个字节;第二个参数可能是个双精度浮点(因为一直在对它作浮点数指令,那两个常数 2.0 和 1600.0 大概是用来限制它的范围的),很有可能就是缩放比例,占8个字节;然后因为前面占了8个字节,所以第三个参数应该会表示成 arg.4,不过具体用途还不清楚...


然后看一下什么地方调用了这个函数...在函数首地址点右键 -> find references to -> selected command...好像只有 009C6190 这一个地方用到了...总之在调用的地方下个断点看看都传进去了些什么参数:
f6.PNG
f7.PNG     f8.PNG

发现点击一次导航器窗口的缩放滑块会调用两次 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 的版本可能就不能用了...为了一点新功能牺牲稳定性感觉挺不值的呢...所以就这样玩玩就好啦,不会放到最终的代码里的...于是今天又白折腾了一整天呢...最后一段不小心打出来这么多省略号...大概真的是有点困了吧...嗯...晚安啦......


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 少女注册中

本版积分规则

合作与事务联系|无图版|手机版|小黑屋|喵玉殿

GMT+8, 2025-10-31 22:03

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表