# The Clockwork `the_clockwork` ist eine Reverse-Engineering-Challenge, die ein System voneinander abhängiger Gleichungen beinhaltet. Uns wird eine Binärdatei `challenge` bereitgestellt und wir müssen die korrekte Eingabe finden, um ihre interne Logik zu erfüllen. ## Informationsbeschaffung ```bash $ file challenge challenge: ELF 64-bit LSB executable, x86-64, ... not stripped ``` Die Binärdatei ist nicht gestrippt und enthüllt Funktionsnamen. Wir analysieren sie mit Ghidra. ## Reverse Engineering ### Main-Funktion Wir lokalisieren die `main`-Funktion (`0x402057`). Die Dekompilierung zeigt die Initialisierung eines Ziel-Arrays und eine Schleife, die die berechneten "Zahnräder" (Gears) verifiziert. ```c undefined8 main(void) { bool bVar1; int iVar2; char *pcVar3; size_t sVar4; long in_FS_OFFSET; int local_164; int local_158 [64]; char local_58 [72]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_158[0] = 0x174; local_158[1] = 0x2fe; local_158[2] = 0x3dc; local_158[3] = 0x30c; local_158[4] = 0xfffffe57; local_158[5] = 0xffffffc6; local_158[6] = 0x28a; local_158[7] = 0x23d; local_158[8] = 0x24d; local_158[9] = 0xee; local_158[10] = 0x183; local_158[0xb] = 0x124; local_158[0xc] = 0x1e0; local_158[0xd] = 0x19c; local_158[0xe] = 0x1ab; local_158[0xf] = 0x444; // ... (Initialisierung geht weiter für 32 Werte) ... local_158[0x1f] = 0x209; // ... (Logik zum Lesen der Eingabe) ... if (sVar4 == 0x20) { // Berechne Zahnräder, speichere Ergebnis in der zweiten Hälfte von local_158 calculate_gears(local_58,local_158 + 0x20); bVar1 = true; local_164 = 0; goto LAB_00402348; } // ... LAB_00402348: if (0x1f < local_164) goto LAB_00402351; // Constraint-Prüfung: // gears[next] * 2 + gears[current] == target[current] // wobei next = (current + 1) % 32 if (local_158[(long)((local_164 + 1) % 0x20) + 0x20] * 2 + local_158[(long)local_164 + 0x20] != local_158[local_164]) { bVar1 = false; goto LAB_00402351; } local_164 = local_164 + 1; goto LAB_00402348; // ... } ``` Die Schleife bei `LAB_00402348` verifiziert, dass für jedes Zahnrad `i`: `gears[i] + 2 * gears[(i+1)%32] == target[i]` ### Calculate Gears Die Funktion `calculate_gears` berechnet das `gears`-Array aus dem Eingabestring. ```c void calculate_gears(char *param_1,undefined4 *param_2) { undefined4 uVar1; uVar1 = f0((int)*param_1); *param_2 = uVar1; uVar1 = f1((int)param_1[1],*param_2); param_2[1] = uVar1; uVar1 = f2((int)param_1[2]); param_2[2] = uVar1; uVar1 = f3((int)param_1[3],param_2[2]); param_2[3] = uVar1; // ... Muster setzt sich fort ... uVar1 = f30((int)param_1[0x1e]); param_2[0x1e] = uVar1; uVar1 = f31((int)param_1[0x1f],param_2[0x1e]); param_2[0x1f] = uVar1; return; } ``` Sie verwendet 32 Hilfsfunktionen (`f0` bis `f31`). - Gerade Indizes hängen nur vom Eingabezeichen ab: `gears[i] = f_i(input[i])` - Ungerade Indizes hängen von der Eingabe und dem vorherigen Zahnrad ab: `gears[i] = f_i(input[i], gears[i-1])` ## Lösung Wir können dieses System mit dem Z3 Constraint Solver modellieren. 1. **Repliziere die `f`-Funktionen**: Wir implementieren die Logik von `f0`...`f31` in Python (extrahiert aus der Disassemblierung). 2. **Definiere Constraints**: Wir erzwingen die Beziehung `gears[i] + 2 * gears[(i+1)%32] == targets[i]`. 3. **Lösen**: Wir bitten Z3, die 32 Eingabezeichen zu finden. ### Solver-Skript ```python import z3 # 1. Ziele aus main extrahiert targets = [ 0x174, 0x2fe, 0x3dc, 0x30c, -425, -58, 0x28a, 0x23d, 0x24d, 0xee, 0x183, 0x124, 0x1e0, 0x19c, 0x1ab, 0x444, -56, -180, 0x13c, 0x25e, 0x1fe, 0x18a, 200, 0x82, 0x233, 0x2da, 0x36e, 0x3c3, 0x47d, 0x2a4, 0x3b5, 0x209 ] # 2. Flaggen-Variablen definieren flag = [z3.BitVec(f'flag_{i}', 32) for i in range(32)] s = z3.Solver() for i in range(32): s.add(flag[i] >= 32, flag[i] <= 126) # 3. Hilfsfunktionen (f0-f31) def c_rem(a, b): return z3.SRem(a, b) def f0(p1): return (p1 ^ 0x55) + 10 def f1(p1, p2): return c_rem((p1 + p2), 200) def f2(p1): return p1 * 3 - 20 def f3(p1, p2): return (p1 ^ p2) + 5 def f4(p1): return (p1 + 10) ^ 0xaa def f5(p1, p2): return (p1 - p2) * 2 def f6(p1): return p1 + 100 def f7(p1, p2): return (p1 ^ p2) + 12 def f8(p1): return (p1 * 2) ^ 0xff def f9(p1, p2): return p2 + p1 - 50 def f10(p1): return (p1 ^ 123) def f11(p1, p2): return c_rem((p1 * p2), 500) def f12(p1): return p1 + 1 def f13(p1, p2): return (p1 ^ p2) * 2 def f14(p1): return p1 - 10 def f15(p1, p2): return (p2 + p1) ^ 0x33 def f16(p1): return p1 * 4 def f17(p1, p2): return (p1 - p2) + 100 def f18(p1): return (p1 ^ 0x77) def f19(p1, p2): return c_rem((p1 + p2), 150) def f20(p1): return p1 * 2 def f21(p1, p2): return (p1 ^ p2) - 20 def f22(p1): return p1 + 33 def f23(p1, p2): return (p2 + p1) ^ 0xcc def f24(p1): return p1 - 5 def f25(p1, p2): return c_rem((p1 * p2), 300) def f26(p1): return p1 ^ 0x88 def f27(p1, p2): return p2 + p1 - 10 def f28(p1): return p1 * 3 def f29(p1, p2): return (p1 ^ p2) + 44 def f30(p1): return p1 + 10 def f31(p1, p2): return (p2 + p1) ^ 0x99 # 4. Zahnräder berechnen gears = [None] * 32 gears[0] = f0(flag[0]) gears[1] = f1(flag[1], gears[0]) gears[2] = f2(flag[2]) gears[3] = f3(flag[3], gears[2]) # ... (Mapping für alle 32 Zahnräder fortsetzen) ... gears[4] = f4(flag[4]); gears[5] = f5(flag[5], gears[4]) gears[6] = f6(flag[6]); gears[7] = f7(flag[7], gears[6]) gears[8] = f8(flag[8]); gears[9] = f9(flag[9], gears[8]) gears[10] = f10(flag[10]); gears[11] = f11(flag[11], gears[10]) gears[12] = f12(flag[12]); gears[13] = f13(flag[13], gears[12]) gears[14] = f14(flag[14]); gears[15] = f15(flag[15], gears[14]) gears[16] = f16(flag[16]); gears[17] = f17(flag[17], gears[16]) gears[18] = f18(flag[18]); gears[19] = f19(flag[19], gears[18]) gears[20] = f20(flag[20]); gears[21] = f21(flag[21], gears[20]) gears[22] = f22(flag[22]); gears[23] = f23(flag[23], gears[22]) gears[24] = f24(flag[24]); gears[25] = f25(flag[25], gears[24]) gears[26] = f26(flag[26]); gears[27] = f27(flag[27], gears[26]) gears[28] = f28(flag[28]); gears[29] = f29(flag[29], gears[28]) gears[30] = f30(flag[30]); gears[31] = f31(flag[31], gears[30]) # 5. Constraints hinzufügen for i in range(32): next_i = (i + 1) % 32 s.add((gears[i] + gears[next_i] * 2) == targets[i]) # 6. Lösen if s.check() == z3.sat: m = s.model() result = "".join([chr(m[flag[i]].as_long()) for i in range(32)]) print("Flag:", result) else: print("No solution found") ``` Das Ausführen des Solvers liefert die korrekte Flagge.