fortify规则库解密之旅
2019-12-23 00:18:28 Author: gv7.me(查看原文) 阅读量:460 收藏

前端时间在学习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。

  1. fortify-common-17.10.0.0156.jar
  2. fortify-crypto-1.0.jar

搜索解密jar

0x03 定位解密方法

3.1 通过调试定位

定位解密方法最好的方法就是调式。打开fortify的\Core\private-bin\awb\productlaunch.cmd脚本,在最后一行如下图位置粘贴上一步复制的调试配置,就可以调试模式启动fortify。然后配置IDEA连接5005端口即可进行调试。

让fortify开启调试模式

通过审计这两个jar代码,基本确定fortify-crypto-1.0.jar就是fortify加解密方法所在。通过函数名,参数类型,代码逻辑确定了如下涉及解密的可疑方法,并给它们都打上断点。

  1. void decrypt(long[] v, long[] k)
  2. void dec(InputStream source, OutputStream dest, long[] usrKey)
  3. InputStream decryptCompressedAfterHeaders(InputStream encrypted, String keyString)
  4. InputStream decryptAfterHeaders(InputStream encrypted, String keyString, boolean compressed)
  5. InputStream decryptCompressed(InputStream encrypted, String keyString)
  6. void encryptAfterHeaders(InputStream stream, OutputStream ciphertext, String keyString, boolean compress)

接着运行fortify扫描一个java web demo,最终漏洞是扫描出来了,但是没有一个可疑方法被调用,甚是奇怪。于是我将所有方法都打上断点,发现扫描期间只有readHeaders(InputStream encrypted)被调用了。

扫描期间只有readHeaders方法被调用

难道fortify并没有在扫描时对规则进行解密,可以直接读取规则内容?后面通过调用栈上下文暂时没发现解密操作。

3.2 通过编码调用定位

这时一个朋友突然叫去包饺子,我才记起是冬至。为了速战速决,我决定 通过写代码直接将规则库传入到可疑方法中进行解密,通过解密结果是否是有意义的明文来判断最终方法。 于是将CryptoUtil类中的所有代码审计一遍之后,发现decryptCompressed()可以解密压缩一个文件,但不确定文件是否可以是规则文件。

下面我们来看看该方法的运行流程。该方法最终会调用decryptAfterHeaders(),它负责控制解密解压整个流程。可以看到如果key没设置会被设置为默认值。接着会调用doBlockCipher()来解密,使用uncompressString来解压。

解密压缩方法decryptAfterHeaders()

我们再来看看doBlockCipher()方法,它可以进行加密和解密。传入的是false所以是解密。

doBlockCipher()方法调用dec对文件进行解密

而最终文件内容会被传入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()可以解密解压一个文件,至于这个文件是否可以是规则库文件,我们可以写如下代码来测试。

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 最后的话

最终为了快速解决问题,通过编码调用锁定解密方法,确实有运气的成分。至此依然存在如下问题,只能等有空再研究。赶时间去朋友那撸猫包饺子去了!

  1. fortify在扫描时没有调用解密方法,难道是加密的规则库可以直接用于扫描?
  2. 如果扫描无需解密规则库,那为何fortify又要在jar中提供解密方法?
  3. 到底解密方法在哪里被调用?

冬至的夜晚


文章来源: http://gv7.me/articles/2019/fortify-rule-library-decryption-process/
如有侵权请联系:admin#unsafe.sh