作者: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的整数(右)的数组模量@0x410afde064 INT表示,2048位密钥大小由于MAT不知道如何导出的BigInteger对象,我用实际int[]参照相应的BigInteger下拉复制出来的二进制内容

也就是说,我右击同时导出其内容的int[]下拉菜单的BigInteger这个过程重复的BigInteger组件8的本地文件文件被命名为每个属性名称

这里的属性窗格和相应BigInteger对象在堆

Dump对应int[]内容

解压后的BigInteger组件下一步是检查检查是否我能够使用它们重新构造RSAPrivateCrtKeySpec所以,我决定前进之前执行两个基本测试

    从文件读取单个int值[]被倾倒和匹配他们对值在MAT
    检查所有的BigInteger组件是正数

我写了一些Java代码帮我测试的所有二进制转储针对这两个条件结果表明,第一个条件是真实所有BigInteger的组件不符合第二个条件38BigInteger的组件负值,如下图所示

这里的匹配整数二进制转储MAT(条件1

下面是(条件1)

我找遍四周确定负值的原因OpenJDK的代码中的注释表明负值可以导致正确ASN
1编码所以,我包括计算并返回相应的代码
2负BigInteger提供之前RSAPrivateCrtKeySpec构造函数

下面提供了最终Java代码读取二进制的BigInteger[])组件从文件系统中并创建RSAPrivateCrtKeyPKCS8格式

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

下一步过程是要转换的私有密钥的PEMPKCS8格式文件,然后通过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

源链接

Hacking more

...