杂学Android

逆向之Smali入门学习

2018-01-15  本文已影响3261人  大表哥007

Smali 概述我们都知道,Dalvik 虚拟机(Dalvik VM)是 Google 专门为 Android 平台设计的一套虚拟机。区别于标准 Java 虚拟机 JVM 的 class 文件格式,Dalvik VM 拥有专属的 DEX 可执行文件格式和指令集代码。smali 和 baksmali 则是针对 DEX 执行文件格式的汇编器和反汇编器,反汇编后 DEX 文件会产生.smali 后缀的代码文件,smali 代码拥有特定的格式与语法,smali 语言是对 Dalvik 虚拟机字节码的一种解释。Smali 语言起初是由一个名叫 JesusFreke 的 hacker 对 Dalvik 字节码的翻译,并非一种官方标准语言,因为 Dalvik 虚拟机名字来源于冰岛一个小渔村的名字,JesusFreke 便把 smali和 baksmali 取自了冰岛语中的“汇编器”和“反编器”。目前 Smali 是在 Google Code 上的一个开源项目。虽然主流的 DEX 可执行文件反汇编工具不少,如 Dedexer、IDA Pro 和 dex2jar+jd-gui,但 Smali 提供反汇编功能的同时,也提供了打包反汇编代码重新生成 dex 的功能,因此 Smali被广泛地用于 APP 广告注入、汉化和破解,ROM 定制等方面。
Smali 语法规范与格式Smali 是对 Dalvik 虚拟机字节码的一种解释,虽然不是官方标准语言,但所有语句都遵循一套语法规范。要了解 smali 语法规范,可以先从了解 Dalvik 虚拟机字节码的指令格式开始。3.1 Dalvik 虚拟机字节码指令格式在 Android 4.0 源码 Dalvik/docs 目录下提供了一份文档 instruction-formats.html,里面详细列举了 Dalvik 虚拟机字节码指令的所有格式.
Dalvik 虚拟机字节码的类型、方法和字段的表示方法3.2.1 类型Dalvik 字节码有两种类型,基本类型和引用类型。对象和数组是引用类型,其它都是基本类型。

Dalvik 字节码类型描述符
描述符 类型

</ignore_js_op><ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op> <ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op> <ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op> <ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op><ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op> <ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op><ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image </ignore_js_op> <ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> image

</ignore_js_op>
在以上指令中,在部分指令助记符后添加了 jumbo 后缀,这是在 Android 4.0 开始的扩展指令,增加了寄存器和常量的取值范围。需要引起注意的是,以上指令表中形如 VA 表示寄存器范围为 v0-v15,形如 VAA 表示寄存器范围为 v0-v255,这一点在理解指令时容易被忽略而导致修改 smali 代码时编译出错。比如方法调用指令 invoke 未添加/range 时传入方法的参数列表的寄存器需要在 v0-v15 范围内,如果不在范围内需要将不合格寄存器赋值给合格寄存器,然后再调用方法。
Smali 格式结构
文件格式
无论是普通类、抽象类、接口类或者内部类,在反编译出的代码中,它们都以单独的Smali 文件来存放。每个 smali 文件头 3 行描述了当前类的一些信息,格式如下。
.class <访问权限> [修饰关键字] <类名>.super <父类名>.source <源文件名>
打开 HelloWorld.smali 文件,头 3 行代码如下。
.class public LHelloWorld;
.super Landroid/app/Activity;
.source "HelloWorld.java"

第 1 行“.class”指令指定了当前类的类名。在本例中,类的访问权限为 public,类名为“LHelloWorld;”,类名开头的 L 是遵循 Dalvik 字节码的相关约定,表示后面跟随的字符串为一个类。

第 2 行的“.super ”指令指定了当前类的父类。本例中的“LHelloWorld;”的父类为“Landroid/app/Activity;”。

第 3 行的“.source”指令指定了当前类的源文件名。经过混淆的 dex 文件,反编译出来的 smali 代码可能没有源文件信息,因此“.source”行的代码可能为空。

前 3 行代码过后就是类的主体部分了,一个类可以由多个字段或方法组成。smali 文件中字段的声明使用“.field”指令。字段有静态字段与实例字段两种。静态字段的声明格式如下。

static fields

.field <访问权限> static [修饰关键字] <字段名>:<字段类型>

