首篇作为开始,先讲讲简单的反编译。反编译通常有几种目的:互相学习、借来用用、嘿嘿(干你,又分为小干干类似微信红包,和大干干改别人的apk帮他上架)。
因为没带kvm回来,mbpr屏幕太小,所以下文环境为windows。
一、反编译
让我们从实战开始,先实践一下怎么去反编译一个apk,看看某些功能的实现。毕竟没有实践的原理都是耍流氓。
这里我们保留互相学习的心态,所以是友善的第一种目的,嘻嘻。
1、准备
工具
- apktool
- jadx(新一代反编译大杀器)
安装包
手机qq 6.2.3 (目标就设定为看看口令红包是怎么做的吧)
2、apktool的使用
首先确保你安装了java 7或以上,并能直接在命令行调用java。
- 下载 windows用wrapper脚本 (mac使用这个)。
- 下载最新的apktool。
- 重命名上面下载的apktool jar文件为apktool.jar。
- 把apktool.bat和apktool.jar放在同一个目录下,并加入path环境变量。
- 现在你可以直接通过命令行调用apktool并查看使用方式了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
apktool v2. 0.3 - a tool for reengineering android apk files
with smali v2. 1.0 and baksmali v2. 1.0
usage: apktool
-advance,--advanced prints advance information.
-version,--version prints the version then exits
usage: apktool if |install-framework [options] <framework.apk>
-p,--frame-path <dir> stores framework files into <dir>.
-t,--tag <tag> tag frameworks using <tag>.
usage: apktool d[ecode] [options] <file_apk>
-f,--force force delete destination directory.
-o,--output <dir> the name of folder that gets written. default is apk.out
-p,--frame-path <dir> uses framework files located in <dir>.
-r,--no-res do not decode resources.
-s,--no-src do not decode sources.
-t,--frame-tag <tag> uses framework files tagged by <tag>.
usage: apktool b[uild] [options] <app_path>
-f,--force-all skip changes detection and build all files.
-o,--output <dir> the name of apk that gets written. default is dist/name.apk
-p,--frame-path <dir> uses framework files located in <dir>.
|
3、jadx的使用
- 下载jadx。
- 运行gradlew dist编译。
- jadx\jadx-gui\build\install\jadx-gui\bin下有可运行的gui
- jadx\jadx-cli\build\install\jadx\bin是命令行程序
- 可以都加入path环境变量,以便直接命令行调用。
4、分析apk文件
first try
虽然我们可以用jadx直接打开apk傻瓜式地去查看源代码,但是为了更理解反编译的过程和工作原理,以便以后在碰到一些问题(比如加壳)的时候可以自己解决,这里我们先装逼一下,使用apktool去进行分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
d:\dev\reverse>apktool d -o qq mobileqq_android_6. 2.3 .apk
i: using apktool 2.0 . 3 on mobileqq_android_6. 2.3 .apk
i: loading resource table...
exception in thread "main" brut.androlib.androlibexception: multiple res specs: attr/name
at brut.androlib.res.data.restypespec.addresspec(restypespec.java: 78 )
at brut.androlib.res.decoder.arscdecoder.readentry(arscdecoder.java: 248 )
at brut.androlib.res.decoder.arscdecoder.readtabletype(arscdecoder.java: 212 )
at brut.androlib.res.decoder.arscdecoder.readtabletypespec(arscdecoder.java: 154 )
at brut.androlib.res.decoder.arscdecoder.readtablepackage(arscdecoder.java: 116 )
at brut.androlib.res.decoder.arscdecoder.readtableheader(arscdecoder.java: 78 )
at brut.androlib.res.decoder.arscdecoder.decode(arscdecoder.java: 47 )
at brut.androlib.res.androlibresources.getrespackagesfromapk(androlibresources.java: 544 )
at brut.androlib.res.androlibresources.loadmainpkg(androlibresources.java: 63 )
at brut.androlib.res.androlibresources.getrestable(androlibresources.java: 55 )
at brut.androlib.androlib.getrestable(androlib.java: 66 )
at brut.androlib.apkdecoder.settargetsdkversion(apkdecoder.java: 198 )
at brut.androlib.apkdecoder.decode(apkdecoder.java: 96 )
at brut.apktool.main.cmddecode(main.java: 165 )
at brut.apktool.main.main(main.java: 81 )
|
竟然报错了,multiple res specs: attr/name,在网上找了找资料,应该是腾讯利用apktool的bug去进行了加壳,除了添加同名id外还做了若干加固,好,你狠,我们下篇文章针对腾讯的壳来分析并修改apktool,这次先用jadx来试试。
second try
如果直接用jadx-gui打开qq的apk,你会发现,卡死了。不错,就是卡死了,因为太大了…
我们打开jadx-gui文件(其实就是个启动的script),加上:
1
|
set java_opts=-server -xms1024m -xmx8192m -xx:permsize=256m -xx:maxpermsize=1024m
|
就跟我们加速as/idea的原理差不多,多给点内存,这样就能顺利地打开了(可能会需要比较久的时间)。
5、字符串大法
为了找到我们的目标,红包,我们首先尝试用字符串搜索大法:在resources -> resources.arsc -> res -> values -> strings.xml找到口令红包对应的
1
|
<string name= "qb_hbdetail_command_word" >口令红包</string>
|
然后crtl+shift+f进行text search,结果…没找到。
我们再使用资源id大法,直接在resources.arsc找到
1
|
0x7f0a0e5a ( 2131365466 ) = string.qb_hbdetail_command_word: 口令红包
|
再搜,好,你狠。。。还是没有。是在下输了。
6、类/函数名大法
我们再祭出第二大杀器,类/函数/变量名大法搜索大法。
通常类名符合的范围更小,所以先只使用class。
试试看红包的英语:redpacket(类名命名所以r和p大写)
ok,我们找到了十几条,开始逐一排查,第一条redpacketinfo点进去一看就是个包含了各种field的ui用的vo类,跳过,再看下一个,从包名com.tencent.mobileqq.data看上去,似乎有戏,qqwalletredpacketmsg:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.tencent.mobileqq.data;
import android.text.textutils;
import com.tencent.mobileqq.hotpatch.notverifyclass;
import cooperation.qzone.util.wifidash;
import java.io.ioexception;
import java.io.objectinput;
import java.io.objectoutput;
import tencent.im.msg.im_msg_body.qqwalletaiobody;
/* compiled from: proguard */
public class qqwalletredpacketmsg {
public string authkey;
private int channelid;
public int conftype;
public qqwallettransfermsgelem elem;
public string envelopename;
public int envelopeid;
public boolean isopened;
public int msgfrom;
public string redpacketid;
public int redtype;
private int resend;
public int templateid;
|
...串行化、读写、构建方法等,可以无视。
从field名来看,这里还是比较可疑的,猜测redtype是不是描述红包类型的。
我们再次使用关键词redtype进行搜索,这次选择code,只进行代码内搜索,结果却发现貌似不对,找到相关的字符串是”查看详情”,貌似是描述红包领取状态的。
不放弃,继续抓住qqwalletredpacketmsg这个类进行搜索,看看是不是有外面包着这个类的class,搜索qqwalletredpacketmsg,范围使用field,排除掉类本身外,只有唯一的结果:messageforqqwalletmsg:
1
2
3
4
5
6
7
8
9
10
11
|
public class messageforqqwalletmsg extends chatmessage {
// 哦哦?command_redpacket?口令红包
public static final int msg_type_command_redpacket = 6 ;
public static final int msg_type_common_redpacket = 2 ;
public static final int msg_type_common_theme_redpacket = 4 ;
public static final int msg_type_individual_redpacket = 2001 ;
public static final int msg_type_lucy_redpacket = 3 ;
public static final int msg_type_lucy_theme_redpacket = 5 ;
public static final int msg_type_public_account_redpacket = 2002 ;
public static final int msg_type_transfer = 1 ;
...
|
我们找到了一个常量字段,目测就是这个描述了是否是口令红包了。在该类搜索此字段还找到
1
2
3
4
5
6
|
public static boolean iscommandredpacketmsg(messagerecord messagerecord) {
if (messagerecord != null && (messagerecord instanceof messageforqqwalletmsg) && ((messageforqqwalletmsg) messagerecord).messagetype == msg_type_command_redpacket) {
return true ;
}
return false ;
}
|
果然,我们再接着分别查找msg_type_command_redpacket和iscommandredpacketmsg,结果只在troopmessagemanager里面找到了一段没成功反编译的代码中对方法iscommandredpacketmsg的引用:
1
2
3
|
l_0x0100:
r2 = com.tencent.mobileqq.data.messageforqqwalletmsg.iscommandredpacketmsg(r25);
if (r2 == 0 ) goto l_0x011e;
|
这里如果是口令红包会继续走下去,而如果不是则会跳到l_0x011e。
而从类的名字来看,troopmessagemanager应该是指群消息管理者,应该没错,毕竟红包也是群消息的一种。
于是我们只能耐心地看下去这段神奇的充满goto的代码。晕着看完后大概看到就是各种逻辑判断和调用msgproxyutils.java去处理消息处理逻辑和缓存。然后就没了…好,你屌,是在下输了。我再试试别的。
7、常量大法
常量大法其实也可以算是字符串搜索的一种,只是不去搜索xml里的,而是使用中文转化为unicode后的字符串去进行查找。自行搜索unicode编码转化可以找到online convertor。
口令红包对应的是”\u53e3\u4ee4\u7ea2\u5305”:
找到2个类共3处代码引用。
最后那个类的起名有点耐人寻味,passwdredbagmanager,密码红包管理器,有点意思:
1
2
3
|
public void b(string str) {
((trooptipsmsgmgr) this .f2203a.getmanager( 80 )).a(str, "\u533f\u540d\u4e0d\u80fd\u62a2\u53e3\u4ee4\u7ea2\u5305\u54e6" , netconninfocenter.getservertime(), baseconstants.default_quick_heartbeat_timeout, f);
}
|
这串unicode转换成中文后是”匿名不能抢口令红包哦”,原来还有这种逻辑,产品经理你真是够了。
这里我们重新从该类的上面看下来,大致扫一扫,发现ondestroy下面有一个方法打的log很神奇:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public long [] m883a(sessioninfo sessioninfo, string str) {
if (qlog.iscolorlevel()) {
qlog.d(f2197a, ( int ) h, "openpasswdredbagbypassword, passwd = " + str);
}
long [] jarr = new long []{ 0 , 0 };
if (sessioninfo == null ) {
return jarr;
}
if (textutils.isempty(str)) {
return jarr;
}
c();
list<string> list = (list) this .f2206a.get(str);
if (list == null || list.isempty()) {
return jarr;
}
passwdredbaginfo passwdredbaginfo;
string str2 = a(sessioninfo.a) + "_" + sessioninfo.f1757a;
for (string str3 : list) {
hashmap hashmap = (hashmap) this .f2209b.get(str3);
if (hashmap != null ) {
passwdredbaginfo = (passwdredbaginfo) hashmap.get(str2);
if (!(passwdredbaginfo == null || a(str3))) {
jarr[g] = passwdredbaginfo.a.uint64_creator_uin.get();
if (!b(str3)) {
if (!c(str3)) {
hashmap.put(str2, passwdredbaginfo);
jarr[f] = 1 ;
break ;
}
jarr[f] = 3 ;
} else {
jarr[f] = 2 ;
}
}
}
}
passwdredbaginfo = null ;
if (passwdredbaginfo == null ) {
return jarr;
}
b(sessioninfo.a, sessioninfo.f1757a, passwdredbaginfo.a.string_redbag_id.get().tostringutf8());
a(sessioninfo, passwdredbaginfo);
return jarr;
}
|
iscolorlevel目测是某种debug用的tag,可能某些环境下部分用户会打开,而从log结合我们平时打log习惯来看,这个方法应该就叫openpasswdredbagbypassword了,第二个参数就是password。终于找到了。看一下逻辑大致是从外面load进来所有红包信息到本类的各种hashmap和list(有一个tag,只会加载第一次,本类多个方法都会调用这个方法),然后根据password从里面找到对应passwdredbaginfo,设置result tag,然后调用了
1
2
|
b(sessioninfo.a, sessioninfo.f1757a, passwdredbaginfo.a.string_redbag_id.get().tostringutf8());
a(sessioninfo, passwdredbaginfo);
|
我们先不急看这两个方法是做什么的。再往下看下一个方法,直接就有:
1
2
3
4
|
public long [] b(sessioninfo sessioninfo, string str) {
if (qlog.iscolorlevel()) {
qlog.d(f2197a, ( int ) h, "openpasswdredbagbyid, id = " + str);
}
|
openpasswdredbagbyid用id打开红包,猜测该id就是我们最早看到的结构里的redpacketid字段。
而该方法同样调用了
1
2
|
b(sessioninfo.a, sessioninfo.f1757a, str);
a(sessioninfo, passwdredbaginfo);
|
看看这两个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public void a(sessioninfo sessioninfo, passwdredbaginfo passwdredbaginfo) {
if (sessioninfo != null && passwdredbaginfo != null ) {
object obj = (sessioninfo.a == 0 || sessioninfo.a == h || sessioninfo.a == action.action_registnewaccount_commitsms || sessioninfo.a == action.action_login) ? g : null ;
string str = sessioninfo.f1757a;
string valueof = string.valueof(passwdredbaginfo.a.uint64_creator_uin.get());
if (obj != null ) {
str = valueof.equals( this .f2213d) ? sessioninfo.f1757a : this .f2213d;
}
jsonobject a = qqwalletmsgitembuilder.a( this .f2203a, sessioninfo, passwdredbaginfo.a.string_redbag_id.get().tostringutf8(), passwdredbaginfo.a.string_authkey.get().tostringutf8(), str, "appid#1344242394|bargainor_id#1000030201|channel#msg" , "graphb" , null );
bundle bundle = new bundle();
bundle.putstring( "json" , a.tostring());
bundle.putstring( "callbacksn" , jbi.a);
intent intent = new intent( this .f2200a, paybridgeactivity. class );
intent.putextras(bundle);
intent.addflags( 268435456 );
intent.putextra( "pay_requestcode" , 5 );
this .f2200a.startactivity(intent);
}
}
public void b( int i, string str, string str2) {
if (!textutils.isempty(str2)) {
hashmap hashmap = (hashmap) this .f2209b.get(str2);
if (hashmap != null ) {
passwdredbaginfo passwdredbaginfo = (passwdredbaginfo) hashmap.get(a(i) + "_" + str);
if (passwdredbaginfo != null && !passwdredbaginfo.f4810a) {
passwdredbaginfo.f4810a = true ;
threadmanager.a( new kmr( this , str2), h, null , true );
}
}
}
}
|
发现第一个方法似乎就直接发请求了,看来只要调用到这里,就是可以领红包了。那最初又是如何来这里的呢?我们搜索对passwdredbagmanager内这两个方法的引用找到basechatpie.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public passwdredbagmanager f25190a;
...
public class enterforsend implements onkeylistener, oneditoractionlistener {
...
// 这里从方法名判断是每次输入点击发送后调用
public boolean oneditoraction(textview textview, int i, keyevent keyevent) {
if (i != basechatpie.dr) {
return false ;
}
string obj = this .a.f25220a.gettext().tostring();
if (obj.length() > 0 ) {
// 调用了外部类的下述方法
long [] a = this .a.a(obj);
// 再进行消息发送
sendmsgparams sendmsgparams = new sendmsgparams();
sendmsgparams.b = this .a.dl;
sendmsgparams.a = this .a.dj;
sendmsgparams.c = this .a.dn;
sendmsgparams.f26863c = this .a.dl;
...
}
return true ;
}
}
// 这里调用了那2个openpasswdredbagxxx方法
public long [] m5613a(string str) {
long [] jarr = null ;
// 非匿名模式才会继续尝试匹配口令红包,原来里里外外都做了判断
if (!anonymouschathelper.a().a( this .f25174a.a)) {
if (textutils.isempty( this .f25269d) || !str.equals( this .f25278e)) {
// 使用密码打开
jarr = this .f25190a.a( this .f25174a, str);
} else {
// 使用redpacketid直接打开
jarr = this .f25190a.b( this .f25174a, this .f25269d);
}
// 无意义的打log打点啥的,华丽丽地无视吧
if (jarr != null && jarr[s] == 1 ) {
this .f25269d = qunuppuploadtask.qunuppappid;
this .f25278e = qunuppuploadtask.qunuppappid;
this .f25228a.sendemptymessage(dz);
if (qlog.iscolorlevel()) {
qlog.d(passwdredbagmanager.a, u, "passwdredbags result[0]=" + jarr[s] + ",result[1]=" + jarr[t] + ",send str=" + str);
}
}
} else if (qlog.iscolorlevel()) {
qlog.d(passwdredbagmanager.a, u, "current is in anonymous, dont search passwdredbags" );
}
return jarr;
}
|
可见每次我们输入消息发送时,都发生了判断,会去查询是不是红包口令,如果是则直接发请求拿红包然后继续,否则直接当做普通消息继续发送。所以如果想要做自动抢红包的话,其实只要直接在收到消息时,调用passwdredbagmanager的open方法即可,连模拟ui、生成请求、发送消息都不用了,我们再也不用昧着良心说口令了。顺便我们还看到了手机qq确实喜欢用activity,这里的红包弹框也是一个单独的activity,而且请求是发送到手q红包那边去的,看来还分业务线。
到此为止我们的目的告一段落,其实继续下去,还可以尝试dump当前activity,用activity名字去查找,或者用hierarchy view看看view id试试。
经过上文的折腾,我们成功反编译了手机qq,并追溯到手机qq红包的数据结构和判断流程。期间经历过数次无用功,但逆向工程正是这么一回事,尤其是静态分析,如果不及时找其他的路,而一路钻牛角尖从一个线索一路去看,很可能会越陷越深,本文的跟踪流程正是不断在坑还小的时候钻出来,然后去找其他的路径,最后才快速地找到了想看的东西。
以上就是本文的全部内容,希望对大家的学习有所帮助。