【移动安全基础篇】——28、Apk加固

1. java  混淆

1)  名称替换
proguard:
                -printmapping map.txt
                -applymapping map.txt
dex2jar:
                d2j-init-deobf –f –o map.txt x.jar
                d2j-jar-remap –f –c map.txt –o x2.jar x.jar
jeb:
                script
2)  字符串加密

3)  反射替换

4)  其他
日志清除:Log.x
XML  混淆:清除 string 信息
Assert  加密:Hook AssertManager.open 方法

5)  Demo 演示
执行了恢复脚本后的效果对比,左为混淆版本,右为恢复版本

恢复脚本:

2. ELF  内存加载
加载 so 文件的函数,so 文件在 assert 文件夹中

so 文件是被加密的,只能看到几个段。因为文件格式不正确,所以需要先将文件格式进行修复。

修改后拖到 IDA 里就不会报错了。

因为 JNI_OnLoad 是被加密的,程序的执行首先会在 init_array 处。

程序在执行的时候只用到了程序头部表和各个段,节区头部表没有被用到。有时候节区头部表会被删掉,如果要恢复的话就需要用到链接视图中的相关信息。

对流程混淆的 so 文件进行恢复

动态调试 so 文件

设置断点

不过由于该 app 中存在反调试,所以调试出错,接下来换另一种方法

手动对 so 文件的该部分进行修改,将其改成死循环。然后对该文件重新打包并签名将该 app 安装之后直接运行并用 ida 进行 attach

程序停在了所修改的地方,然后将其改回去

可以进行动态调试,在解密函数处下个断点,加密方式是 RC4

使用脚本将解密后的部分 patch 出来

打开 patch 后的 so 文件,可以看到代码正常

在这三个函数中,前两个是为了防止 dump,后面一个是对程序的 upx 段进行解码

对其中的加密字符串进行还原

接下来使用动态跟踪的方式对 RC4 操作进行跟踪

整个 upx 的内存段已经被解密了,将其直接 dump 下来

对其文件头进行修改,使其能够被识别

使用 ida 打开后发现该 so 文件依旧是被加密的

此处的 dlopen 实现内存加载在 dlopen 处下好断点

将解密后的数据 patch 出来,将该文件用 ida 打开后,代码已经完全被还原,得到了原始的 so 文件

3. Dex  整体加密
1)  Dex 整体加密方案

替换 classloader 时机:
               Application:attachBaseContext()
              ContentProvider:OnCreate()
              Application:OnCreate()
加载原始 application:
              获取原始 application 的 class name
              加载原始 application 并生成对象
              替换 API 层的所有 Application 引用
              设置 baseContext 并调用原始 application 的 OnCreate()

旧版本中存在的是 DexClassLoader

DexFile 的结构体

得到 DvmDex  结构之后就能够获取原始 Dex  文件
加载 dex 文件操作

2)  Demo 

StubApplication 继承自 Application,通过 StubApplication.interface()释放 so 文件

此处注册了两个 native 方法的函数

先执行 classLoader,然后再执行 interface7()函数,该函数更改了 ContentProvider,也更改了 Application 和 Context 的一些相关引用。stubApplication 的 OnCreate()函数执行结束时,内存中的 Application 的对象(ClassLoader)都指向了新的 Application,与 stubApplication 没有关系了。取 获取 dex  明文将脱壳后的 so 文件替换之前 apk 文件中的 so 文件,并在 so 文件执行时设置了断点。

启动监听端口,然后进行端口转发,然后将目标 app 安装并调试运行

修改入口点

memory 起始地地方和大小

根据 dex 文件头部数据信息 dump 出原始 dex 文件。

4. Dex  方法隐藏
1)  Dex  文件

文件头结构

将被隐藏的方法索引值改为 0,或者将方法的属性改成 native 并将 codeoffset 改为 0

样本中的修复相关信息
2)  Demo  演示

构造函数已经被隐藏了,显示的为 native 方法

初始化功能的函数 StartShell()

StartShell(packageName, iIndex)函数

InitStartShell()函数

native 的 load()方法

找到 fixDvm()函数

该函数中通过内存映射,得到 dex 文件数据

FixDexData 结构

根据修正得到以下数据

FixMethod 函数中根据 MethodID 得到相关字段

修复效果对比

5.  常见的 Anti  手段
1)  java

通过监听这两个函数来进行反调试
绕过手段:定位到相关函数并修改
2)  native

  • ptrace  自身进程:同一时间进程最多只能被一个调试器进行调试
  • 检查父进程名称:由调试器启动时,被调试时的二进制文件的父进程为调试器
  • 态 检查进程运行状态:attach 时被调试进程父进程不再是调试器,此时检查
  • /proc/self/status TracerPid 的值,如果被调试状态 TracerPid 的值为调试进程的 Pid
  • 设置程序运行最大时间:程序调试时运行时间往往大于正常运行时间
  • 防 防 dump:关注/proc/self/mem 和/proc/self/pagemem,检查是否被 dump
  • 检查调试器进程 程:系统中有无调试器进程存在
  • 完整性校验
  • 断点检测
  • 单步检测

3)  emulator

  • 属性检测:Build.BRAND、Build.DEVICE、IMEI、IMSI
  • 虚拟机文件检测:/system/bin/qemu-props、/system/lib/libc_malloc_debug_qemu.so、
  • /sys/qemu_trace
  • 于 基于 cache  行为检测

如果是在模拟器中的 modify 生效,在真实机器中 modify 无效基于代码指令执行检测:

真实机器的 a 值为中间值,模拟器中可能会被合并为一句,a 值为最终值
4)  res

在 string 池的尾部加上一个相同 name 的字段指向一个指定的索引,在 resource 池中对应索引的位置加上不存在的一个 ID,然后在 apk 重打包后会多出来一个属性。

总会有不期而遇的温暖. 和生生不息的希望。
原文地址:https://www.cnblogs.com/devi1/p/13486418.html