老赵说安全系列:破解CISSP-AIO7模拟Exam DB后的反思 – 作者:xiaoguazh

大家好, 我是热爱安全的老赵,喜欢琢磨,不将就,典型的理科男。 但真正全职从事安全岗位,是从2017年才开始的,其实是个嘎嘎新的“老new comer”。 最喜欢和大伙分享自己的学习和心得,期待和也同样热爱安全的朋友们一块交流。 这次的话题是“破解 CISSP-AIO7-模拟Exam DB后的反思”。

一、前言

相信很多从事安全的朋友们都参加过CISSP的学习和考试, OSG和AIO是2个最重要的学习资料(当然还有CBK), 特别是AIO书后赠送的练习题光盘里,有个 “Total Tester” 软件提供了大量的模拟题供练习, 还包括对每个答案的解释… 不仅极大的丰富了我们对CISSP知识点的理解, 也提高了我们对安全体系的整体认知。 在每天手捧Kindle复习CISSP的日子里, 我一直在想,可不可以把 “Total Tester” 里面的试题导出来呢?(PDF, 或者TXT) 这样在Kindle上学习起来就方便多了。

二、 分析和破解

经过几个小时的努力,终于把全部的模拟题导出成TXT (因为是随书赠送的软件和数据, 此破解并不构成侵权)。 取得CISSP证书也有好长一段时间了,回想这段经历, 还是有值得我们反思的地方, 最大的心得就是无论做安全还是开发, 要不停的锻炼自己像黑客一样去思考,更要多动手, 多尝试。

使用的工具列表:

(1)jd-gui-1.4.0.jar – java app 反编译

(2)Jdk -java 编译

(3)7-zip

步骤1: “Total Tester” 主要界面分析:

图1-启动界面-加载题库

图2-题库加载完毕

图3-模拟题练习

图4-答案解析

看似简洁的界面,从黑客的角度来看, 这其实已经给我们提供了不少有价值的信息:

(1) 模拟题库的名字:CISSP7E

(2) 题库中, CISSP的8大领域的关键字:

01:Security and Risk Management

02:Asset Security

03:Security Engineering

04:Communications and Network Security

05:Identity and Access Management

06:Security Assessment and Testing

07:Security Operations

08:Software Development Security

步骤2: 分析 “Total Tester” 应用程序文件, 得出判断:

App类型 :Java 应用

数据文件 : /data/cissp7e/cissp7e_exam_1.ser

模拟题辅助参数 : /data/cissp7e/cissp7e.properties

图5-文件分析

步骤3:破解的思路汇总 (打开头脑,发散思维, 罗列所有可行的破解方式方法)

编号 思路 方法 备注
1 直接从 cissp7e_exam_1.ser 导出 假设数据文件可能是sqlite DB, access DB 或者某种可以方便解析的数据文件 经分析, 发现是通过某种方式序列化的二进制文件, 自行写解析代码, 难度较大
2 利用“Total Tester”已有的lib class读取并导出 尝试利用已有的java class,去打开和读取数据文件, 再根据自己的需要开发导出的代码 移花接木, 最省力的方案
3 从App界面导出 看App界面是否有可以支持导出的菜单或功能 经分析, 此路不可行

决定首先从2号方案开始尝试.

步骤4:深入分析Java程序

(1) 常见的java app都是 jar文件, 这个App 只有totaltester.exe

01. totaltester.exe文件大小 516K, 考虑到这个App的并不复杂,猜测它可能是通过某些工具把jar文件转换成exe。

02. 复制 totaltester.exe, 并修改扩展名为totaltester.exe.zip 或者 totaltester.jar, 使用7-zip尝试打开, 果不其然:

图6-jar文件分析

03. 查看 \META-INF\MANIFEST.MF 文件, 得到 Main-Class

Main-Class:com。totalsem。totaltester。tt6。totaltestergui。AppMain

(2) 使用 jd-gui-1.4.0.jar 分析 Main-Class, 根据执行逻辑, 逐步分析代码

图7-main class 代码

图8-DAOFactory 代码

由此可见, “Total Tester” 确实可以支持不同格式的数据文件,前面步骤3已经初步判定cissp7e_exam_1.ser为序列化的二进制文件, 因此重点分析SerializedFilesExamDAO。 另外,此处getDAOFactory是个 static 静态 public 方法, 这给黑客的“移花接木” 也提供了极大的便利。关键的数据加载方法定义在 SerializedFilesExamDAO 类中:

publicExamJavaBean_Abstract_v6loadExamSerializedData(String suiteAbbreviation,intexamNumber)

图9- SerializedFilesExamDAO 代码

