吾爱破解安卓逆向入门教程

一、环境配置

我们需要哪些环境?(新手版)

1 java jdk: 首先我们要理解,安卓是基于java语言开发的一种应用程序,在开发时依赖于java的jdk环境,因而在逆向时也同样需要使用。

2 反编译工具:目前安卓的逆向工具也逐渐成熟,不过对于新手,我还是推荐较为傻瓜式的编译工具,如ApkIDE(改之理)及AndroidKiller两款软件,这两款软件都集成了很多功能,对于一款简单的apk,只要使用其中一款工具就可以完成修改。

到哪儿可以下载到这些工具?

1 java jdk可以访问甲骨文的官网进行下载,直接百度java jkd就可以看到官网啦

下载完成后就是安装了,安装方法我就不介绍了,常规的windows程序安装方法,一直下一步就ok了。注意期间会弹出jre运行环境的安装窗口,也可以一并安装了哦。

2 反编译工具下载:这里不引导大家去官方网站下载了,大家直接可以在吾爱的爱盘中找到相应的工具包。http://down.52pojie.cn/Tools/Android Tools/

反编译工具下载完成后解压出来就可以使用了,基本不用再设置.

二、初识 APK、Dalvik字节码以及Smali

1. apk是什么?

apk实质上是一个zip压缩包,将apk后缀修改为zip,解压之后可以看到其内部结构:

 

2. apk 的组成

assets: 资源目录1,assets 和 res 都是资源目录但有所区别:

res 目录下的资源文件在编译时会自动生成索引文件(R.java),在Java代码中勇R.xxx.yyy来引用;而asset 目录下的资源文件不需要生成索引,在Java 代码中需要用AssetManager来访问;

一般来说,除了音频和视频资源(需要放在raw或asset下),使用Java开发的Android工程使用到的资源文件都会放在res下;使用C++游戏引擎(或使用 Lua Unity3D等)的资源文件均需要放在 assets 下。

lib: so 库存放位置,一般由NDK编译得到,常见于使用游戏引擎或 JNI native调用的工程中

META-INF: 存放工程一些属性文件,例如 Manifest.MF

res: 资源目录2,

AndroidManifest.xml: Android工程的基础配置属性文件

classes.dex: Java代码编译得到的 Dalvik VM 能直接执行的文件

resources.arsc: 对res 目录下的资源的一个索引文件,保存了原工程中 strings.xml等文件内容

其他文件夹等

3. Dalvik字节码(学习破解的基础)

  • Dalvik 是 google 专门为 Android 操作系统设计的一个虚拟机,经过深度优化。虽然 Android 上的程序是使用 java 来开发的,但是 Dalvik 和标准的 java 虚拟机 JVM 还是两回事。Dalvik VM 是基于寄存器的,而 JVM 是基于栈的;Dalvik有专属的文件执行格式 dex (dalvik executable),而 JVM 则执行的是 java 字节码。Dalvik VM 比 JVM 速度更快,占用空间更少。
  • 通过 Dalvik 的字节码我们不能直接看到原来的逻辑代码,这时需要借助如 Apktool 或 dex2jar+jd-gui 工具来帮助查看。但是,我们最终修改 APK 需要操作的文件是 .smali 文件,而不是导出来的 Java 文件重新编译。

