CTFd is too insufferably slow. You know why? Because they use an SQL database that's bogged down by JOINs instead of a web scale database like MongoDB. MongoDB is web scale. You turn it on and it scales right up. You know what's more web scale though? Nothing. That's right, the throughput of /dev/null is off the charts. Behold, CTFd+, the first databaseless CTF platform. Can you get the flag for the only challenge? RE -- We are given a binary that presents a welcome message along with a dummy PWN challenge, then asks for the flag for said challenge. As this is a mock CTF platform, it is a flag checker RE challenge. The content of the embedded PWN is not relevant: the program just calls `puts` then exits. Ghidra shows that the flag we supply is validated character by character against the result of some computation performed on a static array of data. (Some markup provided by me). ``` undefined8 main(void) { int flag_check_char; size_t flag_in_len; long idx; int *static_data; char flag_in [256]; puts("Welcome to CTFd+!"); puts( "So far, we only have one challenge, which is one more than the number of databases we have.\n " ); puts("Very Doable Pwn - 500 points, 0 solves"); puts("Can you help me pwn this program?"); puts("#include \nint main(void) {\n puts(\"Bye!\");\n return 0;\n}\n"); puts("Enter the flag:"); fgets(flag_in,0x100,stdin); flag_in_len = strcspn(flag_in,"\n"); idx = 0; static_data = INT_ARRAY_00104060; flag_in[flag_in_len] = '\0'; do { flag_check_char = flag_gen(static_data[idx]); if ((char)flag_check_char != flag_in[idx]) { puts("Incorrect flag."); return 0; } idx = idx + 1; } while (idx != 0x2f); puts("You got the flag! Unfortunately we don\'t exactly have a database to store the solve in...") ; return 0; } ``` If at any point in the validation a character fails, the whole flag is considered incorrect. Solution -------- To extract the expected flag, I ported the static data and the `flag_gen` computation logic to a standalone C program and simply print out the value of each character. We know the expected size of the flag string from the bounds of `main`'s while loop (`while (idx != 0x2f)`). ``` #include #include #include typedef unsigned char byte; static const unsigned char DATA[] = { 0x74, 0x92, 0x7f, 0x9c, 0xe9, 0x0c, 0xbb, 0xc2, 0x29, 0x19, 0x9b, 0x40, 0xd7, 0x6e, 0x3d, 0xbe, 0x04, 0xe1, 0x83, 0x4f, 0x83, 0xd4, 0x85, 0x91, 0xaf, 0x70, 0x5d, 0xfd, 0xf1, 0x7f, 0xc4, 0x88, 0x71, 0xfa, 0x78, 0xe6, 0x0d, 0xdf, 0xcb, 0x72, 0xa7, 0x3d, 0xb6, 0xf4, 0x3d, 0x9e, 0x29, 0x54, 0xf4, 0x7b, 0x05, 0xaa, 0xa3, 0x4d, 0x14, 0x14, 0x3c, 0x02, 0xc6, 0xe1, 0x39, 0xb5, 0xb9, 0x74, 0x0f, 0xd8, 0x5f, 0x54, 0x29, 0x73, 0x7a, 0x04, 0x3f, 0xd9, 0x41, 0xad, 0xd0, 0xbc, 0x16, 0x96, 0x50, 0x62, 0x59, 0x76, 0x0f, 0xec, 0xa7, 0xaa, 0x2f, 0xf2, 0xb1, 0x21, 0x7e, 0xb3, 0x80, 0x87, 0x15, 0x14, 0x8d, 0x76, 0x60, 0xad, 0xf3, 0x56, 0x4d, 0x6f, 0x84, 0x2c, 0x3e, 0x57, 0x38, 0x15, 0x9e, 0x7b, 0x95, 0x6a, 0x70, 0x08, 0x03, 0xaa, 0xbc, 0xbf, 0xc7, 0x27, 0x4d, 0x88, 0x2e, 0x47, 0x71, 0x09, 0x34, 0xbc, 0x94, 0xc0, 0x70, 0x95, 0xea, 0x21, 0x55, 0xd6, 0xbe, 0x14, 0x84, 0x86, 0x8d, 0xec, 0xf7, 0xff, 0xff, 0x65, 0x14, 0xaa, 0xa7, 0x6a, 0xd1, 0x21, 0x0c, 0xc1, 0x97, 0x84, 0xf7, 0xd2, 0x3a, 0x51, 0xca, 0xbb, 0x11, 0x62, 0xe5, 0xc8, 0x99, 0x87, 0xbd, 0xfc, 0x37, 0xb5, 0xed, 0x29, 0xcc, 0x44, 0x5d, 0xd9, 0x8a, 0x40, 0xb1, 0x02, 0x09, 0x2d }; int flag_gen(uint param_1) { byte bVar1; uint uVar2; int iVar3; uVar2 = 0; iVar3 = 0; do { bVar1 = (byte)iVar3 & 0x1f; iVar3 = iVar3 + 1; param_1 = (param_1 * param_1 >> bVar1 | param_1 * param_1 << 0x20 - bVar1) * 0x1337 + 0x4201337 ^ uVar2; uVar2 = uVar2 + 0x13371337; } while (iVar3 != 0x20); return (param_1 >> 8) + (param_1 >> 0x10) + param_1 + (param_1 >> 0x18); } int main(void) { const int *arr = (void *)DATA; for (int i = 0; i < 47; i++) { int c = flag_gen(arr[i]); printf("%c", (char)c); } printf("\n"); return 0; } ``` lactf{m4yb3_th3r3_1s_s0m3_m3r1t_t0_us1ng_4_db}