Rolly破解

发布于 26 天前  133 次阅读


Rolly是我查看rss订阅的一款App,中间插着一个广告看着很烦,所以闲着没事用了一上午把本地专业版破解了一下,也就有机会水一篇博客了,(终于想起来更新博客了

Rolly介绍(搬运自酷安)

懒得整了,直接放链接吧,2333...:Rolly RSS Reader-实用RSS阅读器

破解分析

使用Rolly会发现他的专业版是用激活码激活,因此打开string.xml,他这个有国际化,找到zh就可以,搜索激活码,一点点分析。这里从“请输入激活码”开始。

public final void onClick(DialogInterface dialogInterface, int i) {
            EditText editText = this.d;
            h.a((Object) editText, "etCode");
            Editable text = editText.getText();
            String obj = text != null ? text.toString() : null;
            if (obj == null || m.b(obj)) {
                Button button = this.c.d;
                if (button != null) {
                    Snackbar.make(button, (int) R.string.please_input_code, 0).setAction(17039370, new a(this)).show();
                } else {
                    h.b("btnActiveCode");
                    throw null;
                }
            } else {
                this.c.a((ProFragment) obj);
            }
        }

Snackbar.make(button, (int) R.string.please_input_code, 0).setAction(17039370, new a(this)).show() 一路点到关键点:

public final void b(String str) {
        if (getContext() != null) {
            if (!g.b.d()) {
                a.a.a.f.e.e.b(getContext());
                return;
            }
            View inflate = getLayoutInflater().inflate(R.layout.panel_input_activation_code, (ViewGroup) null, false);
            EditText editText = (EditText) inflate.findViewById(R.id.etCode);
            TextView textView = (TextView) inflate.findViewById(R.id.txtClear);
            TextView textView2 = (TextView) inflate.findViewById(R.id.txtPaste);
            if (str != null) {
                editText.setText(str);
            }
            textView.setOnClickListener(new c(editText));
            textView2.setOnClickListener(new d(this, editText));
            Context context = getContext();
            if (context != null) {
                new AlertDialog.Builder(context).setTitle(R.string.activation_code).setView(inflate).setPositiveButton(R.string.next, new e(this, editText)).setNegativeButton(17039360, (DialogInterface.OnClickListener) null).show();
            } else {
                h.b();
                throw null;
            }
        }
    }

可以看出来这个对应了输入激活码的弹框

继续点下去:

 public k invoke(CodeResult codeResult) {
            CodeResult codeResult2 = codeResult;
            if (codeResult2 != null) {
                AlertDialog alertDialog = this.d;
                if (alertDialog != null) {
                    alertDialog.cancel();
                }
                if (codeResult2.isSuccess()) {
                    a.a.a.e.a.c.d();
                    a.a.a.f.e.e.a(ProFragment.a(this.c), R.string.done);
                } else {
                    Context context = this.c.getContext();
                    if (context != null) {
                        new AlertDialog.Builder(context).setTitle(17039380).setMessage(a.a.a.f.e.e.a(codeResult2.getCode())).setNegativeButton(17039360, (DialogInterface.OnClickListener) null).setPositiveButton(R.string.retry, new a.a.a.a.b.d.a(this)).show();
                    } else {
                        h.b();
                        throw null;
                    }
                }
                return k.f671a;
            }
            h.a("it");
            throw null;
        }

这是代码无效的提示框,可以在这个地方重试(可以安装正版看一看,方便理解)

这里直接把if (codeResult2.isSuccess()) 改为if (!codeResult2.isSuccess()) ,这里直接把smali的841行的代码改为if-eqz v1, :cond_26

改完这里就可以实现直接跳转到完成。这里我也抓包看过,应该就是返回的json中code的值为1或者0,比较直白。

然后点进去success之后的方法,细心分析能发现进入了一个多线程:

public static final class e implements Runnable {
        public static final e c = new e();

        public final void run() {
            a.a.a.f.e eVar = a.a.a.f.e.e;
            a.a.a.b.a a2 = a.c.a();
            SharedPreferences sharedPreferences = g.f140a;
            if (sharedPreferences != null) {
                String string = sharedPreferences.getString("token", null);
                if (string != null) {
                    DataResult a3 = eVar.m0a((q.b) a2.a(string));
                    if (a3.isSuccess()) {
                        a.c.a((String) a3.getData());
                        return;
                    }
                    return;
                }
                n.q.c.h.b();
                throw null;
            }
            n.q.c.h.b("preferences");
            throw null;
        }
    }

这里抓包code的返回值就是1,点到a3.isSuccess()中的a.c.a((String) a3.getData());

 public final void a(String str) {
        if (str == null) {
            SharedPreferences sharedPreferences = g.f140a;
            if (sharedPreferences != null) {
                sharedPreferences.edit().putLong("expirationTime", -1).apply();
            } else {
                n.q.c.h.b("preferences");
                throw null;
            }
        } else {
            Date parse = a.a.a.f.e.e.c().parse(str);
            Date date = new Date();
            n.q.c.h.a((Object) parse, "date");
            long time = (parse.getTime() - date.getTime()) / ((long) 86400000);
            if (time < ((long) -1)) {
                a.a.a.f.e.e.a(Event.ProExpired, Long.valueOf(Math.abs(time)));
            }
            a.a.a.f.e.e.a(Event.ProInfoChange, (Object) null);
            long time2 = parse.getTime();
            SharedPreferences sharedPreferences2 = g.f140a;
            if (sharedPreferences2 != null) {
                sharedPreferences2.edit().putLong("expirationTime", time2).apply();
            } else {
                n.q.c.h.b("preferences");
                throw null;
            }
        }
    }

看到parse方法str解析成"yyyy-MM-dd HH:mm:ss"的格式。这里上Frida,直接把str改成"2099-12-12 02:30:22",看一哈效果。

Frida代码:

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
Java.perform(function () {
  // Function to hook is defined here
  var MainActivity = Java.use('a.a.a.e.a');
  var functionA = MainActivity.a;
  // Whenever button is clicked
  MainActivity.a.overload("java.lang.String").implementation = function (v) {
    v = "2099-12-12 02:30:22"
    console.log('Done:' + v);
    // Call the original onClick handler
    functionA.call(this, v);

  };
});
"""

process = frida.get_usb_device().attach('com.blend.rolly')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

结果灰常nice:

所以直接修改smali,把str赋值就ojbk了。

P.S. smali代码写错导致卡第一屏,导致我以为有签名校验,其实没有,有被自己感(xiao)动(si)。

附件

这里提供下成品,100天后过期。链接:Rolly成品