baksmali 在生成 Smali 文件时,会在静态字段声明的起始处添加“static fields”注释,Smali 文件中的注释与 Dalvik 语法一样,也是以井号“#”开头。“.field”指令后面跟着的是访问权限,可以是 public、private、protected 之一。修饰关键字描述了字段的其它属性,如synthetic。指令的最后是字段名与字段类型,使用冒号“:”分隔,语法上与 Dalvik 也是一样的。实例字段的声明与静态字段类似,只是少了 static 关键字,它的格式如下。

#instance fields

.field <访问权限> [修饰关键字] <字段名>:<字段类型>

比如以下的实例字段声明。

#instance fields

.field private btn:Landroid/widget/Button;

第 1 行的“instance fields”是 baksmali 生成的注释,第 2 行表示一个私有字段 btn,它的类型为“Landroid/widget/Button;”。如果一个类中含有方法,那么类中必然会有相关方法的反汇编代码,Smali 文件中方法的声明使用“.method”指令。方法有直接方法与虚方法两种。直接方法的声明格式如下。

direct methods

.method <访问权限> [修饰关键字] <方法原型>

<.locals>

[.parameter]

[.prologue]

[.line]

<代码体>

.end method

“direct methods”是 baksmali 添加的注释,访问权限和修饰关键字与字段的描述相同,方法原型描述了方法的名称、参数与返回值。“.locals ”指定了使用的局部变量的个数。“.parameter”指定了方法的参数,与 Dalvik 语法中使用“.parameters”指定参数个数不同,每个“.parameter”指令表明使用一个参数,比如方法中有使用到 3 个参数,那么就会出现3 条“.parameter”指令。“.prologue”指定了代码的开始处,混淆过的代码可能去掉了该指令。“.line”指定了该处指令在源代码中的行号,同样的,混淆过的代码可能去除了行号信息。

虚方法的声明与直接方法相同,只是起始处的注释为“virtual methods”。如果一个类实现了接口,会在 smali 文件中使用“.implements”指令指出。相应的格式声明如下。

interfaces

.implements <接口名>

“#interfaces”是 baksmali 添加的接口注释,“.implements”是接口关键字,后面的接口名是 DexClassDef 结构中 interfacesOff 字段指定的内容。如果一个类使用了注解,会在 smali 文件中使用“.annotation”指令指出。注解的格式声明如下。

annotations

.annotation [注解属性] <注解类名>

[注解字段=值]

.endannotation

注解的作用范围可以是类、方法或字段。如果注解的作用范围是类,“.annotation”指令会直接定义在 smali 文件中,如果是方法或字段,“.annotation”指令则会包含在方法或字段定义中。例如下面的代码。

instance fields

.field public sayWhat:Ljava/lang/String;

.annotation runtime LMyAnnoField;

info="Hellomyfriend"

.end annotation

.end field

实例字段 sayWhat 为 String 类型,它使用了 MyAnnoField 注解,注解字段 info 值为“Hellomyfriend”。将其转换为 Java 代码为:

@MyAnnoField(info="Hellomyfriend")

 public String sayWhat;

如何分析和修改 Smali 代码

一个完整的的 Android 程序反编译后的代码量可能非常庞大,并且反编译后的 Smali 源码相比 java 的可读性差太多了,我们应该如何定位关键代码,分析并修改它们。

定位分析的方法

关键信息查找法

 程序运行时会呈现给我们很多信息,如提示的文字内容、Log 输出的信息和 ActivityTaskRecord 等信息,那么可以从这些信息入手来定位关键的代码。

 比如,我们想查找程序显示 Toast 时上下文代码,Toast 提示的文字内容则会存放到strings.xml 文件或硬编码到程序代码中,在资源文件中的字符串会有一个 id 索引,只需在反编译的代码中全文检索这个 id 即可找到显示该 Toast 的代码;如果是后者,则在反编译代码中查找这个字符串本身即可。

如在Log中分析到程序发出了一个广播,根据广播的Action字符串查找所有smali代码,也可定位到所有发出和接收该广播的多处代码位置,再逐个分析代码上下文不难定位时何处发出的广播。

对于涉及到程序 UI 逻辑的分析,通常借助 android-sdk 中的工具 hierarchyviewer 来快速分析定位是程序哪个 Activity 甚至是哪个 View 的相关代码。

代码动态调试法

Smali 代码相对复杂冗长,对于需要实现的功能,直接写 Smali 代码既费时间又容易出错,因此通常采用另一种做法,就是先把功能用 Java 源码的方式实现,然后反编译得到 Smali代码,再把 Smali 代码合并到目标代码中。
逆向之Smali入门学习
https://www.52pojie.cn/thread-687375-1-1.html
(出处: 吾爱破解论坛)

上一篇 下一篇

猜你喜欢

热点阅读