逆向一款收費版的開發工具

0、工具介紹

工具的功能展現圖

工具介紹這款輔助開發工具仍是挺有用的,我看應用寶的下載量有幾萬了,當前版本號 3.1.0 。這個應用可以開啓手機上的一些開發中經常使用的設置,應用信息提取,以及反編譯的功能。因此週末的時候研究了一下。應用寶下載地址 。其中我感受比較有用的功能就是 查看Activity的歷史,因此想研究下他的查看Activity的歷史的功能是如何實現的,可是他的查看Activity歷史的功能是收費的,那咱們看一下能不能繞過收費功能。java

一、繞過收費功能的基本思路

這個app在個人角度看來有兩個破解點:android

第一種思路就是硬剛,看下圖,在點擊這些灰色按鈕的時候,普通版這些功能是不可用的,那麼本地在初始化這個界面的時候確定有驗證當前app是否是已經受權了,若是沒有受權就不可用,那麼咱們能夠反轉這個驗證過程理論上來講就能夠實現破解功能的使用了了(而且這個app不須要登錄,那麼驗證過程確定也不會太難。)我大概看了下代碼,這個界面是一個RecyclerView,在初始化的時候會根據受權狀態實例化不一樣的數據對象,可是這個玩意混淆的有點煩,沒再繼續往下跟蹤。json

安全

image.png

第二種破解方案,看下圖,思考一下,這個地方填寫激活碼,若是激活碼正確那麼就能激活,因此說這裏要和服務器進行通訊。而後服務器會驗證激活碼是否是有效,咱們能夠隨便填一個激活碼,而後抓包看一下這個激活動做和服務器通訊的過程。這個網絡協議通常狀況下也就是兩種,一種是TCP協議,一種是HTTP協議。先用Charles抓一下看看能不能抓到。 bash

image.png

我隨便填寫了個 1234567890 看下抓包http的請求和返回結果:服務器

Request:
GET /common/v?c=1234567890&p=cn.trinea.android.developertools&v=310&l=zh_CN&t=1541382122260 HTTP/1.1
//-------------------------華麗分割線---------------------------//
Response:
{
	"code": 11,
	"message": "activationCode length is illegal"
}
複製代碼

看下這個message 它說激活碼過短,那麼多長才算正常長度呢?後面我買了了個激活碼,發現這個激活碼長度爲32個字符,正好是個md5的轉成BigInteger的長度。這是後話。。。網絡

這~~~這個受權的請求和和驗證已經算是很是簡單的了,請求和返回數據都是http協議,並且都是明文,並且請求體沒有校驗參數。這裏猜想一下哈,客戶端拿到這麼個請求結果就能判斷是否是已經激活。理論上來講激活成功和激活失敗的返回的json格式是一致的,固然也不排除會有不一樣的狀況存在。先無論,先按激活成功和失敗的返回結構同樣來猜想,那麼通常咱們用返回的code來判斷。這個code通常爲錯誤碼,並且通常來講0,1,-1 是有很特殊含義的。這裏咱們嘗試用0,1,-1 來替換上面的11 。(這裏只是按照一種正向開發的方式推測)app

那麼怎麼能讓手機收到被修改後的json呢?我用的Charles的URL的映射功能 菜單欄Tools —>Map Local Settings,而後添加一條映射規則,大概長這麼個樣子: 工具

image.png

那個ss.json 文件裏面咱們放以下字符串:post

{
	"code": 0,
	"message": "activationCode length is illegal"
}
複製代碼

其實到這裏呢,這個app就已經破解完了,理論上來講付費功能就可使用了。能這麼作主要是其一本地驗證太過於簡單,並且驗證的地方太少就一次驗證。

二、修改反編譯後的Smali代碼繞過受權驗證

理論上來講全部的發起網絡請求都點擊激活按鈕開始的,那麼咱們就從激活的這個按鈕下手,看下激活按鈕觸發後都進行了什麼操做,以及app是如何處理網絡返回的數據的。

經過ADM 找到,這個激活按鈕的 id,以下圖所示:

image.png

而後去 public.xml 裏面找到 name 爲a1 而且 type 爲id的值 而後在as 裏面全局搜索下id 這個值,發現並很少,一共就是四個,以下圖:

AS中全局搜索 0x7f09001b 的結果

找一下規律,這個16進制的數全局一共可以搜索到四處,雖然這四個在四個不一樣的文件裏面,可是他們對應的變量名都是同樣的都是active,那麼依然是全局搜索:active

