77 views
# Fast Calculator - rev One binary file is given. 64bit ELF file, compiled with golang. ```c= while ( 1 ) { do { while ( 1 ) { printf("Enter your operation: ", v8, v28, v29, v30, v31, v63, v64, v65, v66); while ( 1 ) { v8 = &v74; if ( _isoc99_scanf(" %lf %c %lf", &v74, &v67, v75, v32, v33, v63) == 3 ) break; while ( getchar() != 10 ) ; printf("Enter your operation: ", &v74, v34, v35, v36, v37); } if ( v67 == '+' || v67 == '-' || v67 == '*' || v67 == '/' || v67 == '%' || v67 == '^' ) break; puts("Invalid operation!", &v74, v38, v39); } LODWORD(v79) = v67; v80 = v74; v81 = v75[0]; v76 = calculate(" %lf %c %lf", &v74, v38, v39, v40, v41, *v75, v3, v4, v5, v42, v43, v6, v7, v67, v74, *v75); printf("Result: %lf\n", &v74, v44, v45, v46, v47, v76); v48 = v76; } while ( v76 != 8573.8567 ); ``` Throwing binary into ida and checking the pseudo code, you can see that the calculation formula is input first, calculated with the `calculate` function, and then repeated until the result is `8573.8567`. ```c= enc_data = &v63; j_memcpy(&v63, v83, v70); flip_count = 0; for ( i = 0; i < operations_length; ++i ) { v48 = calculate(...v82[3 * i],*&v82[3 * i + 1],*&v82[3 * i + 2]); if ( gauntlet(v48) ) { i_div = i / 8; v52 = (1 << (7 - i % 8)) ^ enc_data[i / 8]; enc_data[i_div] ^= 1 << (7 - i % 8); ++flip_count; } } v8 = operations_length; printf("I calculated %d operations, tested each result in the gauntlet, and flipped %d bits in the encrypted flag!\n",operations_length,flip_count); puts("Here is your decrypted flag:\n", v8, v57, v58); printf("%s\n\n", enc_data, v59, v60, v61, v62); ``` enc_data -> `encrypted flag` v82 -> `0x00000000004B8240` If the result is 8573.8567, the loop is end, and an operator symbol and two double-type numbers are taken from v82 and called as arguments of the `calculate` function. `gauntlet` function just checks if the number is negative. so, each bit of encrypted flag is xored with 1 when the return value of calculate is negative. That's all. But the final decrypted data is `uiuctf{This is a fake flag. You are too fast!}`. User input has no effect on the decryption. With some guessing, I checked somethings + init_array -> just call `set_fast_math` + another array -> nop + swap the positions of each numbers -> nop So I started debugging and I have encountered some peculiar operations in the calculate function. In one specific calculation, when `-218.05xx` is divided by `218.05xx` using ``%``, the result is -0.0 (`0x8000000000000000`). However, the gauntlet function does not interpret this value as negative. Similarly, in certain square operations, the result is -nan (`0xfff8000000000000`), but the gauntlet function does not consider it negative. As far as I know, the result of the modulo operation is always negative if the dividend is negative. The same is true for squares. I implemented the operation back in Python to handle these exceptions. `solve script` ```python= import struct ops = open("./ops", "rb").read() enc = open("./calc_out", "rb").read() enc = list(enc) c = 0 for i in range(0, len(ops), 24): op = chr(ops[i]) n1, n2 = struct.unpack("<dd", ops[i + 8:i + 24]) if op in ["+", "-", "*", "/"]: res = eval(f"n1 {op} n2") elif op == "%": res = -1 if n1 < 0.0 else 1 elif op == "^": res = -1 if n1 < 0.0 else 1 if res < 0.0: enc[c // 8] ^= 1 << (7 - c % 8) c += 1 print(bytes(enc)) ``` ops -> dumped 0x00000000004B8240 ~ 0x00000000004B8240+0x170 * 0x24 enc -> dumped v83 ~ v83+0x40 > uiuctf{n0t_So_f45t_w1th_0bscur3_b1ts_of_MaThs} second blood 🩸🩸