0
0
0

Android改造者之路 04 · 开打·第一次改造实战

文章摘要
|

第 04 章 开打·第一次改造实战

续写章节|沿用「原木」系列的武侠讲解体,非原作者(dayeve)作品

前情提要

我抱着万能君从藏兵阁出来,天已经黑了。

老头儿在后面喊:“小子,记住——兵器再利,也替你想不了问题。 真正让你走远的,是第二章那七个字。”

我愣了一下:“哪七个字?”

老头儿笑了:“洞察、推算、分析、辨别……剩下的,自己想去。”

不管了。剑已在手,先找个 apk 练练手再说。

嗯,就先从那个满屏广告的天气 app 开始吧。

剧情片段

导演: 这一章就到这儿,收工收工。

助理: 导演,观众问下一章讲啥?

导演: 兵器到手了,自然是——开打。

剧情片段

前情提要

兵器借到手,剑也磨亮了,可真要动手的时候,我反而站在原地发愣。

师傅说过一句话我一直没懂:“看懂一百遍,不如动手改一遍。”

现在懂了——因为当你真的打开那堆 smali,你会发现它跟你想象中的完全不是一回事。

那么,开打。


一、先定一个小目标

新手第一次实战,千万别一上来就挑"去广告 + 破会员 + 过校验"这种组合拳。那不是练手,那是找死。

我们把目标拆成三级,一级一级来:

级别 目标 用到的招式 难度
第一式 改掉界面上一句文案 const-string ★☆☆☆☆
第二式 改掉一个数字(比如试用天数) const / const-wide ★★☆☆☆
第三式 绕过一个判断(比如跳过会员校验) if-eqz / return-void ★★★☆☆

三式练完,你就走完了完整的改造闭环:拆 → 看 → 改 → 装 → 签 → 验。

后面的路,都是这个闭环的重复与加深。


二、战前准备

兵器清单(上一章借的那几把,这里只带必要的):

  • jadx —— 用来"看"
  • ApkTool —— 用来"拆"和"装"
  • MT管理器 —— 手机上的快捷路线(第三章的万能君)
  • zipalign + apksigner —— 用来"盖章"

样本的选择,是新手最容易踩的第一个坑。

别一上来就挑微信、抖音这种加固加壳的大块头。建议按这个顺序挑:

  1. 自己写的 demo —— 最理想。你知道代码长什么样,改起来心里有底
  2. 体积在 10MB 以内的小工具 app —— 没加固、单 dex、逻辑简单
  3. 等熟练了,再去碰加壳的

怎么判断有没有加固?把 apk 解压后看 lib 目录——如果里面躺着 libjiagu.so、libdexhelper.so、libBugly.so 这类奇怪的 so,而且 classes.dex 小得离谱(只有几十 KB),那基本就是套了壳。新手先绕开。

环境:一台开了 USB 调试的手机,或者一个模拟器。装 apk 用的就是 adb install。

adb install -r app_modified.apk

三、标准六步流程

不管你改什么,流程都是这六步。把它背下来:

① 观察  →  ② 定位  →  ③ 拆解  →  ④ 动刀  →  ⑤ 回装  →  ⑥ 盖章验证

① 观察:jadx 打开 apk,先整体逛一圈,看包名、看入口 Activity、看有几个 dex。

② 定位:用搜索找关键词(提示文字、方法名、字符串),锁定要改的那几行。

③ 拆解:apktool d 出 smali。

④ 动刀:编辑 smali。

⑤ 回装:apktool b 打包。

⑥ 盖章验证:zipalign → apksigner → adb install → 打开看效果。

记住:jadx 是用来"看"的,ApkTool 是用来"改"的。 不要试图在 jadx 里改——它改不了;也不要试图在 smali 里读逻辑——那太累。

jadx 找到位置 → 记住类名和方法名 → 去 ApkTool 出的 smali 里找到同一个方法动手,这才是标准工作流。


四、三式实战

下面用一个虚构的 demo 来演示,包名叫 com.demo.weather。真实操作时换成你自己的目标即可。

4.1 第一式:改文案(const-string)

目标:把启动后显示的那句"免费版"改成"尊享版"。

先在 jadx 里搜索字符串"免费版",会看到类似这样的 Java 代码:

private void initView() {
    this.tvTitle.setText("免费版");
}

记住它在哪个类的哪个方法里,然后去 smali/ 目录按路径找到同名文件(com/demo/weather/MainActivity.smali),找到 initView():

改之前:

.method private initView()V
    .registers 3

    const-string v0, "免费版"

    invoke-virtual {p0, v1}, Lcom/demo/weather/MainActivity;->setTitle(Ljava/lang/CharSequence;)V

    return-void
