Skip to content

Commit 83962b3

Browse files
authored
Merge pull request #45 from TheRomanXpl0it/fix-align
Add uiuctf qas writeup
2 parents 3aace56 + 274d3d2 commit 83962b3

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
---
2+
# example: UIUCTF 25 - ELF Capsule
3+
title: UIUCTF 25 - QAS
4+
5+
# date of publication/creation
6+
date: '2025-07-28T11:21:17+02:00'
7+
8+
# add link to your original blog post
9+
upstream: ""
10+
11+
# set to true to use mathjax
12+
math: false
13+
14+
# for ctf writeups add the category for the ctf event
15+
# --> name of ctf + short year (example uiuctf25)
16+
categories:
17+
- writeup
18+
- uiuctf25
19+
tags:
20+
- pwn
21+
- bruteforce
22+
- easy
23+
authors:
24+
- nect
25+
---
26+
27+
The challenge computes the hash of an integer we provide and
28+
prints the flag if the hashed output matches a constant.
29+
30+
The code is very straightforward and the only annoyance
31+
is the usage of confusing type names for integer types.
32+
33+
```c
34+
typedef int not_int_small;
35+
typedef short int_small;
36+
typedef int not_int_big;
37+
typedef not_int_small int_big;
38+
typedef unsigned char quantum_byte;
39+
typedef quantum_byte* quantum_ptr;
40+
41+
typedef struct {
42+
not_int_big val;
43+
} PASSWORD_QUANTUM;
44+
45+
typedef struct {
46+
int_small val;
47+
quantum_byte padding[2];
48+
quantum_byte checksum;
49+
quantum_byte reserved;
50+
} INPUT_QUANTUM;
51+
52+
typedef struct quantum_data_s quantum_data_t;
53+
struct __attribute__((packed)) quantum_data_s {
54+
INPUT_QUANTUM input;
55+
PASSWORD_QUANTUM password;
56+
quantum_byte entropy_pool[8];
57+
quantum_byte quantum_state[16];
58+
};
59+
60+
static inline quantum_byte generate_quantum_entropy() {
61+
static quantum_byte seed = 0x42;
62+
seed = ((seed << 3) ^ (seed >> 5)) + 0x7f;
63+
return seed;
64+
}
65+
66+
void init_quantum_security(quantum_data_t* qdata) {
67+
for (int i = 0; i < 8; i++) {
68+
qdata->entropy_pool[i] = generate_quantum_entropy();
69+
}
70+
for (int i = 0; i < 16; i++) {
71+
qdata->quantum_state[i] = (quantum_byte)(i * 0x11 + 0x33);
72+
}
73+
74+
qdata->input.padding[0] = 0;
75+
qdata->input.padding[1] = 0;
76+
}
77+
78+
not_int_big quantum_hash(INPUT_QUANTUM input, quantum_byte* entropy) {
79+
int_small input_val = input.val;
80+
not_int_big hash = input_val;
81+
82+
hash ^= (entropy[0] << 8) | entropy[1];
83+
hash ^= (entropy[2] << 4) | (entropy[3] >> 4);
84+
hash += (entropy[4] * entropy[5]) & 0xff;
85+
hash ^= entropy[6] ^ entropy[7];
86+
hash |= 0xeee;
87+
hash ^= input.padding[0] << 8 | input.padding[1];
88+
return hash;
89+
}
90+
91+
void access_granted() {
92+
printf("Quantum authentication successful!\n");
93+
printf("Accessing secured vault...\n");
94+
95+
FILE *fp = fopen("flag.txt", "r");
96+
if (fp == NULL) {
97+
printf("Error: Quantum vault is offline\n");
98+
printf("Please contact the quantum administrator.\n");
99+
return;
100+
}
101+
102+
char flag[100];
103+
if (fgets(flag, sizeof(flag), fp) != NULL) {
104+
printf("CLASSIFIED FLAG: %s\n", flag);
105+
} else {
106+
printf("Error: Quantum decryption failed\n");
107+
printf("Please contact the quantum administrator.\n");
108+
}
109+
fclose(fp);
110+
}
111+
112+
int main() {
113+
quantum_data_t qdata;
114+
115+
setvbuf(stdout, NULL, _IONBF, 0);
116+
setvbuf(stdin, NULL, _IONBF, 0);
117+
setvbuf(stderr, NULL, _IONBF, 0);
118+
119+
init_quantum_security(&qdata);
120+
qdata.password.val = 0x555;
121+
122+
printf("=== QUANTUM AUTHENTICATION SYSTEM v2.7.3 ===\n");
123+
printf("Initializing quantum security protocols...\n");
124+
125+
for (volatile int i = 0; i < 100000; i++) { /* quantum processing */ }
126+
127+
printf("Quantum entropy generated. System ready.\n");
128+
printf("Please enter your quantum authentication code: ");
129+
130+
// qdata.input.val is a short !!!
131+
if (scanf("%d", (int*)&qdata.input.val) != 1) {
132+
printf("Invalid quantum input format!\n");
133+
return 1;
134+
}
135+
136+
qdata.input.checksum = (quantum_byte)(qdata.input.val & 0xff);
137+
not_int_big hashed_input = quantum_hash(qdata.input, qdata.entropy_pool);
138+
139+
printf("Quantum hash computed: 0x%x\n", hashed_input);
140+
if (hashed_input == qdata.password.val) {
141+
access_granted();
142+
} else {
143+
printf("Quantum authentication failed!\n");
144+
printf("Access denied. Incident logged.\n");
145+
}
146+
147+
return 0;
148+
}
149+
```
150+
151+
Since the constant is known `0x555` and the domain of the input is small (32-bit), we can just bruteforce it!
152+
153+
Valid solutions can be found by just changing the main function like so:
154+
155+
```c
156+
int main() {
157+
quantum_data_t qdata;
158+
qdata.password.val = 0x555;
159+
160+
for (int i = INT_MIN; i < INT_MAX; i++) {
161+
seed = 0x42;
162+
init_quantum_security(&qdata);
163+
164+
memcpy(&qdata.input.val, &i, sizeof(int));
165+
qdata.input.checksum = (quantum_byte)(qdata.input.val & 0xff);
166+
int hashed_input = quantum_hash(qdata.input, qdata.entropy_pool);
167+
168+
if (hashed_input == qdata.password.val) {
169+
printf("Found %d\n", i);
170+
}
171+
}
172+
printf("Done\n");
173+
}
174+
```
175+
176+
In a few seconds we find a lot of negative numbers (32560 to be exact) that match our expected hash!
177+
178+
```
179+
...
180+
Found -1141148752
181+
Found -1141148750
182+
Found -1141148748
183+
Found -1141148746
184+
Found -1141148744
185+
...
186+
```
187+
188+
Now we can profit:
189+
190+
```bash
191+
$ ncat --ssl qas.chal.uiuc.tf 1337
192+
== proof-of-work: disabled ==
193+
=== QUANTUM AUTHENTICATION SYSTEM v2.7.3 ===
194+
Initializing quantum security protocols...
195+
Quantum entropy generated. System ready.
196+
Please enter your quantum authentication code: -1141148674
197+
Quantum hash computed: 0x555
198+
Quantum authentication successful!
199+
Accessing secured vault...
200+
CLASSIFIED FLAG: uiuctf{qu4ntum_0v3rfl0w_2d5ad975653b8f29}
201+
```
202+
203+
Now that the cheesy solution is out of the way, what is the vuln here?
204+
205+
`scanf("%d", (int*)&qdata.input.val)` reads 4 bytes into the input struct. But the `val` field is a short!
206+
Since the struct is packed, this means that we are overwriting the following field, which in this case is a `char[2]` called `padding`.
207+
208+
Contrary to common sense, this field is actually used in the hash function: `hash ^= input.padding[0] << 8 | input.padding[1];`
209+
210+
By providing certain negative numbers we can manipulate the input and win. Also note that there are no positive solutions.
211+

0 commit comments

Comments
 (0)