通过检索 cissp7e.properties 文件, 发现参数: suiteAbbreviation和examNumber在 cissp7e.properties 中已有定义, 至此方案2已经万事俱备。

SuiteAbbreviation=cissp7e

NumberOfExams=1

总结所有类和方法的调用逻辑:

AppMain:
    ->JFrame_ApplicationMain:
        initializeUserSession()
            ->Mediator_Main:
                showWelcomeCertSelector()
                setSuitesList()
                    ->UserSession:
                        loadData()
                         ->TotalTesterDataFacade:
                             loadAllSuiteData()
                               ->SuiteDataService:
                                    loadData()
                                      ->DAOFactory:
                                         ->SerializedFilesDAOFactory:
                                             ->SerializedFilesExamDAO:

步骤5: 利用 DAOFactory, SerializedFilesExamDAO 和 ExamJavaBean_Abstract_v6开发数据导出代码:Exam2Text.java

保存在 totaltester\com\totalsem\totaltester\tt6\dataaccessobjects\目录下。

package com.totalsem.totaltester.tt6.dataaccessobjects;

import com.totalsem.totaltester.tt.datajavabeans.ExamJavaBean_Abstract_v3;
import com.totalsem.totaltester.tt.datajavabeans.ExamJavaBean_Abstract_v6;
import com.totalsem.totaltester.tt.datajavabeans.QuestionJavaBean_Abstract_v3;
import java.util.*;
import java.lang.*;
import java.io.*;

/**
Export "CISSP All-in-One Exam Guide 7th Edition" exams to text file
*/

public class Exam2Text
{
    public static String _EXAM_DATABASE = "CISSP All-in-One Exam Guide 7th Edition";

    public static Map<Integer, String> _REFERENCES  = new HashMap<Integer, String>() {{
        put(2146397198, "Chap 01: Security and Risk Management");
        put(2146397199, "Chap 02: Asset Security");
        put(2146397200, "Chap 03: Security Engineering");
        put(2146397201, "Chap 04: Communications and Network Security");
        put(2146397202, "Chap 05: Identity and Access Management");
        put(2146397203, "Chap 06: Security Assessment and Testing");
        put(2146397204, "Chap 07: Security Operations");
        put(2146397205, "Chap 08: Software Development Security");
    }};
    
    public static Map<Integer, String> _OBJECTVIES  = new HashMap<Integer, String>() {{
        put(-1899555586, "01 Security and Risk Management");
        put(-1322155798, "05 Identity and Access Management");
        put(-1084562265, "02 Asset Security");
        put(-939137757, "06 Security Assessment and Testing");
        put(531135497, "08 Software Development Security");
        put(1319549224, "07 Security Operations");
        put(1852249901, "04 Communication and Network Security");
        put(1932016476, "03 Security Engineering");
    }};

    /**
    Convert a Question Object into Text
    */
    public static String convertQuestionToText(QuestionJavaBean_Abstract_v3 q)
    {
        if (null == q)
        {
            return null;
        }
        
        StringBuffer buff = new StringBuffer();
        
        buff.append("[Question ID]: ").append(q.getQuestionID()).append("\n");
        buff.append("[Reference  ]: ").append(_REFERENCES.get(new Integer(q.getReferenceID()))).append("\n");
        buff.append("[Objective  ]: ").append(_OBJECTVIES.get(new Integer(q.getObjectiveID()))).append("\n\n");
        
        buff.append("[Question   ]: ").append(q.getQuestionText().trim()).append("\n\n");
        
        String[] choices = q.getChoicesText();
        for (int i=0; i<choices.length; i++)
        {
            buff.append("  ").append((char)(65+i)).append(". ").append(choices[i].trim()).append("\n");
        }
        
        buff.append("\n");

        boolean[] answers = q.getAnswers();
        
        buff.append("[Answer     ]: ");
        for (int i=0; i<answers.length; i++)
        {
            if (true == answers[i])
            {
                buff.append((char)(65+i)).append(" ");
            }
        }
        
        buff.append("\n\n");

        buff.append("[Explanation]: ").append(q.getExplanation().trim()).append("\n");

        return(buff.toString());
    }

