Contents

Ph0wn 2023 - Light weight but heavy duty

This is the solution of the challenge “Light weight but heavy duty” from Ph0wn 2023 CTF we solved together with @FdLSifu. It was a ARM reverse engineering of the block cipher PRESENT.

Details

Category: reverse

Author: Cryptopathe

Points: 500

Description

Pico le Croco, in need of securing his luxurious jacuzzi installation, enlisted the services of a renowned cryptographer, who goes by the name Lars Bogdanov, or something along those lines. Can you crack the algorithm designed to protect the jacuzzi’s remote control?

Bonus for 1st solve: 50

lightweightbutheavyduty_armv7

Solution

Ghidra was not able to directly find the main function but hopefully radare2 does:

1
2
3
4
5
6
7
8
[0x00010508]> fs symbols
[0x00010508]> f
0x000103c8 256 main
0x00010508 1 entry0
0x000105cc 1 entry.fini0
0x000105f4 1 entry.init0
0x00022038 4 obj.stderr
0x0002203c 4 obj.stdout

Now let’s open it in Ghidra:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
undefined main(int param_1,int param_2)

{
  size_t sVar1;
  int extraout_r1;
  byte *pbVar2;
  byte *pbVar3;
  byte bVar5;
  int iVar6;
  byte abStack_28 [8];
  byte abStack_20 [4];
  byte *pbVar4;
  
  if ((param_1 * 0x4c69 + 0x6768) % 0x10001 == 0x39) {
    sVar1 = strnlen(*(char **)(param_2 + 4),0x539);
    FUN_00010e48(sVar1 * 0x7477 + 0x6569,0x10001);
    if (extraout_r1 == 0x97ef) {
      iVar6 = 0;
      bVar5 = 0;
      do {
        FUN_000105f8(abStack_28,(byte *)(*(int *)(param_2 + 4) + iVar6),DAT_00022030);
        pbVar2 = &UNK_000113b3 + iVar6;
        pbVar4 = abStack_28;
        do {
          pbVar3 = pbVar4 + 1;
          pbVar2 = pbVar2 + 1;
          bVar5 = bVar5 | *pbVar4 ^ *pbVar2;
          pbVar4 = pbVar3;
        } while (pbVar3 != abStack_20);
        iVar6 = iVar6 + 8;
      } while (iVar6 != 0x28);
      if (bVar5 == 0) {
        fwrite("\nWell done!\n\n",1,0xd,stdout);
        return 0;
      }
    }
  }
  fwrite("\nGame over, try again!\n\n",1,0x18,stderr);
  return 1;
}

The first two checks are checks of the number of arguments and check on the size of the second argument. For example for the second if we have sVar1 * 0x7477 - 0x6569 == 0x97ef % 0x10001. Thus we can recover the value of sVar1 with Sage:

1
2
3
4
5
6
sage: p = 0x10001
sage: r1 = 0x97ef
sage: F = GF(p)
sage: r1 = F(0x97ef)
sage: (r1-0x6569)/0x7477
40

Meaning that the parameter need to be 40 bytes long.

Then, the loop preforms an operation with the function FUN_000105f8 on block of 8 bytes until it reach the end of the input. When we looked at the function it seems to be a Cryptography operation. We got back to the description of the challenge and it appears that if you search for “Bogdanov, Lars cipher” you quickly find that the block cipher PRESENT. Then we confirmed that the function implement the PRESENT cipher by remarking that it does 31 rounds, it uses the same PRESENT Sbox and it loads a key of 10 bytes.

At the end of the block encryption the result is compared with a value in the code at address 0x113b4. The value can be recovered in radare2:

1
2
3
[0x000113b5]> s 0x113b4
[0x000113b4]> p8 40
47c8a2e0bade478e23290dec2a116f4b7a273d9516fe45d1b5fe2e92916e2ef1e3e219b38cd0e687

It it matches, the program displays the string "\nWell done!\n\n".

The key is pass as the third parameter of the function and is a pointer on the function main itself. It is a nice anti-debug trick because if you insert a breakpoint on main you would have a wrong decryption. We can easily recover the key:

1
2
3
[0x00010508]> s main
[0x000103c8]> p8 10
003180e008219fe50333

Since we have the encrypted value and the key we can decrypt everything in Sage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sage: present = PRESENT(doFinalRound=True)
sage: key = 0x003180e008219fe50333
sage: ciphertexts = [0x47c8a2e0bade478e ,0x23290dec2a116f4b, 0x7a273d9516fe45d1, 0xb5fe2e92916e2ef1, 0xe3e2
....: 19b38cd0e687]
sage: plain = b""
sage: for ciphertext in ciphertexts:
....:     plain += int(present.decrypt(ciphertext, key)).to_bytes(8)
....: 
sage: plain
b'ph0wn{!!n0t-l1ghtweight-crypt0-5killz!!}'

Then we got the flag.

Another description and a solution of this challenge are available in the Ph0wn Mag Issue #1