4. Smali(破解的重中之重)

  • Smali,Baksmali 分别是指安卓系统里的 Java 虚拟机(Dalvik)所使用的一种 dex 格式文件的汇编器,反汇编器。其语法是一种宽松式的 Jasmin/dedexer 语法,而且它实现了 .dex 格式所有功能(注解,调试信息,线路信息等)
  • 当我们对 APK 文件进行反编译后,便会生成此类文件。在Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
  • 原始类型:
  • B—byte
  • C—char
  • D—double
  • F—float
  • I—int
  • J—long
  • S—short
  • V—void
  • Z—boolean
  • [XXX—array
  • Lxxx/yyy—object

这里解析下最后两项,数组的表示方式是:在基本类型前加上前中括号“[”,例如 int 数组和 float 数组分别表示为:[I、[F;对象的表示则以 L 作为开头,格式是 LpackageName/objectName;(注意必须有个分号跟在最后),例如 String 对象在 smali 中为:Ljava/lang/String;,其中 java/lang 对应 java.lang 包,String 就是定义在该包中的一个对象。

或许有人问,既然类是用 LpackageName/objectName; 来表示,那类里面的内部类又如何在 smali 中引用呢?答案是:LpackageName/objectNamesubObjectName;”符号,关于“$”符号更多的规则将在后面谈到.

  • 方法的定义

Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type

注意参数与参数之间没有任何分隔符,举例如下:

hello ()V

没错,这就是void hello()。

hello (III)Z

这个则是boolean hello(int, int, int)。

hello (Z[I[ILjava/lang/String;J)Ljava/lang/String;

看出来这是String hello (boolean, int[], int[], String, long) 了吗?

  • Smali基本语法
  • .field private isFlag:z  定义变量
  • .method  方法
  • .parameter  方法参数
  • .prologue  方法开始
  • .line 123  此方法位于第123行
  • invoke-super  调用父函数
  • const/high16 v0, 0x7fo3  把0x7fo3赋值给v0
  • invoke-direct  调用函数
  • return-void  函数返回void
  • .end method  函数结束
  • new-instance  创建实例
  • iput-object  对象赋值
  • iget-object  调用对象
  • invoke-static  调用静态函数
  • 条件跳转分支
 "if-eq vA, vB, :cond_**"   如果vA等于vB则跳转到:cond_**
 "if-ne vA, vB, :cond_**"   如果vA不等于vB则跳转到:cond_**
 "if-lt vA, vB, :cond_**"   如果vA小于vB则跳转到:cond_**
 "if-ge vA, vB, :cond_**"   如果vA大于等于vB则跳转到:cond_**
 "if-gt vA, vB, :cond_**"   如果vA大于vB则跳转到:cond_**
 "if-le vA, vB, :cond_**"   如果vA小于等于vB则跳转到:cond_**
 "if-eqz vA, :cond_**"      如果vA等于0则跳转到:cond_**
 "if-nez vA, :cond_**"      如果vA不等于0则跳转到:cond_**
 "if-ltz vA, :cond_**"      如果vA小于0则跳转到:cond_**
 "if-gez vA, :cond_**"      如果vA大于等于0则跳转到:cond_**
 "if-gtz vA, :cond_**"      如果vA大于0则跳转到:cond_**
 "if-lez vA, :cond_**"      如果vA小于等于0则跳转到:cond_**

三、深入 Smali 文件

1. Smali中的包信息

  • .class public Lcom/aaaaa; (它是com.aaaaa这个package下的一个类)
  • .super Lcom/bbbbb; (继承自com.bbbbb这个类)
  • .source “ccccc.java” (一个由ccccc.java编译得到的smali文件)

2. Smali中的声明

一般来说,在Smali文件中声明如下:

# annotations

.annotation system Ldalvik/annotation/MemberClasses;
value = {
Lcom/aaa$qqq;,
Lcom/aaa$www;
}
.end annotation

//这个声明是内部类的声明:aaa这个类它有两个成员内部类——qqq和www,内部类将在后面小节中会有提及。

3. 关于寄存器的知识补充

  • 寄存器是什么意思呢?在 smali 里的所有操作都必须经过寄存器来进行:本地寄存器用 v 开头,数字结尾的符号来表示,如v0、v1、v2、…参数寄存器则使用 p 开头,数字结尾的符号来表示,如p0、p1、p2、…特别注意的是,p0 不一定是函数中的第一个参数,在非 static 函数中,p0 代指“this”,p1 表示函数的第一个参数,p2 代表函数中的第二个参数…而在 static 函数中 p0 才对应第一个参数(因为 Java 的 static 方法中没有 this 方法。

4. 寄存器简单实例分析

const/4 v0, 0x1
iput-boolean v0, p0, Lcom/aaa;->IsRegistered:Z
  • 我们来分析一下上面的两句 smali 代码,首先它使用了 v0 本地寄存器,并把值 0x1 存到 v0 中,然后第二句用 iput-boolean 这个指令把 v0 中的值存放到 com.aaa.IsRegistered 这个成员变量中。
  • 即相当于:this.IsRegistered= true;(上面说过,在非static函数中p0代表的是“this”,在这里就是 com.aaa 实例)。

5. Smali中的成员变量

  • 成员变量格式是:.field public/private [static] [final] varName:<类型>。
  • 对于不同的成员变量也有不同的指令。
  • 一般来说,获取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等。
  • 操作的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。
  • 没有“-object”后缀的表示操作的成员变量对象是基本数据类型,带“-object”表示操作的成员变量是对象类型,特别地,boolean 类型则使用带“-boolean”的指令操作。

6. Smali成员变量指令简析

(1) 简析一

sget-object v0, Lcom/aaa;->ID:Ljava/lang/String;
  • sget-object就是用来获取变量值并保存到紧接着的参数的寄存器中,本例中,它获取ID这个String类型的成员变量并放到v0这个寄存器中。
  • 注意:前面需要该变量所属的类的类型,后面需要加一个冒号和该成员变量的类型,中间是“->”表示所属关系。

(2) 简析二

iget-object v0, p0, Lcom/aaa;->view:Lcom/aaa/view;
  • 可以看到iget-object指令比sget-object多了一个参数,就是该变量所在类的实例,在这里就是p0即“this”。
  • 获取array的话我们用aget和aget-object,指令使用和上述一致

(3) 简析三(put指令的使用和get指令是统一的)

const/4 v3, 0x0
sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;
  • 相当于:this.timer= null;
  • 注意,这里因为是赋值object 所以是null,若是boolean的话,大家想应该相当于什么呢?

(4) 简析四

.local v0, args:Landroid/os/Message;
const/4 v1, 0x12
iput v1, v0, Landroid/os/Message;->what:I
  • 相当于:args.what = 18;(args 是 Message 的实例)

四、Smali函数分析

1. Smali中函数的调用

  • smali中的函数和成员变量一样也分为两种类型,分别为direct和virtual之分。那么direct method和virtual method有什么区别呢?
  • 简单来说,direct method 就是 private 函数,其余的 public 和 protected 函数都属于 virtual method。所以在调用函数时,有invoke-direct,invoke-virtual,另外还有invoke-static、invoke-super以及invoke-interface等几种不同的指令。当然其实还有invoke-XXX/range 指令的,这是参数多于4个的时候调用的指令,比较少见,了解下即可。
  • (1).invoke-static:用于调用static函数,例如:
invoke-static {}, Lcom/aaa;->CheckSignature()Z 

这里注意到 invoke-static 后面有一对大括号“{}”,其实是调用该方法的实例+参数列表,由于这个方法既不需参数也是static的,所以{}内为空,再看一个:

const-string v0, "NDKLIB"  
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

这个是调用 static void System.loadLibrary(String) 来加载 NDK 编译的 so 库用的方法,同样也是这里 v0 就是参数”NDKLIB”了。

  • (2).invoke-super:调用父类方法用的指令,一般用于调用onCreate、onDestroy等方法。
  • (3).invoke-direct:调用private函数:
invoke-direct {p0}, Landroid/app/TabActivity;-><init>()V

这里init()就是定义在TabActivity中的一个private函数

  • (4).invoke-virtual:用于调用 protected 或 public 函数,同样注意修改smali时不要错用 invoke-direct 或 invoke-static:
sget-object v0, Lcom/dddd;->bbb:Lcom/ccc;
invoke-virtual {v0, v1}, Lcom/ccc;->Messages(Ljava/lang/Object;)V

这里相信大家都已经很清楚了:

v0是bbb:Lcom/ccc

v1是传递给Messages方法的Ljava/lang/Object参数。

  • (5).invoke-xxxxx/range:当方法的参数多于5个时(含5个),不能直接使用以上的指令,而是在后面加上“/range”,range表示范围,使用方法也有所不同:
invoke-direct/range {v0 .. v5}, Lcmb/pb/ui/PBContainerActivity;->h(ILjava/lang/CharSequence;Ljava/lang/String;Landroid/content/Intent;I)Z

需要传递v0到v5一共6个参数,这时候大括号内的参数采用省略形式,且需要连续。

2. Smali中函数返回结果操作

  • 在Java代码中调用函数和返回函数结果可以用一条语句完成,而在Smali里则需要分开来完成,在使用上述指令后,如果调用的函数返回非void,那么还需要用到move-result(返回基本数据类型)和move-result-object(返回对象)指令:
const-string v0, "Eric"
invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2

v2保存的就是调用t方法返回的String字符串。

3. Smali中函数实体分析–if函数分析

  • .method private ifRegistered()Z
  • .locals 2 //在这个函数中本地寄存器的个数
  • .prologue
  • const/4 v0, 0x1 // v0赋值为1
  • .local v0, tempFlag:Z
  • if-eqz v0, :cond_0 // 判断v0是否等于0,等于0则跳到cond_0执行
  • const/4 v1, 0x1 // 符合条件分支
  • :goto_0 //标签
  • return v1 //返回v1的值
  • :cond_0 //标签
  • const/4 v1, 0x0 // cond_0分支
  • goto :goto_0 //跳到goto_0执行 即返回v1的值 这里可以改成return v1 也是一样的
  • .end method

附加知识:

1. Smali中函数实体分析–if函数分析

const/4 v0, 0x0   //vo =0;

.local v0, i:I

:goto_0

if-lt v0, v3, :cond_0     //  v0小于v3 则跳到cond_0并执行分支 :cond_0

return-void
    :cond_0                // 标签

iget-object v1, p0, Lcom/aaa/MainActivity;->listStrings:Ljava/util/List;        // 引用对象

const-string v2, "Eric"

invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z    // List是接口, 执行接口方法add

add-int/lit8 v0, v0, 0x1  // 将第二个v0寄存器中的值,加上0x1的值放入第一个寄存器中, 实现自增长

goto :goto_0                // 回去:goto_0标签

2. Smali课后习题,翻译成Java代码

.locals 4

const/4 v2, 0x1

const/16 v1, 0x10

.local v1, "length":I

if-nez v1, :cond_1

:cond_0

:goto_0

return v2

:cond_1

const/4 v0, 0x0

.local v0, "i":I

:goto_1

if-lt v0, v1, :cond_2

const/16 v3, 0x28

if-le v1, v3, :cond_0

const/4 v2, 0x0

goto :goto_0

:cond_2

xor-int/lit8 v1, v1, 0x3b

add-int/lit8 v0, v0, 0x1

goto :goto_1

五、Smali实战分析

软件:计算管家 v3.4目标:破解授权会员
目标软件验证方法:软件获取设备号号后调用支付宝进行付费,付费成功后会返回对应参数,点击授权按钮后即完成付费用户授权。
实战目标:点击授权按钮直接授权成功,小雨之前也做过3.3的分析,我的手段比较简单,适合新手

软件界面:

0x0 脱壳
本软件v3.3使用的爱加密壳,v3.4使用的360壳,不过对于那些大鸟们来说,什么壳都没用,目前我们处于smali阶段,不介绍壳的脱法,不像pc破解一上来就告诉你如何脱壳,后面阶段,将会由@PoJie_小雨 给大家讲述脱壳实战。

0x1 运行软件找关键点
我们首先来到付费页面,如下图:

开头中我已经讲过这个软件的验证流程,既然这样,我们直接点击授权按钮观察有什么关键点

软件提示我们并没有获取授权,那么这类文字通常会以文本形式写在String.xml,对应的布局文件或者以unicode形式写在smali中,对于游戏来说,这类可能是以图片形式放在图片文件,或者自定义格式的文件,甚至是通过网络获取的,我们了解之后,先尝试第一种,直接搜索
Androidkiller载入apk

嗯,直接搜索到了,减少了我们很多的工作量,那么,文本都有对应的id,我们再次搜索这个id,等等,我们好像发现了更重要的东西

我们看到了授权成功的文字,那么我们换思路,直接搜索授权成功的id

我们这里看到了这个神奇的数字,新手相信都不知道这个玩意是怎么来的,那么这里我稍微普及下,apk在编写时,开发工具都会对源代码自动编译,生成对应的标示符,通过这些标示符,来引用对应的资源,详细一点的可以阅读这篇文章
http://blog.sina.com.cn/s/blog_7b83134b01016043.html

0x2 尝试破解
我们接着搜索0x7f080100,发现两处可疑处

分别点进去看看,顺便我们也记下授权失败的标示符,0x7f080102,这里我们可以方便我们判断

这里我们看到的就是授权失败时弹出的对话框,我们找到它的标签是:cond_1,接着我们向上翻,看到哪些跳转会跳到:cond_1,我们来到方法的开始

这是个接收信息的方法,通常是由handler实现的,我们大体可以知道,在付费成功后,会想这个函数传递一个message,这个方法就是判断传入的message的值,通过这个值来判断是否付费成功,这里我们看到一个cond_3,我们找到:cond_3,

这里我们看到,获取res资源中的0x7f08011a,并转换为string类型,再传入到Alertdialog的message中,我们看看它到底调用了什么资源

看到是这个,看来跟我们破解无关,跳过,我们就直接针对这两个跳转即可

如果v1<=0或者v1>v2,则跳转到:cond_1,我们不让它跳,在pc破解中,我们有两种方法,一种是设置为相反的跳转,另一种就是nop掉,lez对应的就是ge,不过这种方法不保险,我推荐第二种,不走跳转,直接向下走if函数中的代码,我们删除这两条即可,另一个smali修改方法类似,稍有出入

接收传入的message,判断v0是否等于0x1b207,不等的话,跳转到:cond_2,我们观察到,如果跳转的话,就会越过成功提示的方法,我们不能让它跳,nop掉,下面一个跳转到:cond_1的判断也一并删除,保存,打包测试。

嗯,提示成功,那么我们测试下付费用户的功能是否能够正常使用

ok,付费主题可以使用,说明我们破解成功了,那是不是就结束了呢?当我们退出程序再进入的时候发现主题丢了,什么鬼?来到付费页面,发现授权按钮又可以点击了,之前我们注意到,授权成功后,按钮就会呈现灰色不可点击的状态,那说明了什么,这个程序有重启验证!

0x3 突破重启验证
之前我们在尝试破解时,在成功函数中看到了这段信息,

如果是付费状态,“isDefaultTheme”的值是v1,在方法首可以看到v1定义的是1,这里是个putBoolean,即通过Sharepreferences.editor写入了true,那么我们通过分析发现,我们是在重启后丢失授权信息的,那验证方法肯定在第一个显示的页面被调用,我们找到程序的主页面所在的类,来到HelloWorldActivity.smali下,我们尝试搜索“isDefaultTheme”,结果搜到好几个

[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
.method static synthetic d(Llongbin/helloworld/HelloWorldActivity;)V
    .locals 6
    .prologue
    .line 3763
    iget-object v0, p0, Llongbin/helloworld/HelloWorldActivity;->cc:Landroid/content/SharedPreferences;
    const-string v1, "alipay_appid"
    const-string v2, "89d43e35dd25de5d2ce9334f2624d6a61345893a"
    invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    move-result-object v1
    const-string v0, ""
    :try_start_0
    iget-object v2, p0, Llongbin/helloworld/HelloWorldActivity;->cb:Landroid/content/SharedPreferences;
    const-string v3, "WWxoT2JnPT0="
    const-string v4, ""
    invoke-interface {v2, v3, v4}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    move-result-object v2
    invoke-static {v2}, Llongbin/helloworld/hm;->b(Ljava/lang/String;)Ljava/lang/String;
    :try_end_0
    .catch Ljava/io/UnsupportedEncodingException; {:try_start_0 .. :try_end_0} :catch_0
    move-result-object v0
    :goto_0
    iget-object v2, p0, Llongbin/helloworld/HelloWorldActivity;->cb:Landroid/content/SharedPreferences;
    const-string v3, "font_size"
    const-string v4, "28"
    invoke-interface {v2, v3, v4}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    move-result-object v2
    iget-object v3, p0, Llongbin/helloworld/HelloWorldActivity;->cb:Landroid/content/SharedPreferences;
    const-string v4, "isDefaultTheme"
    const/4 v5, 0x0
    invoke-interface {v3, v4, v5}, Landroid/content/SharedPreferences;->getBoolean(Ljava/lang/String;Z)Z
    move-result v3
    invoke-static {v0}, Llongbin/helloworld/m;->f(Ljava/lang/String;)Ljava/lang/String;
    move-result-object v0
    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v0
    if-eqz v0, :cond_0
    const-string v0, "24"
    invoke-virtual {v2, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v0
    if-eqz v0, :cond_0
    if-eqz v3, :cond_0
    new-instance v1, Llongbin/helloworld/gb;
    const v0, 0x7f090027
    sget-object v2, Llongbin/helloworld/n;->h:[I
    invoke-direct {v1, p0, v0, v2}, Llongbin/helloworld/gb;-><init>(Llongbin/helloworld/HelloWorldActivity;I[I)V
    sget-object v0, Llongbin/helloworld/n;->h:[I
    :goto_1
    new-instance v2, Landroid/app/AlertDialog$Builder;
    invoke-direct {v2, p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V
    new-instance v3, Llongbin/helloworld/cg;
    invoke-direct {v3, p0, v0}, Llongbin/helloworld/cg;-><init>(Llongbin/helloworld/HelloWorldActivity;[I)V
    invoke-virtual {v2, v1, v3}, Landroid/app/AlertDialog$Builder;->setAdapter(Landroid/widget/ListAdapter;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;
    new-instance v0, Llongbin/helloworld/ch;
    invoke-direct {v0, p0}, Llongbin/helloworld/ch;-><init>(Llongbin/helloworld/HelloWorldActivity;)V
    invoke-virtual {v2, v0}, Landroid/app/AlertDialog$Builder;->setOnCancelListener(Landroid/content/DialogInterface$OnCancelListener;)Landroid/app/AlertDialog$Builder;
    invoke-virtual {v2}, Landroid/app/AlertDialog$Builder;->show()Landroid/app/AlertDialog;
    return-void
    :cond_0
    new-instance v1, Llongbin/helloworld/gb;
    const v0, 0x7f090026
    sget-object v2, Llongbin/helloworld/n;->g:[I
    invoke-direct {v1, p0, v0, v2}, Llongbin/helloworld/gb;-><init>(Llongbin/helloworld/HelloWorldActivity;I[I)V
    sget-object v0, Llongbin/helloworld/n;->g:[I
    goto :goto_1
    :catch_0
    move-exception v2
    goto :goto_0
.end method

比如这样的,通过观察,我们发现这些方法都只是调用了isDefaultTheme的布尔值,也就是说,这些并不会改变我们的授权状态,我们需要找到的是会更改布尔值的函数,即putBoolean,我们逐个排查,经过筛选后,我们找到了唯一一个

那其中的v2就是它经过判断后重新赋予的值,我们找到这个值是什么,向上翻

我们看到此处定义的是0,即false,那找到病因了,我们就该下药了

A.方法1:

在传值之前更改v2的值,考虑到不影响整个程序的运行,不建议修改方法开始时的v2值,我们仅在此处修改

B.方法2:
不让程序进行赋值,即跳过这个判断流程,我们找到这个函数体的标签,即:cond_3,根据尝试破解中的手段,跳过这个标签即可。

我们只找到一个,nop掉,为保证程序一定会能跳过此处,建议是nop掉if-nez v3的同时,将下方的if-eqz v0,改成goto :cond_4,这样程序就一定不会走:cond_3这个分支,isDefaultTheme的布尔值就一定会真,好,打包测试

我们退出后重开程序,依然是灰色状态,说明我们真正授权成功拉!

六、了解JAVA反编译工具

首先,我们需要了解下,安卓应用是拿什么语言开发的,毫无疑问,JAVA,那么安卓=java么,显然这不是一个等式。

总的来说Java程序和Android程序的区別在于Android程序是基于组件,基于配置的.Android 虽然使用Java语言 作为开发语言,但是在实际开发中发现,还是与Java SDK 一些不同的地方。Android SDK引用了大部分的Java SDK,少数部分被Android SDK抛弃,比如说界面部分,java.awt package除了java.awt.font被引用外,其他都被抛弃,在Android平台开发中不能使用。

上面废话了一大堆,目的就是说,安卓是用java开发的,不过部分api进行了添加修改和舍弃。那么为什么我们逆向要接触java呢,常规的我们使用apktool进行反编译之后的classes.dex文件都被转换成了smali文件,大家刚接触安卓逆向的可能都知道,smali语言晦涩难懂,比如楼主,到现在smali还是一知半解的,那么有没有什么更好的办法来帮助我们学习逆向呢,当然有,常规的逆向工具已经为我们集成了dex2jar组件,这个组件就是用来将dex文件转换成jar文件,配合神器jd-gui就可以查看java源码,直接打开jd-gui,使用内置的打开功能或者拖拽将jar包放入jd-gui中即可查看。

配合搜索功能,我们可以很轻易的到达我们需要查看的类或者搜索方法甚至是常量,在search for中我们可以设置。

那我们可不可以直接保存出这个java源码放入开发工具直接使用呢,我相信50%是可以的,不过jd-gui的还原能力当然也是有限的,大部分情况下,我们看到的源码虽说功能没有错误,但语法是有错误的,比如下面这张:

我们就讨论下那面那个return,大家玩pc逆向的可能也都清楚,程序遇到return即会跳出判断,循环等等,不会再执行下面的语句,这很明显是反编译问题,那有没有什么更加准确的反编译工具呢,当然有,JEB,绝对神器,目前我们能够免费使用的是14年版的jeb 1.5,官方已经更新到了2.0.1,有条件的可以自己去买个正版,一年1080刀,差不多近7000块钱。可以看下图对比一下反编译准确性

有没有发现这基本就像程序猿写出来的标准代码?JEB的强势之处还远不及如此,一些经过混淆的类,在dex2jar+jd-gui无法显示的时候,JEB可以做到,JEB可以解决大部分这类情况,但也有少数混淆加密过于强大,使得神器也失效。

不过需要注意的是,目前还没有任何一款工具是能够直接修改java源码的,懂得这些工具也是帮助大家分析smali语言,说到底,懂得smali才是王道,毕竟只要能反编译成功就一定能看到smali语言,而二次反编译成java是很有可能失败的。

本课只是对大家进行了解相关工具,具体实践还是得靠大家。

JEB使用入门教程:http://blog.csdn.net/u011069813/article/details/9373465
jd-gui更新地址:https://github.com/java-decompiler/jd-gui/releases
dex2jar更新地址:https://github.com/pxb1988/dex2jar/releases

 

七、了解JEB和IDA使用

距离上次的教程帖已经过去很久了,一直没心思码帖子,关键也不知道该写什么,这次给大家普及下JEBIDA的初步使用教程,具体深入的还是得靠大家自己,所谓师傅领进门,修行靠自身,在下给各位师傅请安了。
本次课程样本:http://www.52pojie.cn/thread-313869-1-2.html

0x0 jeb是什么
JEB是Android应用静态分析的de facto standard,除去准确的反编译结果、高容错性之外,JEB提供的API也方便了我们编写插件对源文件进行处理,实施反混淆甚至一些更高级的应用分析来方便后续的人工分析.(摘自乌云)
jeb凭借其牛X的保护措施和高昂的售价,使得诸多普通逆向爱好者望而却步,当然ida也是,目前网络上仅流传着jeb 1.5和ida 6.6的破解版,虽是旧版,面对很多情况依然是绰绰有余


0x1 使用jeb
第一次使用jeb一般是需要配置一下java环境的

如上图配置,JAVA_HOME根据自己的jre环境设定,之后就可以运行jeb_wincon.bat了,打开jeb后,将apk或者dex文件拖入到jeb窗体中,经过耐心的等待后,就可以看到jeb已经反编译完成了,针对一些加密的apk,apktool反编译不了的,往往jeb能够成功,但不代表所有都能成功,这也是jeb的牛逼之处,安卓加密商针对的更多的是apktool自身的漏洞,反编译完成后,我们就看到程序的smali代码了,


可以点击string查看dex中的字符串,ctrl+f就可以进行搜索了

搜索到需要的字符串后双击就可以来到smali代码处,如果看不懂smali,按下tab就可以看到java代码了,是不是很神奇?

当我们来到一个java文件后,想快速定位到一个命令调用的方法时,可以直接在这条命令上双击即可

还是很方便的吧,点击上方的后退按钮可以快速回到上一级,即返回这条命令的地方

那jeb的使用呢,差不多就这些,其它的自己琢磨吧,切记jeb是无法修改代码的哦(小提示,在代码中右键点击comment可以快速添加备注,点击rename item可以修改函数名哦,在代码被混淆时,这个非常有用)

0x2 ida是什么
交互式反汇编器专业版(Interactive Disassembler Professional),人们常称其为IDA Pro,或简称为IDA,是总部位于比利时列日市(Liège)的Hex-Rayd公司的一款产品。开发IDA的是一位编程天才,名叫Ilfak Guilfanov。
ida的强势之处在于其强大的静态分析能力,不管什么格式的文件,不管此文件是否能够正常打开,都不会影响它的静态分析,配合更加牛逼的f5插件(目前只有ida 6.5版本的插件),可以轻松的看到c的伪代码,对于看不懂汇编的人,这无疑很大程度上帮助他理解代码
PS:当然我们都是盗版用户拉,目前正版版本6.8
在安卓上面,我们使用ida主要是分析so,lua,dll此类文件,dex什么的交给Android killer这类软件即可,本次主要讲了解静态分析,不涉及动态
样本依然是鬼哥的apk文件,我们提取出其中的so文件,用ida打开,默认选项ok等待其分析完成即可

在exports选项卡中可以看到所有调用的函数,我们定位到需要分析的方法,首先我们需要知道怎么知道方法名,在编写jni的时候,每调用一个nativie方法,都会使用这类语句进行调用

public static native 类型 方法名(参数类型)

样本中如下图

那我们可以在ida中快速搜索ggPrintHello即可,回到ida,alt+t可以进行搜索方法名(如果需要搜索字符串,只需要先shift+f12切换到string选项卡,再次alt+t即可),输入后回车就可以定位到该方法,双击进入后

映入我们眼帘的肯定就是汇编代码了

那看不懂怎么办,只需要f5一下即可

是不是相当于汇编来说直观多了,当然ida也支持重命名方法名和参数类型等等

不过ida对中文的支持很不好,除了添加备注外,其余情况下基本不能添加或者修改为中文,那ida分析so的时候也是只能看不能改,怎么才能改呢,这就要借助到16进制编辑器了,定位到我们需要修改的代码

然后切换到HEX VIEW-A就可以看到16进制了,记下当前的地址,

使用如010Eiditor此类软件打开,ctrl+G来到00000BBA地址,修改16进制后保存即可,具体的进制转换自己查资料吧

八、java分析工具jadx,jeb双管齐下

原帖名为:是时候该了解下jadx了,记一次proguard混淆的app的整理笔记
个人觉得这个也偏于基础,就列为第八课吧

样本是一个不错滴可用于逆向分析时的app,叫做当前Activity,开启后可以实时显示当前运行应用的活动名和包名,当时开始整理这个App代码的起因就是了解下这个app到底如何实现这个功能的

还有个关键的原因是他非常的小,才0.1M,相信代码量也不会大,本文呢,主要是面向一些有开发基础和分析经验的小伙伴,因为不涉及native,所以难度也不大

那既然本文标题中说道是时候该了解jadx了,那jadx又是什么鬼,众所周知,在我们把smali转成java源码时通常是借助d2j+jd-gui,或者是JEB1,这两个软件都有各自的优势和缺陷,前者反编译能力较弱,抗干扰能力很弱,对于一些嵌套循环的反编译展示能力很差,后者反编译能力极强,能够代码跟踪,添加备注,方法重命名等等,相对于jd-gui,代码逻辑性较强,比较友好,但也许是因为是老版本的缘故,毕竟新版依然是2.0,部分情况下,jeb的代码结构有点烂,其次变量名不友好,都是以v0,v1_1这样展示的,需要手动修改,不太方便,那这个时候就出来一个jadx了,我们直接对比下

jd-gui效果:

结构及其混乱,友好度非常低,简直无法直视

jeb1效果:

goto是什么鬼,在java中早已摒弃了goto关键字,在c中,goto也是非常不推荐使用的,虽然逻辑感还不错,但在恢复工程时,就有点头疼了

看看我们大杀器,jadx:

强大的逻辑性,对于需要分析app的人员来说,这简直太TM棒了,优雅的代码展示效果,有意义的变量名,让人一看结构就非常清晰,然而

jadx也是有不少缺陷的:
1:稳定性不够高,依赖于jre,在反编译大型apk时,容易假死和崩溃
2:不支持中文unicode显示
3:抗干扰能力也一般,如下图

所以在分析app时,我通常的做法是jeb+jadx双管齐下

那今天主要的内容是如何恢复这个app工程

代码结构如图

很显然,作者在编译时已然选择了proguard进行混淆,proguard在默认情况下会将非四大组件(Activity,Service,Broadcast,Content Provider)的类进行重命名为abcd这类无意义的字母,从而增大app的逆向难度

这次就是要同时使用jadx和jeb工作在恢复工程,最终的展示效果

首先,当然是需要使用Android Studio来新起一个工程,如果你还在使用Eclipse开发安卓的话,就out拉,然后我们逐步对类进行恢复

我们首先要做的就是对逐个变量进行重命名,jeb派上用处了,如下图

然而我们发现在下方getBoolean处显示的是2131034112,这显然是个索引值,可以在R.java中找到,但这样就浪费了我们查找的时间,我们看看jadx

jadx在反编译时就已经帮我们查找到每个对应的索引,这个时候观察起来就非常的直观

整个恢复过程,我们需要做的就是将jeb中的每个资源文件,Manifest文件拷贝到新的工程中,基本没有什么需要修改的,准确性很高,我们主要需要做的就是理解代码作用,整理代码

这个工程没有什么难度,我挑一个这里面最难的进行分析

在分析的过程中在广播类中我们看到了一个av类,这个类引用自v4兼容包,跟踪之后

发现这里面也已经全部被混淆,这个时候只能根据自己的经验判断了,在代码中我们看到实例化了NotificationManager类,那声明了这个,就一定有一个与之对应的Notification类要出现,但我们发现,常规的Notification是引用自import android.app.Notification; 显然于我们看到的是不符合的,这个时候就需要我们去“猜”了,我们来到app文件夹下,观察是否有类似的方法

在这里我们看到了NotificationCompat类,这是一个兼容类,可以适用于较大跨度的api版本,那估计就是这个了,直接看下后面对应的方法

[Java] 纯文本查看 复制代码
1
a(context.getString(R.string.is_running, new Object[]{context.getString(R.string.app_name)}))

这里引用了一个string值,目测就是设置标题栏了,用setContentTitle进行替换,果然,没问题,接着

[Java] 纯文本查看 复制代码
1
a((int) R.drawable.ic_notification)

又是一个图标,这里目测就是显示正文处的图标了,用SetSmallIcon替换,搞定

[Java] 纯文本查看 复制代码
1
b(context.getString(R.string.touch_to_open)).b(-1960480).c(-1).a(!z);

剩下的一起丢上来,b方法调用了一个string值,那应该就是正文内容了,毕竟在观察原版app的时候,正文就是显示的一个固定字符串,不放心的也可以去看下touch_to_open对应的字符串是否与原版app的内容相同

恢复后个结果如图

基本上只要理清了所有的调用关系和逻辑关系,就能够将工程恢复出来,当然只是时间问题了

一个有意义,直观的方法,内容可以很好的帮助我们分析程序,比如在分析木马时就可以很好的理解其具体的作用

最后奉上完成工程和jadx地址

工程:https://github.com/Qrilee/WatchActivity

jadx:https://github.com/skylot/jadx

九、APP动态调试

快一年没更新教程了,期间实在是蛋疼,介于水平有限,也不知道能教什么,这次就讲讲App动态调试吧

0x0 什么是动态调试
我们常规的拿到一个App,往往是通过AndroidkillerJEB ,Jadx来进行反编译后查看源代码,通过一定的特征追踪到程序关键处,对关键处进行分析或是爆破以达到破解,逆向的目的,
静态分析比较考验一个人的分析能力和代码阅读能力,很多新手可能看到smali代码或是java代码内心是拒绝的,因为不了解程序逻辑,不知道下一步会做些什么,那这时候就需要动态调试了,当然了,动静结合才是王道,此篇教程主要讲动态。

0x1 有哪些动态调试手段
1.Idea对smali代码进行动态调试,Eclipse不管是开发还是调试我都不推荐,实在是落后的很,Android Studio是基于Idea修改而来,所以调试方法一样,还有其他一些,我没用过就不提了,Idea需要安装调试插件SmaliIdea,可以在官网下载到最新版:https://bitbucket.org/JesusFreke/smali/downloads
2.IDA对dex、so动态调试,dex是可以通过静态附加进行来进行动态调试的,但IDA对变量类型支持不太友好,所以能用Idea还是用idea吧,IDA主要用于so调试,脱壳,dump数据等等。
3.JEB动态调试,自jeb2.2以上,已经支持动态调试,并且是无缝调试,可以从java层跟踪到native层,并可以正常返回到java层,非常强大,最新版同时支持了arm64调试,奈何买不起。
4.GDB动态调试,个人对GDB的使用仅限于附加,dump,而GDB的功能远不止于此,下断,跳转等等都能做,个人理解不深,就略过了

0x2 Idea调试smali
由于是入门教程,我就讲的详细点,打开Idiea,长这样

点击Configure中的pluign,之后如下图,找到smaliidea的压缩包文件进行安装

安装完成后需要重新启动程序,找个测试样本,反编译,安利一下自己做的反编译工具箱:http://www.52pojie.cn/thread-429318-1-1.html

新建工程,配置一下jdk环境

为了调试方便,请把反编译后的smali文件夹重命名为src,把将工程目录选择为反编译的工程目录

正常的情况应该如下图:

找到Manifest下的启动类,在该类入口出点击左侧栏可以直接下断

之后找到我们需要分析的地方进行下断,我随便找了一处,如下图

之后就需要以debug模式启动App了,通过ApkTool Box可以很轻松的生成

启动后需要打开ddms,或者通过adb shell ps查看进程号,方便起见直接ddms

点击Run ,点击debug,需要配置一下,如下

完成后就可以点击Debug来附加App了,成功后如下

我们直接Ctrl + R让程序恢复运行,程序断在了下一个断点处

比如这里我们需要获取Token值,我们F6单步,顺便说一下,这些调试都是可以把寄存器添加到watch窗口的,这样可以很清楚的看到寄存器的值

看到token值会保存在v2中,我们直接选中v2右键Add to Watches,单步。。。

此处由于是涉及到支付宝登录,出现一些问题,换个app重新来吧,之前步骤略过,这里用了飘云13期教程的例子,本章节也将围绕这个App讲了,

Watches已经显示我们输入的值

调试方法已经教给大家,剩下的就是逻辑分析了,大家自行尝试

0x3 IDA动态调试
IDA动态调试通常有两种,一种直接附加App,另一种是程序运行时,通过IDA将静态分析的文件附加到进程上,通常情况下如果需要刻意分析某个程序中特定so时,则使用第二种方法,脱壳分析时,一般使用第一种

adb forward tcp:23946 tcp:23946

adb shell su -c /data/local/tmp/Android_server

adb shell am start -D -n cn.wq.myandroidtoolspro/cn.wq.myandroidtoolspro.MainActivity

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

adb forward tcp:8700 jdwp:PID

auto fp, dexAddress;
fp = fopen(“D:\\classes.dex”, “wb”);
for ( dexAddress=0x5C9C7000; dexAddress < 0x5C9C7000+0x00566FDC; dexAddress++ )
fputc(Byte(dexAddress), fp);

动态调试通常会使用到这么多指令,转发23946端口,这是andrdoid_server的端口值,以root权限启动android_server监听23946端口,以调试模式启动需要调试的App,转发程序进程的端口到8700

1.直接附加App,这里以调试某壳为例,本章不讲脱壳,脱壳部分会略过,只是讲一个调试的形式

输入上面的指令后,点击Debugger的Attach to Progress就可以看到需要的调试的进程了,如果没有显示进程,请确认android_server文件是否已经被赋予执行权限,并且以root模式启动,jdb附加后就可以正常调试so了

对关键地方下断,就可以对此处寄存器值进行修改,达到反-反调试的目的,处理完这个就可以很轻松的拿到dex

2.静态分析so后附加进程,还是以飘云的例子,反汇编so后找到导出函数

这里因为没有发现java开头的函数,那就知道是动态注册,f5 JNI_Onload函数,发现很混乱,需要导入jni.h处理一下

在Structures标签处按下键盘下的insert按钮点击添加,添加jniEnv和javaVM两个指针,修改后的代码如下

具体操作可以看飘云的视频教程:http://www.52pojie.cn/thread-507566-1-1.html

知道so有反调试后,我们可以尝试静态附加,直接断在Jni_Onload处,下断

选择debugger调试器

成功附加后,选择第二关,即会加载so文件,点击same后来到JNI_Onload处

之间静态看代码时已经看到函数首选会ptrace进程,不去除的话我们将无法调试,直接NOP掉

工具可以在这个帖子中下载:http://www.52pojie.cn/thread-506728-1-1.html

F8运行到此处,直接在16进制窗口填充进去F2即可,

此时程序已经可以正常调试了,剩下的也交给大家自行尝试吧

PS:文章中有几张截图直接引用了视频教程中的过程,本人在实验过程中遇到ida一些问题

总结:这次的教程主要也是讲个方法,具体的还是得大家自行实践,动态调试往往用于那些代码比较复杂,静态分析困难时会使用到,或者是dump一些重要数据,比如脱壳,dump游戏中的lua文件,等等

说说个人对于反调试的理解,目前主要用到的反调试手段有,检测模拟器,检测IsDebuggerConnected,检测android_server名称,检测android_server端口号,检测tracerpid,检测单步调试时间,子进程ptrace主进程,主进程fork子进程,hook fopen函数等等

除fork,hook fopen外,其余几种一般都可以在ida调试时手动去除,或者是通过写hook 插件去除,或者是直接修改系统源码来去除,看大牛写的hook插件并不困难,可以过滤到大部分反调试,最后记得调试一定要用真机,调试一定要用真机,调试一定要用真机。

关于xmsg

技术面前人人平等.同时技术也不分高低贵贱.正所谓学无大小,达者为尊.
此条目发表在逆向分析分类目录,贴了, , 标签。将固定链接加入收藏夹。