在对APP进行安全检测和渗透测试的过程中,常会遇到APP采用一些安全防护措施,测试人员需要绕过这些安全防护措施才能开展后续的测试工作,例如环境安全检测、传输数据加密等。Hook技术可用来改变程序的执行流程,在做APP分析时可用来绕过APP的各种安全限制,便于测试人员对APP进行深入的分析,本文介绍了在Android环境Hook技术的几个典型应用场景,所有Hook代码都基于Xposed框架实现:

  1. Hook修改函数返回值,绕过Root检测。
  2. Hook替换函数内容,绕过HTTPS安全校验。
  3. Hook获取函数输入参数,分析APP加密方法。
  4. Hook定位关键函数,模拟挖掘业务层面安全漏洞。

Hook过Root检测

在测试APP时,常会遇到APP启动时对运行环境进行安全检测,如果系统被Root或者为模拟器运行,就会进行安全告警。有的告警只是提示风险,用户确认后可继续运行APP,有的告警在用户确认后就会直接关闭APP,测试人员也就无法继续进行后续的测试,如下图所示。

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员

这里列举了一些Root环境检测的代码,主要还是针对系统中是否存在提权用的工具进行检测,例如su文件、Superuser.apk等,一旦检测到系统中存在相应的工具,APP就会退出。

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_02

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_03

通过观察代码示例,发现安全检测的操作都放到了一个函数中,这里如果Hook实现安全检测的函数,在函数调用后修改返回值,便可以绕过安全检测。下面编写一个Demo App进行Hook测试,在App启动入口activity的onCreate函数中调用check()函数进行Root环境校验,检验不通过APP退出。

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_04

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_05

下面基于xposed框架编写Hook插件,修改isRoot函数的返回值,绕过Root校验。这里使用xposed的findAndHookMethod API找到要Hook的关键函数,重写afterHookMethod修改函数的返回值,便可达到绕过Root检测的目的:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_06

将插件编译安装后,再次运行APP已无弹框告警,在Logcat日志中可以看到Hook回调函数执行并有打印输出。

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_07

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_08

 

当然,如果确定知道Root检测的目标文件,使用adb进入Android系统,直接将检测的文件更名也可以绕过。

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_09

 

Hook绕过证书校验

APP与后台交互采用HTTPS方式进行通讯,且APP对服务器进行了一些校验,会导致测试人员无法通过BurpSuite等工具拦截数据包进行分析。已经有安全研究人员基于xposed框架编写了工具,针对APP使用SSL的各种证书校验进行HOOK绕过,例如:https://github.com/Fuzion24/JustTrustMe。但在有些情况下,APP对源代码进行了混淆或者自定义实现各种安全检查接口函数,直接使用JustTrustMe插件就不行了,需要分析代码然后Hook绕过。举个例子,下面的示例代码实现了访问”https://www.baidu.com”,并返回首页内容:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_10

如红线所示,代码设置了两个安全检查措施,一个是在发起HTTPS请求时,自定了TrustManager来验证服务器证书,一个通过setHostnameVerifier设置了回调函数来检查域名。先看验证服务器证书,这里重写了checkServerTrusted函数,检查证书链,判断服务器证书公钥是否和本地存储证书公钥相同:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_android_11

再一个验证证书中签发的域名是否为指定的域名:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_12

这里加了验证措施后,一旦使用HTTPS中间人攻击,证书校验就无法通过,比如使用BurpSuite时就无法建立SSL连接。分析JustTrustMe的源码,是支持绕过这两个地方的安全检测的,这里用到了xposed的replaceHookedMethod API接口,直接Hook并替换javax.net.ssl.HttpsURLConnection.setSSLSocketFactory和javax.net.ssl.HttpsURLConnection.setHostnameVerifier两个函数,把两个函数的内容替换为空,如下图所示。

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_13

但测试发现,这里直接用原版的JustTrustMe是无法绕过检测的,上面的Hook的位置看上去是没错的,为什么不行呢?以设置HostnameVerifier为例,看一下主机名校验时抛出的异常:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_14

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_15

