首页
论坛
专栏
课程

[原创] 分析wx骰子随机数流程

2020-2-3 12:40 755

[原创] 分析wx骰子随机数流程

2020-2-3 12:40
755
我是52pj的会员L剑仙,把自己帖子转过来,希望斑竹通过,加入看雪大家庭,原贴在https://www.52pojie.cn/thread-1097114-1-1.html
菜鸟最近闲了下来,简单分析学习下南极公司的骰子和猜拳的实现,记录下笔记
首先.准备工作,jadx反编译wx708另存为as源码
点击骰子表情,通过monitor从onclick开始追踪定位到关键函数com.tencent.mm.sdk.platformtools.bo.ii,jadx反混淆后为函数m13717ii,简单观察函数

准备工作,jadx反编译wx708另存为as源码

首先,通过monitor方法回溯从 performClick往下追踪,寻找到关键函数com.tencent.mm.sdk.platformtools.bo.ii,在jadx反混淆后为函数m13717ii


public static int m13717ii(int i, int i2) {
AppMethodBeat.m3378i(52299);
Assert.assertTrue(i > i2);
int nextInt = new Random(System.currentTimeMillis()).nextInt((i - i2) + 1) + i2;
AppMethodBeat.m3379o(52299);
return nextInt;
}


hook此函数,修改返回值,容易判断:

玩骰子时i=5,i2=0,返回0-5对应1-6点

玩石头剪刀布时i=2,i2=0,返回0-2对应石头剪刀布


var bo = Java.use('com.tencent.mm.sdk.platformtools.bo');

bo.ii.overload('int','int').implementation=function(a1,a2)

{   console.log("hook ii start");

    console.log("a1:"+a1);

    console.log("a2:"+a2);

    var rtn= this.ii(5,0);

    console.log("rtn:"+rtn);

   var threadef = Java.use('java.lang.Thread');

   var threadinstance = threadef.$new();

   var stack = threadinstance.currentThread().getStackTrace();

   function Where(stack){

    for(var i = 0; i < stack.length; ++i){

      console.log(stack[i].toString());

    }

  }

   console.log("Full call stack:" + Where(stack));

    return rtn

}


打印堆栈结果如下


com.tencent.mm.sdk.platformtools.bo.ii(Native Method) m13717ii

com.tencent.mm.plugin.emoji.e.f.n(SourceFile:93) mo46299n

com.tencent.mm.bz.a.n(SourceFile:316) mo46299n

com.tencent.mm.emoji.panel.a.d.a(SourceFile:55) mo46720a

com.tencent.mm.emoji.panel.a.q$1.onClick(SourceFile:27)

android.view.View.performClick(View.java:6294)

android.view.View$PerformClick.run(View.java:24770)

android.os.Handler.handleCallback(Handler.java:790)

android.os.Handler.dispatchMessage(Handler.java:99)

android.os.Looper.loop(Looper.java:164)

android.app.ActivityThread.main(ActivityThread.java:6494)

java.lang.reflect.Method.invoke(Native Method)

com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:440)

com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)



首先,通过抛异常然后调用Exception的run方法的方式 反射调用main方法

然后,开启loop循环消息队列处理message

进入 PerformClick触发方法com.tencent.mm.emoji.panel.a.q$1.onClick,后面就进入到关键的逻辑了

我们倒着看



public final EmojiInfo mo46299n(EmojiInfo emojiInfo) {
AppMethodBeat.m3378i(52890);
if (emojiInfo.field_catalog == EmojiGroupInfo.BmT && emojiInfo.field_type == EmojiInfo.Bur && emojiInfo.getContent().length() > 0 && EmojiInfo.m66104Qp(C9015bo.getInt(emojiInfo.getContent(), 0))) {
Cursor Qu = C33825j.getEmojiStorageMgr().BkN.mo55784Qu(C9015bo.getInt(emojiInfo.getContent(), 0));

这个 Cursor Qu的getCount()得到的数值就是最大点数,如果是骰子则为6
if (Qu != null && Qu.getCount() > 1) {
int ii = C9015bo.m13717ii(Qu.getCount() - 1, 0); 这一句调用random产生骰子或划拳结果
emojiInfo = new EmojiInfo();
Qu.moveToPosition(ii); 更新变动表情为确定点数表情
emojiInfo.convertFrom(Qu);
}
if (Qu != null) {
Qu.close();
}
}
AppMethodBeat.m3379o(52890);
return emojiInfo;
}



传入参数是 EmojiInfo类,表情类Lcom/tencent/mm/storage/emotion/EmojiInfo,可以简单看一下这个类的数据结构


public final Cursor mo55784Qu(int i) {
AppMethodBeat.m3378i(62821);
Cursor query = this.f15356db.query("EmojiInfo", null, "catalog=? and temp=?", new String[]{String.valueOf(i), AppEventsConstants.EVENT_PARAM_VALUE_NO}, null, null, null);
AppMethodBeat.m3379o(62821);
return query;
}

这里是query 函数用法query(table,columns, selection, selectionArgs, groupBy, having, orderBy, limit)

很清晰,query通过查询 EmojiInfo表的 catalog 目录和temp应该是当前索引表情定位到具体表情属性,返回到上层函数通过getCount()获取子表情,如骰子应该是6个





com.tencent.mm.bz.a.n(SourceFile:316)

