CISCN2021 HMIControl
首先经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 {}
}
最后,希望出题人在出爆破类题时看好时间复杂度,审题人也可以认真负责评估题目在放题时间内能否解出,也算是给比赛选手提高一点做题体验吧...