发现这里实际上用到的是com.android.okhttp.internal.http.HttpsURLConnectionImpl而非javax.net.ssl.HttpsURLConnection,找到源码看到HttpsURLConnectionImpl是HttpsURLConnection的派生类,并且重写了setHostnameVerifier函数,所以在APP程序里调用setHostnameVerifier实际上调用的是com.android.okhttp.internal.http.HttpsURLConnectionImpl的setHostnameVerifier函数,所以JustTrustMe中Hook并替换 javax.net.ssl.HttpsURLConnection.setHostnameVerifier自然是无效的:

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_16

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_17

HttpsURLConnectionImpl中重写了setHostnameVerifier函数:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_18

再看com.android.okhttp.internal.http.HttpsURLConnectionImpl.setHostnameVerifier函数实现,这里的delegate.client.setHostnameVerifier(hostnameVerifier),实际上会调用OkHttpClient的setHostnameVerifier函数:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_19

所以我们只要hook并替换 com.android.okhttp.internal.http.HttpsURLConnectionImpl.setHostnameVerifier或com.android.okhttp.OkHttpClient.setHostnameVerifier都能起到效果,改完之后的Hook代码就变成:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_android_20

这里还可以直接对APP程序中的verify函数下手,因为最终需要调用这个函数进行验证,把它替换掉也可以。这里我们将APP打包,然后取出dex文件反编译观察,源代码中的verify函数实际是在com.example.logindemo.info$1$2.smali文件中,这里的$号表示内部类,我们要把verify内容替换为空,参考JustTrustMe的方法,编写hook代码:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_21

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_22

针对checkServerTrusted的绕过,方法相同,这里就不再赘述。

Hook 分析加密方法

前面的两节用到了afterHookMethod修改结果,用replaceHookedMethod替换方法,还有一个常用的beforeHookedMethod方法,用于在Hook目标函数之前做一些操作。例如我们在分析代码时找到了加密用到的函数,可以使用beforeHookedMethod得到密密钥和要加密的明文,下面的代码使用com.example.logindemo.AESUtils类的encrypt函数对关键信息进行了加密:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_23

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_24

现在我们想知道APP在调用encrypt传入的参数是什么,可以利用beforeHookedMethod:

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_25

当APP运行到被hook的encrypt函数时,就会在logcat中将encrypt传入的参数打印出来:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_26

在实际测试中,如果无法还原APP加密算法,但是可以定位加密函数的位置,就可这种Hook的方法,直接修改加密函数的传入参数,测试越权、SQL注入等漏洞。

无源码Hook找关键函数

上面的示例分析,都是在已知源码的情况下,分析要Hook的函数,但实际测试中很多APP采用了加固技术,测试人员不一定能够逆向得到源码,对测试人员对APP进行分析增加了难度,在这种场景下,采用Hook技术查找关键函数调用也是一种可以达到测试目的的方法。这里以Hook加密函数为例,使用到了xposed框架的xserver组件(https://github.com/monkeylord/XServer),根据关键字查找加固APP运行时用调用的加解密函数,通过hook的方式修改在对关键参数加密前进行修改。

下面的例子中,APP登录时会向后台提交HTTP请求,根据用户ID查询用户的信息,APP对用户ID进行了加密,无法直接修改,如下图所示:

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_27

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_28

对搜索到的加密函数都进行HOOK,然后操作APP登录,这时Hook到的encrypt函数被调用,xserver的页面显示被Hook函数传入的参数,其中有一个就是登录的用户名admin,函数返回值和HTTP请求中的user_id一致:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_sed_29

这里可以利用xserver调用加密函数,将admin改为其他用户名other,得到新的加密user_id:

检测到android系统以userdebug模式编译构建 检测到该应用在hook_android_30

检测到android系统以userdebug模式编译构建 检测到该应用在hook_测试人员_31

Xserver返回加密后的值:

 

检测到android系统以userdebug模式编译构建 检测到该应用在hook_android_32

将加密后的值替换请求中的user_id,即可返回其他用户的信息(模拟越权):

检测到android系统以userdebug模式编译构建 检测到该应用在hook_android_33

 

全文完。

本文涉及的源码地址:https://gitee.com/lezi123/test-hook。