The challenge computes the hash of an integer we provide and prints the flag if the hashed output matches a constant.

The code is very straightforward and the only annoyance is the usage of confusing type names for integer types.

Here’s the code stripped from comments:

typedef int not_int_small;
typedef short int_small;
typedef int not_int_big;
typedef not_int_small int_big;
typedef unsigned char quantum_byte;
typedef quantum_byte* quantum_ptr;

typedef struct {
    not_int_big val;
} PASSWORD_QUANTUM;

typedef struct {
    int_small val;
    quantum_byte padding[2];
    quantum_byte checksum;
    quantum_byte reserved;
} INPUT_QUANTUM;

typedef struct quantum_data_s quantum_data_t;
struct __attribute__((packed)) quantum_data_s {
    INPUT_QUANTUM input;
    PASSWORD_QUANTUM password;
    quantum_byte entropy_pool[8];
    quantum_byte quantum_state[16];
};

static inline quantum_byte generate_quantum_entropy() {
    static quantum_byte seed = 0x42;
    seed = ((seed << 3) ^ (seed >> 5)) + 0x7f;
    return seed;
}

void init_quantum_security(quantum_data_t* qdata) {
    for (int i = 0; i < 8; i++) {
        qdata->entropy_pool[i] = generate_quantum_entropy();
    }
    for (int i = 0; i < 16; i++) {
        qdata->quantum_state[i] = (quantum_byte)(i * 0x11 + 0x33);
    }

    qdata->input.padding[0] = 0;
    qdata->input.padding[1] = 0;
}

not_int_big quantum_hash(INPUT_QUANTUM input, quantum_byte* entropy) {
    int_small input_val = input.val;
    not_int_big hash = input_val;

    hash ^= (entropy[0] << 8) | entropy[1];
    hash ^= (entropy[2] << 4) | (entropy[3] >> 4);
    hash += (entropy[4] * entropy[5]) & 0xff;
    hash ^= entropy[6] ^ entropy[7];
    hash |= 0xeee;
    hash ^= input.padding[0] << 8 | input.padding[1];
    return hash;
}

void access_granted() {
    printf("Quantum authentication successful!\n");
    printf("Accessing secured vault...\n");

    FILE *fp = fopen("flag.txt", "r");
    if (fp == NULL) {
        printf("Error: Quantum vault is offline\n");
        printf("Please contact the quantum administrator.\n");
        return;
    }

    char flag[100];
    if (fgets(flag, sizeof(flag), fp) != NULL) {
        printf("CLASSIFIED FLAG: %s\n", flag);
    } else {
        printf("Error: Quantum decryption failed\n");
        printf("Please contact the quantum administrator.\n");
    }
    fclose(fp);
}

int main() {
    quantum_data_t qdata;

    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    init_quantum_security(&qdata);
    qdata.password.val = 0x555;

    printf("=== QUANTUM AUTHENTICATION SYSTEM v2.7.3 ===\n");
    printf("Initializing quantum security protocols...\n");

    for (volatile int i = 0; i < 100000; i++) { }

    printf("Quantum entropy generated. System ready.\n");
    printf("Please enter your quantum authentication code: ");

    // qdata.input.val is a short !!!
    if (scanf("%d", (int*)&qdata.input.val) != 1) {
        printf("Invalid quantum input format!\n");
        return 1;
    }

    qdata.input.checksum = (quantum_byte)(qdata.input.val & 0xff);
    not_int_big hashed_input = quantum_hash(qdata.input, qdata.entropy_pool);

    printf("Quantum hash computed: 0x%x\n", hashed_input);
    if (hashed_input == qdata.password.val) {
        access_granted();
    } else {
        printf("Quantum authentication failed!\n");
        printf("Access denied. Incident logged.\n");
    }
    return 0;
}

Since the constant is known (0x555) and the domain of the input is small (32-bit), we can just bruteforce it!

Valid solutions can be found by changing the main function like so:

int main() {
    quantum_data_t qdata;
    qdata.password.val = 0x555;

    for (int i = INT_MIN; i < INT_MAX; i++) {
        seed = 0x42;
		init_quantum_security(&qdata);

		memcpy(&qdata.input.val, &i, sizeof(int));
		qdata.input.checksum = (quantum_byte)(qdata.input.val & 0xff);
		int hashed_input = quantum_hash(qdata.input, qdata.entropy_pool);

		if (hashed_input == qdata.password.val) {
			printf("Found %d\n", i);
		}
    }
	printf("Done\n");
}

In a few seconds we find a lot of negative numbers (32560 to be exact) that match our expected hash!

...
Found -1141148752
Found -1141148750
Found -1141148748
Found -1141148746
Found -1141148744
...

Now we can profit:

$ ncat --ssl qas.chal.uiuc.tf 1337
== proof-of-work: disabled ==
=== QUANTUM AUTHENTICATION SYSTEM v2.7.3 ===
Initializing quantum security protocols...
Quantum entropy generated. System ready.
Please enter your quantum authentication code: -1141148674
Quantum hash computed: 0x555
Quantum authentication successful!
Accessing secured vault...
CLASSIFIED FLAG: uiuctf{qu4ntum_0v3rfl0w_2d5ad975653b8f29}

With the cheesy solution out of the way, what is the vuln here?

scanf("%d", (int*)&qdata.input.val) reads 4 bytes into the input struct. But the val field is a short! Since the struct is packed, this means that we are overwriting the following field, which in this case is a char[2] called padding.

Contrary to common sense, this field is actually used in the hash function: hash ^= input.padding[0] << 8 | input.padding[1];

By providing certain negative numbers we can obtain the right output value and win. Also note that there are no positive solutions.