設備要求
已root的Android手機。
注:下面使用的兩個微信安裝包,com.tencent.mm.apk為舊版本6.5.16,weixin_new.apk為新版本6.7.3
背景
最近在弄一些關於微信的東西,測試過程中,本來打算強行停止后重新啟動微信,結果手殘點到卸載了。當我重新安裝后出現了尷尬的情況,登錄的時候,提示微信版本過低,需要安裝最新版才能登錄。
但是之前做的一些東西都是基於老版本的微信,所以不能安裝新版本,必須想辦法在老版本登錄才行。
操作過程
嘗試1、替換版本號
最開始的想法是,既然要驗證版本,那我就把舊版本的偽裝一下,讓它變成新版本的試試。
但是,因為沒有時間去仔細分析微信是怎么驗證的,於是就抱着僥幸心理,寫了個xposed模塊替換版本號,
一般情況下是通過以下代碼獲取版本號的:
PackageInfo packageInfo = getPackageManager().getPackageInfo("com.tencent.mm",0); int versionCode = packageInfo.versionCode; String versionName = packageInfo.versionName;
所以去hook getPackageInfo方法,將其返回的PackageInfo中的versionCode和versionName替換成新版本的值就行,
但是,由下圖可知PackageManager是一個抽象接口,
所以不能直接hook它的getPackageInfo方法,要先獲取getPackageManager返回的對象的真實類型,先隨便創建一個工程,通過以下代碼獲取真實的PackageManager類型,
Log.v("test", getPackageManager().getClass().toString());
查看日志,可知真實類型為android.app.ApplicationPackageManager,
然后通過反編譯最新版本的微信,獲得versionCode和versionName,
最后的hook代碼如下:
if (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; } } }); }
編譯安裝后重啟,一邊輸入帳號密碼,一邊祈禱,結果還是提示版本過低,,
嘗試2、pm uninstall -k 命令
想着之前舊版本的都能正常使用,說明只要有登錄數據,就可以使用舊版本的,所以想着先用新版本的把帳號登錄,然后使用 pm uninstall -k 命令,卸載應用但保留數據,然后安裝舊版本的。
先安裝一個新版本的微信,
登錄帳號,
使用pm uninstall -k com.tencent.mm卸載后安裝舊版本的微信,
結果打開還是不行,還是提示版本過低,
於是懷疑卸載的時候數據被一起刪了,根本就沒有保留,再次執行pm uninstall -k com.tencent.mm,查看data目錄,微信的目錄已經不存在了。
嘗試3、adb install -r 命令
同樣先用新版本的把帳號登錄,然后使用 adb install -r 命令強制安裝,結果還是不行,不能安裝比當前版本低的,,,
嘗試4、替換安裝目錄
既然用 pm uninstall -k 卸載時保留數據不行,那么就嘗試手動替換。
首先安裝低版本的微信,把安裝目錄復制一份,然后卸載。
再安裝新版本的微信,並登錄帳號。
用之前保存的舊版本安裝目錄替換新版本的安裝目錄,然后重啟手機。
重啟后提示登錄錯誤,重新輸入密碼即可,
不知道為什么,這個手機在我寫這篇博文的時候,關於微信的頁面還是新版本的版本號,而我之前弄的另一個手機就是真實的舊版本號,
但是這個手機系統設置中微信的版本號已經變成舊版本的了,也不影響使用。
另一個手機的,
自動化程序
把之前替換的過程寫了個自動替換的程序,代碼如下,其中使用的ShellUtils在之前的一篇【Android手機插上usb能充電但不能識別的一種解決方法】能找到:
package com.example.wxreversion;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.File;
public class MainActivity extends AppCompatActivity
{
Handler handler;
TextView textView;
private void LogV(String s)
{
Log.v("test", s);
}
private void textAppend(final String s)
{
LogV("textAppend:" + s);
handler.post(new Runnable()
{
@Override
public void run()
{
LogV("run:" + s);
textView.setText(textView.getText() + s);
}
});
}
private String getPath()
{
String path = null;
textAppend("-----------------------------\n");
if (!ShellUtils.checkRootPermission())
{
textAppend("獲取root權限失敗,請在設置中授予權限!\n");
return path;
}
path = ShellUtils.execCommand("pm path com.tencent.mm", true).successMsg;
if (path != null)
{
try
{
path = path.substring(path.indexOf('/'), path.lastIndexOf('/'));
} catch (Throwable throwable)
{
path = null;
}
}
if (path == null)
{
textAppend("未找到微信安裝目錄,請先安裝!\n");
} else
{
textAppend("找到安裝目錄:" + path + "\n");
}
return path;
}
private boolean isEnpty(String string)
{
if (string == null || string.length() == 0)
{
return true;
}
return false;
}
private void putResult(ShellUtils.CommandResult result)
{
textAppend("返回碼:" + result.result + "\n");
if (!isEnpty(result.successMsg))
{
textAppend(result.successMsg + "\n");
} else if (!isEnpty(result.errorMsg))
{
textAppend("錯誤消息:" + result.errorMsg + "\n");
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
textView = (TextView) findViewById(R.id.textView);
textView.setMovementMethod(ScrollingMovementMethod.getInstance());
((Button) findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
new Thread()
{
@Override
public void run()
{
String path = getPath();
if (path == null)
{
return;
}
textAppend("正在保存安裝目錄!\n");
ShellUtils.CommandResult result = ShellUtils.execCommand("cp -af " + path + " /data/local/tmp/com.tencent.mm", true);
putResult(result);
if (!isEnpty(result.errorMsg))
{
return;
}
textAppend("正在卸載微信!\n");
result = ShellUtils.execCommand("pm uninstall com.tencent.mm", true);//-k無用
putResult(result);
if (!isEnpty(result.errorMsg))
{
return;
}
textAppend("請安裝新版微信,並在登錄成功后點擊【覆蓋安裝文件並重啟】按鈕!\n");
}
}.start();
}
});
((Button) findViewById(R.id.button2)).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
new Thread()
{
@Override
public void run()
{
String path = getPath();
if (path == null)
{
return;
}
textAppend("正在檢查是否有備份的安裝文件!\n");
if (!new File("/data/local/tmp/com.tencent.mm").exists())
{
textAppend("不存在備份的安裝文件,請先安裝低版本微信后點擊【覆蓋安裝文件並重啟】按鈕!\n");
return;
}
textAppend("已有備份的安裝文件,正在刪除當前的安裝目錄!\n");
ShellUtils.CommandResult result = ShellUtils.execCommand("rm -rf " + path + "/*", true);
putResult(result);
if (!isEnpty(result.errorMsg))
{
return;
}
textAppend("正在覆蓋安裝目錄!\n");
result = ShellUtils.execCommand("cp -af /data/local/tmp/com.tencent.mm/* " + path, true);
ShellUtils.execCommand("rm -rf /data/local/tmp/com.tencent.mm", true);
putResult(result);
if (!isEnpty(result.errorMsg))
{
return;
}
textAppend("系統將在10秒后重啟!\n");
try
{
sleep(10 * 1000);
handler.post(new Runnable()
{
@Override
public void run()
{
ShellUtils.execCommand("reboot", true);
}
});
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}.start();
}
});
textView.append("-----------------------------\n");
textView.append("該應用需要root權限!\n");
if (ShellUtils.checkRootPermission())
{
textView.append("獲取root權限成功!\n");
} else
{
textView.append("獲取root權限失敗,請在設置中授予權限!\n");
}
}
}
運行如下:
先安裝舊版本微信,
運行該程序,點擊左邊的【保存安裝文件並卸載】按鈕,
安裝新版本微信並登錄,
點擊右邊的【覆蓋安裝文件並重啟】按鈕,
重啟后重新輸密碼登錄即可。
完整代碼 wxreversion.rar
安裝包在release目錄中