.end method

改之后:

.method private initView()V
    .registers 3

    const-string v0, "尊享版"

    invoke-virtual {p0, v1}, Lcom/demo/weather/MainActivity;->setTitle(Ljava/lang/CharSequence;)V

    return-void
.end method

就改一个字符串。保存,回装,签名,安装——你会看到界面上的字变了。

这一刻,你就正式入门了。

坑点:如果你在 smali 里搜不到这句文案,说明它不在代码里,而在资源文件里(res/values/strings.xml),代码只引用了一个 0x7f0xxxxx 的 id。这时候要去改 res/values/strings.xml 里对应的那一条,或者用 MT管理器 的 Arsc 编辑器直接改。

4.2 第二式:改数值(const / const-wide)

目标:把 7 天试用改成 99 天。

jadx 里看到:

private static final int TRIAL_DAYS = 7;

在 smali 里,这个 7 会变成十六进制 0x7:

改之前:

const/16 v0, 0x7

改之后(0x63 = 99):

const/16 v0, 0x63

几个常识要记住:

  • smali 里的数字是十六进制,0x10 是 16 不是 10。改之前先换算
  • const/4 范围很小(-8 ~ 7),const/16 到 0xFFFF,const 到 0xFFFFFFFF
  • 改到期时间通常用 const-wide(64 位时间戳,单位毫秒)
  • 数字改太大可能溢出,比如 const/16 塞不下 99999

坑点:本地改天数,只在 app 自己算的情况下有效。如果试用天数是服务器下发的,你改了本地也没用——打开就又被服务端改回去了。判断是否本地计算,最简单的办法是断网打开 app 看看。

4.3 第三式:绕过判断(if-eqz / return-void)

这是真正有意思的地方,也是改造者最常用的两招。

招式 A:强行翻转判断

jadx 里看到:

private void checkVip() {
    if (!isVip()) {
        showBuyDialog();   // 不是会员就弹购买框
    }
}

对应的 smali:

改之前:

invoke-direct {p0}, Lcom/demo/weather/MainActivity;->isVip()Z
move-result v0
if-eqz v0, :cond_vip          # v0 == 0(不是会员)就跳到弹窗

:cond_vip
invoke-direct {p0}, Lcom/demo/weather/MainActivity;->showBuyDialog()V

改法一(翻转条件):把 if-eqz 改成 if-nez。

if-nez v0, :cond_vip          # 改成 v0 != 0 才跳,逻辑正好反过来

改法二(强行赋值,更常用也更稳):在 move-result 后面插一行,强制把结果置为 1(真)。

invoke-direct {p0}, Lcom/demo/weather/MainActivity;->isVip()Z
move-result v0
const/4 v0, 0x1               # ← 加这一行:不管算出来是什么,强制为真
if-eqz v0, :cond_vip

为什么改法二更常用?因为你不用去推理原来的判断逻辑。不管它是 if-eqz 还是 if-nez,把结果钉死成 1 或 0,后面的分支自然就走你想要的那条。这就是第二章说的"掌控关键片段的表演进度,即可影响全局剧情"。

招式 B:让方法直接空手而归

想去掉开屏广告?找到那个显示广告的方法,在第一行就让它返回。

改之前:

.method private showSplashAd()V
    .registers 4

    invoke-direct {p0}, Lcom/demo/weather/MainActivity;->loadAd()V
    # ... 一堆广告逻辑 ...

    return-void
.end method

改法一(方法内加 return-void):

.method private showSplashAd()V
    .registers 4

    return-void                 # ← 加这一行,进来就走,什么也不干

    invoke-direct {p0}, Lcom/demo/weather/MainActivity;->loadAd()V
    return-void
.end method

改法二(把调用处注释掉,更安全):找到谁在调用它,在那一行的 invoke 前面加个 #。

# invoke-direct {p0}, Lcom/demo/weather/MainActivity;->showSplashAd()V

推荐新手用改法二。原因:改法一会在方法里留下不可达代码,某些严格校验下反而会出问题;而注释掉调用行,等于"压根没叫它",最干净。

注意 smali 里的注释符号是 #,跟 Java 的 // 不一样。


五、小剧场模式:第一次翻车

小剧场模式

导演: 这一场拍主角第一次翻车,表情要到位。

助理: 3 2 1。。。action

我: (信心满满)改完了!回装!签名!安装!

手机: 解析包时出现问题。

我: ???

钤印君: (慢悠悠走过来)你是不是忘了 zipalign 要在 apksigner 之前?

我: 啊……我好像是先签的名。

钤印君: 那可不就白忙了。先对齐,后盖章,顺序反了签名就碎了。

我: (重做一遍)好了!这回装上了!点开——