    public static void main(String args[])
    {
        DAOFactory daoFactory = DAOFactory.getDAOFactory(2); //return SerializedFilesExamDAO
        ExamDAO examDAO = daoFactory.getExamDAO();

        ExamJavaBean_Abstract_v6 examJavaBean = examDAO.loadExamSerializedData("cissp7e",1);

        HashMap all_q = examJavaBean.getQuestionBeans();

        if (all_q.size()==0)
        {
            System.out.println("\nAbort! Exam Database is Empty!\n");
            System.exit(1);
        }

        String txtFile = null; 
        if (args.length > 0)
        {
            txtFile = args[0];
        }
        else
        {
            txtFile = _EXAM_DATABASE+".txt";    //default text file name
        }

        BufferedWriter writer = null;
        try
        {
            writer = new BufferedWriter(new FileWriter(txtFile));
            
            writer.write("===========================================\n");
            writer.write("= " + _EXAM_DATABASE + " =\n");
            writer.write("= " + all_q.size() + " Questions                          =\n");
            writer.write("===========================================\n");
            
            writer.write("\n\n\n");
            
            Iterator iter = all_q.entrySet().iterator();
            while (iter.hasNext())
            {
                Map.Entry entry = (Map.Entry) iter.next();
                //Integer q_Id = (Integer)entry.getKey();
                QuestionJavaBean_Abstract_v3 q = (QuestionJavaBean_Abstract_v3)entry.getValue();

                writer.write(convertQuestionToText(q));
                writer.write("\n\n");
            }
        }
        catch(Exception e)
        {
            System.out.println("\nError! Fail to read Questions!\n");
        }
        finally
        {
            try
            {
                writer.close();
            }
            catch(Exception e)
            {}
        }
    }
}

步骤6:Java编译, 执行

(1)目录结构 (拷贝 data目录, 放在 totaltester 子目录下)

./
  ├─totaltester.jar
  │
  ├─totaltester
  │  │  CISSP All-in-One Exam Guide 7th Edition.txt
  │  │  tt3.properties
  │  │  version.properties
  │  │
  │  ├─com
  │  │  ├─totalsem
  │  │      ├─totaltester
  │  │          ├─tt6
  │  │              ├─dataaccessobjects
  │  │                  ├─Exam2Text.java
  │  ├─data
  │  │  └─cissp7e
  │  │          cissp7e.properties
  │  │          cissp7e_exam_1.ser

(2)  编译 (利用原生的jar来编译导出文件的java)

> javac -cp ".\totaltester.jar" ".\totaltester\com\totalsem\totaltester\tt6\dataaccessobjects\Exam2Text.java"

(3) 执行

> java -cp ".\totaltester.jar;.\totaltester;" com.totalsem.totaltester.tt6.dataaccessobjects.Exam2Text

(4)查看导出结果

图10- 导出结果, 上传Kindle

三、反思和预防

作为开发者, 我们该怎样更好的保护软件中的数据不被随意读取, 导出或者其他利用? 从破解“Total Tester”这个例子里能给我们怎样的启示?

“Total Tester”存在的缺陷:

编号 缺陷 备注
1 数据文件未做特别的保护 数据文件缺乏加扰, 加密
通HEX查看器(例如HxD64)很容易分析数据内容
2 数据文件的关键参数暴露在properties文件中 容易在代码中被识别和定位
SuiteAbbreviation=cissp7e
NumberOfExams=1
3 Java代码未做特别保护 Java文件缺乏加扰
反编译后, 容易阅读和分析代码逻辑
4 关键的数据加载为静态公共方法: public static DAOFactory getDAOFactory() 极易被直接利用
5 SerializedFilesExamDAO类中除load()之外还有store()方法 为篡改数据文件提供了便利

预防措施:

编号 方案建议 备注
1 对数据文件压缩并加密 阻止对文件直接分析
增加使用HEX工具直接查看文件的难度
2 对数据文件进行伪装, 例如将扩展名改为jpg/exe/dll等等 减少黑客对数据文件的定位和识别
3 避免暴露数据文件的关键参数 避免黑客在Properties/INI/XML/JSON/YAML等文件中查找关键信息
4 Java代码编译加扰 增加反编译后阅读代码逻辑的难度
5 Jar文件签名, 增加完整性检查 避免jar被恶意篡改和执行
6 对EXE文件加壳处理, 比如压缩壳, 加密壳 阻止黑客使用7-zip/tar等工具直接查看java程序细节
7 避免对关键逻辑使用静态公共方法 降低代码/API被直接利用的可能性
8 减少不必要的数据文件API接口, 例如只读的业务, 应该避免提供修改和保存的API 减少黑客对数据文件的可操作性

小结

我们可能没办法彻底阻止软件中的数据被读取和利用, 但我们可以通过改进我们的设计和采用相关技术让”移花接木”变得更加困难。

要更好的保护我们的软件, 在设计和开发时, 就一定要像黑客一样去思考, 只有这样才能想”黑客”所想, 难”黑客”所难。

*本文原创作者:xiaoguazh,本文属于FreeBuf原创奖励计划,未经许可禁止转载

来源:freebuf.com 2020-02-12 08:00:42 by: xiaoguazh

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论