安卓逆向之Native Library

admin 2024年2月9日16:33:12评论21 views字数 2785阅读9分17秒阅读模式

No.0

前言

在安卓开发中, 处于对性能或者安全性的角度来考虑, 开发者可以编写一种称为本地库的文件(Native Library). 这些库文件通常用c/c++来编写, 编译后以.so作为后缀名, 可以为java层提供接口, 让我们可以在安卓开发中的java代码里面调用这些c/c++编写的函数.

这些文件在编译打包成apk后, 会被放入APK的lib子目录中.

No.1

实例

现在有一个安卓应用, 这个应用可以计算两个数的和, 但是点击计算后弹出需要我们付费完整版

安卓逆向之Native Library

点击去激活, 提示要输入激活码, 我们随便输入内容, 显示用户名或者序列号错误

安卓逆向之Native Library

我们开始分析这个软件, 首先使用Android Killer打开这个软件. 我们在smali/com/example/myapplication文件夹下面发现了LicenseFragment.smali, 和他的子文件 显而易见的这个就是这个软件的验证界面

安卓逆向之Native Library

然后我们打开这几个文件, 在LicenseFragment$1.smali中有一个onClick的事件处理函数, 这里就是验证页面上的那个按钮

安卓逆向之Native Library

这两行用来获取两个输入框的内容, 也就是用户名和序列号

安卓逆向之Native Library

这里调用了checkserial方法, 把返回值放到p1(112行), 下面的if-nez语句判断p1也就是checkserial的返回值是否为0. 如果不为0就跳转到:cond_1, 否则执行124行, 124行的unicode字符解码之后为注册成功. 那么就表示当checkserial返回0的时候就表示注册成功.

安卓逆向之Native Library
安卓逆向之Native Library

我们尝试在全部代码中找checkserial函数, 发现在LicenseFragment.smali文件中声明了checkserial函数, 但是声明语句中有一个native, 也就表示这个函数是位于native library中的.

安卓逆向之Native Library

继续查看LicenseFragment.smali文件, 发现这个Fragment确实导入了一个名为native-lib的文件, 根据命名规则导入的文件应该为libnative-lib.so.

安卓逆向之Native Library

我们在lib文件夹下查看, 发现有几个文件夹对应了不同的平台, 每个平台文件夹下面都有libnative-lib.so的文件, dalvik虚拟机会根据不同平台加载不同的库文件

安卓逆向之Native Library

我们打开arm64-v8a下面的libnative.so, 这个系统架构也就是arm64, 是现在安卓系统的架构.

使用ghidra打开这个文件, 在左上角的函数列表我们看到了几个可疑的函数, 第一个是Java_com_example_myapplication_LicenseFragment_checkserial 这个函数毫无疑问就是我们的checkserial函数, 第二个是strcmp函数, 我们可以猜测这个函数是来比较序列号的.

安卓逆向之Native Library

所以我们从strcmp入手, 发现这个strcmp函数正好被checkserial引用了, 那么直接跳转过去

安卓逆向之Native Library

到这里之后, 发现这里确实在比较字符串并且做判断. 我们分析一下这段汇编代码

安卓逆向之Native Library

在分析代码之前, 我们补充一点arm汇编的语句

· mov 移动指令

· bl 分支链接 相当于x86汇编中的call

· ldr 从内存中取出值放到寄存器中

· ldur 跟ldr作用大致相同, 但是ldr需要字节对齐, ldur不需要

· b.cc b是跳转指令, 后面可以跟cc写条件, 比如b.ne当不相等的时候跳转

· ldp 同时取两个值到两个寄存器

· add 加法指令

· csetm 根据后面指定的条件, 如果满足条件给寄存器置为1

下面我们分析一下这段关键代码

mov          x0,sp 把sp(栈指针)移动到x0mov          x1,x19 把x19移动到x1bl           ::strcmp 调用strcmp, 那么x0和x1就是他的两个参数ldr          x8,[x23 , #0x28 ] 从x23偏移0x28处取值到x8cmp          w0,#0x0 比较w0是否等于0 (w0存的是上面函数返回值)ldur         x9,[x29 , #local_48 ] 从x29偏移#local_48处去一个值csetm        w0,ne 这里就是根据上面cmp比较结果来置位, 如果上面cmp比较不相等(不等于0)就给w0全部置1, 也就是-1. 如果为0, 就置0也就等价于 如果上面strcmp返回0, 那么这个函数返回0, 如果返回值不为0, 这个函数返回-1cmp          x8,x9 比较x8, x9是否相等, 也就是比较canaryb.ne         LAB_0010083c 如果不相等就跳转
ldp          x20 ,x19 ,[sp, #local_10 ]ldp          x22 ,x21 ,[sp, #local_20 ]ldp          x29 =>local_40 ,x30 ,[sp, #0x20 ]ldr          x23 ,[sp, #local_30 ]add          sp,sp,#0x60retLAB_0010083c:bl           ::__stack_chk_fail x8不等于x9就发生了栈溢出, 终止程序

经过上面分析, 发现checkserial 返回0还是其他就取决于csetm w0,ne这一条语句, 我们只需要修改这条语句, 让他给w0赋值为0就可以注册成功了

修改该语句为mov w0, #0 这样函数的返回值就永远为0, 那么就可以注册成功

安卓逆向之Native Library

导出文件, 然后覆盖掉源文件

在Android Killer中重新编译安装apk

安卓逆向之Native Library

再次随便输入, 然后点击检查按钮, 显示注册成功, 软件修改完成

安卓逆向之Native Library

No.2

注意

需要注意的是如果是用的x86版本模拟器的话, 例如Android Studio的模拟器, 或者是一些Android x86镜像, 我们需要修改x86_64下的libnative-lib.so

使用ghidra打开这个文件, 现在就是x86下的汇编

安卓逆向之Native Library

我们分析下这段判断代码

MOV          RDI ,RSPMOV          RSI ,R14CALL         <EXTERNAL>::strcmp rdi和rsi就是strcmp的两个参数NEG          EAX 把返回值取反, 会对CF标志位做出影响SBB          EAX ,EAX 如果eax为0, 那么cf=0 这条语句执行后也为0, 如果eax不为0 cf为1, 那么执行后eax为-1MOV          RCX ,qword ptr FS:[0x28 ] 取canaryCMP          RCX ,qword ptr [RSP  + local_28 ] 判断是否有栈溢出JNZ          LAB_00100889 如果有栈溢出 退出程序ADD          RSP ,0x28POP          RBXPOP          R12POP          R14POP          R15RETXOR          EDX ,EDXTEST         SIL ,0x1JNZ          LAB_0010080eJMP          LAB_00100834CALL         <EXTERNAL>::__stack_chk_fail

这里设置返回值为0还是-1, 关键的语句就是sbb eax, eax 这条语句与cf有关, 那么我们修改该语句为正常的sub语句, 就能保证eax一直为0

安卓逆向之Native Library

导出文件并覆盖源so, 然后再Android Killer中编译

运行程序尝试注册, 发现注册成功

安卓逆向之Native Library

No.3

原文始发于微信公众号(隐雾安全):安卓逆向之Native Library

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月9日16:33:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   安卓逆向之Native Libraryhttp://cn-sec.com/archives/2210323.html

发表评论

匿名网友 填写信息