分析并操控微信猜拳和掷骰子

微信表情包自带两个小游戏,掷骰子和猜拳,那么来说一下我是如何分析并写一个logos插件来实现操控掷骰子和猜拳结果,挺有趣的。

首先按照惯例用工具把微信砸壳,我用的是CrackerXI,使用很方便。把二进制文件传到mac上。

先把二进制文件放入反编译器中,微信文件体积很大,高达140MB,所以得给反编译器点时间运行。我用的是Hopper dissembler, 正版要99美元。建议支持正版,挺值的。~ 加上在濒临放弃的时候一考虑到沉没成本,nnd,放弃学习逆向的话岂不是99美元就白费了。

等Hopper时,使用class-dump导出头文件。

class-dump -s -S -H 微信二进制文件 -o 导出地址

屏幕快照 2020-05-09 下午11.35.41

导出了16227个头文件,可见微信规模之巨大。

还有UI调试工具要用到,使用的是腾讯的免费软件lookin,这里是下载地址

工具准备好了,开始分析。

首先打开微信进入个人聊天界面,在mac打开lookin。

骰子和猜拳的图标存在于表情包的发送栏和已经发出去或已经收到的聊天界面中。从UI层面有两个切入点,我选第二个。原因是前者实现了按钮的功能,手指摁下这个按钮会触发一条反应链,如果从它触发的反应链入手研究将会比较困难,因为你不知道在这条链的哪一部分负责随机产生骰子点数和猜拳结果。后者的话,最直接的就是显示的图标(石头或剪刀。。etc),这个对象之所以会这样显示肯定是有个数据源决定的,可以顺着这个线索摸上源头。


定位到骰子图标后看到它是GameMessageCellView的一部分,进入GameMessageCellView的头文件看看。


很显眼的名字,getGameImageByIndex,并且它的参数是个整数,感觉这个参数就是一重数据源。

接下来使用动态调试,

一个用来ssh的端口

iproxy 2222 22

一个让lldb连接debugserver的端口

proxy 1999 1999

ssh上设备,

debugserver 127.0.0.1:1999 -a "WeChat"

接着用lldb连接debugserver

process connect connect://127.0.0.1:1999

连接后打印微信的ASLR偏移

image list -o -f | grep WeChat

进入hopper定位[GameMessageCellView getGameImageByIndex:]
这个函数貌似很长很复杂,但用伪代码模式一看就很明显了。


参数确实是直接决定显示图片的第一数据源。

在函数开始处下一个断点,0x1bad988加上刚才的偏移量0x102c88000 = 0x104835988。注意断点必须是发一次表情包断一次,这样才能确保摸上去的这条路上含有生成随机数的部位,不然可能会摸上另外一条路。

打印lr寄存器中的地址,lr保存子程序返回的地址,在其他函数中被调用的位置。

这个值减去偏移量=,hopper跳到这里康康。

跳到了某个函数里,是谁不重要,重要的是getGameImageByIndex的参数是哪来的。

往上看,$x2 来自于$x0, 而$x0是再往上三行objc_msgSend的返回值,而这个函数名叫做realGameIndex,很可疑。

在第一个objc_msgSend处下个断点,看下调用者是谁。
屏幕快照 2020-05-10 上午10.30.07
这个函数原来是[GameMessageViewModel realGameIndex],它是第二数据源。

hopper跳到GameMessageViewModel的头文件,发现realGameIndex是一个read only 的property。在hopper跳转到realGameIndex康康。

屏幕快照 2020-05-10 上午11.08.01

看得出它先在BaseMessageViewModel找到一个叫m_messageWrap的ivar,调用了其m_uiGameContent并返回。这个m_uiGameContent就是第三数据源。用lldb下断点发现这个函数全称是[CMessageWrap m_uiGameContent].

ps:顺便请教个问题 ~ 我不太懂如何拿到m_messageWrap,GameMessageViewModel和BaseMessageViewModel关系是怎么样的呢?

继续,来到CMessageWrap头文件康康。


m_uiGameContent是一个属性,刚才是从它的getter方法摸过来的,这个属性的值不会平白无故冒出来,只可能是由setter提供的。它的setter,也就是setM_uiGameContent,是第四数据源。

继续。。lldb定位到setM_uiGameContent, 只有一个-[CExtendInfoOfEmoticon setM_uiGameContent:], 猜想CExtendInfoOfEmoticon是一条正常微信信息的附加值。这次不要老办法打印$lr了,交叉引用看看在程序哪里会调用这个setter。

直觉说[GameController SetGameContentForMsgWrap:]最靠谱,过去康康。

屏幕快照 2020-05-10 上午11.47.10

上面几行有GameContent,MD5等字眼,最重要是最上边那行有一个’random‘,但仔细一看,紧接着$x0就被栈中的一个值给覆盖了,有哪位大佬能告诉我这个random存在的意义吗。

函数结构是这样的:
屏幕快照 2020-05-10 上午11.51.11

上面几个模块是判断game type,并用random产生随机值的。图片够清的话可以看看,但随机数怎么产生不是重点。重点是已经摸到源头了,这就是随机数的发源地,在这里hook是最稳妥的。

重点函数有四个

  1. [GameController getMD5ByGameContent:]

很直接,九个cases,对应三个剪刀石头布加上六个骰子点数。参数是1到9的随机数。

  1. [CExtendInfoOfEmoticon setM_nsEmoticonMD5:]
    设置游戏信息的MD5数值,参数就是[GameController getMD5ByGameContent:]。

  2. [CExtendInfoOfEmoticon setM_uiGameContent:]
    刚刚见过的熟人,参数也是随机数。

  3. [iConsole logWithLevel:(int)arg1 module:(const char *)arg2 errorCode:(unsigned int)arg3 file:(const char *)arg4 line:(int)arg5 func:(const char *)arg6 format:(NSString *)arg7]
    位于函数尾部,应该是记录作弊之类的。error code给他0应该就好了。

最后小总结:)

Mac版微信通过game content中的MD5进行显示,而iPhone是由[CExtendInfoOfEmoticon setM_uiGameContent:]的参数决定的。所以在我测试的时候往往会出现我手机明明按了骰子却显示拳头,电脑显示剪刀,而别人的手机显示拳头的尴尬场面,结论是两个都要hook。

game type数值为1代表石头剪刀布,数值为2代表掷骰子。在game type与game content不符的情况下,手机显示骰子滚动的动画效果,最后一秒定格在一个拳头上,在别人手机上也是如此,很搞笑。

屏幕快照 2020-05-10 下午12.14.43

第一次完全自己分析,过程其实很乱,写下来把思路理清。关注越狱有一段时间了,但逆向方面刚入门,啰嗦之处请多包涵。

3 Likes

大佬,Lookin的库是framework,你是怎么注入进程的?求方法。

https://lookin.work/get/

dlopen

我当年拿着这个功能跟着我的屌丝朋友(非技术)炫耀,结果被diss了。
https://jingyan.baidu.com/article/cbcede07ff993f42f50b4d5c.html
:grin:

3 Likes

厉害,无侵入式操控 :sweat_smile:

xswl,一力降十会

DYLD_INSERT_LIBRARIES=/var/mobile/Containers/Data/Application/1001B68E-AECD-44B6-AB4C-C32599DB0926/Documents/LookinServer.framework/LookinServer /var/containers/Bundle/Application/67376181-0442-4324-AF1E-77C90B09701F/QQ.app/QQ

Framework 怎么注入啊?求大佬指导

发个帖子还得审核,这不典型的流程又臭又长?:smile: