您的位置:首页 > 理论基础 > 计算机网络

dx.jar部分类的用法——以检测https劫持漏洞为例

2014-10-19 15:12 302 查看
本文原载,欢迎转载,转载请注明出处(好像大家都喜欢这么写)。
在ADT的build-tools的目录下,有一个叫做dx.jar的包,这个包可以解析dex文件(目前用到的就是这么多),但是在网上一搜都没看到什么相关接口的资料,只能自己慢慢看(老大给了我很多帮助),算是稍微会了一些相应的接口调用,总结总结,也给需要的朋友一些帮助。





1.为什么要用dx.jar

因为它能解析dex文件,所以用它来获取dalvik指令,而这样就可以对整个dex文件进行扫描,发现一些应用程序的漏洞神马的。由于自己也是初学者,所以就挑了一个简单的https劫持漏洞,漏洞原理可以看下面几篇文章:
http://security.tencent.com/index.php/blog/msg/41
http://www.programmer.com.cn/15036/
简单来说就是使用自己的类覆盖了google官方默认证书检查的类,以免自己的证书会报异常,而在检测服务器是否可信的函数中却没有进行安全处理,比如某银行网银安卓客户端:



还有一种情况就是直接信任来自所有服务器的证书,来自同一个客户端:





2.制定检测方案

了解了漏洞的形成原理,就可以有针对性的进行检测。思路如下:
(1).由dx.jar包中的函数获得dex文件中的所有类
(2).由于有两种原因导致的漏洞,所以分两部分检测,首先检测是否信任所有服务器的证书,也就是有没有setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)这行代码。这个只要检测每个函数中的代码是不是有SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER字符串就可以了。
(3).第二部分则是通过检测checkServerTrusted函数是不是有throw这条语句,因为无论如何,只要验证服务器端不可信,便会抛出,只要没有throw抛出异常的代码,那便可以判断肯定存在问题。

3.调用dx.jar解析dex

3.1获得dex对象

首先是利用dex类解析dex文件,而dex又有多个构造函数,如下所示:





我这里是先用zipfile进行解压获得class.dex的File对象,然后作为参数传入,其实直接传入apk文件的路径也是可以的,因为它会帮你进行解压缩,代码如下:

