[toc]
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成品
兄弟,逛油猴看到你的插件
因一些不可说的原因来逛逛你的主站
没有想到你主站做得这么大(虽然有几个板块没有完善)
以上吐槽,以下问题
你搭个这玩意拿什么支撑?
还是只是爱好而已?
不可能一直赔钱搭着就为了爱好吧兄弟?
询问完毕哈哈哈!
期待回复
当作进步的踏板和分享的平台~