Smali中全局搜索 active 的結果

而後咱們就經過這個按鈕id的引用鎖定了這個按鈕的點擊事件,其實尋找點擊事件的方式不少,針對從這個app的角度來講,若是你輸入一個字符,會有一個toast,說「激活碼長度不能小於9位」,而後能夠從這個提示入手,依然能定位到 d.b.a.c.h.b.a 這個類。若是從尋找點擊事件的角度來講的話,經過Xposed或者 經過Android Device Monitor 裏面的 Method Profiling 方法都能獲取到。

//.class public Ld/b/a/c/h/b/a; (d.b.a.c.h.b.a)類裏面
    public void onClick(View view) {
        if (this.g.a()) {
            int id = view.getId();
            if (id == this.c.getId()) {
                d.b.a.c.j.a.a(this.e, "pud", true);
                d.b.a.c.h.a.a.a(getActivity());
            } else if (id == this.b.getId()) {
                dismiss();
            } else if (id == d.b.a.c.h.j.a.active) {    //下面是核心代碼
                Object obj = this.a.getText().toString();
                if (!TextUtils.isEmpty(obj)) {
                    String trim = obj.trim();
                    if (!TextUtils.isEmpty(trim) && trim.length() >= 10) {   //這裏會判斷輸入的字符串長度
                        i b = b();    //關鍵的就是他了
                        if (b == null) {
                            ad.a(this.e, c.payable_is_null, 1);    //這應該是個log
                            CrashReport.postCatchedException(new TrineaUploadException("payable is null in DonationVersionDialogFragment", new NullPointerException()));
                            return;
                        }
                        b.d(trim);    //調用 i 的 d方法也就是 c.b.c 類裏面的d(String str)方法
                        dismiss();
                    }
                }
                ad.b(this.e, c.code_length_tip, Integer.valueOf(9));   //提示激活碼不能小於9位
            }
        }
    }
複製代碼

c.b.c 類裏面的d(String str)方法

若是本地簡單的驗證成功會把字符串發往服務器,網絡請求是這個類。看裏面的網絡成功的回調,這個回調匿名對象對應的Smali 文件爲.class Ld/b/a/c/h/a$1;

網絡請求及其返回

找到 Smali對應的文件,先反轉這個if(找到 Smali裏面的代碼 把if-eqz改爲if-nez),咱們試試能不能破解。測試發現反轉了if就能破解了、而後就沒有必要再分析其餘代碼的含義了。從新打包簽名就可使用了。

注意:apktool重打包會失敗。解決方法看後面

d.b.a.c.h.a$1.smali

處理到這一步,其實已經算是破解了收費功能,可是他裏面還有個激活碼長度限制,那麼這裏購買一個真正的激活碼看看,發現他是32個字符。哈哈32個字符~恩 md5。那這裏其實本身隨機生成一個字符串,md5一下就能夠,而後把咱們的代碼插入到 d.b.a.c.h.a(String,String,b) 方法開始的時候,修改第二個參數。就能夠了。

而後咱們破解這個10的限制,基本流程以下:

  • 這個10 (Smali 裏面搜索 0xa) 有兩個地方驗證到了、一個是:圖形化界面 d.b.a.c.h.b.a 一個是網絡請求類 d.b.a.c.h.a
  • 咱們把驗證長度都改爲1(也就是0x1),這樣只要隨便輸入一個字符串就能驗證成功了
  • 可是好像這樣有bug,也就是本地還有地方用到了這個激活碼,而且對長度有限制
  • 那也好說,咱們發送的時候,把用戶輸入的激活碼md5一下。
  • 而後就能夠啦

上面的難點是如何修改原來的代碼,把攔截用戶輸入的激活碼,修改後再發送給服務器。

咱們在AndroidStudio中建立一個工具類,把包名改爲上面和上面的類同一個包名

package d.b.a.c.h;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Author: liuqiang
 * Date: 2018-11-03
 * Time: 13:42
 * Description:獲取一個字符串的md5的字符串的表現形式相似於:
 * 914674d1b303467d54c0673893a19ab3 個形式
 */
public class bbch {

    public static String getHash(String s) {
        try {

            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(s.getBytes("UTF-8"));
            byte[] md5Array = md5.digest();
            //byte[]一般咱們會轉化爲十六進制的32位長度的字符串
            return new BigInteger(1, md5Array).toString(16);
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            return "1234567890";
        }
    }
}
複製代碼

