Heading up the steeple gave you and your companion a nice view over the
outbreak situation in your city. But it also attracted a lot of unwanted
attention. Zombies are surrounding your spot and are looking for an
entrance to the building. You obviously need some bait to lure them away so
you can flee safely. Solve this challenge to find out which human bodypart
zombies like the most.

https://ctf.fluxfingers.net/challenges/mealtime.exe

credits: 200 +3 (1st), +2 (2nd), +1 (3rd)

The challenge takes a 256 bits key as argv[1], cuts it into 4 64 bits blocks, encrypts it using a modified TEA with a constant 32 bits key (different for each block), then compares the ciphered block to a 64 bits constant block. The goal was to find each 64 bits block independently then concatenate them to get the key. I’ll only detail what we did for one block, the other three blocks were the same with a different key/ciphered block.

This Win32 executable used a simple SeDebugPrivilege trick to try to stop us from debugging. After patching this, we were able to run it inside a debugger to test if our implementation of the encryption algorithm we reversed was correct. After a lot of failed tries (being tired doesn’t help), we found that this code implemented the same algorithm:

void tea(unsigned int* pdw1, unsigned int* pdw2)
{
    unsigned int dw1 = *pdw1, dw2 = *pdw2;
    unsigned int cipher = 0;
    int i;

    for (i = 0; i < 64; ++i)
    {
        dw1 += (cipher + 0x78756c66) ^ (dw2 + ((dw2 << 4) ^ (dw2 >> 5)));
        cipher -= 0x61c88647;
        dw2 += (cipher + 0x78756c66) ^ (dw1 + ((dw1 << 4) ^ (dw1 >> 5)));
    }

    *pdw1 = dw1;
    *pdw2 = dw2;
}

int main(void)
{
    unsigned int dw1 = 0x83ffeeea; // first part of input block
    unsigned int dw2 = 0xec0ac902; // second part of input block

    tea(&dw1, &dw2);

    printf("0x%08x 0x%08x\n", dw1, dw2);

    return 0;
}

From there, we could either try to find a vulnerability in the algorithm and write a bruteforcer, or take the “lazy” route: provide a representation of the problem in a DIMACS file and run cryptominisat on it to solve the problem automagically. This Python script generated the DIMACS description of the problem (see my blog post about SAT and hash cracking for the CNFGenerator code and the several cnf_* functions):

gen = CNFGenerator()
dw2 = cnf_int(gen, 32)
dw1 = cnf_int(gen, 32)

cipher = cnf_const(gen, 0)
addcst = cnf_const(gen, 0x63737265)
subcst = cnf_const(gen, 0x61c88647)

for i in xrange(32):
    cipher_plus = cnf_add(gen, cipher, addcst)
    sum1 = cnf_add(gen, dw2, cnf_xor(gen, cnf_sll(gen, dw2, 4), cnf_srl(gen, dw2, 5)))
    dw1 = cnf_add(gen, dw1, cnf_xor(gen, cipher_plus, sum1))

    cipher = cnf_sub(gen, cipher, subcst)

    cipher_plus = cnf_add(gen, cipher, addcst)
    sum2 = cnf_add(gen, dw1, cnf_xor(gen, cnf_sll(gen, dw1, 4), cnf_srl(gen, dw1, 5)))
    dw2 = cnf_add(gen, dw2, cnf_xor(gen, cipher_plus, sum2))

cnf_equal(gen, dw1, 0x131af1be)
cnf_equal(gen, dw2, 0x4bb34049)

print gen.output()

This generates a DIMACS file with 23520 variables and 139232 clauses. CryptoMiniSAT can solve this in about 0.06s, generating correct values for the initial dw1 and dw2: 615f7a6e 645f6572.

Repeating this technique on the three remaining 64 bits blocks gives us the following key: --delicious_brainz_are_delicious.