CISCN2021 HMIControl

May 16, 2021

首先经IDA告知这是个C#编译出来的文件,然后直接就转到dnspy分析了。

因为有很多个文件,且只看exe似乎没什么东西,于是将这些文件先全反编译过来再说。dnspy支持批量到处工程,因此就直接按住ctrl挨个点就好了。

(导出方法:批量选之后,文件-导出到工程,然后选个文件夹就ok)

因为刚开始看,也没什么头绪(毕竟我也没编过c#),那就直接从字符串开始找起。

这很好,直接就能看到flag是在哪生成的,直接去看一眼。

之前接触过一点点modbus的我看到4开头的地址就想起modbus指令对应的内存空间,大概是sign short类型的范围,猜测text与相应内存或者寄存器的值有关。接下来看看有没有modbus相关配置,在上面我看到了PLCAddress,就顺着这个搜一下看看。

这里好像硬编码到41045了。目前暂时不知道这是什么意思,但是可以上下看看都有什么有意思的。

欸?Modbus TCP连接?用tcpview看看这个端口是什么状态,是server吗?

感觉像client,直接掏出modbus slave(无限制版自己到网上搜就好了),按照上图配置(UnitId 也叫 SlaveId,配置里正常填就好了)

当然,这里地址为什么写1044?根据规则,41001这种地址经过转化之后就是1000,其中开头4代表Holding Register(程序也在后面提示了4x),去掉4之后减去一个1。我看这个地址跟之前八个寄存器地址离着不远,就直接开了10个寄存器。

现在就可以直接测试了。因为这里默认配置,最大设置的就是一个short。这里全填成30000看看。

变红了...经过测试,如果数小一点就会变黄,数大一点就会变红,那...会不会有中间状态?经过漫长的测试,我不仅测出了中间状态,而且还测出了刚好满足的最小的值:

41045与kPa上面的数相关,41046与罐子里的黑白条有关。

这时候就该调试了。

打个断点在生成哈希上

看了一下上面的GetHash里面有md5,md5一下看看是不是

没什么问题...接下来就可以研究插值给的规律了。我断点下在了这你们随意。

经过研究我分析出了一个表格。

reg为数组数,addr为modbus地址,begin与and是代码中标注的大小值,min_start是最小满足要求的值,step表示寄存器每加1会加多少,reg_start_num表示寄存器为该值时能得到程序要求的最小满足值。

剩下就是爆破了,我就做到这了...

为了验证到底是除法还是乘法(毕竟有精度问题),我看了一下back trace

看样子只是乘法,怪不得在测试的时候发现这个间隔是稳定的。

那就可以准备爆破了。我和我队友分别试了3种语言,python、c#、golang

先说一下,浮点数在编译语言中一般的处理,其实是有偏差的,总会出点特别小的数。然而题目里是浮点数直接转字符串求md5的,因此,对于浮点数的误差是不允许出现的。

python当时跑的时间很长,就直接放弃了python,我这边选择了golang,队友为贴合题目语言选的C#。C# 11线程跑一遍大概要18h+,而golang(经过各种调试之后)5000秒也就是两个点不到就能跑完...

尽管如此,golang的效率我依旧没搞清楚是不是极限,我前几个版本写的不是内存泄露的厉害(10G内存打底每秒1G)就是跑不动多线程,希望有了解这方面的大佬教教我wuwu

还需要注意的点是,首先,代码里参与爆破的数组长度为9,但是寄存器就只有8个,于是数组最后一格是空着的,在生成字符串的时候就得多一个逗号;其次,这个程序建议在cmd里跑而不是直接双击打开(对windows用户来说),外面套一个cmd至少会见到程序的最后一面(x),不至于程序被杀了都不知道发生了什么;最后,为什么除的是1663962100,生成的哈希数比这个大,但是我上一版没写好就只能看到它到哪个数上满足条件,那个数就是1开头的这个数...

下面就是golang源码,同时希望师傅能教教我怎么优化~

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"github.com/pkg/profile"
	"github.com/shopspring/decimal"
	"os"
	"runtime"
	"strings"
	"sync/atomic"
	"time"
)