/* */ public Dex(File
file)
/* */ throws IOException
/* */ {
/* 103 */ if (FileUtils.hasArchiveSuffix(file.getName()))
{
/* 104 */ ZipFile zipFile = new ZipFile(file);
/* 105 */ ZipEntry entry = zipFile.getEntry("classes.dex");
/* 106 */ if (entry
!= null) {
/* 107 */ loadFrom(zipFile.getInputStream(entry));
/* 108 */ zipFile.close();
/* */ } else {
/* 110 */ throw new DexException("Expected
classes.dex in " + file);
/* */ }
/* 112 */ } else if (file.getName().endsWith( ".dex"))
{
/* 113 */ loadFrom(new FileInputStream(file));
/* */ } else {
/* 115 */ throw new DexException( "unknown
output extension: " + file);
/* */ }
/* */ }
3.2获取dex基本信息

由于检测方案基于静态扫描,所以解析了dex文件之后就要获得文件中所有类的定义、所有类型名称以及所有的字符串信息,代码如下:

Dex dex = new Dex(zipFile.getInputStream(entry));
zipFile.close();
//获取所有类
Iterable<ClassDef> all_class = dex.classDefs();
//获取dex文件中所有类型名
List<String> type_names = dex.typeNames();
//获取dex文件中所有字符串
List<String> dex_strings = dex.strings();
3.3调用检测漏洞的函数

获得X509TrustManager和SSLSocketFactory类的类名,然后判断是否有这两个类的子类,如果是就将这个类传入相应的处理函数进行进一步的处理,返回值则是有漏洞的类路径:

//获取X509TrustManager和SSLSocketFactory类所在的索引,以便后面进行对比
int x509_idx
= type_names.indexOf("Ljavax/net/ssl/X509TrustManager;" );
int sslsocket_idx
= type_names.indexOf("Lorg/apache/http/conn/ssl/SSLSocketFactory;" );

for(ClassDef
one_class : all_class)
{
int super_type_idx
= one_class.getSupertypeIndex();
//对所有的类进行检查
if(super_type_idx
== sslsocket_idx)
vulnerable_method.addAll( sslHostnameVerifier(one_class, dex, type_names,
dex_strings));

if(super_type_idx
== x509_idx)
vulnerable_method.addAll( x509TrustVerifier(one_class, dex, type_names,
dex_strings));
}
3.3验证是否信任所有服务器——sslHostnameVerifier函数

首先是获取类的所有函数,同时还有ALLOW_ALL_HOSTNAME_VERIFIER字符串的索引用于后面进行比对:

HashSet<String> vulnerable_method = new HashSet<String>();
//获取类数据
ClassData data = dex.readClassData(one_class);
//获取所有方法
Method[] all_method = data.allMethods();
int verifier_idx = dex_strings.indexOf( "ALLOW_ALL_HOSTNAME_VERIFIER" );
然后从每个函数中获取全部代码并由decodeAll解码成一个指令数组,这个数组的每一项就是一句smali代码,分别对应这不同的指令格式:





for(Method method :
all_method)
{
//获取方法的所有代码并转化成instructions
Code code = dex.readCode(method);
DecodedInstruction[] instructions = DecodedInstruction.decodeAll(code.getInstructions());
for(DecodedInstruction
instruction : instructions)
{
if(instruction
== null)
continue;
//获取指令在dex文件中的索引
int field_idx
= instruction.getIndex();
try
{
//根据field_idx获得nameIndex并和之前的verifier_idx进行比较
int field_string_idx
= dex.nameIndexFromFieldIndex(field_idx);
if(field_string_idx
== verifier_idx)
{
int method_string_idx
= dex.nameIndexFromMethodIndex(method.getMethodIndex());
//将出现漏洞的类添加到返回值中
vulnerable_method.add( type_names.get(one_class .getTypeIndex())
+ "->" + dex_strings.get(method_string_idx));
}
}
catch(Exception
e)
{
continue;
}
}
}
这样就找到了有漏洞的函数了。

3.4验证checkServerTrusted是否有thow语句

主要流程是差不多的,有所不同的是这里添加了对于函数是否为checkServerTrusted的判断:

int method_string_idx = dex.nameIndexFromMethodIndex( method.getMethodIndex());
String method_name = dex_strings.get(method_string_idx);
//判断函数是否是checkServerTrusted
if(method_name.indexOf("checkServerTrusted" )
== -1)
continue;
如果是,则获得函数中所有代码并检查是否有throw语句,而这条语句的指令可以在这里找到,可以看到其指令数值为39:







最后就是判断部分了:

for(DecodedInstruction instruction
: instructions)
{
if(instruction
== null)
continue;
if(instruction.getOpcode()
== 39)
{
is_throw = true;
break;
}
}
//如果checkServerTrusted函数中没有throw代码,则肯定存在漏洞
if(!is_throw)
vulnerable_method.add(type_names.get(one_class.getTypeIndex()) + "->" +
method_name);

4.总结与不足
这次接触到的dx.jar里面的函数还只是一小部分,以后用到了会再多总结总结。上面提到的检测http的例子,其实还是有一些特殊情况检测不到的,比如不一定只有继承了X509TrustManager才能覆盖里面的checkServerTrusted函数,在实例化的时候可以通过将checkServerTrusted函数作为参数传递进去,还有一种情况就是并没有直接使用ALLOW_ALL_HOSTNAME_VERIFIER,而是传入一个继承自HostnameVerifier的类,而这个类里面的verify函数并没有验证,而是直接返回空:









可能也还有更多的情况没有考虑进去,因为刚接触这方面,所以大家看了有什么宝贵意见还望批评指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: