当‘EFBFBD’和它的朋友相遇:研究字符数组转换字符串
2017-01-10 14:38
351 查看
你正在进行另一项令人兴奋的android应用评估,这个app存在了很多漏洞,你恐怕开发团队需要重新设计整个应用,而不是去修复这些让你想摧毁的bug,特别是当你遇见下面的加密密钥出现在你的输出栏中
加密密钥:
EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
现在你的头部倾斜成一个相当准确的22度角,兴奋和欢悦的心情转变成了好奇心。为什么这个加密密钥会有这么多’EFBFBD’的字符串?你的Substrate调式代码出现了输出错误?开发人员在生成密钥时做了什么误操作?还是说你确定输出的加密密钥是用来保护应用数据的?
你可能已经猜到了,上述情况是发生在一次真实的app应用评估中。虽然我们没有应用源代码,但是我们能够反编译APK。通过结合使用自定义的Sbustrate制造的钩子运行在我们评估的应用中,可以筛选出相应的反编译出来的混淆代码,从结果来看,我们确定我们的调式是正确的,上面的字符串确实是加密密钥。下一步就是查看应用程序的密钥生产过程的逻辑步骤,在这里我们找到的罪魁祸首。
为了你们不用看反编译的混淆代码那么吃力,我们花了点时间写了一个差不多的小应用程序,让我们来看看是否可以确定加密密钥中’EFBFBD’字符串模式出现的真正原因。
你发现问题了吗?如果没有,不要担心,我们将揭秘一切。
总体而言,这个方法是使用用户的密码算出加密密钥。这个加密密钥用于保护应用程序保存在手机中的隐私信息。首先,传入用户的密码、盐值,迭代数(2000)和导出密钥的长度(256位)来初始化PEBKeySpec对象。然后使用PBKDF2算法加密存入theKey字节数组中。到目前为止,至少到下一语句之前操作都是没有问题的。看起来是字节数组中的加密密钥转换为字符串时出现了重复‘EFBFBD’模式的操作。
让我们在示例应用中插入一些调式语句,来查看字符串转换前和字符串转换后加密密钥有没有发生变化。由于加密密钥包含不可输出的字符,我们使用自己编写的‘bytesToHex’方法将其转换为十六进制,然后输出出来观看。
运行我们的应用程序,结果表明我们存储在字符数组中的加密密钥是正确的,转换后加密密钥就出现了重复‘EFBFBD’的情况,以下是程序的输出:
Key (Before Casting):
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
Key (After Casting):
EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
到了现在,我们可以确定将字符数组转换为字符串会使加密密钥产生奇怪的‘EFBFBD’模式。所以,这里可以联系到一个问题,‘EFBFBD’到底是什么意思?
查看Android文档:“平台默认字符集是UTF-8。(以前的默认字符集取决于用户的环境)”在字符集是UTF-8的情况下,十六进制的‘EFBFBD’表示替换字符。简而言之,替换字符用于表示其值为未知或者不可表示的字符。这意味着每个‘EFBFBD’映射到原始密钥在字符串转换后无法表示的一个字节(或二个十六进制位置)。为了简单说明,我们将加密密钥上的‘EFBFBD’使用‘**’表示:
转换前:
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
转换后:
!!******603466**7B**6C24E2B2AA576A******0C6B********76**********
我们现在可以轻松地看到在字符串转换后无法表示的字节在哪里。需要注意的是‘!!’我们人为加上去的,并不是转换的字符串,用于表示字符串的开头。结果表示原始加密密钥转换的第一个字节并不能在安卓中显示。
字符数组转换字符串-不同的操作系统
在发现Android上字符串转换出现异常后,我们很好奇如果在不同的操作系统上运行类似的java程序,结果会怎么样。
我们选择在window下进行实验,下面为我们使用的示例程序。注意,我们添加了调式语句来识别正在使用的字符集。根据java文档,“默认字符集是在虚拟机启动期间决定的,通常取决于底层操作系统的区域设置和字符集。”
在Windows8.1(x64)操作系统上使用Eclipse运行上面的代码,运行结果如下:
默认字符集:
windows-1252
加密密钥(转换前):
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
加密密钥 (转换后):
B7B0F83F603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
虽然看起来加密密钥转换前和转换后是一模一样的,其实它们还是不一样的。在转换前的第四个字节是‘8D’,而转换后的第四个字节是‘3F’。这里发生了什么?
在这种特殊情况下,底层操作系统的默认字符集是“Windows-1252(或CP-1252)”.根据MSDN技术文档可知,Windows下‘81’,‘8D’,‘8F’,‘90’和‘9D’被保留,未定义其值。所以,在字符串转换后,原始密钥的‘8D’转换为‘3F’,‘3F’代表的是‘?’字符。
我们总结了下不同平台下转换同一个加密密钥的结果会发生什么样的情况:
加密密钥 (转换前的字符数组):
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
Android4.3 加密密钥 (转换后):
EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
Windows 8.1 (转换后):
B7B0F83F603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
Ubuntu 12.04 (转换后):
EFBFBDEFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
MacOS X 10.10 (转换后):
EFBFBDEFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
虽然我们花了很多时间走到这一步,我们这样做的目的…毕竟,事实胜于雄辩。正如我们所看到的,在加密操作下进行字节数组转换为字符串的结果都是无法预测的,无法进行正确的转换。从加密密钥的角度来看,‘EFBFBD’(或者‘3F’)可能导致熵的损坏。从加密数据的角度来看,从字节数据替换为字符串时,它改变了数据的内容。这些都不是你达到的目的。
简而言之,如果可以的话,我们要避免将字节数组转换为字符串的行为。现在大多数的加密方法/函数都能接受字节数组作为参数,并不需要进行字节数组转换为字符串的操作。如果你一定要在处理加密操作时进行字节数组转换字符串的话,可以考虑将字节数组的内容转换为Base64编码字符串或者其十六进制等效字符。
希望这边博文能够让你们了解到在运行程序时对加密算法执行验证的重要性。当然如果只是查看反编译/反汇编代码的话,很有可能就会忽略掉这些难以识别的安全问题。但是我们可以通过钩子钩住方法或者调试的方法进行动态分析程序,这样程序中的加密操作出现的安全问题就能更容易被我们发现。
参考来源
[1]http://developer.android.com/reference/java/nio/charset/Charset.html
[2]http://www.fileformat.info/info/unicode/char/0fffd/index.htm
[3]http://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html#defaultCharset()
[4]http://en.wikipedia.org/wiki/Windows-1252
[5]http://msdn.microsoft.com/en-us/goglobal/cc305145.aspx
翻译自:https://blog.gdssecurity.com/labs/2015/2/18/when-efbfbd-and-friends-come-knocking-observations-of-byte-a.html
加密密钥:
EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
现在你的头部倾斜成一个相当准确的22度角,兴奋和欢悦的心情转变成了好奇心。为什么这个加密密钥会有这么多’EFBFBD’的字符串?你的Substrate调式代码出现了输出错误?开发人员在生成密钥时做了什么误操作?还是说你确定输出的加密密钥是用来保护应用数据的?
研究安卓应用源代码
你可能已经猜到了,上述情况是发生在一次真实的app应用评估中。虽然我们没有应用源代码,但是我们能够反编译APK。通过结合使用自定义的Sbustrate制造的钩子运行在我们评估的应用中,可以筛选出相应的反编译出来的混淆代码,从结果来看,我们确定我们的调式是正确的,上面的字符串确实是加密密钥。下一步就是查看应用程序的密钥生产过程的逻辑步骤,在这里我们找到的罪魁祸首。
为了你们不用看反编译的混淆代码那么吃力,我们花了点时间写了一个差不多的小应用程序,让我们来看看是否可以确定加密密钥中’EFBFBD’字符串模式出现的真正原因。
public static String genKey(){ PBEKeySpec localPBEKeySpec = new PBEKeySpec( getPassword().toCharArray(), getSalt(), 20000, 256); try { byte[] theKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") .generateSecret(localPBEKeySpec).getEncoded(); return new String(theKey); }catch (InvalidKeySpecException e) { e.printStackTrace(); }catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; }
你发现问题了吗?如果没有,不要担心,我们将揭秘一切。
总体而言,这个方法是使用用户的密码算出加密密钥。这个加密密钥用于保护应用程序保存在手机中的隐私信息。首先,传入用户的密码、盐值,迭代数(2000)和导出密钥的长度(256位)来初始化PEBKeySpec对象。然后使用PBKDF2算法加密存入theKey字节数组中。到目前为止,至少到下一语句之前操作都是没有问题的。看起来是字节数组中的加密密钥转换为字符串时出现了重复‘EFBFBD’模式的操作。
让我们在示例应用中插入一些调式语句,来查看字符串转换前和字符串转换后加密密钥有没有发生变化。由于加密密钥包含不可输出的字符,我们使用自己编写的‘bytesToHex’方法将其转换为十六进制,然后输出出来观看。
public static String genKey(){ PBEKeySpec localPBEKeySpec = new PBEKeySpec( getPassword().toCharArray(), getSalt(), 20000, 256); try { byte[] theKey = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”) .generateSecret(localPBEKeySpec).getEncoded(); Log.i(_TAG, “Key (Before Casting): ” + bytesToHex( theKey ) ); return new String(theKey); } ..snip.. }
public static void test() { String theKey = genKey(); Log.i(_TAG, “Key (After Casting): ” + bytesToHex(theKey.getBytes())); }
运行我们的应用程序,结果表明我们存储在字符数组中的加密密钥是正确的,转换后加密密钥就出现了重复‘EFBFBD’的情况,以下是程序的输出:
Key (Before Casting):
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
Key (After Casting):
EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
字符数组转换字符串-Android
到了现在,我们可以确定将字符数组转换为字符串会使加密密钥产生奇怪的‘EFBFBD’模式。所以,这里可以联系到一个问题,‘EFBFBD’到底是什么意思?
查看Android文档:“平台默认字符集是UTF-8。(以前的默认字符集取决于用户的环境)”在字符集是UTF-8的情况下,十六进制的‘EFBFBD’表示替换字符。简而言之,替换字符用于表示其值为未知或者不可表示的字符。这意味着每个‘EFBFBD’映射到原始密钥在字符串转换后无法表示的一个字节(或二个十六进制位置)。为了简单说明,我们将加密密钥上的‘EFBFBD’使用‘**’表示:
转换前:
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
转换后:
!!******603466**7B**6C24E2B2AA576A******0C6B********76**********
我们现在可以轻松地看到在字符串转换后无法表示的字节在哪里。需要注意的是‘!!’我们人为加上去的,并不是转换的字符串,用于表示字符串的开头。结果表示原始加密密钥转换的第一个字节并不能在安卓中显示。
字符数组转换字符串-不同的操作系统
在发现Android上字符串转换出现异常后,我们很好奇如果在不同的操作系统上运行类似的java程序,结果会怎么样。
我们选择在window下进行实验,下面为我们使用的示例程序。注意,我们添加了调式语句来识别正在使用的字符集。根据java文档,“默认字符集是在虚拟机启动期间决定的,通常取决于底层操作系统的区域设置和字符集。”
public static String genKey(){ PBEKeySpec localPBEKeySpec = new PBEKeySpec( getPassword().toCharArray(), getSalt(), 20000, 256); try { byte[] theKey = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”) .generateSecret(localPBEKeySpec).getEncoded(); System.out.println(“Key (Before Casting): ” + bytesToHex( theKey ) ); return new String(theKey); } ..snip.. } return null; } public static void main(String[] args) { System.out.println(“Default Charset: ” + Charset.defaultCharset().name()); String theKey = genKey(); System.out.println(“Key (After Casting): ” + bytesToHex(theKey.getBytes())); }
在Windows8.1(x64)操作系统上使用Eclipse运行上面的代码,运行结果如下:
默认字符集:
windows-1252
加密密钥(转换前):
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
加密密钥 (转换后):
B7B0F83F603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
虽然看起来加密密钥转换前和转换后是一模一样的,其实它们还是不一样的。在转换前的第四个字节是‘8D’,而转换后的第四个字节是‘3F’。这里发生了什么?
在这种特殊情况下,底层操作系统的默认字符集是“Windows-1252(或CP-1252)”.根据MSDN技术文档可知,Windows下‘81’,‘8D’,‘8F’,‘90’和‘9D’被保留,未定义其值。所以,在字符串转换后,原始密钥的‘8D’转换为‘3F’,‘3F’代表的是‘?’字符。
字符数组转换字符串-总结
我们总结了下不同平台下转换同一个加密密钥的结果会发生什么样的情况:
加密密钥 (转换前的字符数组):
B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
Android4.3 加密密钥 (转换后):
EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
Windows 8.1 (转换后):
B7B0F83F603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
Ubuntu 12.04 (转换后):
EFBFBDEFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
MacOS X 10.10 (转换后):
EFBFBDEFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD
注意
虽然我们花了很多时间走到这一步,我们这样做的目的…毕竟,事实胜于雄辩。正如我们所看到的,在加密操作下进行字节数组转换为字符串的结果都是无法预测的,无法进行正确的转换。从加密密钥的角度来看,‘EFBFBD’(或者‘3F’)可能导致熵的损坏。从加密数据的角度来看,从字节数据替换为字符串时,它改变了数据的内容。这些都不是你达到的目的。
简而言之,如果可以的话,我们要避免将字节数组转换为字符串的行为。现在大多数的加密方法/函数都能接受字节数组作为参数,并不需要进行字节数组转换为字符串的操作。如果你一定要在处理加密操作时进行字节数组转换字符串的话,可以考虑将字节数组的内容转换为Base64编码字符串或者其十六进制等效字符。
希望这边博文能够让你们了解到在运行程序时对加密算法执行验证的重要性。当然如果只是查看反编译/反汇编代码的话,很有可能就会忽略掉这些难以识别的安全问题。但是我们可以通过钩子钩住方法或者调试的方法进行动态分析程序,这样程序中的加密操作出现的安全问题就能更容易被我们发现。
[1]http://developer.android.com/reference/java/nio/charset/Charset.html
[2]http://www.fileformat.info/info/unicode/char/0fffd/index.htm
[3]http://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html#defaultCharset()
[4]http://en.wikipedia.org/wiki/Windows-1252
[5]http://msdn.microsoft.com/en-us/goglobal/cc305145.aspx
翻译自:https://blog.gdssecurity.com/labs/2015/2/18/when-efbfbd-and-friends-come-knocking-observations-of-byte-a.html
相关文章推荐
- asp.net(c#)字符串转换成字符数组 字符串转换成int 数组
- [转]字符串和字符数组之间的转换
- 字符数组与字符串转换
- 字符数组转换字符串:
- 字符串与字符数组的相互转换
- C字符串、字符数组、指针数组、指向数组的指针 彻底研究
- 《C++标准函数库》中说的有三个函数可以将字符串的内容转换为字符数组和C—string (CString、char*、String三种类型互换)
- c++中字符数组与字符串的转换
- 主题:Java整数-字节,字节-字符,字节数组-十六进制字符串转换
- C++ 字符串,字符数组,数字之间转换
- asp.net(c#)字符串转换成字符数组 字符串转换成int 数组
- java中字符数组和字符串的相互转换
- php里如何把字符串转换成字符数组
- @V@ java代码笔记2010-06-12:java控制台输入各类型类实现;以及判断输入字符串里面是否有数字的两种方法:方法1:转换成字符数组;方法2:正则表达式。
- 字符数组转换字符串,字符串数组转换为字符串的问题
- 字符数组与字符串间的转换
- 如何将字符数组转换为字符串
- StringBuffer转换为最大字符数为1300的字符串数组
- 常用的字符串和字符数组的转换和注意点
- IP字符串转换成4字节无符号字符数组[转]