术→技巧, 研发

Android应用:apk文件反编译

钱魏Way · · 14 次浏览

APK文件简介

每个需要安装到android平台的应用都要被编译打包为一个单独的文件,后缀名为.apk(Android application package),其中包含了应用的二进制代码、资源、配置文件等。

apk文件实际是一个zip压缩包,可以通过解压缩工具解开(将后缀名改为.zip后再用解压缩文件解压)。APK文件的大致目录结构如下:

|– AndroidManifest.xml
|– META-INF
| |– CERT.RSA
| |– CERT.SF
| `– MANIFEST.MF
|– classes.dex
|– res
| |– drawable
| | `– icon.png
| `– layout
| `– main.xml
`– resources.arsc

AndroidManifest.xml

AndroidManifes.xml官方解释是应用清单,每个应用的根目录中都必须包含一个,并且文件名必须一模一样。这个文件中包含了APP的配置信息,系统需要根据里面的内容运行APP的代码,显示界面。

上述的功能是非常笼统的解释,具体到细节就是:

  • 为应用的 Java 软件包命名。软件包名称充当应用的唯一标识符。
  • 描述应用的各个组件,包括构成应用的 Activity、服务、广播接收器和内容提供程序。它还为实现每个组件的类命名并发布其功能,例如它们可以处理的 Intent 消息。这些声明向 Android 系统告知有关组件以及可以启动这些组件的条件的信息。
  • 确定托管应用组件的进程。
  • 声明应用必须具备哪些权限才能访问 API 中受保护的部分并与其他应用交互。还声明其他应用与该应用组件交互所需具备的权限
  • 列出 Instrumentation类,这些类可在应用运行时提供分析和其他信息。这些声明只会在应用处于开发阶段时出现在清单中,在应用发布之前将移除。
  • 声明应用所需的最低 Android API 级别
  • 列出应用必须链接到的库

上面是官方的解释。很多东西现在还不能理解,也没有用到,先挑能理解的进行解释。

  • 第一条:提供软件包名。这就是我们的apk的名字,通常我们的名字都是类似”com.android.helloworld”这种,和Java类名类似,目的是确定使其成为一个唯一值。
  • 第二条:描述应用的各个组件。这是用来定义四大组件用的。我们最常用的就是Activity组件。它需要定义组件的表现形式(组件名、主题、启动类型),组件可以响应的操作(例如某个启动意图)等。
  • 第三条、第四条和第五条:还没用到,不做解释。
  • 第五条:声明最低API级别。这个级别在gradle文件中也能定义,字段是minSdkVersion。在AndroidManifest.xml文件中定义的情况比较少。
  • 第六条:列出必要的lib库。这东西在0以后的Android Studio似乎也没什么功能,因为在3.0以后编译用的是CMakeLists.txt文件,以及build.gradle文件来指定库。

AndroidManifest.xml文件示例:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.sample.teapot"
          android:versionCode="1"
          android:versionName="1.0.0.1" >

  <uses-feature android:glEsVersion="0x00020000"></uses-feature>

  <application
      android:allowBackup="false"
      android:fullBackupContent="false"
      android:supportsRtl="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:theme="@style/AppTheme"
      android:name="com.sample.teapot.TeapotApplication"
      >

    <!-- Our activity is the built-in NativeActivity framework class.
         This will take care of integrating with our NDK code. -->
    <activity android:name="com.sample.teapot.TeapotNativeActivity"
              android:label="@string/app_name"
              android:configChanges="orientation|keyboardHidden">
      <!-- Tell NativeActivity the name of our .so -->
      <meta-data android:name="android.app.lib_name"
                 android:value="TeapotNativeActivity" />
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

这是Google官方示例中的teapots项目中的一个文件,我们就针对这份文件来分析字段的意义。字段的意义参考的是官方文档

<manifest>元素:所有的xml都必须包含<manifest>元素。这是文件的根节点。它必须要包含<application>元素,并且指明xmlns:android和package属性。

<manifest>元素中的属性

  • xmlns:android 这个属性定义了Android命名空间。必须设置成”http://schemas.android.com/apk/res/android”。不要手动修改。
  • package这是一个完整的Java语言风格包名。包名由英文字母(大小写均可)、数字和下划线组成。每个独立的名字必须以字母开头。包名也代表着唯一的application ID,用来发布应用。但是,要注意的一点是:在APK构建过程的最后一步,package名会被gradle文件中的applicationId属性取代。如果这两个属性值一样,那么万事大吉,如果不一样,那就要小心了。构建APK的时候,构建系统使用这个属性来做两件事:
    • 生成java类时用这个名字作为命名空间(用于访问APP的资源)。比如:package被设置成com.sample.teapot,那么生成的R类就是:com.sample.teapot.R
    • 用来生成在manifest文件中定义的类的完整类名。比如package被设置成sample.teapot,并且activity元素被声明成<activity android:name=”.MainActivity”>,完整的类名就是com.sample.teapot.MainActivity。
  • android:versionCode:内部的版本号。用来表明哪个版本更新。这个数字不会显示给用户。显示给用户的是versionName。这个数字必须是整数。不能用16进制,也就是说不接受”0x1″这种参数
  • android:versionName:显示给用户看的版本号。

<manifest>元素中的元素

  • <uses-feature>元素:Google Play利用这个元素的值从不符合应用需要的设备上将应用过滤。这东西的作用是将APP所依赖的硬件或者软件条件告诉别人。它说明了APP的哪些功能可以随设备的变化而变化。使用的时候要注意,必须在单独的<uses-feature>元素中指定每个功能,如果要多个功能,需要多个<uses-feture>元素。比如要求设备同时具有蓝牙和相机功能:
<uses-feature android:name="android.hardware.bluetooth" />
<uses-feature android:name="android.hardware.camera" />
  • <uses-feature>的属性:
    • android:name:该属性以字符串形式指定了APP要用的硬件或软件功能。
    • android:required:这项属性如果值为true表示需要这项功能否则应用无法工作,如果为false表示应用在必要时会使用该功能,但是如果没有此功能应用也能工作。
    • android:glEsVersion:指明应用需要的Opengl ES版本。高16位表示主版本号,低16位表示次版本号。例如,如果是要2的版本,就是0x00030002。如果定义多个glEsVersion,应用会自动启用最高的设置。
  • <application>元素:此元素描述了应用的配置。这是一个必备的元素,它包含了很多子元素来描述应用的组件,它的属性影响到所有的子组件。许多属性(例如icon、label、permission、process、taskAffinity和allowTaskReparenting)都可以设置成默认值。
    • <application>的属性
      • android:allowBackup:表示是否允许APP加入到备份还原的结构中。如果设置成false,那么应用就不会备份还原。默认值为true。
      • android:fullBackupContent:这个属性指向了一个xml文件,该文件中包含了在进行自动备份时的完全备份规则。这些规则定义了哪些文件需要备份。此属性是一个可选属性。默认情况下,自动备份包含了大部分app文件。
      • android:supportsRtl:声明你的APP是否支持RTL(Right To Left)布局。如果设置成true,并且targetSdkVersion被设置成17或更高。很多RTL API会被集火,这样你的应用就可以显示RTL布局了。如果设置成false或者targetSdkVersion被设置成16或更低。哪些RTL API就不起作用了。该属性的默认的值是false。
      • android:icon:APP的图标,以及每个组件的默认图标。可以在组价中自定义图标。这个属性必须设置成一个引用,指向一个可绘制的资源,这个资源必须包含图片。系统不设置默认图标。例如mipmap/ic_launcher引用的就是下面的资源
      • android:label:一个用户可读的标签,以及所有组件的默认标签。子组件可以用他们的label属性定义自己的标签,如果没有定义,那么就用这个标签。标签必须设置成一个字符串资源的引用。这样它们就能和其他东西一样被定位,比如@string/app_name。当然,为了开发方便,你也可以定义一个原始字符串。
      • android:theme:该属性定义了应用使用的主题的,它是一个指向style资源的引用。各个activity也可以用自己的theme属性设置自己的主题。
      • android:name:Application子类的全名。包括前面的路径。例如sample.teapot.TeapotApplication。当应用启动时,这个类的实例被第一个创建。这个属性是可选的,大多数APP都不需要这个属性。在没有这个属性的时候,Android会启动一个Application类的实例。
    • <activity>元素:该元素声明一个实现应用可视化界面的Activity(Activity类子类)。这是<application>元素中必要的子元素。所有Activity都必须由清单文件中的<activity>元素表示。任何未在该处声明的Activity对系统都不可见,并且永远不会被执行。
      • android:name:Activity类的名称,是Activity类的子类。该属性值为完全限定类名称,例如sample.teapot.TeapotNativeActivity。为了方便起见,如果第一个字符是点(’.’),就需要加上<manifest>元素中的包名。应用一旦发布,不应更改该名称。没有默认值,必须指定该名称。
      • android:label:Activity标签,可以被用户读取。该标签会在Activity激活时显示在屏幕上。如果未设置,用<application>中的label属性。对属性的设置要求和<application>中一样。
      • android:configChanges:列出 Activity 将自行处理的配置更改消息。在运行时发生配置更改时,默认情况下会关闭 Activity 然后将其重新启动,但使用该属性声明配置将阻止 Activity 重新启动。 Activity 反而会保持运行状态,并且系统会调用其 onConfigurationChanged()方法。注:应避免使用该属性,并且只应在万不得已的情况下使用。 如需了解有关如何正确处理配置更改所致重新启动的详细信息,请阅读处理运行时变更。这属性可以设置的项很多,这里列出常用的项:
        • orientation:屏幕放心啊发生了变化,比如用户旋转了设备
        • keyboardHidden:键盘无障碍功能发生了变化,比如用户显示了硬件键盘
        • android:launchMode:关于如何启动Activity的指令。一共有四种指令:“standard”、“singleTop”、“singleTask”、“singleInstance”默认情况下是standard。这些模式被分为两大类:”standard”和”singleTop”是一类。该模式的Activity可以多次实例化。实例可属于任何任务,并且可以位于Activity堆栈中的任何位置。”singleTask”和”singleInstance”是一类。该模式只能启动任务,它们始终位于Activity堆栈的根位置。此外,设备一次只能保留一个Activity实例。设置成singleTask后,系统在新任务的根位置创建Activity并向其传送Intent。如果已经存在一个Activity实例,则系统会通过调用该实例的onNewIntent()方法向其传送Intent而不是创建一个新的Activity实例。
      • android:theme:设定主题格式,与<application>中的theme类似。
    • <meta-data>元素:指定额外的数据项,该数据项是一个name-value对,提供给其父组件。这些数据会组成一个Bundle对象,可以由metaData字段使用。虽然可以使用多个<meta-data>元素标签,但是不推荐这么使用。如果有多个数据项要指定,推荐做法是:将多个数据项合并成一个资源,然后使用一个<meta-data>包含进去。该元素有三个属性:
      • android:name:数据项名称,这是一个唯一值。
      • android:resource:一个资源的引用。
      • android:value:数据项的值。
    • <intent-filter>元素:指明这个activity可以以什么样的意图(intent)启动。该元素有几个子元素可以包含。我们先介绍遇到的这两个:
      • <action>元素:表示activity作为一个什么动作启动,intent.action.MAIN表示作为主activity启动。
      • <category>元素:这是action元素的额外类别信息,intent.category.LAUNCHER表示这个activity为当前应用程序优先级最高的Activity。

需要注意的是,直接通过zip解压出来的AndroidManifest.xml文件是经过压缩过的。如果直接用记事本打开是乱码。可以通过AXMLPrinter2工具解开。具体流程后面会详细介绍。

Res文件夹

在Android项目文件夹里面,主要的资源文件是放在res文件夹,res文件夹下为所有的资源文件。

  • res/assets:assets文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像xml,java文件被预编译,可以存放一些图片,html,js, css等文件。assets目录不会被映射到R中,因此,资源无法通过id方式获取,必须要通过AssetManager进行操作与获取,assets下可以有多级目录(只有 /assets 目录可以包含任意子目录列表。每个其他目录只能有该目录级别的文件)。注意:Gradle默认不创建asserts文件夹,但他的路径已经存在于main 文件夹下面了。
  • res/animator:用于定义属性动画的 XML 文件。
  • res/anim:用于定义动画对象。存放定义了补间动画(tweened animation)或逐帧动画(frame by frame animation)的XML文件。
  • res/drawable:存放各种图片类型,不能纯数字定义文件名,另可以新建.xml文件类型 通常自定义控件样式时会在此文件夹中新建个.xml格式文件作为背景图。
  • res/color:用于定义颜色状态列表的 XML 文件。请参阅颜色状态列表资源
  • res/layout:用于定义用户界面布局的 XML 文件。
  • res/menu:用于定义应用菜单(如选项菜单、上下文菜单或子菜单)的 XML 文件。
  • res/raw:需以原始形式保存的任意文件。如要使用原始 InputStream 打开这些资源,请使用资源 ID(即raw.filename)调用 Resources.openRawResource()。但是,如需访问原始文件名和文件层次结构,则可以考虑将某些资源保存在 assets/ 目录(而非 res/raw/)下。assets/ 中的文件没有资源 ID,因此您只能使用 AssetManager 读取这些文件。
  • res/values:包含字符串、整型数和颜色等简单值的 XML 文件。其他 res/ 子目录中的 XML 资源文件会根据 XML 文件名定义单个资源,而 values/ 目录中的文件可描述多个资源。对于此目录中的文件,<resources> 元素的每个子元素均会定义一个资源。例如,<string> 元素会创建string 资源,<color> 元素会创建 R.color 资源。由于每个资源均使用自己的 XML 元素进行定义,因此您可以随意命名文件,并在某个文件中放入不同的资源类型。但是,您可能需要将独特的资源类型放在不同的文件中,使其一目了然。例如,对于可在此目录中创建的资源,下面给出了相应的文件名约定:
    • xml:资源数组(类型数组)。
    • xml:颜色值。
    • xml:尺寸值。
    • xml:字符串值。
    • xml:样式。
  • res/xml:可在运行时通过调用getXML() 读取的任意 XML 文件。各种 XML 配置文件(如可搜索配置)都必须保存在此处。
  • res/xml:带有扩展名的字体文件(如 .ttf、.otf 或 .ttc),或包含 <font-family> 元素的 XML 文件。

resources.arsc文件

resource.arsc文件是Apk打包过程中的产生的一个资源索引文件。在对apk进行解压或者使用Android Studio对apk进行分析时便可以看到resource.arsc文件。通过学习resource.arsc文件结构,可以帮助我们深入了解apk包体积优化中使用到的 重复资源删除、资源文件名混淆技术。

arsc文件作用

在Java中访问一个文件是需要提供文件路径,如:

new File("./res/drawable-xxhdpi/img.png");

而在Android中,却可以通过drawable Id获得资源文件:

getDrawable(R.drawable.img);

这里凭一个id就能获取资源文件内容,省去了文件路径的手动输入,其背后就是通过读取arsc文件实现的。这些R.drawable.xxx、R.layout.xxx、R.string.xxx等的值(存储在R.jar或者R.java文件中)称之为资源索引,通过这些资源索引可以在arsc文件中查取实际的资源路径或资源值。例如: getDrawable(R.drawable.img)在编译后成了getDrawable(2131099964),再将id转为十六进制:2131099964 = 0x7f06013c。这时的资源索引为0x7f06013c。

资源索引具有固定的格式:0xPPTTEEEE,PackageId(2位) + TypeId(2位) + EntryId(4位) :

  • PP:Package ID,包的命名空间,取值范围为[0x01, 0x7f],第三方应用均为7f。
  • TT:资源类型,有anim、layout、mipmap、string、style等资源类型。
  • EEEE:代表某一类资源在偏移数组中的值

所以0x7f06013c中 PackageId = 0x7f、TypeId = 0x06、EntryId = 0x013c

最简单的我们可以将arsc函数想象成一个含有多个Pair数组的文件,且每个资源类型(TypeId)对应一个Pair[](或多个,为了便于理解先只认为是一个)。因此在arsc中查找0x7f06013c元素的值,就是去设法找到TypeId=0x06所对应的数组Pair[],然后找到其中的第0X013c号元素Pair[0X013c]。这个元素恰好就是Pair(“img”, “./res/drawable-xxhdpi/img.png”),左边是资源名称img,右边是资源的文件路径”./res/drawable-xxhdpi/img.png”,有了文件路径,程序便可以访问到对应的资源文件了。

当然实际的arsc文件在结构上要稍微复杂一点,下面开始分析arsc文件结构。

chunk

为了便于理解,在正式介绍resource.arsc(以下简称arsc)文件前,需要对chunk进行解释一下,在其他文章中也多次使用了“chunk”这个词。chunk翻译为中文就是“块、部分(尤指大部分,一大块)”的意思,例如:一棵树,可以分为三个chunk(部分):树冠、树茎、树根。也可以将一棵树视为一个chunk,这个chunk就是这棵树。

arsc文件结构

resources.arsc是一个二进制文件,其内部结构的定义在ResourceTypes.h,arsc文件结构:

图片整体描述了arsc文件中各个chunk的关系(注意结合图片左右两侧内容):

  • 整个arsc文件是一个 RES_TABLE_TYPE 类型的chunk;
  • RES_TABLE_TYPE 可分为三个部分:文件头部和两个子chunk( RES_STRING_POOL_TYPE 、 RES_TABLE_PACKAGE_TYPE );
  • RES_TABLE_PACKAGE_TYPE 中包含了:头部、资源类型字符串常量池、资源项名称字符串常量池、多个子chunk(RES_TABLE_TYPE_SPEC_TYPE和RES_TABLE_TYPE_TYPE );
  • 每种类型的chunk都含有一个头结构

META-INF目录

META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全。Android开发环境对每一个需要Release的APK都会进行签名,在APK文件被安装时,Android系统会对APK的签名信息进行比对,以此来判断程序的完整性,最终确定APK是否可以正常安装使用,一定程度上达到安全的目的。

META-INF目录下看到四个文件:MANIFEST.MF、CERT.SF、CERT.RSA

  • MF(摘要文件):程序遍历APK包中的所有文件,对非文件夹非签名文件的文件,逐个用SHA1生成摘要信息,再用Base64进行编码。如果APK包的文件被修改,在APK安装校验时,被修改的文件与MANIFEST.MF的校验信息不同,程序将无法正常安装。
  • SF(对摘要文件的签名文件):对于生成的MANIFEST.MF文件利用SHA1-RSA算法对开发者的私钥进行签名。在安装时只有公共密钥才能对其解密。解密之后将其与未加密的摘要信息进行比对,如果相符则文件没有被修改。
  • RSA 保存公钥、加密算法等信息。

在APK进行安装时,可以通过MANIFEST.MF文件开始的环环相扣来保证APK的安全性。比如拿到一个apk包后,如果想要替换里面的一幅图片,一段代码, 或一段版权信息,想直接解压缩、替换再重新打包,基本是不可能的。如此一来就给病毒感染和恶意修改增加了难度,有助于保护系统的安全。但这些文件或者密钥如果被攻击者得到或者被攻击者通过某些技术手段攻破,则Android操作系统无法验证其安全性。

classes.dex文件

dex文件是Android系统中的一种文件,是一种特殊的数据格式,和APK、jar 等格式文件类似。能够被DVM识别,加载并执行的文件格式。相对于PC上的java虚拟机能运行.class;android上的Davlik虚拟机能运行.dex。当Java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右。

在明白什么是 Dex 文件之前,要先了解一下 JVM,Dalvik 和 ART。JVM 是 JAVA 虚拟机,用来运行 JAVA 字节码程序。Dalvik 是 Google 设计的用于 Android平台的运行时环境,适合移动环境下内存和处理器速度有限的系统。ART 即 Android Runtime,是 Google 为了替换 Dalvik 设计的新 Android 运行时环境,在Android 4.4推出。ART 比 Dalvik 的性能更好。Android 程序一般使用 Java 语言开发,但是 Dalvik 虚拟机并不支持直接执行 JAVA 字节码,所以会对编译生成的 .class 文件进行翻译、重构、解释、压缩等处理,这个处理过程是由 dx 进行处理,处理完成后生成的产物会以 .dex 结尾,称为 Dex 文件。Dex 文件格式是专为 Dalvik 设计的一种压缩格式。所以可以简单的理解为:Dex 文件是很多 .class 文件处理后的产物,最终可以在 Android 运行时环境执行。

Java 代码转化为 dex 文件的流程如图所示,当然真的处理流程不会这么简单,这里只是一个形象的显示:

lib文件夹

引用的第三方sdk的so文件,有C/C++编译而成。

Apk的打包流程

我们先对 安卓的打包流程进行一个简单的了解,从而明白.java文件 是一步步成为 apk中的一部分的,在生成 apk 的过程中主要包含以下流程,括号中代表使用的工具:

  • 打包资源文件,生成java文件 (aapt)
  • 处理aidl文件,生成相应的.java文件 (aidl)
  • 编译项目源代码,生成class文件 (javac)
  • 转换所有的class文件,生成dex文件 (dx)
  • 编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。 (apkbuilder)
  • 对APK文件进行签名 (jarsigner)
  • 对签名后的APK文件进行对齐处理 (zipalign)

APK文件的反编译

资源文件获取

apktool 主要用于资源文件的获取,Apktool的主要作用:

  • 将资源文件还原成原始形式(9.png、xml)
  • 将Android的dex文件反编译为smali源码
  • 将反编译的资源重新编译成APK/JAR

所以Apktool不光能够拆解apk,还能加已经拆解的apk资源重新组装成apk。

使用就比较简单了,直接执行如下命令就可以进行反编译:

apktool d bar.apk //直接解码
apktool d bar.apk -o baz //解码到baz的文件夹中

Apktool既然能够进行反编译,那也能重新编译成Apk文件:

apktool b bar // 在父目录执行building
apktool b . // 在当前bar目录执行building
apktool b bar -o new_bar.apk //  在父目录执行building并生成名为new_bar的apk文件

使用apktool大概有如下作用:

  • 查看AndroidManifest文件内容,知道每一个Activity的绝对路径,方便后面查看代码快速定位
  • 拿到完整的资源文件(assest、drawable、resouces…目录下的文件)
  • 如果我们熟悉smali语法的话,还可以将别人的代码进行修改然后重新打包成apk(比如破解会员验证)

解压后获得AndroidManifest.xml文件、assets文件夹、res文件夹、smali文件夹等。original文件夹是原始的AndroidManifest.xml文件,res文件夹是反编译出来的所有资源,smali文件夹是反编译出来的代码。注意,smali文件夹下的结构和我们的源代码的package一模一样,只不过换成了smali语言。它有点类似于汇编的语法,是Android虚拟机所使用的寄存器语言。

使用apktool版本时如果版本过低会报如下错误:

.........apktool..........
I: Baksmaling...
testI: Loading resource table...
Exception in thread "main" brut.androlib.AndrolibException: Could not decode arsc file
        at brut.androlib.res.decoder.ARSCDecoder.decode(ARSCDecoder.java:55)
        at brut.androlib.res.AndrolibResources.getResPackagesFromApk(AndrolibResources.java:315)
        at brut.androlib.res.AndrolibResources.loadMainPkg(AndrolibResources.java:50)
        at brut.androlib.res.AndrolibResources.getResTable(AndrolibResources.java:43)
        at brut.androlib.Androlib.getResTable(Androlib.java:44)
        at brut.androlib.ApkDecoder.getResTable(ApkDecoder.java:148)
        at brut.androlib.ApkDecoder.decode(ApkDecoder.java:98)
        at brut.apktool.Main.cmdDecode(Main.java:120)
        at brut.apktool.Main.main(Main.java:57)
Caused by: java.io.IOException: Expected: 0x001c0001, got: 0x00000000
        at brut.util.ExtDataInput.skipCheckInt(ExtDataInput.java:48)
        at brut.androlib.res.decoder.StringBlock.read(StringBlock.java:45)
        at brut.androlib.res.decoder.ARSCDecoder.readPackage(ARSCDecoder.java:97)
        at brut.androlib.res.decoder.ARSCDecoder.readTable(ARSCDecoder.java:82)
        at brut.androlib.res.decoder.ARSCDecoder.decode(ARSCDecoder.java:48)
        ... 8 more

解决方案是去官网下载最新版本,替换后如果发现如下错误:

.........apktool..........
SmaliDebugging has been removed in 2.1.0 onward. Please see: https://github.com/
iBotPeaches/Apktool/issues/1061

原因是最新版的apktool已将SmaliDebugging移除,我的解决方案是下载2.0.9版本。

XML文件的反编译

在apk中的xml文件是经过压缩的,可以通过AXMLPrinter2工具解开,具体命令类似:

java -jar .\AXMLPrinter2.jar .\AndroidManifest.xml > .\AndroidManifest.txt

apktool已经自带AndroidManifest.xml文件解压,所以这个工具价值不大。

classes.dex文件反编译

classes.dex是java源码编译后生成的java字节码文件。但由于Android使用的dalvik虚拟机与标准的java虚拟机是不兼容的,dex文件与class文件相比,不论是文件结构还是opcode都不一样。目前常见的java反编译工具都不能处理dex文件。Android模拟器中提供了一个dex文件的反编译工具,dexdump。用法为首先启动Android模拟器,把要查看的dex文件用adb push上传的模拟器中,然后通过adb shell登录,找到要查看的dex文件,执行dexdump xxx.dex。但是这样得到的结果,其可读性是极差的。下面介绍一个可读性比较好的工具。

  • dex2jar:把dex文件反编译为jar文件的工具。
  • JD-GUI:把jar反编译为java的工具。

反编译的步骤:

  • 从APK中提取dex文件,对APK文件解压即可得到。 将其放到dex2jar的目录下,打开cmd,运行bat classes.dex,生成classes.dex.dex2jar.jar。
  • 运行JD-GUI工具,打开上面的jar文件,即可看到源代码。

dex2jar软件包中包含的工具:

  • d2j-jar2dex:调用dx将jar转换为dex
  • d2j-jar-remap:重命名jar文件中的包(package)/类(class)/方法(method)/域(field)
  • d2j-dex2jar:将dex转换为jar
  • dex2jar:此工具已被弃用,若可能请使用d2j-dex2jar
  • d2j-jasmin2jar:将.j文件编译为.class文件
  • d2j-jar-access:增加或移除jar文件中对类(class)/方法(method)/域(field)的访问
  • d2j-asm-verify:校验jar文件中的.class 文件
  • d2j-dex-dump:将.dex或.apk文件中的数据dump至dump.jar文件中
  • d2j-init-deobf:为反混淆jar文件生成初始化配置文件
  • d2j-apk-sign:用测试证书对apk文件进行数字签名
  • d2j-jar2jasmin:反汇编jar文件中的.class文件至jasmin文件

Enjarify是由Google推出的一款基于Python3开发,类似dex2jar的反编译工具,它可以将Dalvik字节码转换成相对应的Java字节码,有比dex2jar更优秀的兼容性,准确性及更高的效率。

有时使用 JD-GUI 反编译的时候会有部分文件(Constants类即常量类)打开是 // INTERNAL ERROR // ,解决办法,使用其他Luytenjadx代替。

自动化工具汇总(一键反编译Apk)

onekey-decompile-apk

onekey-decompile-apk是先前我使用的apk反编译工具,功能比较简单,主要是apktool/dex2jar/jd-gui只需执行一步即可反编译出apk所有文件(资源文件和jar等等)。使用方法非常简单:

  • 解压缩下载的onekey-decompile-apk.zip
  • 将apk文件放到onekey-decompile-apk目录下
  • 将apk文件拖拽到_onekey-decompile-apk.bat上

由于作者长时间没有更新,其实自己可升级_tool目录下的工具,并在_onekey-decompile-apk.bat中修改成最新版软件的路径。

TTDeDroid

TTDeDroid也是一键反编译工具,支持反编译decompile apk/aar/dex/jar,主要集成了jadx/dex2jar/enjarify(不需要手动安装Python)

Google 官方:android-classysh

android-classysh是Google退出的一键反编译工具,,直接打开Apk文件,就可以看到Apk中所有的文件结构,甚至还集成了dex文件查看,java代码查看,方法数分析、导入混淆mapping文件等一系列工具。谷歌推出这个工具的目的是为了让我们开发者更清楚的了解自己的Apk中都有什么文件、混淆前后有什么变化,并方便我们进一步优化自己的Apk打包实现。比较不好的体验是无法进行导出。

Python实现的工具:Androguard

Androguard集成了反编译资源、代码等各种文件的工具包。需要安装Python环境来运行这个工具,这个工具按照不同的反编译需求,分别写成了不同的py功能模块,还有静态分析的功能。所以如果想要用Python开发一个解析Apk文件并进行静态扫描分析的服务,可以引用这个工具来实现。

Android Killer

AndroidKiller集Apk反编译、Apk打包、Apk签名,编码互转,ADB通信(应用安装-卸载-运行-设备文件管理)等特色功能于一身,支持logcat日志输出,语法高亮,基于关键字(支持单行代码或多行代码段)项目内搜索,可自定义外部工具;吸收融汇多种工具功能与特点,打造一站 式逆向工具操作体验,大大简化了用户在安卓应用/游戏修改过程中的各类繁琐工作。

GDA

GDA除了反编译外,还支持包过滤的分析功能,提供有算法工具,文件转换工具等等

Bytecode Viewer

Bytecode Viewer是一个强大的反编译工具,其集成了6个Java反编译库(包含Fernflower和CFR),Andorid反编译类库和字节码类库。

这个工具提供GUI界面,可以提升使用CFR的用户体验。

APK反编译高级篇

Android APK中的Java代码可以被反编译到什么程度主要看APK的加密程度。

  • 第一种情况:无混淆无加密无加壳。直接利用Dex2jar和JD-GUI可把源码从APK里抠出来,代码逻辑清晰,基本上做到可复用,只是资源文件的引用需要计算一下。
  • 第二种情况:混淆。通常是利用ProGuard做的防护。因为是对jar做的不可逆混淆(除非有mapping),因此不能还原成原来的代码。但是代码结构,代码逻辑一致,只要花长时间对代码进行梳理一样可找准核心代码,解密方法跟第一种一致。
  • 第三种情况:加密。以DexGuard为例。对于这种代码加密的方法,在程序运行中必定会进行解密,只要抽出它解密的逻辑便可。
  • 第四种情况:加壳。这种情况跟第三种类似。无论你怎么加壳,运行的时候必定是Dalvik可识别的Odex代码,建议直接在内存里dump出来。

Android混淆处理:ProGuard

Android SDK自带了混淆工具Proguard。它位于SDK根目录\tools\proguard下面。 ProGuard是一个免费的Java类文件收缩,优化,混淆和预校验器。它可以检测并删除未使用的类,字段,方法和属性。它可以优化字节码,并删除未使用的指令。它可以将类、字段和方法使用短无意义的名称进行重命名。最后,预校验的Java6或针对Java MicroEdition的所述处理后的码。 如果开启了混淆,Proguard默认情况下会对所有代码,包括第三方包都进行混淆,可是有些代码或者第三方包是不能混淆的,这就需要我们手动编写混淆规则来保持不能被混淆的部分。

Android中的“混淆”可以分为两部分,一部分是 Java 代码的优化与混淆,依靠 proguard 混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源。

代码混淆

  • 压缩(Shrinking):默认开启,用以减小应用体积,移除未被使用的类和成员,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未被使用的类和成员)。
  • 优化(Optimization):默认开启,在字节码级别执行优化,让应用运行的更快。
  • 混淆(Obfuscation):默认开启,增大反编译难度,类、函数、变量名会被随机命名成无意义的代号形如:a,b,c…之类的,除非用keep保护。

上面这几个功能都是默认打开的,要关闭他们只需配置对应的规则即可。 混淆后默认会在工程目录app/build/outputs/mapping/release下生成一个mapping.txt文件,这就是混淆规则,我们可以根据这个文件把混淆后的代码反推回源本的代码。原则上,代码混淆后越乱越无规律越好,但有些地方我们是要避免混淆的,否则程序运行就会出错。

资源压缩

资源压缩将移除项目及依赖的库中未被使用的资源,这在减少 apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade 文件中,将 shrinkResources 属性设置为 true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效。 资源压缩包含了“合并资源”和“移除资源”两个流程。 “合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止, gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:

  • src/main/res/ 路径
  • 不同的构建类型(debug、release等等)
  • 不同的构建渠道
  • 项目依赖的第三方库 合并资源时按照如下优先级顺序:

依赖 -> main -> 渠道 -> 构建类型

举个例子,假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。 同时,如果重复资源在同一层次出现,比如src/main/res/ 和 src/main/res2/,则 gradle 无法完成资源合并,这时会报资源合并错误。 “移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出。

DexGuard混淆(防二次打包)

DexGuard 与 Android 上主流的混淆工具 ProGuard 同属一家公司开发,但相比免费的 ProGuard 功能更多,混淆力度也更大。

  • Progurad是免费的,而且已经集成到Android ADT中了,使用起来很方便。Proguard只能保护代码,却不能保护我们的apk文件。任何人都可以使用apktool工具,反编译我们开发的apk文件,进而更改其中各种资源,或者更改部分代码,甚至是注入代码,然后再打包回apk,二次发布后,达到自己的目的。或者是加入了广告,或者是增加了恶意木马病毒等。不需要multi-dex。
  • DexGuard是收费的,DexGuard是在Proguard基础上,加入了更多的保护措施。使用DexGuard混淆后,生成的apk文件,就无法正常使用apktool反编译了。尽管还是能够反编译出部分资源文件,但是由于反编译过程不完全,就无法再打包成apk了。这样就保护了我们的apk文件,不会被二次打包发布了。代码混淆力度更大 + 资源混淆 + so加壳等。自带multi-dex扫描。

Apk文件加壳

所谓 Apk 加壳,就是给目标 Apk 加一层保护程序,把重要数据信息隐藏起来。加壳程序可以有效阻止对程序的反编译和逆向分析。Apk 壳本质的功能就是实现类加载器。系统先执行壳代码,然后将加了密的 dex 进行解密操作,再加载到系统内存中运行。

安卓 dex 加壳原理

加壳过程中主要有三个程序:

  • 需要加壳的源 Apk1(未加壳的原始应用)
  • 壳程序 APK2(用来解密并运行 apk1 程序)
  • 加密工具 (将源 APK1 进行加密,并和壳程序 APK2 的 dex 合并成新的 dex)

加壳过程:

  • 得到需要加密的 apk 和自己的脱壳程序 apk1。
  • 利用加壳工具对源 apk 进行加密。把加密后的 apk 数据写入脱壳程序的 Dex 末尾,并在文件尾部添加加密数据的大小;修改脱壳程序 Dex 头中的signature 和 file_size 头信息;合并得到新的 Dex1 文件。
  • 然后将新的 Dex1 文件替换原脱壳程序 apk1 中 dex 文件。生成新的 apk,叫做脱壳程序 apk。

脱壳过程:

  • 读取 Dex 文件末尾数据获取待脱壳加密数据长度。
  • 从 Dex 文件读取脱壳数据,解密加密数据。以文件形式保存解密的数据到 *.apk 文件.
  • 通过 DexClassLoader 动态加载 *.apk。

壳史

第一代壳:DEX 加密 (混淆技术)

  • Dex 字符串加密
  • 资源加密
  • 对抗反编译
  • 反调试
  • 自定义 DexClassLoader

第二代壳:Dex 抽取与 So 加固 (加壳技术)

  • 对抗第一代壳常见的脱壳法
  • Dex Method 代码抽取到外部
  • Dex 动态加载
  • So 加密

类抽取常规的有隐藏 dex 文件和修改 dex 结构。隐藏 dex 文件是通过对目标 dex 文件进行整体加密或压缩方式把整个 dex 转换为另外一个文件存放在 assert 文件夹中或者其它地方,然后利用类加载器技术进行内存解密并加载运行。而修改 dex 结构则是抽取 DexCode 中的字节码指令后用零去填充,或者修改方法属性等操作,运行时在内存中做修正,修复等处理工作。

关于 Dex 动态加载,就要提到 dalvik 虚拟机了。它和 java 虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。在标准虚拟机中,类加载可以从 class 文件中读取,也可以是其他二进制流,这样就可以在程序运行时手动加载 class,从而达到代码动态执行的目的。常用的有两个类:DexClassLoader 和 PathClassLoader,PathClassLoader 是安卓应用中的默认加载器。区别:

  • DexClassLoader 可以加载任何路径的 dex/dex/jar
  • PathClassLoader 只能加载 data/app 中的 apk,也就是已经安装到手机中的这个也是 PathClassLoader 作为默认的类加载器的原因,因为一般程序都是安装了,再打开,这时 PathClassLoader 就去加载指定的 apk.

第三代壳:Dex 动态解密与 So 混淆 (指令抽离)

  • Dex Method 代码动态解密
  • So 代码膨胀混淆
  • 对抗之前出现的所有脱壳法

第四代壳:arm vmp(指令转换)

vmp:用 vmp 加固后的还原过程比较复杂和困难,需要用大量的时间作分析。

壳的识别

国内提供 apk 加固的第三方技术公司有:娜迦、爱加密、梆梆加固、360加固保、百度加固、腾讯加固等。

经过加固后的apk,通过dex2jar反编译:

腾讯乐固:

360加固:

通过查看资料发现大多数加密后都会生成相应的特征 so 文件。这样就可以根据 so 来查壳。

特征 So 文件 所属加固公司
libchaosvmp.so 娜迦
libddog.so 娜迦
libfdog.so 娜迦
libedog.so 娜迦企业版
libexec.so 爱加密
libexecmain.so 爱加密
ijiami.dat 爱加密
ijiami.ajm 爱加密企业版
libsecexe.so 梆梆免费版
libsecmain.so 梆梆免费版
libSecShell.so 梆梆免费版
libDexHelper.so 梆梆企业版
libDexHelper-x86.so 梆梆企业版
libprotectClass.so 360
libjiagu.so 360
libjiagu_art.so 360
libjiagu_x86.so 360
libegis.so 通付盾
libNSaferOnly.so 通付盾
libnqshield.so 网秦
libbaiduprotect.so 百度
aliprotect.dat 阿里聚安全
libsgmain.so 阿里聚安全
libsgsecuritybody.so 阿里聚安全
libmobisec.so 阿里聚安全
libtup.so 腾讯
libexec.so 腾讯
libshell.so 腾讯
mix.dex 腾讯
lib/armeabi/mix.dex 腾讯
lib/armeabi/mixz.dex 腾讯
libtosprotection.armeabi.so 腾讯御安全
libtosprotection.armeabi-v7a.so 腾讯御安全
libtosprotection.x86.so 腾讯御安全
libnesec.so 网易易盾
libAPKProtect.so APKProtect
libkwscmm.so 几维安全
libkwscr.so 几维安全
libkwslinker.so 几维安全
libx3g.so 顶像科技
libapssec.so 盛大
librsprotect.so 瑞星

这个特点可以作为加壳厂商的特征。这样就可以得到厂商加壳的套路,脱壳也就有了破解之法。
这里提一下网上的一款 apk 查壳工具:ApkScan-PKID.其工作原理就是根据 apk 加壳后生成的特征文件来匹配加壳厂商的。

常用脱壳软件

VirtualXposed

VirtualXposed:无需root手机即可使用Xposed框架。

ZjDroid

ZjDroid是基于Xposed Framewrok的动态逆向分析模块,逆向分析者可以通过ZjDroid完成以下工作: 1、DEX文件的内存dump 2、基于Dalvik关键指针的内存BackSmali,有效破解主流加固方案 3、敏感API的动态监控 4、指定内存区域数据dump 5、获取应用加载DEX信息。 6、获取指定DEX文件加载类信息。 7、dump Dalvik java堆信息。 8、在目标进程动态运行lua脚本。

FDex2

FDex2,可以从安卓app中dump导出有用的dex文件,供后续再从dex导出jar包,jar包导出java源码,功能非常的强大。

frida

frida的原理是,通过在PC上安装Frida,手机上运行frida-server,实现PC对手机的控制,同时通过js注入的方式,将dex从“壳”里“钩”出来。它是一款基于Python的hook(钩子)工具,因此在安装它之前我们需要先配置Py环境,现在的frida仅支持3.7以下的环境,3.8以上的暂不支持。FRIDA-DEXDump

dumpDex

dumpDex是一款Android脱壳工具,需要xposed支持

drizzleDumper

drizzleDumper是一款基于内存搜索的Android脱壳工具,可以从运行中的安卓app中,利用ptrace机制,导出dex文件

参考链接:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注