ctf-writeups

Spy PLC

PLC operators have detected that the test mode has been activated and intruders have sent a message through it. What message is this PLC transmitting?

Note: Find the spy PLC message and wrap with ASIS{}!

Attachment:

PROGRAM ENVMon
	VAR
		TempRaw AT %IW0 : INT;
		HumRaw AT %IW1 : INT;
		PressRaw AT %IW2 : INT;
		GasRaw AT %IW3 : INT;
	END_VAR
	VAR CONSTANT
		T_Thr_Max : INT := 1200;
		H_Thr_Max : INT := 900;
		P_Thr_Max : INT := 3000;
		G_Thr_Max : INT := 1000;
		T_Thr_Min : INT := 200;
		H_Thr_Min : INT := 200;
		P_Thr_Min : INT := 500;
		G_Thr_Min : INT := 100;
	END_VAR
	VAR
		StrongHeat AT %QX0.0 : BOOL;
		ColdHumid AT %QX0.1 : BOOL;
		HighPress AT %QX0.2 : BOOL;
		LowPress AT %QX0.3 : BOOL;
		LowGas AT %QX0.4 : BOOL;
		HighGas AT %QX0.5 : BOOL;
		HeatAndPressure AT %QX0.6 : BOOL;
		DryAndGas AT %QX0.7 : BOOL;
	END_VAR
	VAR
		TestMode : BOOL := True;
		StepSelect : INT := 1;
		AutoPlay : BOOL := True;
		stepP : INT := 1;
		Tmr : TON;
	END_VAR
	VAR
		TT AT %MW0 : INT;
		HH AT %MW1 : INT;
		PP AT %MW2 : INT;
		GG AT %MW3 : INT;
	END_VAR
	VAR
		TEMP_CASES : ARRAY [1..32] OF INT := [180, 180, 1300, 1300, 1300, 180, 900, 1300, 1300, 180, 180, 400, 1300, 180, 400, 400, 900, 180, 1300, 180, 900, 1300, 400, 900, 180, 400, 1300, 180, 900, 400, 1300, 400];
		HUM_CASES : ARRAY [1..32] OF INT := [150, 1000, 150, 150, 1000, 400, 150, 150, 1000, 400, 800, 400, 1000, 800, 400, 150, 150, 400, 1000, 150, 400, 1000, 150, 1000, 400, 400, 150, 400, 150, 800, 150, 400];
		PRESS_CASES : ARRAY [1..32] OF INT := [1000, 400, 400, 3500, 1000, 3500, 3500, 3500, 1000, 3500, 400, 2000, 1000, 2000, 2000, 2000, 1000, 1000, 1000, 3500, 2000, 1000, 3500, 2000, 2000, 1000, 3500, 3500, 3500, 2000, 3500, 3500];
		GAS_CASES : ARRAY [1..32] OF INT := [300, 300, 300, 300, 300, 50, 300, 50, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 50, 300, 300, 50, 300, 300, 300, 300, 300, 300, 300, 50, 50];
		NCASES : INT := 32;
		T_mid : INT;
		H_mid : INT;
		P_mid : INT;
		G_mid : INT;
		Tz : INT;
		Hz : INT;
		Pz : INT;
		Gz : INT;
	END_VAR

	T_mid := (T_Thr_Min + T_Thr_Max) / 2;
	H_mid := (H_Thr_Min + H_Thr_Max) / 2;
	P_mid := (P_Thr_Min + P_Thr_Max) / 2;
	G_mid := (G_Thr_Min + G_Thr_Max) / 2;

	IF TempRaw <= T_Thr_Min THEN 
		Tz := 0;
	ELSIF TempRaw <= T_mid THEN 
		Tz := 1;
	ELSIF TempRaw < T_Thr_Max THEN 
		Tz := 2;
	ELSE 
		Tz := 3;
	END_IF;

	IF HumRaw <= H_Thr_Min THEN 
		Hz := 0;
	ELSIF HumRaw <= H_mid THEN 
		Hz := 1;
	ELSIF HumRaw < H_Thr_Max THEN 
		Hz := 2;
	ELSE 
		Hz := 3;
	END_IF;

	IF PressRaw <= P_Thr_Min THEN 
		Pz := 0;
	ELSIF PressRaw <= P_mid THEN 
		Pz := 1;
	ELSIF PressRaw < P_Thr_Max THEN 
		Pz := 2;
	ELSE 
		Pz := 3; 
	END_IF;

	IF GasRaw <= G_Thr_Min THEN 
		Gz := 0;
	ELSIF GasRaw <= G_mid THEN 
		Gz := 1;
	ELSIF GasRaw < G_Thr_Max THEN 
		Gz := 2;
	ELSE 
		Gz := 3; 
	END_IF;
	 
	IF TestMode THEN
			IF AutoPlay THEN
					Tmr(IN:=TRUE, PT:=T#2000ms);
					IF Tmr.Q THEN
							stepP := stepP + 1;
							IF stepP > NCASES THEN stepP := 1; END_IF;
							Tmr(IN:=FALSE);
					END_IF;
					IF NOT Tmr.Q THEN Tmr(IN:=TRUE); END_IF;
			ELSE
					IF StepSelect < 1 THEN stepP := 1;
					ELSIF StepSelect > NCASES THEN stepP := NCASES;
					ELSE stepP := StepSelect;
					END_IF;
			END_IF;
			TempRaw := TEMP_CASES[stepP];
			HumRaw := HUM_CASES[stepP];
			PressRaw := PRESS_CASES[stepP];
			GasRaw := GAS_CASES[stepP];

	END_IF;
	StrongHeat := (Tz = 1) OR (Tz = 3);
	ColdHumid := (Tz >= 2);
	HighPress := (Hz = 1) OR (Hz = 3);
	LowPress := (Hz >= 2);
	HighGas := (Pz = 1) OR (Pz = 3);
	LowGas := (Pz >= 2);
	HeatAndPressure := (Gz = 1) OR (Gz = 3);
	DryAndGas := (Gz >= 2);

END_PROGRAM

CONFIGURATION Config0
	RESOURCE Res0 ON PLC
		TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
		PROGRAM instance0 WITH task0 : ENVMon;
	END_RESOURCE
END_CONFIGURATION

Ask ChatGPT to solve it:

Solve CTF challenge:

<attachment here>

Response:

Nice one — the program’s outputs are wired to the wrong zone variables (the boolean outputs use the wrong `*z` variables), so the 8 QX outputs per test case form bytes. I simulated the 32 test cases, read the 8 booleans per case (in the order they appear in the code), reversed each 8-bit sequence (LSB first), and converted to ASCII.

The recovered message is:

PLCs_4r3_tHe_heaRT_0f_1ndUstri35

So the CTF flag (wrapped in the usual format) is:

crew{PLCs_4r3_tHe_heaRT_0f_1ndUstri35}

If you want, I can paste the short Python simulation I used to extract it.

Flag: ASIS{PLCs_4r3_tHe_heaRT_0f_1ndUstri35}.