app攻防-壳&smali&脱壳实操

admin 2024年6月15日11:07:14评论3 views字数 6674阅读22分14秒阅读模式

 

首发于公号:剑客古月的安全屋

目录

  • 前言
  • smali基础知识
  • ssl https校验及双向认证
  • 脱壳方法与实操

0x01 前言

本期内容主要围绕app的壳进行展开,由于笔者的理解或者语言表达原因,可能内容会有些晦涩难懂,还请诸位看官见谅。

0x02 smali

1.smali基础介绍

什么是smali?-> Android虚拟机的反汇编语言

Android代码一般是用java编写,执行java程序就需要用到java虚拟机,但由于平台适应性和性能考虑,并没有用标准的JVM,使用了专门的Android虚拟机(5.0以下为Dalvik,5.0以上为ART)。

而在Android虚拟机上,可执行文件不是class文件,而是编译打包好后的dex文件,dex文件反编译就是smali代码。这也是为什么说smali是android的反汇编语言。

2.smali语法

在逆向时看懂smali可以提升不少效率

2.1 数据类型

Java 和 Smali 的类型对应关系和jni类似,如下所示: 基本数据类型

Smali数据类型 Java数据类型
V void
Z boolean
B byte
S short
C char
I int
J long
F float
D double

对象类型

