常见APK破解思路

  1. 1 背景
  2. 2 替换关键程序
    1. 2.1 经典贪吃蛇大作战
    2. 2.2 车来了
  3. 3 修改程序运行逻辑
    1. 3.1 单机斗地主
    2. 3.2 美易公司起名
    3. 3.3 RE文件管理器
  4. 4 参考文献

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方法中的下面部分复制到payFailedpayCancel方法中。

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所示。

图1 修改经典贪吃蛇大作战的支付逻辑

2.2 车来了

首先使用AndroidKiller反编译车来了APK,找到包名com.ygkj.chelaile.standard,如图2所示。

图2 查看车来了APP的包名

然后使用命令adb shell dumpsys activity top查看APP的PID为10764,接着打开monitor通过pid:10764过滤APP的的Log信息,通过观察发现Log中出现了成功发送简单上报广告时间埋点之类的信息,右键 -> Fiter similar messages…,即可看到广告的域名信息atrace.chelaile.net.cn,如图3所示。

图3 获取广告的域名

这时,我们复制广告的域名atrace.chelaile.net.cn,然后在AndroidKiller中进行查找,并替换为127.0.0.1导致拼接的URL无法访问,从而达到去掉广告的效果,如图4所示。

图4 替换域名去广告

最后,编译、打包、签名,重新安装到真机中,在首页的推荐线路中就不会出现广告,如图5所示。

图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所示。

图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所示。

图7 修改美易公司取名的查询函数

3.3 RE文件管理器

APP关闭的时候弹出广告,着实烦人,破解思路如下。

首先,在真机处于弹出广告的界面时,电脑端输入adb shell dumpsys activity top命令查看当前activity的包名+类名com.AddDouDouWall2.WebPageDownLoadMainActivity,如图8所示。

图8 查看activity的包名+类名

然后,使用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所示查找用例

图9 查找WebPageDownLoadMainActivity用例

最终,发现在com.speedsoftware.rootexplorer.RootExploreronDestroy()方法中发现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所示。

图10 修改RE文件管理器的运行逻辑

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。

×

喜欢就点赞,疼爱就打赏