手机: 很抱歉,"天气"已停止运行。

我: ……又怎么了?

拆解君: 你看看我回编译时报的警告。.registers 3,但你实际用了 v0、v1、v2、v3 四个寄存器——多出来的那个没地方站。

我: 那我改回 .registers 4?

拆解君: 对。寄存器宁可多,不可少。少了必崩,多了只是浪费。

我: (再改再装)这次起来了!可是……文案怎么没变?

执行君: 你改的是 smali/ 里那个 dex。我这 app 有两个 dex——你找错片场了,smali_classes2/ 里还有一份同名的呢。

我: ……(瘫坐)

透视君: 别急。jadx 里你搜到几处"免费版"?

我: 三处。

透视君: 那你就得改三处。改造最忌讳的就是改了一处就以为大功告成。

导演: cut!! 这条过。

我: (小声)所以第三章老头儿说的"洞察、推算、分析、辨别",是这个意思啊……

小剧场模式


六、翻车急救手册

上面那场戏里的坑,新手基本都会踩一遍。整理成表,翻车时对着查:

症状 最可能的原因 怎么查
解析包时出现问题 ① 没签名 ② zipalign/apksigner 顺序反了 ③ 包本身损坏 重跑 zipalign → apksigner,用 apksigner verify 验证
安装后立刻闪退 ① smali 语法错误 ② .registers 数量不够 ③ 返回值类型不匹配 看 apktool b 的报错;寄存器改大一点;logcat 看崩溃栈
改了没效果 ① 有多 dex,改错文件 ② 同一个判断有多处 ③ 数据在服务端 jadx 里全局搜,看命中几处;断网试一次
回编译失败 ① aapt 版本不兼容 ② 缺 framework ③ apk 被加固 升级 ApkTool;apktool if framework-res.apk;新手先换未加固的样本
装上能开,一联网就打回原形 服务端校验 说明本地改无效,需要配合抓包或放行服务端逻辑(后话)

排查的通用姿势:

adb logcat | grep -i "AndroidRuntime"     # 看崩溃原因

崩溃日志里会直接告诉你哪个类、哪个方法、出了什么错。学会看 logcat,你的排错速度能快十倍。


七、手机上的快捷路线

如果你嫌电脑上敲命令麻烦,第三章那位**万能君(MT管理器)**可以一把梭:

  1. 打开 MT管理器,点进 apk 文件
  2. 点 classes.dex → 选「Dex 编辑器」
  3. 搜索类名或字符串,定位到方法
  4. 直接编辑 smali,保存
  5. 退出时自动重新打包 + 自动签名
  6. 安装

优点:不用配环境、不用记命令、自动签名。
缺点:改大段逻辑不如电脑舒服;多 dex 要逐个进。

建议:练手用 MT管理器找感觉,正经干活回到 ApkTool + jadx。两条路线不冲突,混着用最舒服。


八、一条铁律

最后,说点比技术更重要的事。

藏兵阁门口那副对联的下联是"术不在玄,管用则行",但老头儿没告诉我,那副对联还有个横批没挂出来——他是在我第三次来借兵器时才说的:

器可用之于人者,亦可用之于己。

改造技术本身是中立的,就像第二章说的——改造者是唯一中立的存在,最终会变成什么样子,取决于你自己的决定。

所以:

  • 拿自己写的 app 或明确授权的 app 练手
  • 不要去改别人的付费功能并分发
  • 不要碰涉及账号、支付、隐私的东西
  • 只做技术讨论与学习,不传播成品

这不只是"政治正确"——这也是这行能走多远的天花板。


我把那个天气 app 的开屏广告去掉了,界面清爽了不少。

虽然只是注释掉了一行 invoke。

但那是我第一次,真正意义上让一个程序听我的话。

师傅端着茶杯走过来,瞟了一眼屏幕:“改了?”

“改了。”

“改了几处?”

“……三处。前两处没反应。”

师傅笑了:“记住这个感觉。改造这件事,八成的时间花在找对地方,两成的时间花在动手。 新手总觉得是自己招式不行,其实多半是压根没找对地方。”

我点点头,正想说话,手机突然一震。

屏幕亮起来,是那个天气 app 弹出的新提示:

「检测到应用已被修改」

我:???

师傅放下茶杯,笑得更开心了:“哦?看来是时候跟你讲讲——那些不希望你改造的人,都藏了什么手段了。”

(未完待续。。。)

剧情片段

导演: 收工。下一场,该轮到保卫者登场了吧?

助理: 对,听说还要讲签名校验、加固、混淆那一套。

导演: 行,那就叫——道高一尺。

剧情片段

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论

欢迎来到我的博客!