Smali数据类型 Java数据类型
Lpackage/name/ObjectName; ObjectName
[I int[]

2.2 语法关键字

2.2 语法关键词

关键词 说明
.class 定义java类名
.super 定义父类名
.source 定义Java源文件名
.filed 定义字段
.method 定义方法开始
.end method 定义方法结束
.annotation 定义注解开始
.end annotation 定义注解结束
.implements 定义接口指令
.local 指定了方法内局部变量的个数
.registers 指定方法内使用寄存器的总数
.prologue 表示方法中代码的开始处
.line 表示java源文件中指定行
.paramter 指定了方法的参数
.param 和.paramter含义一致,但是表达格式不同

2.3 smali格式

一般来说,打开smali文件,头三行的格式如下

.class <访问权限> [关键修饰字] <类名>;
.super <父类名>;
.source <源文件名>

类的成员变量,类似于private,public

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

函数的声明格式

.method <访问权限> [关键修饰字]  <方法原型>
<.locals> <.registers> // 函数中非参数的变量的多少
[.param] // 方法参数
[.line]
<代码>
.end method

2.4 smali指令

Smali 指令有常量操作指令,方法调用指令,移位指令,分支判断指令。

常量操作指令主要是const相关指令,格式如下:

const-<类型> 寄存器, 操作数
const-string v1, "test" // 定义字符串“test”并存到 v1 寄存器中
const/16 v1, 0x1e // 定义了16位的数据常量"0x1e",并存到v1寄存器中

方法调用,以invoke开头的指令

invoke-kind {vA,vB,vC},mehtod@DDDD
//  mehtod@DDDD 表 示 函 数 的 引用;vA,vB,vC则表示函数的参数
// 其定义顺序与调用函数的参数一 一对应;kind则代表被调用的类型
// kind 主要有static、virtual、 super(被调用的函数是静态函数、正常的函数和父类函数等)

invoke-super {p0, p1}, Landroidx/Appcompat/App/AppCompatActivity;->onCreate (Landroid/os/Bundle;)V
// 调用AppCompatActivity的onCreate方法,p0是this,p1是bundle类型参数

移位指令

move v1,p1 // 将 p1 赋值给 v1

分支判断

if-[test] v1,v2, [condition] // 如果v1, v2 满足test,则跳转到condition 执行

if-ge p1, v0, :cond_0 // 如果 p1 >= v0, 则跳转到 cond_0

2.5 代码实例解读

代码解读1
const/4 v0, 0x1 //把值0x1存到v0本地寄存器
iput-boolean v0,p0,Lcom/aaa;->IsRegisterd:Z //把v0中的值赋给com.aaa.IsRegistered,p0代表this,相当于this.Isregistered=true

这段Smali代码表示的是一个方法参数的定义。让我们逐个解析它:

v0:这是一个寄存器标识符,用于表示方法内部的一个临时变量。
p0:这是一个参数标识符,用于表示方法的第一个参数。
Lcom/aaa;:这是一个类引用,指向名为aaa的包名为com的类。
->IsRegisterd:这是一个方法引用,指向名为IsRegisterd的方法。
:Z:这是方法的返回类型,Z表示布尔类型。
因此,这段Smali代码表示的是一个名为IsRegisterd的布尔类型方法,它是类com.aaa的成员方法,且具有一个布尔类型的参数。

sget-object v0,Lcom/aaa;->ID:Ljava/lang/String;
获取ID这个String类型的成员变量并放到v0这个寄存器中
iget-object v0,p0,Lcom/aaa;->view:Lcom/aaa/view;
iget-object比sget-object多一个参数p0,这个参数代表变量所在类的实例。这里p0就是this
代码解读2
const/4 v3, 0x0
sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;

相当于  this->timer=null;

代码解读3
.local v0, args:Landroid/os/Message;
const/4 v1, 0x12
iput v1,v0,Landroid/os/Message;->what:I

将args实例化为messgae,相当于 args.what = this->v1

代码解读4
.method private ifRegistered()Z
    .locals 2            // 本地寄存器的个数
    .prologue
    const/4 v0, 0x1      //v0赋值为1
    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执行
.end method
代码解读5
const-string v0, "NDKLIB"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

这段Smali代码表示的是加载一个名为"NDKLIB"的本地库。让我们逐个解析它:

  • const-string v0, "NDKLIB":这行代码将字符串常量"NDKLIB"存储到寄存器v0中。const-string指令用于将字符串常量存储到寄存器中。
  • invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V:这行代码调用了java.lang.System类的静态方法loadLibraryinvoke-static指令用于调用静态方法。方法的参数是寄存器v0中的字符串常量。Ljava/lang/String;表示方法的参数类型为java.lang.StringV表示方法的返回类型为void

综合起来,这段Smali代码的作用是调用System.loadLibrary("NDKLIB")方法,用于加载名为"NDKLIB"的本地库。

代码解读6
const-string v0, "Eric"
invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2

这段Smali代码表示的是调用Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;方法,并将结果存储在寄存器v2中。让我们逐个解析它:

  • const-string v0, "Eric":这行代码将字符串常量"Eric"存储到寄存器v0中。const-string指令用于将字符串常量存储到寄存器中。
  • invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;:这行代码调用了Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;方法。invoke-static指令用于调用静态方法。方法的参数是寄存器v0中的字符串常量。Ljava/lang/String;表示方法的参数类型为java.lang.StringLjava/lang/String;表示方法的返回类型为java.lang.String
  • move-result-object v2:这行代码将方法调用的结果对象存储到寄存器v2中。move-result-object指令用于将方法调用的返回值存储到寄存器中。

综合起来,这段Smali代码的作用是调用Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;方法,参数为字符串常量"Eric",并将返回的字符串结果存储在寄存器v2中。

0x03 ssl https校验

上节课稍微提到了证书校验这个问题。这里进行一下补充

一般来说,现在的app大多数都配置了证书校验,主要情况分为两种

单向校验-> 客户端存在校验服务端证书,服务端不存在校验
双向校验-> 客户端存在证书校验,服务端也存在证书校验,双向认证。

前者结果即简单抓不了包,但取消代理后可抓到包

后者结果即报错404,httptimeout等错误

当然前后者造成的原因还有app存在代理检测

app攻防-壳&smali&脱壳实操

借助vpn或者流量转发可进行绕过排除

但现在大多数app都采用双向校验,需要反编译app后安装证书,这个我们后期再讲

0x04 壳

1.壳的识别

我们首先需要知道壳是什么东西?什么是加固?

说简单点,就是在没有防护措施的app套一层壳,使黑客无法直接进行反编译。
说复杂点,就是对dex文件进行加密合成到壳文件中,在壳中有自定义的loader可以进行解密,可将apk的指令解密到对应的内存中

那如何识别壳呢?

如果加了壳,在直接进行反编译后的文件中会有一些特征,比如说最常见的就是看lib下是否存在特定so文件

app攻防-壳&smali&脱壳实操

识别工具像常见的pkid

app攻防-壳&smali&脱壳实操

像我这里随手查了一款apk

app攻防-壳&smali&脱壳实操

2.壳的历史

第一代壳就是最简单的dex加密

第二代壳则是第一代壳的升级版,加密对象变成了apk内的代码文件dex,并且把dex代码加密到了native层,即so文件

第三代和第四代大多都是企业版付费使用,使用了动态解密和so混淆+保护技术,难度技术较高,基本无法短时间破解,此处不再赘述,第四代更是使用了自己的虚拟机指令与解释器。

3.脱壳方法

此处仅介绍几款常用的方法

1.内存dump

目前市面上最常见的是内存dump

内存加载壳dex,壳dex加载原dex,这个时候只需要把内存中的原dex dump出来即可

当然该方法只适用于第一二代壳,三四代由于动态运行的原因从而导致内存中不太可能存在完整的dex文件

2.动态调试法

该方法依旧内核依旧是内存dump法,需要通过调试器找到dex文件在内存中完全解密的时间,然后将其dump出来

3.hook法

本质上来说跟前两种差不多,依旧适用于前两代壳

4.dex2oat法

ART 模式下,dex2oat 生成 oat 时,内存中的 dex 是完整的,此时可以用修改后的 dex2oat 文件替换原系统的 dex2oat 文件。

0x05 脱壳工具及实操

这里推荐两款,并在后文给大家做案例演示

1.blackdex

BlackDex是一个运行在Android手机上的脱壳工具,支持android5.0~12比较宽泛,无需依赖任何环境任何手机都可以使用,包括模拟器。只需几秒,即可对已安装包括未安装的APK进行脱壳。安装上该工具既使用,有点很明显,就是不用root,也不用什么Xposed、面具、frida,更不用刷机,直接就能脱壳。缺点就是针对一些运行中加载的类和方法可能就脱不到了,因为这种脱壳方法不会去运行程序。

个人不建议使用

app攻防-壳&smali&脱壳实操

2.ReFlectMaster

一体化神器,网上吹的很神,我并没有仔细看过原理,可以作为脱壳的第一选择,脱不了就用其他的脱

首先需要准备一个最好7.0以下版本的安卓机,我这里选择的是模拟器,配上root+xposed框架,我这里还用到了mt管理器

app攻防-壳&smali&脱壳实操

随后下载该工具移入模拟器下载

app攻防-壳&smali&脱壳实操

重启一下

app攻防-壳&smali&脱壳实操

模块启用

app攻防-壳&smali&脱壳实操

随后进入模块,启动后根据acitivity进行取出dex然后反编译分析

app攻防-壳&smali&脱壳实操

3.frida-dexdump

采用的frida框架

pip install frida-dexdump -i https://pypi.tuna.tsinghua.edu.cn/simple

这里还是建议使用模拟器,安卓版本较低,不容易出问题

进入python环境的安装目录xxxPython37Scripts,定位到 frida-dexdump 命令的目录

app攻防-壳&smali&脱壳实操

同目录起个cmd

pip install frida==12.8.0 -i https://pypi.tuna.tsinghua.edu.cn/simple

pip install frida-tools==5.3.0 -i https://pypi.tuna.tsinghua.edu.cn/simple

app攻防-壳&smali&脱壳实操

frida-dexdump -U -f packagename

关于脱壳命令:

  • 指定App的应用名称:frida-dexdump -U -n 保利票务
  • 指定App的应用进程ID:frida-dexdump -U -p 3302
  • 指定App的应用包名:frida-dexdump -U -f com.iCitySuzhou.suzhou001

此处给几个错误信息解决方法

1.Failed to enumerate applications: unable to handle 64-bit processes due to build configuration->fridaserver版本安装不对,请参考我前文进行配置

2.adb server version (36) doesn‘t match this client (41);killing...
-> 模拟器adb环境与本机不同,进行修改即可

这里把当前目录下所有dex文件拉入jadx即可

但这里有14个文件

app攻防-壳&smali&脱壳实操

不妨可以合并成一个dex文件进行分析,毕竟这么多拉进来肯定嘎嘎报错

import os, sys

if __name__ == "__main__":
    if len(sys.argv) < 2 :
        print("start error!start need 2 arguments!")
        sys.exit()

print(sys.argv[1])
path = os.path.split(__file__)[0] + '\'
print(path)
files= os.listdir(path) 
for file in files: 
    if file.find(".dex") > 0: 
        sh = sys.argv[1] + ' -j 1 -r -d ' + path + " " + path + file
        print(sh)
        os.system(sh)

执行命令如下

app攻防-壳&smali&脱壳实操

app攻防-壳&smali&脱壳实操

python XXXXXXXDex2Java.py D:course_studyjadx-1.4.4bin

这样做肯定也有坏处,就是源码不够全

后续将继续精进app技术,敬请关注

文章参考

https://juejin.cn/post/7216968724938195001
https://www.jianshu.com/p/9931a1e77066
https://www.isisy.com/1420.html

原文始发于微信公众号(剑客古月的安全屋):app攻防-壳&smali&脱壳实操

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月15日11:07:14
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   app攻防-壳&smali&脱壳实操https://cn-sec.com/archives/2852539.html

发表评论

匿名网友 填写信息