作者:Gursev Singh Kalra
翻译:Bincker http://t.qq.com/bincker
最近我评估了一个具有广泛的加密控制,以保护客户端服务器通信,并确保其本地存储的Android应用程序。它的源代码是完全混淆了,这两个因素相结合,逆向起来觉得非常困难。在这篇博客中,我将详细介绍如何使用Eclipse内存分析工具(MAT)和Java代码把android应用程序内存当中重构(重建)一个RSA私钥(RSAPrivateCrtKey)和dump出 X.509证书的部分细节。
使用Eclipse MAT来分析Android系统内存镜像
Eclipse MAT主要主要功能有一个Java堆分析仪,除此之外还具有识别内存泄漏方面检测时候挺广泛使用的。比如可用于识别和Android应用程序的内存转储中的敏感信息,执行一些内存取证等功能…如果你是初次尝试Android的内存分析,我建议你用这个工具,他给你带来更多的便利。你还可以使用下面的链接进行更详细的了解:
http://help.eclipse.org/kepler/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
http://eclipsesource.com/blogs/2013/01/21/10-tips-for-using-the-eclipse-memory-analyzer/
现在我们回到正题,– 目标android程序
定位加密文件(资料)
作为逆向工程过程中的一部分,我用dex2jar反编译java文件的应用程序apk文件,并开始对其进行分析。虽然应用程序逻辑和检讨其混淆代码,我偶然发现了一个java文件(com.pack.age.name.hbjava)包含实例变量SSLSocketFactory的X509TrustManager型的。显然,这个类执行重要的客户端 – 服务器通信的加密操作。
所以我顺藤摸瓜这class文件,并以识别他的源码,如加密文件的来源和所有企图带领我去的一个一个跳转地址。然后,我觉得使用Eclipse MAT直接查看应用程序堆栈。我启动应用程序,并进行一些操作,以确保应用程序加载所需的加密材料,然后执行下面的HPROF文件的步骤来创建包含应用程序堆转储。
从列表中选择应用程序正在运行的应用程序 选择“Show heap updates”选项为目标应用 选择“Dump the HPROF File”进行分析。 因为我有MAT插件安装,ADT Android的内存转储转换为HPROF格式并提交分析。如果你没安装MAT插件,你需要将生成的dump转换程序来自ADT集成的MAT插件可读格式 HPROF-CONV。dump完了堆栈后,点击“Dominator Tree”可以查看对象结构,找出通过正则过滤掉不需要的东西后具有SSLSocketFactory和X509TrustManager的实例变量的类的名称
Dump出 证书
X.509证书由不同长度的字节数组组成,且提取的证书速度也很快。
我对字节数组右击 -> navigated to Copy -> Save Value to File -> 选定的位置保存该文件并单击“完成”即可。
MAT 允许你写的char[],字符串,StringBuffer和StringBuilder的复制在一个文本文件中,但它巧妙地当前环境中处理了byte []
注意一点是 在Windows系统上,导出的文件扩展名是DER扩展名。下面的截图显示提取的证书的步骤
首先选择 “Save Value to File” 功能为
byte[]
:
下一页保存文件证书为1.der:
现在你可以看到从Android应用程序中提取的根CA证书:
提取RSAPrivateCrtKey
下面我们将看到,第二个重要的组成部分是的RSAPrivateCrtKey和提取,这是确实有点复杂。接下来咱们分步骤进行检索rsaprivatekeycrtkey:
定位组件构成的rsaprivatecrtkeyspec 复制所有组件,并将它们存储在文件系统 从这些组件计算正BigInteger值 从它的组件构造RSAPrivatecrtKeySpec 使用RSAPrivatecrtKeySpec 对象构建RSAPrivatecrtKey 按照 PKCS8 格式把RSAPrivatecrtKey 写入近系统 可选: 使用OpenSSL把PKCS8转换为PEM 从OpenSSL的PEM文件中提取公钥现在让我们看看在所涉及的细节。
从上面的第一张图像对应的第三个组成部分的一个实例RSAPrivatecrtKeySpec为出发点来构建key
选择com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.bcrsaprivatecrtkey进入MAT的配置树,MAT的支配树填充“属性”选项卡上的关的 需要建立RSAPrivateCrtKeySpec时的几个参与BigInteger的实例信息(类型,实例名称和对象引用)。
以下是参与BigInteger组件构成的rsaprivatecrtkeyspec:
modulus publicExponent privateExponent primeP primeQ primeExponentP primeExponentQ crtCoefficient
我用这个信息的BigInteger组件值分隔到不同的变量,因为它们的值复制到文件系统(见下图)。例如,crtCoefficient@0x410b0080在“属性”选项卡(左)被映射到32的整数(右)的数组。模量@0x410afde0是64 INT长表示,2048位密钥大小。由于MAT不知道如何导出的BigInteger对象,我用实际的int[]内参照相应的BigInteger下拉复制出来的二进制内容。
也就是说,我右击同时导出其内容的int[]下拉菜单下的BigInteger。这个过程重复的BigInteger组件8的本地文件和文件被命名为每个属性名称。
这里的属性窗格和相应的BigInteger对象在堆
Dump对应的int[]内容。
解压后的BigInteger组件下一步是检查,检查是否我能够使用它们重新构造RSAPrivateCrtKeySpec。所以,我决定在前进之前执行两个基本的测试。
从文件读取单个int值[]被倾倒和匹配他们对值在MAT 检查所有的BigInteger组件是正数我写了一些Java代码帮我测试的所有二进制转储针对这两个条件。结果表明,第一个条件是真实所有BigInteger的组件,但不符合第二个条件38的BigInteger的组件有负值,如下图所示。
这里的匹配整数的二进制转储对MAT(条件1)
下面是负的值(条件1):
我找遍四周确定为负值的原因和OpenJDK的代码中的注释表明负值可以导致不正确的ASN。
1编码。所以,我包括计算并返回相应的代码
2补负BigInteger值的提供值之前到RSAPrivateCrtKeySpec构造函数。
下面提供了最终的Java代码读取二进制的BigInteger([])组件从文件系统中,并创建RSAPrivateCrtKey在PKCS8格式。
import java.io.DataInputStream; import java.io.EOFException; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.security.KeyFactory; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.Security; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.ArrayList; import org.bouncycastle.jce.provider.BouncyCastleProvider; public class Generate Key { public static BigInteger bitIntFromByteArray(int[] byteArrayParam) { byte[] localByteArray = new byte[byteArrayParam.length * 4]; ByteBuffer byteBuffer = ByteBuffer.wrap(localByteArray); IntBuffer intBuffer = byteBuffer.asIntBuffer(); intBuffer.put(byteArrayParam); BigInteger bigInteger = new BigInteger(localByteArray); if(bigInteger.compareTo(BigInteger.ZERO) < 0) bigInteger = new BigInteger(1, bigInteger.toByteArray()); return bigInteger; } public static BigInteger bigIntegerFromBinaryFile(String filename) throws IOException { ArrayList<Integer> intArrayList = new ArrayList<Integer>(); DataInputStream inputStream = new DataInputStream(new FileInputStream(filename)); try { while (true) intArrayList.add(inputStream.readInt()); } catch (EOFException ex) { } finally { inputStream.close(); } int[] intArray = new int[intArrayList.size()]; for(int i = 0; i < intArrayList.size(); i++) intArray[i] = intArrayList.get(i); return bitIntFromByteArray(intArray); } public static void main(String[] args) throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, FileNotFoundException, IOException, ClassNotFoundException { Security.addProvider(new BouncyCastleProvider()); BigInteger crtCoefficient = bigIntegerFromBinaryFile("h:\\key-coeffs\\crtCoefficient"); BigInteger modulus = bigIntegerFromBinaryFile("h:\\key-coeffs\\modulus"); BigInteger primeExponentP = bigIntegerFromBinaryFile("h:\\key-coeffs\\primeExponentP"); BigInteger primeExponentQ = bigIntegerFromBinaryFile("h:\\key-coeffs\\primeExponentQ"); BigInteger primeP = bigIntegerFromBinaryFile("h:\\key-coeffs\\primeP"); BigInteger primeQ = bigIntegerFromBinaryFile("h:\\key-coeffs\\primeQ"); BigInteger privateExponent = bigIntegerFromBinaryFile("h:\\key-coeffs\\privateExponent"); BigInteger publicExponent = bigIntegerFromBinaryFile("h:\\key-coeffs\\publicExponent"); System.out.println("crtCoefficient\t" + crtCoefficient); System.out.println("modulus\t" + modulus); System.out.println("primeExponentP\t" + primeExponentP); System.out.println("primeExponentQ\t" + primeExponentQ); System.out.println("primeP\t" + primeP); System.out.println("primeQ\t" + primeQ); System.out.println("privateExponent\t" + privateExponent); System.out.println("publicExponent\t" + publicExponent); RSAPrivateCrtKeySpec spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient); KeyFactory factory = KeyFactory.getInstance("RSA", "BC"); PrivateKey privateKey = factory.generatePrivate(spec); System.out.println(privateKey); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); FileOutputStream fos = new FileOutputStream( "h:\\key-coeffs\\private-pkcs8.der"); fos.write(pkcs8EncodedKeySpec.getEncoded()); fos.close(); } }
PEM转换PKCS8
下一步过程是要转换的私有密钥的PEM从PKCS8格式文件,然后通过OpenSSL命令生成的私钥与公钥:
openssl pkcs8 –inform DER –nocrypt –in private-pkcs8.der –out privatePem.pem openssl rsa –in privatePem.pem –pubout
这里是OpenSSL的转换的PKCS8:
最后,我们输出的RSA私钥:
我们可以使用OpenSSL从privatePem.pem文件中提取的公钥:
结论
内存分析是一种强大的技术,可以用来识别和提取运行应用程序的敏感信息。然后,可以使用所提取的信息来搞定客户端的安全控制机制。
原文:http://blog.opensecurityresearch.com/2013/10/extracting-rsaprivatecrtkey-and.html
作者:Gursev Singh Kalra