原文始发于微信公众号(sunfishi):CISCN2021 HMI 工控
CISCN2021 HMI
HMI
第一次玩工控…
.NET开发的软件,直接放dnSpy
,题目给了hint:分析Modbus协议,找出触发flag的特殊事件
粗略看了下,知道这是一个客户端,所以需要整一个服务端来交互,找了个测试工具
https://github.com/study4coder/HslCommunicationDemo
开了服务端,就可以用WireShark抓包了,然而这并不是流量分析题,回到re上
下面找flag触发点
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**
控件的属性,并且有上下限
到这里我就没了思路…算是半吊子的工控逆向探索。到比赛结束这题都还是0,等官方出WP再补上吧..
填坑
2021-05-24
等了好久,还是没有官方的WP出来,也许是我太天真了把… 只好自己继续冲了。
花了近一天时间(中途上课去了),终于找到了规律,花了大半个小时爆出了flag….(题是真的….)
讲一下规律把,之前以为他是0.0001一点点加上去的,想了想不可能,时间复杂度算下来,就算是超算也算不出来。
于是再次上dnSpy动态调试了一番,看到调用堆栈,瞬间找到了突破点
反复分析一下AdvancedHMIControls.dll!AdvancedHMIControls.SubscriptionHandler.SubscribedDataReturned
就能发现它的规律:0.00305和0.0153交替出现,而且分别对应控件的PLCAddress
一番调试过后,总结出以下规律
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整除的最小值
如AnalogValueDisplay3:Math.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,如果您有什么更好的方式或意见,欢迎师傅们和我交流。
最后挂个爆出来的图?