Tags: rev arm32 

Rating:

DEF CON CTF Qualifier 2022

not-so-advanced

not-so-advanced.gba

Tags: arm arm32 gba rom rev

Summary

GBA, groan...

I spend more time tooling up for these type of challenges than actually solving them.

With the help of teammate dobs, I didn't have to do too much researching on tooling; dobs set me up with the Ghidra plugin GhidraGBA and found the functions of interest. However, we still lost time finding GBA emulators and getting debuggers working with them. I guess we could have just Read the code, should be easy, but I prefer to noodle my way through with a debugger to check my assumptions. I ended up using mGBA with its internal debugger (GDB had numerous problems with a stable and useful remote connection).

Analysis

Give it a go

From here you up/down/left/right to input a string of lowercase letters and underscores then enter with start (or return if not using a controller).

Then a check function, checks it out:

Decompile with Ghidra

longlong main(void)
{
  int iVar1;
  uint in_lr;
  undefined auStack268 [256];
  undefined uStack12;
  
  FUN_080009cc();
  FUN_08000e74("Read the code, should be easy\n");
  uStack12 = 0;
  FUN_08000340(auStack268);
  FUN_08000e74("Checking it out\n");
  iVar1 = checkit(auStack268);
  if (iVar1 == 0x12e1) {
    FUN_08000e74("That works");
  }
  else {
    FUN_08000e74(&DAT_08005ce4);
  }
  return (ulonglong)in_lr << 0x20;
}

main does very little, just gets your input and passes to checkit, if checkit returns 0x12e1, then you've solve it.

undefined8 checkit(int param_1)
{
  int iVar1;
  ushort extraout_r1;
  ushort extraout_r1_00;
  uint uVar2;
  undefined4 in_lr;
  uint local_18;
  ushort local_14;
  ushort local_12;
  
  iVar1 = FUN_08001148(param_1);
  local_12 = 1;
  local_14 = 0;
  if (iVar1 == 9) {
    for (local_18 = 0; local_18 < 9; local_18 = local_18 + 1) {
      thunk_FUN_08000cc8((uint)local_12 + (uint)*(byte *)(param_1 + local_18),0xfff1);
      thunk_FUN_08000cc8((uint)local_14 + (uint)extraout_r1,0xfff1);
      local_14 = extraout_r1_00;
      local_12 = extraout_r1;
    }
    uVar2 = (uint)(local_14 ^ local_12);
  }
  else {
    uVar2 = 0xffffffff;
  }
  return CONCAT44(in_lr,uVar2);
}

Clearly we can tell from the loop that the input must be 9 in length, but then ...

I got lazy

thunk_FUN_08000cc8 calls FUN_08000ba0 (not shown) and that looked like a lot of work, so instead I set two break points with mGBA's debugger where local_12 and local_14 were being updated:

b 0x80002FA
b 0x800030C

Then looked at the values on stack on each loop iteration:

r/2 0x03007DDE
r/2 0x03007DDC

It was easy to see a pattern:

local_12 += (uint)*(byte *)(param_1 + local_18) // input byte
local_14 += local_12

Solve

#!/usr/bin/env python3

from itertools import product

for i in product('abcdefghijklmnopqrstuvwxyz_', repeat = 9):
    local_12 = 1
    local_14 = 0
    for j in i:
        local_12 += ord(j)
        local_14 += local_12
    if local_12 ^ local_14 == 0x12e1:
        print(''.join(i))
        break

Output:

aaaaaanzb

Check:

BTW, this is just one of 1000s that works, just remove the break from the solve above and see for yourself.

Original writeup (https://github.com/datajerk/ctf-write-ups/tree/master/dc2022q/not-so-advanced).