设备要求 已root的Android手机。 注:下面使用的两个微信安装包,com.tencent.mm.apk为旧版本6.5.16,weixin_new.apk为新版本6.7.3 背景 最近在弄一些关于微信的东西,测试过程中,本来打算强行停止后重新启动微信,结果手残点到卸载了。当我重新安装后出现了尴尬的情况,登录的时候,提示微信版本过低,需要安装最新版才能登录。 0 [5 l b2 ^1 s$ q2 [' E0 @- i
但是之前做的一些东西都是基于老版本的微信,所以不能安装新版本,必须想办法在老版本登录才行。
& x: ~3 E! [+ v% f8 T7 S Y操作过程尝试1、替换版本号 最开始的想法是,既然要验证版本,那我就把旧版本的伪装一下,让它变成新版本的试试。 但是,因为没有时间去仔细分析微信是怎么验证的,于是就抱着侥幸心理,写了个xposed模块替换版本号, 一般情况下是通过以下代码获取版本号的: PackageInfo packageInfo = getPackageManager().getPackageInfo("com.tencent.mm",0);int versionCode = packageInfo.versionCode;String versionName = packageInfo.versionName;+ t5 O6 ^( x, I8 } f4 }
所以去hook getPackageInfo方法,将其返回的PackageInfo中的versionCode和versionName替换成新版本的值就行, 但是,由下图可知PackageManager是一个抽象接口, 所以不能直接hook它的getPackageInfo方法,要先获取getPackageManager返回的对象的真实类型,先随便创建一个工程,通过以下代码获取真实的PackageManager类型, Log.v("test", getPackageManager().getClass().toString());2 u: V$ I( I; g" Y, k, g' Y) F! }
. i2 [1 L* K- ~- w& Z' W% T
查看日志,可知真实类型为android.app.ApplicationPackageManager, 然后通过反编译最新版本的微信,获得versionCode和versionName, 最后的hook代码如下: [url=][/url]
# T$ h4 s2 R7 O$ S, M9 _5 wif (loadPackageParam.packageName.equals("com.tencent.mm")){ XposedHelpers.findAndHookMethod( "android.app.ApplicationPackageManager", loadPackageParam.classLoader, "getPackageInfo", String.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (param.args[0].equals("com.tencent.mm")) { PackageInfo packageInfo = (PackageInfo) param.getResult(); packageInfo.versionName = "6.7.3"; packageInfo.versionCode = 1360; } } });}[url=][/url]
: ^1 b; [0 B, \4 _& p
# U( p: w v6 r. e- V' h" y% k6 Q1 _! V) B8 N
编译安装后重启,一边输入帐号密码,一边祈祷,结果还是提示版本过低,,
0 n$ a6 v+ z4 n尝试2、pm uninstall -k 命令 想着之前旧版本的都能正常使用,说明只要有登录数据,就可以使用旧版本的,所以想着先用新版本的把帐号登录,然后使用 pm uninstall -k 命令,卸载应用但保留数据,然后安装旧版本的。 先安装一个新版本的微信, 登录帐号, 8 p1 Q0 n+ ^7 g
使用pm uninstall -k com.tencent.mm卸载后安装旧版本的微信, 结果打开还是不行,还是提示版本过低, - d1 ]6 V+ M# \: I- t% E
于是怀疑卸载的时候数据被一起删了,根本就没有保留,再次执行pm uninstall -k com.tencent.mm,查看data目录,微信的目录已经不存在了。 尝试3、adb install -r 命令 同样先用新版本的把帐号登录,然后使用 adb install -r 命令强制安装,结果还是不行,不能安装比当前版本低的,,, . d j0 }! X9 [) y) |6 P0 @
尝试4、替换安装目录 既然用 pm uninstall -k 卸载时保留数据不行,那么就尝试手动替换。 首先安装低版本的微信,把安装目录复制一份,然后卸载。 再安装新版本的微信,并登录帐号。 用之前保存的旧版本安装目录替换新版本的安装目录,然后重启手机。 重启后提示登录错误,重新输入密码即可, 不知道为什么,这个手机在我写这篇博文的时候,关于微信的页面还是新版本的版本号,而我之前弄的另一个手机就是真实的旧版本号, 但是这个手机系统设置中微信的版本号已经变成旧版本的了,也不影响使用。 另一个手机的, / U, V% H2 |# }" h4 ^, k# S1 ]
自动化程序[url=][/url]
7 z! `* A. D- P$ T# k" i8 G, c% _$ gpackage com.example.wxreversion;6 u+ j1 I% j! H9 _; Y9 w/ T; F& P7 n
, {8 A0 _# E+ J* u( o) y
import android.os.Handler;9 k( M: ^9 ?4 f2 W/ @+ I z
import android.support.v7.app.AppCompatActivity; S3 k: r2 I; v6 {" j
import android.os.Bundle;/ ~) F. T9 M! k+ ^' Y
import android.text.method.ScrollingMovementMethod;
( m' @" D4 B/ n; c* g( Cimport android.util.Log;2 O, d( o. m$ e: t) Y
import android.view.View;
2 r5 ~+ O& L9 B. f# F" Q, ]import android.widget.Button;
; m( I* k0 u L; p( j2 C$ @# H6 fimport android.widget.TextView;* _3 K5 P; T4 f+ B2 j, b2 {& M+ s, ?/ ~
" y+ @6 n3 Z4 D8 z$ X) F7 s) M
import java.io.File;% K( K. O1 G* y j3 C/ X& f: D1 o4 D
3 I" v5 a( s4 y0 |: d; _! P! }public class MainActivity extends AppCompatActivity' i6 s$ [ h" D# L
{
) m- F3 L i) u Handler handler;5 S9 S% O8 m, Q" n1 D; o$ _9 o
TextView textView;
- R T$ a _5 J' p0 N8 y
. m( x7 N# G' _0 C& d
! K" a" \ c' A- J private void LogV(String s)- b" H. U5 u( M' L# n
{3 _6 X* o7 `4 L3 f, N. m
Log.v("test", s);2 D" j K" f7 Q; e# |+ g: e
}
4 G L: c! c" b+ I3 R) Q3 r3 K$ ^+ s8 M# C5 L I+ b4 {
private void textAppend(final String s)
, S( n: e: h6 p$ h( P" ^/ U: l6 R {2 F4 u. P" s+ u# D* j
LogV("textAppend:" + s);
! a/ S) l9 F& n! } handler.post(new Runnable(): E) _* M) o% ~- i$ J- Y) J$ Y
{0 }) U3 X6 c" q: P" z% n
@Override
- d$ C- P* h0 h% L" g' k public void run()% m2 x$ n2 v# h# G# t7 L
{
$ W6 w; L5 K7 }" @* ?3 _& \ LogV("run:" + s);
# N, r; A9 a3 r$ T* v1 ~
6 l1 r8 N$ o4 h8 T textView.setText(textView.getText() + s);
* D3 e" Z- h# S. V5 C }+ I2 F& b g: F; I3 \& y) c
});+ s* D( K1 G+ z }) @, f
}
* x* K" ^- T H$ d$ g( {5 f0 f6 e$ {* W) V8 V" m( W
private String getPath()
. h! \7 U4 n) s+ T6 K2 C7 d { q3 t2 J0 P3 e6 g$ E
String path = null;
. A, C% K" P6 E0 C& g) a0 k textAppend("-----------------------------\n");8 ?$ v3 A- y+ A- O. d6 x0 j+ A: W- j
% A# a @4 O5 u. B$ |
6 h! l( b. u% {; z if (!ShellUtils.checkRootPermission())' n, X4 N1 D, J0 u( J
{
) ~ v+ M! P$ q textAppend("获取root权限失败,请在设置中授予权限!\n");
; e: C% p7 D) D: }! x {9 D7 [ return path;2 L* e, P8 v* q3 a7 T
}! h n/ l4 E0 j$ b
" ]- X5 U$ }) `3 \& R6 P path = ShellUtils.execCommand("pm path com.tencent.mm", true).successMsg;" y# b0 C) B& b
if (path != null)$ w% J' i+ L# @/ D3 y; s' ]1 t
{$ \3 k& F. Q- D0 q$ t/ M$ m
try4 N- ^0 \$ w- G* \
{( h3 s. a2 V' Q( N
path = path.substring(path.indexOf('/'), path.lastIndexOf('/'));
3 Q7 S8 [" w/ ~: l8 N: y } catch (Throwable throwable)$ |3 @2 |, i4 u9 G, t
{2 a! N8 L0 ^4 |& C5 D
path = null;
1 y) D6 u' S) K) G, V }
) L, ]. ~7 H, h4 H& c) T" l% U8 V. q" } }- c% e3 f$ T2 M- j5 [* `9 U
5 I6 e, s7 ^" z8 M+ U
if (path == null)
9 b, v- \6 }- k e7 n {
/ o5 K7 p& E/ G8 n9 { textAppend("未找到微信安装目录,请先安装!\n");2 ?5 O0 i- F; h! u4 I2 n, a8 g$ ?
} else, K) z2 o% s; p- @7 N4 G0 A7 M
{6 O/ {7 K. W# s& J! w
textAppend("找到安装目录:" + path + "\n");9 T* `6 ~/ w( c5 m% k6 `$ K
}. s+ N; Z5 k; |% K, ^" n8 ?
# x- ~; j/ T& D- W7 S. w$ G0 @: d return path;2 \* Z* z% V: ~" E7 U& {
}
) `4 J' V6 t' J8 f: w. w4 a8 n
) _7 r) a4 e. K private boolean isEnpty(String string)
) V3 `! O7 h* q6 R% m {! r4 f2 G. t7 \3 m" v& q
if (string == null || string.length() == 0)( o0 P- D5 q) D, w# d7 U
{
( I0 ^- H$ H; g0 q& T% ^+ w return true;
: |4 |% W- U% b) J7 A9 _6 c) { }+ w0 V" R8 z% b! ? W
return false;4 I/ J. _5 O0 }6 k: x4 W+ f. b$ Z* ^
}
5 x% e7 a' r6 T5 y) b
' q9 ?9 {8 w' D" O+ f e$ Z private void putResult(ShellUtils.CommandResult result)
# f2 a j, R5 V) r3 F1 y {
' Y$ \* ^% v8 q+ a/ A textAppend("返回码:" + result.result + "\n");5 B6 R8 P& ]5 A- q& @3 Y' n
7 l" D4 x7 O/ Z, D
if (!isEnpty(result.successMsg))3 l0 Y% M9 W6 a, o& N+ i
{. A5 B' }, D# d. W% m. h/ G7 C
textAppend(result.successMsg + "\n");
- F7 Z; {2 X) l. X4 _% ^6 C6 l } else if (!isEnpty(result.errorMsg))( ^+ B( M. U0 s
{
# ~5 n2 x8 ]. A& Z3 }& N/ x$ q l textAppend("错误消息:" + result.errorMsg + "\n");$ C# h& w0 {1 K9 H
}
8 \8 Q* f8 g _% P+ f }
( Q7 z/ k' v0 a6 J( }6 [# J
' U0 N- U- F. ]2 V" \* S! g @Override
0 N2 @/ G* w4 J* j2 { protected void onCreate(Bundle savedInstanceState)5 S& [2 t9 i5 x/ R0 g% T, S- _
{9 J Y+ D2 l+ c2 S. `2 B7 B; f v
super.onCreate(savedInstanceState);
, J, I+ U( z- H' ?: j3 W setContentView(R.layout.activity_main);
" E- W6 e0 h' V handler = new Handler(); s, C A# z8 |8 R1 m# ?$ e) J+ X5 i
) U& g2 H Q: |, r( c$ i0 K
textView = (TextView) findViewById(R.id.textView);! e0 P1 G! U& I6 Z1 k
textView.setMovementMethod(ScrollingMovementMethod.getInstance());' ^8 x* J* ^4 {; e' g% Z
. G3 G( i. u, O: a# e
((Button) findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener()
0 a0 H2 d% U8 R6 ` {
/ n* ]) ~. }: c. g @Override
. I3 v( x! [! _ public void onClick(View v)( i) _$ r+ v& Y' \( d% ?3 Q4 u
{
2 z; {# M) |+ D7 n( }: Y2 g
3 k+ |4 J1 X4 V8 |: V% S+ n6 R9 } new Thread()/ o2 e9 }& A7 y- P' z1 M
{ Y# r' U& n4 `/ C
@Override; A% S9 z! ?! e+ ~$ J1 p6 z( _
public void run(), Z5 i1 ~9 `! @! Q$ a! ~
{
g) N- A0 w4 t. f7 j0 w# O String path = getPath(); O( n% A- O, t8 N) F t: t, N
if (path == null), o# x2 R a5 S6 L: p8 T
{9 T# v5 C1 S6 z
return;
2 _; g, t$ P6 g8 I( l+ n }0 F' q- C, A; S" S
textAppend("正在保存安装目录!\n");
, L7 j& e4 I4 @- B n8 p2 J: A: \ ShellUtils.CommandResult result = ShellUtils.execCommand("cp -af " + path + " /data/local/tmp/com.tencent.mm", true);
7 i- Z% E* z9 l+ Z# q putResult(result);/ m" Q0 H6 P: b p! P
if (!isEnpty(result.errorMsg))
8 | B& t- P9 h9 S+ o0 \- a {* I, U3 I. E2 \0 S, v0 b* U' D) J, r/ d
return;
% ~3 E( T$ A4 s9 i7 b- m$ f9 m& { } A, V) ?6 v2 |- ]5 M: c
# w- K% t* ?% ]) ?) A! { textAppend("正在卸载微信!\n");
& ?/ R& Z3 N9 V7 f result = ShellUtils.execCommand("pm uninstall com.tencent.mm", true);//-k无用
8 |0 x l1 `. S- X1 F* c putResult(result);
% s$ {1 j" n2 `8 D. Y if (!isEnpty(result.errorMsg))
; Z; J% X- T" _$ T, w. ?# ]* N: ^ {
- T$ F: Q* `8 o* B& \ return;- `0 H) y2 B C
}
) H7 E$ ?' v0 ~$ `. f
9 h& l8 w7 F! } textAppend("请安装新版微信,并在登录成功后点击【覆盖安装文件并重启】按钮!\n");% d7 h* U- z0 w* g7 w+ p
}. [( ]- ]' z; K! h8 a' e8 A+ q3 V
}.start();7 f$ { I8 I4 T6 P, _
+ y+ g8 \4 T1 p }
* b/ e' {. G+ R8 [ });6 b, ^$ {8 J( v1 `
4 `* p' h, p: J$ h4 c; y6 u9 t1 {! w) Q9 d3 e. q
((Button) findViewById(R.id.button2)).setOnClickListener(new View.OnClickListener()/ M! \: O+ \# \9 t8 P
{
% P6 ~- \" V4 x @Override
' T: C& F: z, E6 N public void onClick(View v)
! S8 `% Q5 ]. j; K- H! v {
$ S5 G9 n) M% L5 x) i& g5 w: U8 y
8 V: i, \& q" F$ q4 J m, h new Thread()
8 A" V8 n6 d9 V- U% a* | {
& G2 U0 @+ f4 l1 T6 i4 B. s @Override
9 M. a% L" x r public void run()
4 I- E; D0 S: \5 W {$ f B+ @% N- \4 N; V: @$ Z5 ?$ M
String path = getPath();$ b$ a6 X* h# s, B
if (path == null), T% l0 o( Q' a2 f% ]/ }
{: @7 ?4 j: O, J( p: w' J" f
return;
# f/ I8 v, b/ B( n2 P& G( W9 G }- f9 { C. g7 K
9 s4 d7 N6 N' Q1 h# c! [: r+ | textAppend("正在检查是否有备份的安装文件!\n");
+ g- n2 L* N2 c3 { if (!new File("/data/local/tmp/com.tencent.mm").exists())
3 y$ y! Z0 r/ ` w: |9 s( d. l {9 w4 |# w4 _, N; U, U
textAppend("不存在备份的安装文件,请先安装低版本微信后点击【覆盖安装文件并重启】按钮!\n");
( w+ P. K7 s9 \2 v. q return;" S( I0 N! e- L/ r& W0 j( v
}% R2 D n! b0 v
& I0 K5 h- I0 D$ Q9 Z textAppend("已有备份的安装文件,正在删除当前的安装目录!\n");
W/ J! @; |! s5 z$ F2 B ShellUtils.CommandResult result = ShellUtils.execCommand("rm -rf " + path + "/*", true);, J0 e$ u8 W! ^3 B6 v7 O
putResult(result);
. c0 \3 c( k7 D* u! m$ k) H" C if (!isEnpty(result.errorMsg))2 P& X. W3 t( Q2 Q0 `/ Z {7 C
{3 e3 r# Y& s; b" {6 W
return;
7 ?' U+ f+ B; G+ \& K8 o5 ?/ U }- S( q2 u# C+ X* e' w4 t6 w
2 A* \( k/ b: _) o( X9 H0 d$ y9 s4 H$ W- \- l
textAppend("正在覆盖安装目录!\n");2 a( w$ M" E9 f$ z3 G, @
result = ShellUtils.execCommand("cp -af /data/local/tmp/com.tencent.mm/* " + path, true);7 E, C1 y9 K% s
ShellUtils.execCommand("rm -rf /data/local/tmp/com.tencent.mm", true);- J* I# s) t, Y3 ]% \+ c) S6 y/ ?1 h
putResult(result);$ X6 g- q3 O) K* Q& p5 p
if (!isEnpty(result.errorMsg))
0 C2 |4 f+ _, v. O# x. U/ \ {+ E) f: o8 \) T# u
return;+ T1 {6 H4 ~4 [' o2 ?2 t2 G# Q
}5 Q9 M4 v0 d: Y+ g, r, Z
6 p" T0 D* C4 e0 r4 ~) w/ w+ k* t% b textAppend("系统将在10秒后重启!\n");; A: J. K* t/ K& E0 _& T
+ O: b; a+ r3 m9 J
9 G- L5 O- c0 A% ? y try- W. R( Z- c/ I- ^) g
{
5 @ r5 S. H* ]! R sleep(10 * 1000); W ~3 m1 Z7 |- d+ P- q
handler.post(new Runnable()+ `5 U) S8 o! a; R2 `/ B
{: Q& g6 l R+ Z5 k; n# U7 m$ d( _; c" O
@Override
% M/ I, F6 @. s/ b$ w2 J6 ? public void run()+ d$ G/ f0 e; A) q; G' r$ }% t8 S
{) J# x+ x% V B; d* y
ShellUtils.execCommand("reboot", true);
8 w/ P+ ` C, N, r) ^8 g, x }
4 l# Q( u0 s$ t( O: y });
0 w& u ^' o) R1 Y' y } catch (InterruptedException e)
- }" I) G7 F! ] {
' f! C' z; X/ Y2 f& S. } e.printStackTrace();
; }* h# @; ~( |; @% [( k+ _ }4 C+ f- s2 c6 E2 S4 b5 e/ e
}% R3 I X% `7 d. ^6 L0 r1 _
}.start();) |9 X% ?+ a: G' ]
# j* _- ?- r' B, ` }" P# Z. |; c7 K3 t1 z/ H; R5 {4 s
});
H6 A( f0 D5 w @, ~8 A4 o% Q, p7 g! s( S
textView.append("-----------------------------\n");+ q/ r3 D9 W; r. o) }+ B
textView.append("该应用需要root权限!\n");' X& T* {( W& e) X# S
if (ShellUtils.checkRootPermission())
+ H/ @ @; I/ F6 Y$ q3 h' X {9 q# i; {9 Y( \2 {; N' t, b
textView.append("获取root权限成功!\n");- V0 [4 C: Y) F
} else, P, k- L' H6 u% z L0 B2 `) s7 K
{
2 v5 T" B$ ?' j9 o textView.append("获取root权限失败,请在设置中授予权限!\n");
0 q/ W1 h2 H# u+ w5 R5 O } i+ O& A/ @, [# S: a
, O6 Z$ V% S3 \$ ^4 [% [+ h
* r4 j/ S7 @& U3 G9 w
}
$ V- M: p) `# m! u( s0 V# t}[url=][/url]6 L2 K2 S, O0 d4 W6 P& y* ~
# H: t; J3 O! C1 D- r$ g. y; Y4 b
5 x' o$ l b1 A8 `5 A! y
运行如下: 先安装旧版本微信, 运行该程序,点击左边的【保存安装文件并卸载】按钮, 安装新版本微信并登录, 点击右边的【覆盖安装文件并重启】按钮, 重启后重新输密码登录即可。
5 Z+ h% ?* f9 p+ s9 u# @' {$ Y安装包在release目录中
6 _+ t7 r! |0 ]( _ |