CISCN2021 HMI 工控

工控安全 3年前 (2021) admin
654 0 0

CISCN2021 HMI

HMI

第一次玩工控…

.NET开发的软件,直接放dnSpy,题目给了hint:分析Modbus协议,找出触发flag的特殊事件

粗略看了下,知道这是一个客户端,所以需要整一个服务端来交互,找了个测试工具

https://github.com/study4coder/HslCommunicationDemo

开了服务端,就可以用WireShark抓包了,然而这并不是流量分析题,回到re上

下面找flag触发点

CISCN2021 HMI 工控

dnSpy反编译的代码并不是太好,换ILSpy

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// AdvancedHMIControls.AnalogValueDisplay
using System;
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
private void UpdateText()
{
    string text = "";
    string text2 = "";
    double result;
    if (m_ShowValue)
    {
        text = m_Value;
        if (!string.IsNullOrEmpty(m_NumericFormat))
        {
            if (double.TryParse(Value, out result))
            {
                try
                {
                    text = result.ToString(m_NumericFormat);
                }
                catch (Exception ex)
                {
                    ProjectData.SetProjectError(ex);
                    Exception ex2 = ex;
                    text = "Check Numeric Format";
                    ProjectData.ClearProjectError();
                }
            }
        }
        else
        {
            text = Value;
        }
    }
    if (!string.IsNullOrEmpty(m_Prefix))
    {
        text = m_Prefix + text;
    }
    if (!string.IsNullOrEmpty(m_Suffix))
    {
        text += m_Suffix;
    }
    base.Text = text;
    if (double.TryParse(Value, out result))
    {
        if (result > m_ValueLimitUpper)
        {
            base.ForeColor = m_ForeColorOverLimit;
            return;
        }
        if (result < m_ValueLimitLower)
        {
            base.ForeColor = m_ForeColorUnderLimit;
            return;
        }
        base.ForeColor = m_ForeColorInLimits;
    }
    if (PLCAddressValue != null && int.TryParse(PLCAddressValue.PLCAddress, out var result2))
    {
        if ((result2 == 41049) & (Strings.Len(text) < 30))
        {
            combined[0] = text;
        }
        if (result2 == 41048)
        {
            combined[1] = text;
        }
        if (result2 == 41047)
        {
            combined[2] = text;
        }
        if (result2 == 41050)
        {
            combined[3] = text;
        }
        if (result2 == 41053)
        {
            combined[4] = text;
        }
        if (result2 == 41054)
        {
            combined[5] = text;
        }
        if (result2 == 41052)
        {
            combined[6] = text;
        }
        if (result2 == 41051)
        {
            combined[7] = text;
        }
    }
    int num = 0;
    int num2 = 0;
    do
    {
        if (string.IsNullOrEmpty(combined[num2]))
        {
            num = 1;
            break;
        }
        num2 = checked(num2 + 1);
    }
    while (num2 <= 7);
    if (num == 0)
    {
        text2 = GetHash(string.Join(",", combined));
        Console.WriteLine("Booooooooooooooooom!");
        if (Operators.CompareString(text2.Substring(0, 10), "F0B278CCB9", TextCompare: false) == 0)
        {
            Console.WriteLine("CISCN{" + text2 + "}");
        }
    }
}

逻辑很清楚,只要combined数组(大小为9)元素都不为NULL或空,就能触发flag事件。然而并不知道text是什么

用dnSpy动态调试下,可以看到text其实是每个**AnalogValueDisplay**控件的属性,并且有上下限

CISCN2021 HMI 工控

到这里我就没了思路…算是半吊子的工控逆向探索。到比赛结束这题都还是0,等官方出WP再补上吧..

填坑

2021-05-24

等了好久,还是没有官方的WP出来,也许是我太天真了把… 只好自己继续冲了。

花了近一天时间(中途上课去了),终于找到了规律,花了大半个小时爆出了flag….(题是真的….)

讲一下规律把,之前以为他是0.0001一点点加上去的,想了想不可能,时间复杂度算下来,就算是超算也算不出来。

于是再次上dnSpy动态调试了一番,看到调用堆栈,瞬间找到了突破点

CISCN2021 HMI 工控

反复分析一下AdvancedHMIControls.dll!AdvancedHMIControls.SubscriptionHandler.SubscribedDataReturned就能发现它的规律:0.00305和0.0153交替出现,而且分别对应控件的PLCAddress

CISCN2021 HMI 工控

一番调试过后,总结出以下规律

No Name Range Address StringIndex Step
1 AnalogValueDisplay3 [52.8, 52.9] 41047 2 0.00305
2 AnalogValueDisplay2 [25, 25.1] 41048 1 0.0153
3 AnalogValueDisplay1 [62.1, 62.2] 41049 0 0.00305
4 AnalogValueDisplay4 [406.6, 406.7] 41050 3 0.0153
5 AnalogValueDisplay8 [54, 54.1] 41051 7 0.00305
6 AnalogValueDisplay7 [158, 158.1] 41052 6 0.0153
7 AnalogValueDisplay5 [22, 22.1] 41053 4 0.00305
8 AnalogValueDisplay6 [13.1, 13.2] 41054 5 0.0153
  • No:序号
  • Name:控件名
  • Range:取值范围
  • Address:((AdvancedHMIControls.AnalogValueDisplay)this.m_Parent).m_PLCAddressValue.m_PLCAddress
  • StringIndex:文本排列顺序
  • Step:步长

这里需要注意的是,num就是PLC中的寄存器的值,而且是乘法,所以需要取到范围内能够被Step整除的最小值

AnalogValueDisplay3Math.Ceil(52.8 / 0.00305) * 0.00305 即 52.8016

如此,时间复杂度大幅度降低,可以考虑爆破了

但是,用什么语言实现是一个头疼的问题,试了试使用Python爆破,慢的离谱….

写了份Go来爆破。事实证明,Go真香(花的时间依旧在40分钟左右,CPU环境:Intel i5 -10200H)

挂上代码(tips:强迫症,挂上了自己写的控制台美化模块,可能会造成性能损失,换成普通的fmt速度应该还能更快)

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package main
import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "github.com/shopspring/decimal"
    "math"
    // "runtime"
    "strings"
    "time"
    "github.com/Mas0nShi/goConsole/console"
)
var pCount, maxCount int64
var isOK = make(chan bool, 1)
var startTimeStamp int64
func getHashMD5(s string) string {
    md5obj := md5.New()
    md5obj.Write([]byte(s))
    md5Str := hex.EncodeToString(md5obj.Sum(nil))
    return md5Str
}
func dequePush(channel chan string)  {
    str := <- channel
    hash := getHashMD5(str)
    if hash[:10] == "f0b278ccb9" {
        fmt.Println("Success, rawText: " + str)
        fmt.Println("your flag is: " + hash)
        isOK <- true
    }
    pCount++
    close(channel)
}
func floatRange(start float64, end float64, step float64) []string {
    dstart := decimal.NewFromFloat(start)
    dend := decimal.NewFromFloat(end)
    dstep := decimal.NewFromFloat(step)
    var dslice []string
    for !dstart.GreaterThan(dend) {
        dslice = append(dslice, dstart.String())
        dstart = dstart.Add(dstep)
    }
    return dslice
}
func int64toString(n int64) string {
    buf := [11]byte{}
    pos := len(buf)
    i := n
    signed := i < 0
    if signed {
        i = -i
    }
    for {
        pos--
        buf[pos], i = '0'+byte(i%10), i/10
        if i == 0 {
            if signed {
                pos--
                buf[pos] = '-'
            }
            return string(buf[pos:])
        }
    }
}
func backEcho() {
    time.Sleep(3 * time.Second)
    console.Log("Time: " + int64toString(time.Now().UnixNano() / 1e6 - startTimeStamp) + " ms " + "Progress: " +decimal.NewFromFloat(float64(pCount)/float64(maxCount) *100).Round(2).String() + "% Counts: " + int64toString(maxCount) + "" + "/" +int64toString(pCount))
    select {
    case <-isOK:
        return
    default:
        backEcho()
    }
}
func main() {
    console.Info("Initialization params")
    AnalogValueDisplay1range := floatRange(math.Ceil(62.1 / 0.00305) * 0.00305, 62.2, 0.00305)
    AnalogValueDisplay2range := floatRange(math.Ceil(25 / 0.0153) * 0.0153, 25.1, 0.0153)
    AnalogValueDisplay3range := floatRange(math.Ceil(52.8 / 0.00305) * 0.00305, 52.9, 0.00305)
    AnalogValueDisplay4range := floatRange(math.Ceil(406.6 / 0.0153) * 0.0153, 406.7, 0.0153)
    AnalogValueDisplay5range := floatRange(math.Ceil(22 / 0.00305) * 0.00305, 22.1, 0.00305)
    AnalogValueDisplay6range := floatRange(math.Ceil(13.1 / 0.0153) * 0.0153, 13.2, 0.0153)
    AnalogValueDisplay7range := floatRange(math.Ceil(158 / 0.0153) * 0.0153, 158.1, 0.0153)
    AnalogValueDisplay8range := floatRange(math.Ceil(54 / 0.00305) * 0.00305, 54.1, 0.00305)
    pCount = 0
    maxCount = int64(len(AnalogValueDisplay1range) * len(AnalogValueDisplay2range) *len(AnalogValueDisplay3range) * len(AnalogValueDisplay4range) *len(AnalogValueDisplay5range) * len(AnalogValueDisplay6range) *len(AnalogValueDisplay7range) * len(AnalogValueDisplay8range))
    // runtime.GOMAXPROCS(8)
    console.Info("Start Run")
    go backEcho()
    startTimeStamp = time.Now().UnixNano() / 1e6
    for _, value1 := range AnalogValueDisplay1range {
        for _, value2 := range AnalogValueDisplay2range {
            for _, value3 := range AnalogValueDisplay3range {
                for _, value4 := range AnalogValueDisplay4range {
                    for _, value5 := range AnalogValueDisplay5range {
                        for _, value6 := range AnalogValueDisplay6range {
                            for _, value7 := range AnalogValueDisplay7range {
                                for _, value8 := range AnalogValueDisplay8range {
                                    select {
                                    case <-isOK:
                                        console.Info("End Run")
                                        return
                                    default:
                                        strSilce := []string {value1, value2, value3, value4, value5, value6, value7, value8, ""}
                                        str := strings.Join(strSilce, ",")
                                        channel := make(chan string, 80)
                                        channel <- str
                                        go dequePush(channel)
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Go接触的时间不多,可能会有效率更高的方法,或是代码存在缺陷或BUG,如果您有什么更好的方式或意见,欢迎师傅们和我交流。

最后挂个爆出来的图?

CISCN2021 HMI 工控

原文始发于微信公众号(sunfishi):CISCN2021 HMI 工控

版权声明:admin 发表于 2021年11月7日 上午6:50。
转载请注明:CISCN2021 HMI 工控 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...