Mas0n
to be reverse engineer🐧
翻车鱼

CISCN2021 WriteUp

第一次打国赛,队友带飞。re做的一塌糊涂…呜呜呜

Glass

拿到手是个APK,主逻辑康康

https://cdn.shi1011.cn/2021/05/a9faf27363d12c3e18e684352a685187.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

分析native-lib.so,很容易识别出是RC4算法 + 一系列异或,IDA重命名下

https://cdn.shi1011.cn/2021/05/df8fbdbc146eae8b95567bf2fb52d043.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5
int __fastcall anyCrypto(int result, int a2, int a3, int a4)
{
  int i; // r4
  int v5; // r6
  char v6; // r5
  char v7; // lr
  char v8; // r12
  int j; // lr
  int k; // r6

  for ( i = 0; i < a2; i += 3 )
  {
    v5 = result + i;
    v6 = *(result + i + 2);
    v7 = *(result + i + 1);
    v8 = *(result + i) ^ v6;
    *(result + i) = v8;
    *(v5 + 2) = v6 ^ v7;
    *(v5 + 1) = v7 ^ v8;
  }
  for ( j = 0; j < a2; j += a4 )
  {
    for ( k = 0; (a4 & ~(a4 >> 31)) != k && j + k < a2; ++k )
      *(result + k) ^= *(a3 + k);
    result += a4;
  }
  return result;
}

拿着unk_497C倒着异或,再来遍RC4就行

#include <iostream>

static const std::string base64_chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789+/";

static inline bool is_base64(unsigned char c) {
    return (isalnum(c) || (c == '+') || (c == '/'));
}
//A\nB                          3
std::string base64_encode(char const* bytes_to_encode, int in_len) {
    std::string ret;
    int i = 0;
    int j = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];

    while (in_len--) {
        char_array_3[i++] = *(bytes_to_encode++);
        if (i == 3) {
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for(i = 0; (i < 4) ; i++)
                ret += base64_chars[char_array_4[i]];
            i = 0;
        }
    }

    if (i)
    {
        for(j = i; j < 3; j++)
            char_array_3[j] = '\0';

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
        char_array_4[3] = char_array_3[2] & 0x3f;

        for (j = 0; (j < i + 1); j++)
            ret += base64_chars[char_array_4[j]];

        while((i++ < 3))
            ret += '=';

    }

    return ret;

}

std::string base64_decode(std::string & encoded_string) {
    int in_len = encoded_string.size();
    int i = 0;
    int j = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::string ret;

    while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
        char_array_4[i++] = encoded_string[in_]; in_++;
        if (i ==4) {
            for (i = 0; i <4; i++)
                char_array_4[i] = base64_chars.find(char_array_4[i]);

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (i = 0; (i < 3); i++)
                ret += char_array_3[i];
            i = 0;
        }
    }

    if (i) {
        for (j = i; j <4; j++)
            char_array_4[j] = 0;

        for (j = 0; j <4; j++)
            char_array_4[j] = base64_chars.find(char_array_4[j]);

        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

        for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
    }

    return ret;
}


std::string sub_10D4(std::string result, int a2, std::string a3, int a4)
{
    int i; // r4
    char* v5; // r6
    char v6; // r5
    char v7; // lr
    char v8; // r12
    int j; // lr
    int k; // r6

    for ( j = 32; j >=0; j -= a4 ) {
        for ( k = a2 - j >= 8 ? 7 : a2 - j - 1; k>=0 ; --k )
            result[j + k] ^= a3[k];

    }


    for ( i = 36; i >=0; i -= 3 ) {
        result[i+1] = result[i+1] ^  result[i];
        result[i + 2] = result[i + 2] ^ result[i+1];
        result[i] = result[i] ^ result[i + 2];

    }
    return result;
}



int main() {
    using namespace std;
    string str = sub_10D4("\xA3\x1A\xE3\x69\x2F\xBB\x1A\x84\x65\xC2\xAD\xAD\x9E\x96\x05\x02\x1F\x8E\x36\x4F\xE1\xEB\xAF\xF0\xEA\xC4\xA8\x2D\x42\xC7\x6E\x3F\xB0\xD3\xCC\x78\xF9\x98\x3F", 39,  "12345678", 8);
    cout <<  base64_encode(str.c_str(), str.length()) << endl;

    return 0;
}

跑一遍RC4或者找个在线RC4解码完事

HMI

第一次接盘工控…

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

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

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

下面找flag触发点

https://cdn.shi1011.cn/2021/05/eff5f86215e3540316b217925ae1c2c1.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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

// 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控件的属性,并且有上下限

https://cdn.shi1011.cn/2021/05/3e40bcd67ed3c4032caea5628e679092.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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

填坑

2021-05-24

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

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

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

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

https://cdn.shi1011.cn/2021/05/c6d35e943d7f8689cc3b1fa417fc7715.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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

https://cdn.shi1011.cn/2021/05/03d4cc9a152a105931b70113af5e7ff4.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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

NoNameRangeAddressStringIndexStep
1AnalogValueDisplay3[52.8, 52.9]4104720.00305
2AnalogValueDisplay2[25, 25.1]4104810.0153
3AnalogValueDisplay1[62.1, 62.2]4104900.00305
4AnalogValueDisplay4[406.6, 406.7]4105030.0153
5AnalogValueDisplay8[54, 54.1]4105170.00305
6AnalogValueDisplay7[158, 158.1]4105260.0153
7AnalogValueDisplay5[22, 22.1]4105340.00305
8AnalogValueDisplay6[13.1, 13.2]4105450.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来爆破。事实证明,Go真香(花的时间依旧在45分钟左右,CPU环境:Intel i5 -10200H)

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

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,如果您有什么更好的方式或意见,欢迎师傅们和我交流。

最后挂个爆出来的图?

https://cdn.shi1011.cn/2021/05/3eb8fe18684e92ca054da4294ef8be99.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

本文链接:https://blog.shi1011.cn/ctf/1441
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

翻车鱼

CISCN2021 WriteUp
第一次打国赛,队友带飞。re做的一塌糊涂...呜呜呜 Glass 拿到手是个APK,主逻辑康康 分析native-lib.so,很容易识别出是RC4算法 + 一系列异或,IDA重命名下 int __f…
扫描二维码继续阅读
2021-05-24