var ops uint64 = 0
var rawList []string
const NCPU=11
var  passRaw = make(chan string,500)
var  exitChan = make(chan bool)



func GetMD5Hash(text string) string {
	hash := md5.Sum([]byte(text))
	return hex.EncodeToString(hash[:])
}

func GenFloat(start_ float32,end_ float32,step_ float32) []string{
	start := decimal.NewFromFloat32(start_)
	end := decimal.NewFromFloat32(end_)
	step := decimal.NewFromFloat32(step_)
	var tmp []string
	t := start
	for{
		if t.GreaterThan(end) {
			break
		}
		tmp = append(tmp,t.String())
		t = t.Add(step)
	}
	return tmp
}

func CheckMD5(rawText string){
	//atomic.AddUint64(&ops, 1)
	//fmt.Println(rawText,GetMD5Hash(rawText))
	if GetMD5Hash(rawText)[:10] == "f0b278ccb9"{
		//for i:=0;i<NCPU;i++{
		//	exitChan<- true
		//}
		fmt.Println("rawText: "+rawText)
		fmt.Println("hash: "+ GetMD5Hash(rawText))
		//bufio.NewReader(os.Stdin).ReadBytes('\n')
		os.Exit(0)
	}

}


func ThreadDoing(count int){
	fmt.Printf("Thread %d start!\n",count)
	for{
		select {
		case tmpRaw := <- passRaw:
			atomic.AddUint64(&ops, 1)
			CheckMD5(tmpRaw)
		case <-exitChan:
			return
		}
	}

}

func main() {
	count := 0
	defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()
	//profile.Start(profile.MemProfileAllocs, profile.ProfilePath("."))
	//aaa := decimal.NewFromFloat32(62.10105)
	l1 := GenFloat(62.10105, 62.2, 0.00305)
	l2 := GenFloat(25.0002, 25.1, 0.0153)
	l3 := GenFloat(52.8016, 52.9, 0.00305)
	l4 := GenFloat(406.6128, 406.7, 0.0153)
	l5 := GenFloat(22.0027, 22.1, 0.00305)
	l6 := GenFloat(13.1121, 13.2, 0.0153)
	l7 := GenFloat(158.0031, 158.1, 0.0153)
	l8 := GenFloat(54.00025, 54.1, 0.00305)
	runtime.GOMAXPROCS(11)
	fmt.Println("start!")
	for i:=0;i<NCPU;i++{
		go ThreadDoing(i)
	}
	go func() {
		for{
			time.Sleep(5*time.Second)
			opsFinal :=atomic.LoadUint64(&ops)
			fmt.Printf("ops:%d/1663962100 = %f %%\n",opsFinal,(opsFinal/1663962100.0 * 100))
			//fmt.Println("ops:", opsFinal,"/1663962100")
		}
	}()

	for _,k1 := range l1{
		for _,k2 := range l2{
			for _,k3 := range l3{
				for _,k4 := range l4{
					for _,k5 := range l5{
						for _,k6 := range l6{
							for _,k7 := range l7{
								for _,k8 := range l8{
									//var runList []string
									//runList = append(runList,k1.String(),k2.String(),k3.String(),k4.String(),k5.String(),k6.String(),k7.String(),k8.String(),"")
									//rawText := strings.Join(runList,",")
									runList := []string{k1,k2,k3,k4,k5,k6,k7,k8,""}
									rawText := strings.Join(runList,",")
									go func() {
										passRaw <- rawText
									}()
									}
								}
							}
						}
					}
				}
			}
		}
println(count)
	select {}
}

最后,希望出题人在出爆破类题时看好时间复杂度,审题人也可以认真负责评估题目在放题时间内能否解出,也算是给比赛选手提高一点做题体验吧...

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
京ICP备18053813号-1