public final EmojiInfo mo46299n(EmojiInfo emojiInfo) {
boolean z;
AppMethodBeat.m3378i(62615);
if (((C2666h) C2700g.aaW().aaw()).abR()) {
EmojiInfo n = ((C33772d) C2700g.m5024ae(C33772d.class)).getEmojiMgr().mo46299n(emojiInfo);
AppMethodBeat.m3379o(62615);
return n;
}
Bundle bundle = new Bundle(EmojiInfo.class.getClassLoader());
bundle.putParcelable("emoji", emojiInfo);
Bundle call = C8960ah.getContext().getContentResolver().call(Uri.parse("content://com.tencent.mm.storage.provider.emotion/"), "getRandomEmoji", null, bundle);
if (call == null) {
C8953ab.m13552e("MicroMsg.EmotionStorageResolver", "[getRandomEmoji] bunndle is null! ");
AppMethodBeat.m3379o(62615);
return null;
}
call.setClassLoader(EmojiInfo.class.getClassLoader());
if (call.containsKey("data")) {
EmojiInfo emojiInfo2 = (EmojiInfo) call.getParcelable("data");
AppMethodBeat.m3379o(62615);
return emojiInfo2;
}
String str = "MicroMsg.EmotionStorageResolver";
String str2 = "[getRandomEmoji] bundle is null?";
Object[] objArr = new Object[1];
if (call == null) {
z = true;
} else {
z = false;
}
objArr[0] = Boolean.valueOf(z);
C8953ab.m13553e(str, str2, objArr);
AppMethodBeat.m3379o(62615);
return null;
}



我们正常情况下直接走第一个if通过query查询数据库得到 emojiInfo对象

而下面通过bundle传递emojiInfo对象,通过ContentProvider的 getRandomEmoji也获得了一个emojiInfo对象返回,应该是另一个实现random筛子的机制,熟悉四大组件ContentProvider的同学很容易就能看懂,这里我们不在深入跟踪了




public final void mo46720a(Context context, int i, C32002y yVar) {
EmojiInfo emojiInfo;
String string;
AppMethodBeat.m3378i(178705);
C0748k.m2427m(context, "context");
C8953ab.m13556i(C32114f.TAG, "onClick: " + i + ", " + yVar);
if (yVar == null) {
AppMethodBeat.m3379o(178705);
return;
}
switch (yVar.type) {
case 0:
C31981h hVar = (C31981h) yVar;
EmojiInfo emojiInfo2 = hVar.fja;
EmojiInfo emojiInfo3 = hVar.fja;
if (emojiInfo3.getGroup() == EmojiGroupInfo.BmT) {
C2658a ae = C2700g.m5024ae(C33772d.class);
C0748k.m2426l(ae, "MMKernel.plugin(IPluginEmoji::class.java)");
EmojiInfo n = ((C33772d) ae).getProvider().mo46299n(emojiInfo3);
C0748k.m2426l(n, "MMKernel.plugin(IPluginE…er.getRandomEmoji(toSend)");
emojiInfo = n;
} else {
emojiInfo = emojiInfo3;
}
C32015d.m54248Yj().mo46658b(i, emojiInfo2.field_md5, "", emojiInfo2.field_designerID, emojiInfo2.field_activityid);
C38218j jVar = this.fmq;
if (jVar != null) {
jVar.mo36093y(emojiInfo);
AppMethodBeat.m3379o(178705);
return;
}
AppMethodBeat.m3379o(178705);
return;
//后面的case与表情无关,省略
}




这个函数我们直接进入yVar.type =0的case0执行,看他的字符串也很容易看出来 这里可以简单看一下C31981h

(com.tencent.mm.emoji.a.a.h)类的数据结构,

通过hVar.fja属性取出EmojiInfo赋值给emojiInfo2和emojiInfo3

如果emojiInfo3.getGroup() == EmojiGroupInfo.BmT,猜测是判断是否属于筛子或者猜拳表情组

如果属于,调用mo46299n()获取emojiInfo,如果不属于,直接赋值

猜测C32015d.m54248Yj().mo46658b(i, emojiInfo2.field_md5, "", emojiInfo2.field_designerID, emojiInfo2.field_activityid)这一句功能是在界面上显示出EmojiInfo图像,如果是筛子,就是变动的筛子画面

下一句jVar.mo36093y(emojiInfo)显示出固定点数的EmojiInfo图像,然后返回

再往上就是onclick方法了



结尾,我们顺序梳理一下流程
1.系统函数调用,略过
2.点击触发com.tencent.mm.emoji.panel.a.q$1.onClick方法,内部调用mo46720a(com.tencent.mm.emoji.panel.a.d.a)方法
3.进入mo46720a,yVar.type=0直接进入case0,判断表情属于EmojiGroupInfo.BmT组后,调用mo46299n(com.tencent.mm.bz.a.n)方法4.进入mo46299n(com.tencent.mm.bz.a.n)方法,进入第一个if分支,调用mo46299n(com.tencent.mm.plugin.emoji.e.f.n)方法
5.进入mo46299n(com.tencent.mm.plugin.emoji.e.f.n)方法,通过query查询数据库EmojiInfo表的catalog 目录和temp当前索引表情定位到具体表情属性,存储在Qu里,调用com.tencent.mm.sdk.platformtools.bo.ii
6.进入m13717ii(com.tencent.mm.sdk.platformtools.bo.ii)方法,以当前时间戳为种子,通过random产生随机数


ps:有同学问我,直接hook函数m13717ii就可以控制随机数了,为啥还要分析流程,其实,流程中的每一个函数都可以hook,只不过参数更麻烦一点,熟悉这个流程,熟悉内部机制,我们不但可以模仿实现类似的功能,版本更新后再次逆向也有了思路,还有其他的好处,下一篇简单分析撤回消息的调用流程,我们不见不散。




[招生]科锐逆向工程师培训(3月6日远程教学班首开特惠, 第37期) !

最后于 2020-2-7 11:58 被挤蹭菌衣编辑 ,原因:
最新回复 (1)
挤蹭菌衣 2020-2-15 08:39
2
0
感谢斑竹大大
游客
登录 | 注册 方可回帖
返回