spguangz 发表于 2016-1-10 19:32

安卓逆向之给激活码添加一个「口令」

这次要破解的软件叫Stellio Music Player,是一款手机音乐播放器。
它的破解点是激活码。

用到的工具:
用Androidkiller反编译和修改代码,由于激活码是网络验证,所以用Charles抓包分析。
static/image/hrline/4.gif

一、激活码破解篇
本篇的目的:你输入任意激活码都能激活成功。

1.去除自校验
这个软件有签名校验,先去除。
方法是常用的方法了,详见:BT种子搜索1.5.7签名校验破解过程简介
贴一下修改后的代码:

invoke-virtual {p0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;

    move-result-object v0

    const/16 v3, 0x40

    const-string p1, "/sdcard/123.apk"
        //自定义验证包的路径

    invoke-virtual {v0, p1, v3}, Landroid/content/pm/PackageManager;->getPackageArchiveInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;
    //把getPackageInfo改为getPackageArchiveInfo
       
    move-result-object v3

    .line 128
    sget-boolean v0, Lru/stellio/player/Utils/m;->a:Z

    if-nez v0, :cond_3

    iget-object v0, v3, Landroid/content/pm/PackageInfo;->signatures:[Landroid/content/pm/Signature;
    //读取签名
        //省略

2.破解激活码
自校验去掉之后,我们进去看看,发现软件只有10天试用期。
买买买!点击购买,会弹出一个弹框:

如图,它有2种解锁完整版的方式:
a.去购买解锁包解锁
b.用激活码激活解锁
这里选择激活码。

输入任意激活码,会有错误提示:

由于我们要求输入「任意」激活码都能激活成功,所以先去掉格式的限制。

按图索骥,最后在smali里删掉一个跳转即可,比较简单就不贴代码了。

去掉激活码格式的限制后,继续往下走。
由于是联网激活,所以抓包看一下吧。
我输入“呵呵”,然后点击激活。
一方面app上会提示激活码无效,另一方面Charles上显示以下数据:



也就是说,输入的key如果是无效的,发送到服务器上,会验证失败,然后返回error。

先把链接复制下来,搜索看一下。
搜索http://stellio.ru/api/license.php

.method public static a(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z
    .locals 6

    .prologue
    const/4 v5, 0x0

    .line 47
    new-instance v0, Ljava/util/ArrayList;

    invoke-direct {v0}, Ljava/util/ArrayList;-><init>()V

    .line 49
    new-instance v1, Lru/stellio/player/Helpers/i;

    const-string v2, "key"

    invoke-direct {v1, v2, p0}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 50
    new-instance v1, Lru/stellio/player/Helpers/i;

    const-string v2, "android_id"

    invoke-direct {v1, v2, p1}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 51
    new-instance v1, Lru/stellio/player/Helpers/i;

    const-string v2, "imei"

    invoke-static {}, Lru/stellio/player/App;->a()Lru/stellio/player/App;

    move-result-object v3

    invoke-static {v3}, Lru/stellio/player/Utils/k;->i(Landroid/content/Context;)Ljava/lang/String;

    move-result-object v3

    invoke-direct {v1, v2, v3}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 52
    new-instance v1, Lru/stellio/player/Helpers/i;

    const-string v2, "lock"

    invoke-static {}, Lru/stellio/player/MainActivity;->g3()Ljava/lang/String;

    move-result-object v3

    invoke-direct {v1, v2, v3}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 54
    invoke-static {}, Lru/stellio/player/Fragments/SettingsFragment;->d()Landroid/content/SharedPreferences;

    move-result-object v1

    .line 55
    new-instance v2, Lru/stellio/player/Helpers/i;

    const-string v3, "utm_source"

    const-string v4, "utm_source"

    invoke-interface {v1, v4, v5}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v4

    invoke-direct {v2, v3, v4}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 56
    new-instance v2, Lru/stellio/player/Helpers/i;

    const-string v3, "utm_medium"

    const-string v4, "utm_medium"

    invoke-interface {v1, v4, v5}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    invoke-direct {v2, v3, v1}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 58
    if-eqz p2, :cond_0

    .line 59
    new-instance v1, Lru/stellio/player/Helpers/i;

    const-string v2, "bind"

    invoke-direct {v1, v2, p2}, Lru/stellio/player/Helpers/i;-><init>(Ljava/lang/String;Ljava/lang/String;)V

    invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

    .line 61
    :cond_0
    const-string v1, "http://stellio.ru/api/license.php"

    invoke-static {v1, v0}, Lru/stellio/player/Apis/c;->a(Ljava/lang/String;Ljava/util/List;)Ljava/lang/String;

    move-result-object v0

    .line 62
    invoke-static {v0}, Lru/stellio/player/Apis/c;->b(Ljava/lang/String;)Z

    move-result v0

    return v0
.end method
结合刚才抓包的图,看来找到关键的地方了。
由于这个方法是布尔值类型的,我尝试在最后强制令其返回true,果然激活成功了。
不过为了搞清楚来龙去脉,可以再往上一层走。
因它最后调用了b(Ljava/lang/String;)Z,所以去b(Ljava/lang/String;)Z看一下。

.method private static b(Ljava/lang/String;)Z
    .locals 3

    .prologue
    .line 66
    const-string v0, "ok"

    invoke-virtual {v0, p0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_0

    .line 67
    const/4 v0, 0x1

    .line 69
    :goto_0
    return v0

    .line 68
    :cond_0
    const-string v0, "error"

    invoke-virtual {v0, p0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_1

    .line 69
    const/4 v0, 0x0

    goto :goto_0

    .line 71
    :cond_1
    new-instance v0, Ljava/io/IOException;

    new-instance v1, Ljava/lang/StringBuilder;

    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    const-string v2, "Unknown server response "

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-direct {v0, v1}, Ljava/io/IOException;-><init>(Ljava/lang/String;)V

    throw v0
.end method
这时就很清楚了:只有当返回 "ok"时,才激活成功。刚才返回"error"自然是激活失败的。
改法:删掉第一个跳转,这样永远返回真,于是无论是"ok"还是"error"都能激活成功了。


激活前后对比:


至此激活码破解篇结束。
http://www.52pojie.cn/static/image/hrline/4.gif
二、激活码改造篇
本篇的目的:你只能使用我指定的激活码才能激活成功。
本篇的想法来源于最近很火的口令红包:
你要拿我的红包,必须输入我指定的口令。
同理,我们DIY一个「口令」激活码吧!

首先分析本软件的激活流程,以找到适合的切入点。

由于激活码的验证部分已经破解了,所以可考虑在激活码的格式上做文章。

1.隐藏的彩蛋?
按图索骥,找到激活码的格式代码:

.method public static a(Ljava/lang/String;)Z
    .locals 3

    .prologue
    const/4 v1, 0x1

    const/4 v0, 0x0

    .line 48
    invoke-static {p0}, Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z

    move-result v2

    if-eqz v2, :cond_1
    //验证输入是否为空,非空跳到激活码的格式验证,否则返回false
       
    .line 58
    :cond_0
    :goto_0
    return v0

    .line 51
    :cond_1
    const-string v2, "{4}-{4}-{4}-{4}"
    //激活码的正确格式,和错误提示是一样。
          
    invoke-static {v2}, Ljava/util/regex/Pattern;->compile(Ljava/lang/String;)Ljava/util/regex/Pattern;

    move-result-object v2

    .line 53
    invoke-virtual {v2, p0}, Ljava/util/regex/Pattern;->matcher(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;

    move-result-object v2

    invoke-virtual {v2}, Ljava/util/regex/Matcher;->matches()Z

    move-result v2

    if-eqz v2, :cond_2
    //验证格式是否相符,相符则返回true
       
    move v0, v1

    .line 54
    goto :goto_0

    .line 55
    :cond_2
    const-string v2, "appoftheday"

    invoke-virtual {v2, p0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v2

    if-eqz v2, :cond_0
    //虽然格式不相符,但只要输入的是appoftheday,一样返回true!

    move v0, v1

    .line 56
    goto :goto_0
.end method
分析见注解,它既用了matches(相符),又用了equals(相等)。
如果格式相符,或者输入为"appoftheday",就通过激活码格式的验证了。
"appoftheday"作为隐藏要素,它是一枚特殊的激活码,经测试,也是无效的激活码。{:1_903:}

2.不一样的激活码:「口令」激活码
为了达到「口令」的效果,我们去掉matches代码,仅保留equals即可。
比如:

.method public static a(Ljava/lang/String;)Z
    .locals 3

    const-string v0, "吾爱破解"

    invoke-virtual {v0, p0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_0
//只能输入"吾爱破解",否则返回false

    const/4 v0, 0x1

    :goto_0
    return v0
       
    :cond_0
       
    const/4 v0, 0x0

    goto :goto_0
.end method
当然还要把提示改了,否则人家都不知道要输入什么了。
还有上面把激活码的格式限制去掉了,所以也要改回来的。
改造后的激活码如下图。


3.不一样的激活码:「口令」升级版
假如你不想这个「口令」千遍一律的话,也可以再改。
举个例子:别人必须输入手机的imei才能激活成功。
为了省事,首先看有没有哪个方法是描述读取手机imei的,然后我们改为调用该方法即可。
搜索getDeviceId。

.method public static i(Landroid/content/Context;)Ljava/lang/String;
    .locals 1

    .prologue
    .line 409
    const-string v0, "android.permission.READ_PHONE_STATE"

    invoke-static {p0, v0}, Landroid/support/v4/content/a;->a(Landroid/content/Context;Ljava/lang/String;)I

    move-result v0

    if-eqz v0, :cond_0

    .line 410
    const-string v0, ""

    .line 413
    :goto_0
    return-object v0

    .line 412
    :cond_0
    const-string v0, "phone"

    invoke-virtual {p0, v0}, Landroid/content/Context;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;

    move-result-object v0

    check-cast v0, Landroid/telephony/TelephonyManager;

    .line 413
    invoke-virtual {v0}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;

    move-result-object v0

    goto :goto_0
.end method
原来Lru/stellio/player/Utils/k;下的i(Landroid/content/Context;)Ljava/lang/String;就是读取imei的方法。
再搜索Lru/stellio/player/Utils/k;->i(Landroid/content/Context;)Ljava/lang/String; 时发现它已经被调用过了。

const-string v2, "imei"

    invoke-static {}, Lru/stellio/player/App;->a()Lru/stellio/player/App;

    move-result-object v3

    invoke-static {v3}, Lru/stellio/player/Utils/k;->i(Landroid/content/Context;)Ljava/lang/String;

    move-result-object v3
简直天助我也,复制过去改一下就行了:

.method public static a(Ljava/lang/String;)Z
.locals 3

invoke-static {}, Lru/stellio/player/App;->a()Lru/stellio/player/App;

move-result-object v0

invoke-static {v0}, Lru/stellio/player/Utils/k;->i(Landroid/content/Context;)Ljava/lang/String;

move-result-object v0
//调用了读取imei的方法

invoke-virtual {v0, p0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v0

if-eqz v0, :cond_0
//只能输入"手机imei",否则返回false

const/4 v0, 0x1

:goto_0
return v0

:cond_0

const/4 v0, 0x0

goto :goto_0
.end method
同样还要把提示改了,否则人家都不知道要输入什么了。

如你所见,这种「口令」激活码并非真正意义上的激活码,不过效果已经达到了。
至此激活码改造篇结束。
http://www.52pojie.cn/static/image/hrline/4.gif
全文结束。代码贴多了,好像太长了。{:1_907:}
附本文用到的app:
链接: http://pan.baidu.com/s/1dEnjHq5 密码: p7jh
有兴趣也可以研究一下,说不定有更多更有趣的发现哦!{:1_918:}


ly2121 发表于 2016-3-14 07:57

代码竟然未加密~都是裸码~
裸码的APK很容易分析~
这篇比较适合刚入坑的鞋童{:17_1089:}

13929861564 发表于 2016-1-16 12:26

看过楼主很多android破解的帖子,可以发一下你常用android破解工具的下载链接吗?我现在一直用androidkiller和fiddler

消炎止痛膏 发表于 2016-1-10 19:54

沙发啊,必须顶起来,学习了!

就爱玩玩 发表于 2016-1-10 19:54

好详细的教程,我啥时候才能学会啊

寂月 发表于 2016-1-10 19:54

大牛,学习了,本帖已经收藏,另外:我真的很难得抢到一楼

xxxioneAald 发表于 2016-1-10 20:04

楼主推荐一款安卓抓包的软件啊,平时不怎么接触电脑。。。

蓦留 发表于 2016-1-10 20:05

已收藏,学习学习

nuzamana 发表于 2016-1-10 20:12

好详细的教程学习了

52_po_jie 发表于 2016-1-10 20:17

很牛逼啊!

jht168888 发表于 2016-1-10 20:17

嘿嘿 , 谢谢楼主分享

一生情独醉 发表于 2016-1-10 20:19

楼主相当的有才!
页: [1] 2 3 4 5
查看完整版本: 安卓逆向之给激活码添加一个「口令」