咱們把這個類經過as編譯成Smali文件,而後在d/b/a/c/h/a類的a方法的開頭處引用以下代碼:

#d.b.a.c.h.a 類裏面的方法
.method public a(Ljava/lang/String;Ljava/lang/String;Ld/b/a/c/b;)V
    .locals 8

    const/4 v0, 0x0
#在這裏引用咱們自定義的工具類
    invoke-static {p2}, Ld/b/a/c/h/bbch;->getHash(Ljava/lang/String;)Ljava/lang/String;
#從而修改第二個參數的值
    move-result-object p2

    .line 57
    #.....省略其餘代碼
.end method
複製代碼

若是翻譯成java代碼以下圖所示

注入本身代碼後的樣子

而後再從新打包簽名,運行app。選擇 使用支付寶購買-->填寫任意激活碼--->點擊激活按鈕。就能使用啦~~~

三、 遇到的幾個錯誤

重打包失敗。這個我還真的不知道啥緣由,不過猜想是由於這個app的資源混淆的問題。即便 使用apktool d -r xxx.apk命令不反編譯資源文件重打包運行以後也是出錯,那麼只好去用Smali/baksmali 去搞了。

具體步驟以下:

//前提條件 會 baksmali/smali 的基本操做
//用zip的方式解壓apk
//把 classes.dex 單獨 搞出來 
//而後運行 baksmali d classes.dex -o out
//而後classes.dex 會被反編譯成 Smali文件存放到 -o 指定的目錄
//修改 out 中的對應文件
//而後把Smali編譯成dex 運行: smali a out/ -o ./out/classes.dex
//而後把out 文件夾中的 classes.dex 覆蓋上面解壓的apk中的dex
//而後刪除原來的簽名文件
//而後用zip壓縮這些文件,重命名apk
//而後簽名這個apk
複製代碼

四、學到了一個混淆操做

咱們知道通常狀況下Activity的子類是不能混淆的,可是呢這句話說的不徹底。確切的說應該是在Manifest文件中註冊的Activity是不能混淆的。由於Manifest文件中要寫一個Activity的class的路徑的字符串。若是原始的類被混淆了,而字符串沒有修改,那麼Android系統在作安全驗證的時候就會找不到Activity,那麼就沒辦法經過安全驗證。可是設想一下若是咱們的繼承關係是這個樣子的:

MainActivity--->BaseActivity ---->Activity
複製代碼

那麼試問,這個BaseActivity是否是能夠混淆,通過驗證這個是能夠混淆的。由於BaseActivity 不須要在Manifest文件中註冊,而MainActivity須要。那麼咱們變通一下。把這個繼承鏈加長

a--->MainActivity--->BaseActivity ---->Activity
複製代碼

這個時候,a 類的實現是這個樣子的:

public class a extends MainActivity {
}
複製代碼

其實a裏面啥也沒有,就是個佔位符,這個a並非混淆生成的,而是咱們原本就把這個類命名爲a。而且在Manifest文件裏面咱們就註冊這個a類。那麼試問這個時候,MainActivity 和 BaseActivity是否是就能夠參與混淆。而這個時候咱們的全部業務依然在MainActivity裏面實現,a僅僅是個看起來像是混淆名字的佔位符而已。。。。

五、總結一下

其實這個app去除受權碼校驗很簡單,經過Charles代理就能夠解決,可是這個app仍是有不少能夠借鑑的地方的,看下知識點:

  • 網絡抓包的工具的使用
  • Charles 映射修改網絡返回的數據
  • 逆向過程當中如何定位點擊事件的實現類
  • Android Device Monitor 的使用
  • AndroidStudio全局搜索功能
  • baksmali/smali 的使用,和重打包的方式
  • 這個應用的Activity「混淆了」,這裏所謂的混淆是一種僞的混淆
  • 如何在原來的代碼中插入本身的代碼

而後就能夠反編譯代碼學習他是如何記錄Activity的歷史的了。

最後

本文的目的只有一個就是學習逆向分析技巧關注Android產品的安全。從分析上來看,混淆仍是頗有必要的,好像這個應用裏面還作了資源混淆。可是這個app沒有作簽名驗證,沒有作網絡加密,http協議用的明文,請求參數沒有作校驗,應用沒有加固。若是咱們想要更好的防禦本身的app能夠從這幾方面下手。若是有人利用本文技術進行非法操做帶來的後果都是操做者本身承擔,和本文以及本文做者沒有任何關係。開發不易若是感受工具好用,請聯繫做者購買。

相關文章
相關標籤/搜索