Android逆向学习笔记3

课程来源:bilibli,视频链接:Android逆向学习(自学专用版)

第三阶段:业务APP安全漏洞分析

Android客户端常见安全漏洞:

  1. 组件/控件安全
    1. Android组件安全
    2. Activity界面劫持
    3. Backup备份安全
    4. Java调试开启安全
    5. 本地拒绝服务安全
    6. WebView远程代码执行安全
  2. 数据安全
    1. WebView密码明文保存漏洞
    2. 本地数据全局读写漏洞
    3. 界面敏感数据显示泄露
    4. 调试日志泄露
    5. 内置账号/IP泄露
  3. 业务安全
    1. HTTP/HTTPS中间人攻击
    2. 数据封包弱加密
    3. 手机验证码机制安全
    4. 服务器权限绕过
    5. 用户敏感信息泄露
    6. 手势密码绕过
    7. 会话保持机制安全
    8. 服务器请求重放攻击

组件/控件安全

Android有四大组件(都可以在AndroidManifest.xml文件中查看):

  1. Activity界面

    对于程序中的每一个界面都是一个Activity,每个界面都会有不同的功能,同时界面的切换需要满足一定条件(如果能够绕过条件进行切换,则会有安全隐患)

  2. Service服务

    Service服务伴随程序启动,一直在后台运行,主要是起到检测作用的执行代码。服务一般用于时刻检测客户端的更新状态、是否异地登录、上传用户的操作信息。

  3. ContentProvider数据

    用于保护和获取数据,并使其对所有应用程序可见,这是不同应用程序之间数据传输的唯一方式

  4. Broadcast广播

    当前程序检测到外界的某种运行环境发生变化时执行的逻辑代码,比如程序的自启、网络变化、实时消息(打车软件)

四大组件的访问权限控制主要是由android:exported属性进行控制,true表示可以对外进行访问或者是用;false表示不可。

  • 当Activity的exported设置为true时,可以在免Root环境下直接打开,如指令adb shell am start -n 包名/Activity名

Backup备份安全

Android API Level 8 及以上的安卓系统提供了为应用程序数据的备份和恢复功能。此功能的开关位于应用程序中AndroidManifest.xml文件中的allowBackup属性值,其属性值默认是true。当其为true时,用户可以通过adb backupadb restore来执行对应用数据的备份和恢复,这可能会带来一定风险。

备份指令:

adb backup -nosystem -noshared -apk -f com.xx.xxxx F:\Restore

还原指令:

adb restore -f F:\Restore -apk com.xx.xxxx

Java调试开启风险

Java调试的开启是指在客户端开发时,主配置文件AndroidManifest.xml中设置了Debuggable的属性为true,会产生以下风险:

  • jdb调试:获取和篡改用户敏感信息,甚至分析并修改代码实现的业务逻辑,例如窃取用户密码,绕过验证码防护等。
  • Release和Debug的调试:这两个模式下,程序运行着两种不同的逻辑流程,如
    • Debug走程序中的内测流程,使用内网IP,Log调试日志全开;
    • Release下则走线上发布的版本。

Android的本地拒绝服务漏洞

拒绝服务:用户使用不了APP,APP产生崩溃、假死等。

通常情况下程序A和程序B是平行运行的,互不干扰,但在某些情况下会产生影响,如程序A有漏洞,程序B可以攻击:

  1. 程序A读取公共区域的数据,程序B把公共数据修改了, 程序A在读取时出现解析错误导致崩溃;
  2. 程序A的Android组件有暴露的情况,可以被程序B任意的调用,程序B在恶意调用A时传递恶意参数,从而导致A崩溃。

但是有一种情况是不能称为漏洞的:当程序B拥有Root权限时,可以直接杀死进程A,此时不能视为A有漏洞。

Android提供了Intent机制来协助应用间的交互和通讯,Intent负责对一次操作的动作、动作涉及的数据进行描述,系统根据Intent描述来调用相应的Activity、Service和BroadCast等组件,完成组件调用。如果程序没有对Intent.getXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,就会导致攻击者可通过向受害者应用发送此类空数据、异常或者畸形数据来使该应用崩溃,简单的说就是通过intent发送空数据、异常畸形数据给应用,会导致其崩溃。

程序崩溃有以下四种情况(通过查看日志了解是哪种报错):

image-20221020162553655

有一款工具可以查看程序对外暴露了哪些组件,并测试是否存在有崩溃的可能:超级拒绝服务漏洞检测工具。

WebView常见安全漏洞点

WebView是APP的内置浏览器,其可能存在的问题:

  1. 当用户登录后,用户的登录状态怎么从原生控件转到WebView能识别到的Cookie

    通常情况下,原生的用户状态是token,但是WebView使用的是Cookie,二者需要进行转换,会存在风险,如:

    1. 转换时使用的是HTTP;
    2. 转换的时候忽略了token,只认id,就容易出现越权漏洞。
  2. WebView有很多设置点,如果设置不准确就会出现问题,容易出现问题的有:

    1. setSavePassword
    2. addJavaScript
  3. WebView加载的页面的源代码容易暴露:这些代码都是在前端完成的, 而前端的代码只能混淆不能加密,因此存在暴露的风险

  • WebView密码明文存储漏洞

    (mWebView.getSettings().setSavePassword(false))

    在使用WebView过程中忽略了WebView的setSavePassword,当用户选择保存在WebView中输入的用户名和密码,则会被明文形式保存到应用数据目录的databases/webview.db中。

  • WebView忽略SSL证书验证错误漏洞

    (handler.proceed())

    WebView组件加载网页发生证书认证错误时,该方法调用了handler.proceed()来忽略认证错误的问题

  • WebView远程代码执行漏洞

    漏洞成因: Android系统为了方便应用中Java代码和网页里的Javascript代码进行交互,在WebView控件中实现了addJavascriptInterface接口,网页中的Javascript代码可以利用该接口定义的名字调用Java层的代码。而Java对象继承关系会导致很多Public的函数(其中包括getClass)都可以在JS中访问,所以JS可以通过该接口利用Java的反射机制获得系统类的函数,绕开Webview所在的代码上下文。 该漏洞曾在乌云上风靡一时!然而,该漏洞已一去不返,目前只存在Android4.4.2 (API:17)及以下。

本地保存数据安全分析

  • 检测APP客户端的隐私数据的工具:MT管理器、RE管理器,但是手机需要root
  • APP本地保存数据的隐私路径在/data/data/packagename
  • 敏感数据的保存的关键文件夹:
    • shared_prefs:内部保存的都是xml文件,有可能保存用户信息、账号信息、设备信息等
    • databases:SQL数据,一般保存联系人信息、聊天记录、多账号登录信息等
    • files:保存开发者自定义开发的一些数据
    • app_webview:保存的是WebView控件产生的数据,可能会有cookie
**本地数据全局读写漏洞**

APP在创建数据库时,将数据库设置了全局的可读权限,攻击者恶意读取数据库内容,获取敏感信息;

在设置数据库属性时如果设置全局可写,攻击者可能会篡改、伪造内容,可以能会进行诈骗行为,造成用户财产损失。

以下为一种实现方式的漏洞代码样例:

try {
	SQLiteDatabase rdb = openOrCreateDatabase("all_r_db", Context.MODE_WORLD_READABLE, null);
	SQLiteDatabase wdb = openOrCreateDatabase("all_r_db", Context.MODE_WORLD_WRITEABLE, null, null);
} catch(SQLiteException e) {
	e.printStackTrace();
}

正确做法:应该是使用MODE_PRIVATE模式创建数据库 **App本地数据保存需要关注的点**

  • App客户端本地需要保存哪些数据,不能保存哪些数据
    • 密码的相关数据 (x)
    • 会话保持相关的数据。cookies/token/session(需要加密)
    • 个人隐私数据
    • 敏感的个人信息
  • App客户端本地保存的数据应该存档的路径、文件的格式
    • SharePerefence格式/Sqldb数据库格式
    • 私有目录下/SD卡路径下(不能使用SD卡保存数据)
  • App客户端本地保存数据的形式
    • 明文/密文
    • 对称算法/非对称算法

Fiddler

