前端时间在学习fortify的规则编写,充分利用污点回溯功能来扫描出比较新的漏洞,比如fastjson反序列化等新爆发出来的漏洞。网上有比较好的资料是《fortify安全代码规则编写指南》,但是很缺例子。一直想参考参考官方的规则库,但是是加密的,万般无奈只能踏上解密之旅。
0x01 解密思路
猜测fortify会和AWVS一样,会将规则库加载到内存当中进行解密,然后在使用其进行代码扫描。基于这个想法,它必然存在一个解密方法,而这个方法肯定在某个jar当中。锁定jar之后,就可以审计jar的所有方法。给所有方法打上断点,调式来确定最终解密方法名,传入的参数和返回值时什么。最后理清了解密流程后,我们就可以写代码来模拟这个过程,来解密规则库。
0x02 定位解密jar
通过反编译发现fortify依赖的jar基本都没有混淆,说明我们可以通过jar名和类名来初步锁定加密方法所在jar。类名搜索工具使用的是我在《如何快速找到POC/EXP依赖的jar?》一文中开发的SearchClassInJar.jar
。在分别尝试encrypt
,decrypt
,crypto
,rule
,fortify
等关键字后,最终找到两个可疑jar。
- fortify-common-17.10.0.0156.jar
- fortify-crypto-1.0.jar
0x03 定位解密方法
3.1 通过调试定位
定位解密方法最好的方法就是调式。打开fortify的\Core\private-bin\awb\productlaunch.cmd
脚本,在最后一行如下图位置粘贴上一步复制的调试配置,就可以调试模式启动fortify。然后配置IDEA连接5005端口即可进行调试。
通过审计这两个jar代码,基本确定fortify-crypto-1.0.jar
就是fortify加解密方法所在。通过函数名,参数类型,代码逻辑确定了如下涉及解密的可疑方法,并给它们都打上断点。
- void
decrypt
(long[] v, long[] k)
- void
dec
(InputStream source, OutputStream dest, long[] usrKey)
- InputStream
decryptCompressedAfterHeaders
(InputStream encrypted, String keyString)
- InputStream
decryptAfterHeaders
(InputStream encrypted, String keyString, boolean compressed)
- InputStream
decryptCompressed
(InputStream encrypted, String keyString)
- void
encryptAfterHeaders
(InputStream stream, OutputStream ciphertext, String keyString, boolean compress)
接着运行fortify扫描一个java web demo
,最终漏洞是扫描出来了,但是没有一个可疑方法被调用,甚是奇怪。于是我将所有方法都打上断点,发现扫描期间只有readHeaders(InputStream encrypted)
被调用了。
难道fortify并没有在扫描时对规则进行解密,可以直接读取规则内容?后面通过调用栈上下文暂时没发现解密操作。
3.2 通过编码调用定位
这时一个朋友突然叫去包饺子,我才记起是冬至。为了速战速决,我决定 通过写代码直接将规则库传入到可疑方法中进行解密,通过解密结果是否是有意义的明文来判断最终方法。 于是将CryptoUtil类中的所有代码审计一遍之后,发现decryptCompressed()
可以解密压缩一个文件,但不确定文件是否可以是规则文件。
下面我们来看看该方法的运行流程。该方法最终会调用decryptAfterHeaders()
,它负责控制解密解压整个流程。可以看到如果key
没设置会被设置为默认值。接着会调用doBlockCipher()
来解密,使用uncompressString
来解压。
我们再来看看doBlockCipher()
方法,它可以进行加密和解密。传入的是false
所以是解密。
而最终文件内容会被传入dec()
方法解密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| private static void dec(InputStream source, OutputStream dest, long[] usrKey) throws IOException { long[] k = (long[])((long[])usrKey.clone()); byte[] byteBuf = new byte[8]; byte[] byteBufDelay = null; long[] unsigned32Buf = new long[2]; long top = 4294967295L;
int bytesRead; while((bytesRead = source.read(byteBuf)) != -1) { if (bytesRead < 8) { throw new IOException("invalid encrypted stream"); }
byteArrayToUnsigned32(byteBuf, unsigned32Buf); decrypt(unsigned32Buf, k); k[0] = k[0] + 17L & top; k[1] = k[1] + 17L & top; k[2] = k[2] + 17L & top; k[3] = k[3] + 17L & top; unsigned32ToByteArray(unsigned32Buf, byteBuf); if (source.available() == 0) { int bytesToWrite = byteBuf[7]; if (bytesToWrite > 8 || bytesToWrite < 0 || byteBufDelay == null) { throw new IOException("invalid encrypted stream"); }
dest.write(byteBufDelay, 0, bytesToWrite); }
if (byteBufDelay != null) { dest.write(byteBufDelay, 0, 8); byte[] t = byteBufDelay; byteBufDelay = byteBuf; byteBuf = t; } else { byteBufDelay = byteBuf; byteBuf = new byte[8]; } }
}
|
至此我们确定decryptCompressed()可以解密解压一个文件,至于这个文件是否可以是规则库文件,我们可以写如下代码来测试。
发现解密结果是有意义的xml文件内容,完美解密!
0x04 编写解密程序
理清的整个过程后,解密就很简单了。说白了就是批量调用fortify自带的fortify-crypto-1.0.jar
中的com.fortify.util.CryptoUtil.decryptCompressed()
方法进行解密。最后附上解密程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import java.io.*; import static com.fortify.util.CryptoUtil.decryptCompressed;
public class FortifyRuleDecrypter { private String ruleDir; private String saveDir;
FortifyRuleDecrypter(String ruleDir,String saveDir){ this.ruleDir = ruleDir; this.saveDir = saveDir; }
public void doDecrypt(){ File encryptRule = new File(ruleDir); if(encryptRule.isFile()) { if(encryptRule.getName().endsWith(".bin")) { decryptRule(encryptRule, new File(saveDir + File.separator + encryptRule.getName() + ".xml")); }else{ System.out.println("[-] The rule file suffix is.bin!"); System.exit(0); } }
if (encryptRule.isDirectory()) { File[] listFile = encryptRule.listFiles(); for(File file:listFile){ if(file.getName().endsWith(".bin")){ File saveName = new File(saveDir + File.separator + file.getName().replace(".bin","") + ".xml"); decryptRule(file,saveName); } } }
}
public void decryptRule(File encFile, File decFile){ try { InputStream ruleStream = decryptCompressed(new FileInputStream(encFile), null); OutputStream outputStream = new FileOutputStream(decFile); byte[] b = new byte[1024]; while ((ruleStream.read(b)) != -1) { outputStream.write(b); } ruleStream.close(); outputStream.close(); System.out.println(String.format("[+] success %s -> %s",encFile.getName(),decFile.getAbsolutePath())); }catch (Exception e){ System.out.println(String.format("[-] fail %s -> %s",encFile.getName(),decFile.getAbsolutePath())); e.printStackTrace(); } }
public static void main(String[] args) { if(args.length != 2){ System.out.println("Usage: java -jar FortifyRuleDecrypter.jar [rule_dir|rule_file] <save_dir>"); System.exit(0); } FortifyRuleDecrypter decrypter = new FortifyRuleDecrypter(args[0],args[1]); decrypter.doDecrypt(); } }
|
0x05 最后的话
最终为了快速解决问题,通过编码调用锁定解密方法,确实有运气的成分。至此依然存在如下问题,只能等有空再研究。赶时间去朋友那撸猫包饺子去了!
- fortify在扫描时没有调用解密方法,难道是加密的规则库可以直接用于扫描?
- 如果扫描无需解密规则库,那为何fortify又要在jar中提供解密方法?
- 到底解密方法在哪里被调用?
文章来源: http://gv7.me/articles/2019/fortify-rule-library-decryption-process/
如有侵权请联系:admin#unsafe.sh