新东方在线APP破解版实现——“单点登录破解”
新东方在线APP破解版实现——“单点登录破解”
探索如何实现市面上安卓付费app的单点登录功能:从而实现一人购买多人使用的vip账户
本文不会直接修改源码,所有修改均在线上app直接实现,但不可否认的是身为开发人员,对相关流程的熟悉确实加快了破解进度本文以新东方在线安卓最新版3.2.7为破解对象
一、实现原理分析
1 绕过签名验证,如果有服务端校验还需要找到原签名信息 2 拷贝已登录用户的sid userId等一致的客户端请求头信息
二、实现步骤
- 修改代码重新打包app
- 签名校验绕过测试
- 登录信息复制登录测试
- 修改app代码实现2、3步并重新打包
- 验证是否成功
三、详细实现
3.1 重新打包安装查看
- keytool 生成 keystore文件
keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystore 2. 然后jarsigner签名
jarsigner -keystore demo.keystore -storepass 1234qwer -signedjar signed.apk Koolearn_v3.2.7.apk demo.keystore 3. 然后安装 显然请求是不通的,因为做了签名校验 源码直接用此签名调试 发现 拼接url地址参数值的generateSign方法返回了null
3.2 绕过签名校验
3.2.1代码分析
- 下载so查看工具 IDA6.5,静态分析so文件用 010 Editor,修改so文件16进制码
- 搜索含有关键词“sign" "signature"的方法,找到几个关键方法checkSign() getSign(),generateSign(),观察checkSign,猜测应该有固定的签名字符串,结果竟然没有找到
- 观察generateSign方法,F5查看C代码,发现签名校验只是做了简单的字符串相等比较,参数拼接并未使用该签名字符串,只是看了下chekAppSign返回值是否为true,为false就直接返回并打印日志说签名错误,既然这样,如果把这个签名判断稍微修改一下,岂不是有可能发生奇迹,好的 试一下,准备修改so文件
3.2.2 反编译libgetParams-jni.so文件 并修改判断逻辑
把so拖入IDA直接定位到关键代码处,图中的地址A46C代码: CMP R0, #1 实际为 checkSign返回值判断,BEQ意思是上一行的CMP比较指令如果R0等于0就跳转到loc_A47E处,也就是成功运行处。但实际程序运行后R0的值不等于0。 咱们要做的就是把下一个语句 BEQ loc_A47E,跳转逻辑改为 B loc_A47E ,可以看出跳转指令B对应的16进制机器码为E0,我们现在要做的就是把A46C处的指令改成B loc_A47E,意思就是不用判断上一行的CMP指令直接跳转。 然后通过010Editor等工具找到A460处地址:将D0改为E0
改完后查看 BEQ loc_A47E 已经修改为B loc_A47E 
- 修改好so文件后,可以尝试重新打包,签名,查看效果。
- 为了及时看到效果 直接放到源码里并用另一个签名文件进行打包调试观察
- 奇迹发生了 课程网页竟然加载出来了,登录尝试,同样成功,到此,关键破解点已成功破击,成功的绕过了本地签名校验
3.3 本地用户相关缓存信息读取及复制
最终目的: 进行本地信息的读取和写入,并提供相应UI界面。这样就可以从登陆成功的手机上复制关键信息到另一个未登录的手机上,实现类似cookie共享的机制。这样就可以成功的实现一人购买多人使用的破解版apk
原理:
- 通过dex2jar工具反编译解压完apk文件得到的classes.dex文件可以看到jar文件,然后通过jd-gui工具进行寻觅关键java代码
- 写一个辅助apk ,提供缓存preferencs,userDao数据的方法,并弹窗显示等功能,供koolearn3.2.7的注入代码调用。
- 将写好的apk反编译成 smali文件 复制到koolearn3.2.7反编译好的目录中
- 通过1处找到的位置找到koolearn3.2.7 smali登录成功后的代码调用,并调用辅助apk的smali代码,实现弹窗显示关键信息 或者窗口复制关键信息 关键代码其实就是:登录请求成功返回后的代码逻辑 实现方式:
- 反编译后找到LogintActivity ,然后分析找到了c类中的第一个a方法即为登录请求成功后的关键核心方法,而处理逻辑只有一个调用如下图所示:

- 观察发现一个比较尴尬的问题是 User对象没有实现序列化接口,无法直接转换成字符串,需要增加一个转换逻辑
- 辅助apk开发 提供两个类:MapUtils(字符串和User相互转换辅助类) SecretShowToast(继承dialog,为了反编译集成方便,使用纯代码写布局,给用户提供复制粘贴userId,sid等关键信息的视图入口)
- 反编译该apk,得到smali文件,复制到koolearn3.2.7反编译好的文件夹中

- 修改MyFramentByKotlin.smli代码,读取本地user信息 入侵代码如下:
invoke-static {}, Lcom/koolearn/android/utils/o;->a()Lcom/koolearn/android/model/User;
move-result-object v0 //获取本地User对象 赋值到v0
if-nez v0,:cond_10 // 如果不为null,跳转到标签cond_10处
new-instance v2, Lcom/leixun/haha/SecretShowToast;
invoke-virtual {p0}, Lcom/koolearn/android/BaseFragment;->getActivity()Landroid/support/v4/app/FragmentActivity;
move-result-object v0
const-string/jumbo v1, ""
invoke-direct {v2,v0, v1}, Lcom/leixun/haha/SecretShowToast;-><init>(Landroid/content/Context;Ljava/lang/String;)V
invoke-virtual {v2}, Lcom/leixun/haha/SecretShowToast;->show()V
goto/16 :goto_0 // 跳出该分支 返回
:cond_10
new-instance v1, Ljava/lang/StringBuilder; // 创建StringBuilder对象
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
invoke-virtual {v0}, Lcom/koolearn/android/model/User;->getUser_id()Ljava/lang/String;
move-result-object v2 // 获取UserId 赋值到v2 下一步填充到StringBuilder中
invoke-virtual {v1,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
const-string/jumbo v2, ","
invoke-virtual {v1,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v0}, Lcom/koolearn/android/model/User;->getSid()Ljava/lang/String;
move-result-object v2
invoke-virtual {v1,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
const-string/jumbo v2, ","
invoke-virtual {v1,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v0}, Lcom/koolearn/android/model/User;->getUser_name()Ljava/lang/String;
move-result-object v2
invoke-virtual {v1,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
const-string/jumbo v2, ","
invoke-virtual {v1,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v0}, Lcom/koolearn/android/model/User;->getMobile_number()Ljava/lang/String;
move-result-object v2
invoke-virtual {v1,v2}, 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
new-instance v2, Lcom/leixun/haha/SecretShowToast;
invoke-virtual {p0}, Lcom/koolearn/android/BaseFragment;->getActivity()Landroid/support/v4/app/FragmentActivity;
move-result-object v0 // 获取activity对象引用
invoke-direct {v2,v0, v1}, Lcom/leixun/haha/SecretShowToast;-><init>(Landroid/content/Context;Ljava/lang/String;)V
invoke-virtual {v2}, Lcom/leixun/haha/SecretShowToast;->show()V
// 显示SecretShowToast弹出框
goto/16 :goto_0
- SecretShowToast.smli 代码,点击写入时,将输入框中复制的信息写入本地,完成登录功, 侵入代码如下:
// v5,v1,v4,v0赋值代码忽略
new-instance v2, Lcom/koolearn/android/model/User;
invoke-direct {v2}, Lcom/koolearn/android/model/User;-><init>()V
invoke-virtual {v2,v5},Lcom/koolearn/android/model/User;->setUser_id(Ljava/lang/String;)V
invoke-virtual {v2,v1},Lcom/koolearn/android/model/User;->setSid(Ljava/lang/String;)V
invoke-virtual {v2,v4},Lcom/koolearn/android/model/User;->setUser_name(Ljava/lang/String;)V
invoke-virtual {v2,v0},Lcom/koolearn/android/model/User;->setMobile_number(Ljava/lang/String;)V
// 调用本地的用户信息写入方法 完成登录
invoke-static {v2}, Lcom/koolearn/android/utils/o;->a(Lcom/koolearn/android/model/User;)V
四、使用方式
下载破解版app到本地 登录后会弹框提示信息复制成功 然后使用微信或者其他工具中粘贴该机密信息
然后在另一款手机上粘贴该机密信息,点击写入,退出再进入即可实现一个账户多个手机使用了
tips:由于User对象里面的字段较多,demo只添加了几个,所以我的页面再进入时会发生奔溃现象(headImg等字段的原因,毕竟是破解版),同样选课界面也是,不过课程界面的课是可以正常播放的
破解版下载地址:请联系作者本人