一个抓包的工具:Fiddler,该工具的好处:

  • 直接截获HTTP/HTTPS支持各种查看格式,比较友好;
  • 手机端发出的HTTP/HTTPS,从而调试程序;
  • 截获HTTP/HTTPS后,可以任意修改Request或者Responce

使用该工具必要的条件:安装Fiddler的机器,需要跟安卓手机在同一个网络里,否则手机不能把HTTP发送到fiddler的机器上来

image-20221020200144023

有一种组合可以抓取95%以上的APP网络请求数据方式(均为手机APP):Xposed + JustMePlush + Postern

  • Xposed: Patch系统函数的框架,可以在这个框架上自定义各种上帝模式的插件,可以修改系统、干涉其他进程(hook等);
  • JustMePlush: 是一个Xposed插件,可以对系统验证SSL证书的函数、okHttps框架证书校验的函数进行Patch;
  • Postern: 是设置系统中全局代理的工具,类似于VPN。

如果在抓包的时候发现没抓到(或者为灰色),在JustMePlash多点击几次,然后重启模拟器,重新抓包。

剩下的5%:

  1. 不支持模拟器运行不支持Root .不支持Xposed等等 换真机,然后在真机上配置Xposed+JustMeplush+Post.ern… 不支持Root: 隐藏Root 不支持Xposed: 隐藏Xposed
  2. 双向校验: 在APP包体内找证书文件:.p12 .pfx后缀证书文件; 找证书的安装密码

网络协议数据包弱加密检测

数据加密方式:

  1. 网络请求数据整体加密Qm get/post的数据是一个单独的太长串,这就整体加密 即:加密函数(username=admin&paswword=123456) —> 一个大长串。
  2. 参数的分别进行加密 username=加密函数( admin)&password=加密函数(123456),然后传到服务器

  3. sign的加密 MD5(usecname=加密函数( admin)&password=加密函数(123456)) ==== sign的值 为了防止黑客单独修改内部某个参数的值。

对于APP客户端来说,包括应用程序(Android/IOS/Html5/小程序)最常见的算法:

  1. 对称算法: AES DES 3DES PEB等,ECB/BCB模式; 在做算法逆向的时候,最重要的是找到key/IV
  2. 非对称算法:(公钥加密,私钥解密)RSA等。一般公钥在APP内
  3. 哈希算法: MD5 SHA-1 SHA-256等, 一般做验签、验证使用。
  4. 国密算法: SM3, SM4等

对于APP的算法逆向,重心是找key

有一个逆向工具:RECalcTool,该工具可以计算哈希值、编码解码、解压、加密算法解密等等

所谓的弱加密是指的其加密过程为国际通用算法,且调用的函数为系统函数。APP网络协议封包普遍存在弱加密易解密的主要原因是:Key/IV/CER等密钥的硬编码。 客户端App弱加密算法通用检测方案

➢Xposed框架: inspeckage Inspeckage是-一个为Android应 用程序提供动态分析的工具。通过应用hook到Android API的功能,Inspeckage将帮助您了解Android应用程序在运行时做什么。

  • 本地数据
  • 网络传输数据
  • 通用算法hook
  • 自定义方法hook
  • ……

➢注入框架: Frida Frida是一款基于python + java的hook框架可运行在Android/iOs/Windows/等各平台,主要使用动态二进制插桩技术。

验证码机制

手机短信验证码

手机验证码通常作为个敏感操作过程的二次校验因子。

  • 双重校验的登录:验证密码+验证手机验证码
  • 大额转账的时候:支付密码+手机验证码

手机验证码的机制的安全性,可以从以下几个方面来说:

  1. 无限请求。对向服务器请求下发验证码的时候,是否可以无限制次数的重复请求。如短信炸弹!

    防范机制:在60秒之内只能向同一个手机号发送一条短信;

  2. 验证码重复使用。

  3. 验证码是否是真正随机。

    看是否存在测试的验证码,比如说测试用的验证码时1497。

    假如验证码的生成与时间戳有关系,我向服务器发送请求验证码—->服务器返回:时间戳+手机验证码—–>用户输入验证码,发送请求:时间戳+手机验证码。用户只需要将时间戳和验证码对应就行了

    关于这一条,其实时间戳+手机号+手机验证码可能可以解决(个人的想法,将这三者结合计算一个哈希值之类的作为消息传送)

  4. 手机验证码返回本地。服务器将验证码明文或者验证码的MD5返回APP。

  5. 验证码的错误次数是否有限制。(防止爆破)4位验证码不超过5分钟爆破。

  6. 手机验证码的其他绕过手段:如修改false/true,APP的界面强制跳转。

