1 背景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 总的来说,在APK破解中,快速定位关键代码的思路如下: 1.分析流程 搜索特征字符串 搜索关键API 通过方法名来判断方法的功能 2.快速定位关键代码 反编译APK程序,AndroidManifest.xml => 包名/系统版本/组件 程序的MainActivity(程序入口界面) 每个Android程序有且只有一个MainActivity 分析程序的执行流程 需重点关注的Application Application执行时间 授权验证 3.定位关键代码的技巧 信息反馈法 (资源id/字符串) 特征函数法 (API函数) 顺序查看法 (分析程序执行流程/病毒分析) 代码注入法 (动态调试/插入Log/查看Logcat/分析加解密) 栈跟踪法 (动态调试/函数调用流程) Method Profiling (方法剖析 => 动态调试/热点分析/函数调用流程)
接下来,就对几款有漏洞的APP进行破解。
2 替换关键程序 2.1 经典贪吃蛇大作战 首先,进入游戏的无尽模式 后,就可以开始游戏了。然后,如果死亡后会提示购买复活大礼包 。这时就猜想购买后可以一直复活继续游戏,如果修改程序逻辑为支付取消也是复活 就行了。接着,有了思路后,我们可以搞事了。
这里,有一个知识点,支付宝付款成功 后会返回9000 字符串,付款失败 会返回8000 字符串,以后可能会用到。
常规思路,用Jadx打开APK后,搜索支付成功 字符串,发现如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void DX_Pay(HashMap<String, String> payParams) { new AlertDialog.Builder(activity); EgamePay.pay(activity, payParams, new EgamePayListener() { /* class com.qy.zombie.zombie.AnonymousClass3 */ @Override // cn.egame.terminal.paysdk.EgamePayListener public void paySuccess(Map<String, String> map) { Toast.makeText(zombie.activity, "支付成功", 0).show(); zombie.BuySccess(); } @Override // cn.egame.terminal.paysdk.EgamePayListener public void payFailed(Map<String, String> map, int errorInt) { Toast.makeText(zombie.activity, "支付失败" + errorInt, 0).show(); zombie.BuyFailed(); } @Override // cn.egame.terminal.paysdk.EgamePayListener public void payCancel(Map<String, String> map) { Toast.makeText(zombie.activity, "支付取消", 0).show(); zombie.BuyFailed(); } }); }
这里我们发现,可以将支付成功 的代码复制到支付失败 和支付取消 的代码中,这样就达到了我们的目的。
说干就干,用AndroidKiller打开APK,搜索Unicode格式 的支付成功 字符串,将paySuccess 方法中的下面部分复制到payFailed 和payCancel 方法中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const-string v1, "\u652f\u4ed8\u6210\u529f" const/4 v2, 0x0 invoke-static {v0, v1, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; move-result-object v0 invoke-virtual {v0}, Landroid/widget/Toast;->show()V .line 121 invoke-static {}, Lcom/qy/zombie/zombie;->BuySccess()V .line 122 return-void
最后,再编译、打包、签名,重新安装到真机中,就会在无尽模式 中达到不死的效果,如图1所示。
2.2 车来了 首先使用AndroidKiller反编译车来了 APK,找到包名com.ygkj.chelaile.standard ,如图2所示。
然后使用命令adb shell dumpsys activity top 查看APP的PID为10764 ,接着打开monitor 通过pid:10764 过滤APP的的Log信息,通过观察发现Log中出现了成功发送简单上报广告时间埋点 之类的信息,右键 -> Fiter similar messages…,即可看到广告的域名信息atrace.chelaile.net.cn ,如图3所示。
这时,我们复制广告的域名atrace.chelaile.net.cn,然后在AndroidKiller中进行查找,并替换为127.0.0.1 ,导致拼接的URL无法访问,从而达到去掉广告的效果 ,如图4所示。
最后,编译、打包、签名,重新安装到真机中,在首页的推荐线路中就不会出现广告,如图5所示。
3 修改程序运行逻辑 3.1 单机斗地主 首先,打开APP后,发现右下角有个记牌器的按钮,2天对应2元 ,7天对应6元 ,30天对应20元 ,这是特征字符串,后续可以根据这个定位关键代码。接着,在Jadx中搜索9000 字符串,通过人工过滤后,发现如下可疑代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override // com.june.game.a.c public void a(String str) { String a2 = new e(str).a(); if (TextUtils.equals(a2, "9000")) { if (2 == this.f782a.f) { UMGameAgent.pay(2.0d, "card_note_2", 1, 0.0d, 2); } else if (7 == this.f782a.f) { UMGameAgent.pay(6.0d, "card_note_7", 1, 0.0d, 2); } else if (30 == this.f782a.f) { UMGameAgent.pay(20.0d, "card_note_30", 1, 0.0d, 2); } this.f782a.d = a.c().d().getSharedPreferences("game", 0); this.f782a.d.edit().putInt("note_card_type", this.f782a.f).commit(); this.f782a.d.edit().putLong("buy_note_card_time", System.currentTimeMillis()).commit(); this.f782a.c(); return; } if (TextUtils.equals(a2, "8000")) { } }
虽然这里的代码做了混淆 ,但是我们还是能够发现记牌器付款金额和使用天数对应的代码 。然后,我们就可以修改程序运行逻辑,在不支付的情况下让其跳转到付款后的路径 。这里,我们将不支付的路径跳转到支付30元的路径。
用AndroidKiller打开APK,搜索支付失败的字符串8000 ,如下所示。
1 2 3 4 5 6 7 8 9 10 11 .line 108 :cond_4 const-string v1, "8000" invoke-static {v0, v1}, Landroid/text/TextUtils;->equals(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Z move-result v0 if-eqz v0, :cond_1 goto :goto_1
接着,猜想付款失败后程序才会跳转跳转到cond_4 处,先前的程序肯定会做出跳转的操作,将先前的smali代码if-eqz v1, :cond_4 修改为if-nez v1, :cond_4 ,这样就修改成功了。
最后,依然是编译、打包、签名,重新安装到真机中,就会在点击记牌器并且不付款的情况下使用记牌器的效果,如图6所示。
【注】我们还可以通过修改不同地方程序的运行逻辑 来达到不付款购买计时器的效果;或者修改付款金额 为0.01元等进行破解。
3.2 美易公司起名 首先,开门见山给出本APP的破解思路,即修改查询函数或者构造函数的返回值,让其返回true,这样便使普通用户变成会员用户 。
然后,使用Jadx搜索GetVip ,找到com.meiyiming.gsname.GlobalVar.GetVip() 函数,猜测其是提供给其他界面查询用户等级的接口,代码如下。
1 2 3 4 5 6 public boolean GetVip() { if (this.SessenID.equals(a.e)) { return true; } return false; }
将普通用户的查询结果返回会员用户的查询结果,在return v0之前添加smali代码const/4 v0, 0x1 。最后,编译、打包、签名,重新安装到真机中,就会在点击金木水火土 的时候不弹出“VIP会员,可以选择五行。VIP会员升级请点击右下角会员专区!”的提示,如图7所示。
3.3 RE文件管理器 在APP关闭的时候弹出广告 ,着实烦人,破解思路如下。
首先,在真机处于弹出广告的界面时,电脑端输入adb shell dumpsys activity top 命令查看当前activity的包名+类名 为com.AddDouDouWall2.WebPageDownLoadMainActivity ,如图8所示。
然后,使用Jadx打开APK文件,搜索WebPageDownLoadMainActivity ,得到方法com.AddDouDouWall2.WebPageDownLoadMainActivity.onCreate() ,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /* access modifiers changed from: protected */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout frameLayout = new FrameLayout(this); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-1, -1); setContentView(frameLayout, params); DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); float f = metrics.density; this.webView = new WebView(this); WebSettings settings = this.webView.getSettings(); settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); settings.setSupportZoom(false); settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(true); settings.setCacheMode(-1); settings.setDomStorageEnabled(true); settings.setBlockNetworkImage(true); settings.setJavaScriptEnabled(true); settings.setAllowFileAccess(true); this.webView.setDownloadListener(this); this.webView.setWebViewClient(new UserEntryGuide(this)); this.webView.loadUrl("http://www.doudoubird.com:8080/ddn_app/selectAppList?aidx=2"); addContentView(this.webView, params); startService(new Intent(this, DownLoadManagerService.class)); }
从上述代码我们可以知道,当用户点击屏幕上的广告时,activity会启动service直接去网络下载文件 。因此,我们需要查看哪个地方调用了WebPageDownLoadMainActivity类 ,双击选中WebPageDownLoadMainActivity,如图9所示查找用例 。
最终,发现在com.speedsoftware.rootexplorer.RootExplorer 的onDestroy() 方法中发现WebPageDownLoadMainActivity用例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 /* access modifiers changed from: protected */ @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity public void onDestroy() { Intent iii; w a2; if (isNetworkAvailable(this)) { startActivity(new Intent(this, WebPageDownLoadMainActivity.class)); } int i2 = 0; while (true) { if (i2 >= this.k.f3534a.size()) { iii = 1; break; } v a3 = a(i2); if (a3 != null && (a2 = a3.a()) != null && a2.p()) { iii = null; break; } i2++; } if (!(iii == null || BackgroundWorker.c || ig.aP == null)) { ig.aP.a(ig.bj + " \"" + ig.n() + "/.\"*"); } this.e = true; if (this.am != null) { try { unregisterReceiver(this.am); } catch (Exception e2) { } } try { c.disconnect(); } catch (Exception e3) { } if (this.bI != null) { this.bI.onDestroy(); } d = null; super.onDestroy(); if (A) { Runtime.getRuntime().exit(0); } }
经查看上面的代码,我们可以修改程序的跳转逻辑,有网络的时候不启动Activity,没有网络的时候启动Activity ,这样程序就不会下载任何垃圾软件。以防万一,我这里把程序中下载APK的链接http://www.doudoubird.com:8080/ddn_app/selectAppList?aidx=2 改成https://xingshuaikun.github.io/ 。
最后,依然是编译、打包、签名,重新安装到真机中,就会在有网络的情况下不弹出广告,在没有网络的情况下弹出界面,如图10所示。
4 参考文献 [1]丰生强. Android软件安全权威指南[M]. 电子工业出版社, 2019. [2]https://ctf-wiki.org/android/basic_operating_mechanism/java_layer/smali/smali/ [3]https://blog.csdn.net/freeking101/article/details/105669581 [4]https://blog.csdn.net/freeking101/article/details/105759124
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。