第一次打国赛,队友带飞。re做的一塌糊涂…呜呜呜
Glass
拿到手是个APK,主逻辑康康
分析native-lib.so
,很容易识别出是RC4算法 + 一系列异或,IDA重命名下
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触发点
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
控件的属性,并且有上下限
到这里我就没了思路…算是半吊子的工控逆向探索。到比赛结束这题都还是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来爆破。事实证明,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,如果您有什么更好的方式或意见,欢迎师傅们和我交流。
最后挂个爆出来的图?
发表回复