图形验证码
  1. 图形验证码必须要是服务器下发。本地生成图形验证码是可以通过网络抓包绕过。
  2. 图形验证码的刷新问题。图形验证码要在用户操作失败的时候及时更新,比如任何情况下的登录失败,都必须要更新图形验证码。
  3. 图形验证码的识别性。

Xposed框架hook插件编写

编写准备(直接在AndroidStudio编写就行)

  1. 需要将XposedBridgeAPI放到工程中(libs文件夹下)

  2. 在加入的位置右键,选择Add As Library

  3. 修改build.gradle

    1. 删除implementation files('lebs\\XposedBridgeApi.jar')
    2. implementation fileTree改成provided fileTree
  4. AndroidManifest.xml中修改配置,使得Xposed知道这个是一个框架

    在application中添加meta-data片段

    <meta-data android:name="xposedmodule" android:value="true"/>			<!-- 表示编写的是Xposed插件 -->
    <meta-data android:name="xposeddescription" android:value="sample"/>	<!-- Xposed插件内容描述 -->
    <meta-data android:name="xposedminversion" android:value="54"/>			<!-- Xposed插件支持的最低版本 -->
    
  5. 编写hook,以下是一个样例(部分)

public class xposeds implements IXposedHookLoadPackage{
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        String TAG = "Xposedhook";
        String packname = loadPackageParam.packageName;  // 获取当前加载的包名
        Log.e(TAG, "启动的APP:"+packname);
        if(packname.equals("com.example.vaoqian.mytext")){  // 当加载的是我们要hook的包时进行操作
            Log.e(TAG, "hook successfully! start");
            XposedHelpers.findAndHookMethod("com.example.vaoqian.mytext.MainActivity",  // 要hook的Activity
                                           loadPackageParam.classLoader,
                                           "isOK",  // 要hook的函数名
                                           String.class,  // 函数的参数
                                           String.class,  // 函数的参数
                                           new XC_MethodHook() {
                                               // hook操作函数
                                               // 示例中是要获取用户名和密码,而这两个作为参数传递到isOK函数中,因此在before中操作
                                               @Override
                                               protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                                                   // 在目标函数调用之前执行的操作
                                                   super.beforeHookedMethod(param);
                                                   String account = (String) param.args[0];
                                                   String password = (String) param.args[1];
                                                   Log.e(TAG, "用户输入的用户名:"+account);
                                                   Log.e(TAG, "用户输入的密码:"+password);
                                               }
                                               
                                               @Override
                                               protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                                                   // 在目标函数调用之后执行的操作
                                                   super.afterHookedMethod(param);
                                                   // 模板获取正确的用户密码示例
                                                   Class clazz = para.thisObject.getClass(); // 获取当前运行的类
                                                   Field username = clazz.getDeclaredField("ACCOUNT"); // 获取ACCOUNT对象
                                                   Field password = clazz.getDeclaredField("PASSWORD");
                                                   username.setAccessable(true); // 设置对象可以访问
                                                   passworc.setAccessable(true);
                                                   
                                                   String usernameS = (String) username.get(param.thisObject); // 类型转换
                                                   String passwordS = (String) password.get(param.thisObject);
                                                   
                                                   Log.e(TAG, "username:" + usernameS + "password:" + passwordS);
                                                   
                                                   // 也可以直接设置函数的返回值
                                                   param.setResult(true);
                                               }
                                           });
        }
    }
}
  1. 告诉Xposed框架自己编写的hook类的位置:src -> main -> assets下写一个初始化,xposed_init文件,在里面写上自己刚刚写的类的路径,如:com.xposeds

© 2022. All rights reserved.

Powered by Hydejack v7.5.2