---
# System prepended metadata

title: Write-up CyberEDU

---

# Write-up CyberEDU

## Mobile

### tinygame

Chall [tinygame](https://app.cyber-edu.co/challenges/9fcbff8d-0037-4316-938b-0eb6b4b74350?tenant=cyberedu) is a challenge from [D-CTF2025](https://app.cyber-edu.co/challenges?tenant=cyberedu&sortBy=newest).
![image](https://hackmd.io/_uploads/rktsnSIDZx.png)
It splits the flag into two parts, and you have to find both to put them back together.

PartB
Drop the file into jadx
![image](https://hackmd.io/_uploads/rk7xaBIDWl.png)
`MainActivity`: The logic code found is not here. The application uses `Engine.findAndBuildLoader` to load a class named `ui.UiFactory` at runtime.
![image](https://hackmd.io/_uploads/H1AWaHIw-g.png)
`Engine.java`: Noticed the presence of the native library `libtinymedia.so` and native functions. Specifically, the `getFlagPartB()` function.
![image](https://hackmd.io/_uploads/BkdnTB8DZg.png)

Decomplie it using apktool. And drop `/lib/arm64-v8a/libtinymedia.so` into IDA pro.

```C++
__int64 __fastcall Java_com_tinygame_app_Engine_getFlagPartB(__int64 a1)
{
  const void *v1; // x19
  size_t v2; // x20
  __int64 v3; // x22
  __int128 *v4; // x24
  __int64 v5; // x25
  __int64 v6; // x28
  char *v7; // x21
  __int64 v8; // x0
  __int64 v9; // x20
  bool v10; // zf
  unsigned __int64 v11; // x0
  char v12; // w27
  __int64 v13; // x0
  void *v14; // x21
  __int64 v15; // x0
  __int64 v16; // x21
  __int64 v17; // x23
  __int64 v18; // x1
  size_t v19; // x23
  __int64 v20; // x0
  __int64 v21; // x23
  __int64 v22; // x1
  __int64 v23; // x0
  __int64 v24; // x29
  __int64 v25; // x1
  __int64 v26; // x21
  __int64 v28; // x0
  __int64 v29; // x24
  __int64 v30; // x1
  __int64 v31; // x1
  __int64 v32; // x0
  __int64 v33; // [xsp+8h] [xbp-168h] BYREF
  size_t v34; // [xsp+10h] [xbp-160h] BYREF
  void *v35; // [xsp+18h] [xbp-158h]
  size_t v36; // [xsp+20h] [xbp-150h]
  _QWORD v37[3]; // [xsp+28h] [xbp-148h] BYREF
  char *v38; // [xsp+40h] [xbp-130h] BYREF
  __int64 v39; // [xsp+48h] [xbp-128h]
  __int64 v40; // [xsp+50h] [xbp-120h]
  __int128 v41; // [xsp+58h] [xbp-118h] BYREF
  __int128 v42; // [xsp+68h] [xbp-108h]
  __int128 v43; // [xsp+78h] [xbp-F8h]
  __int64 v44; // [xsp+88h] [xbp-E8h]
  __int128 fd; // [xsp+90h] [xbp-E0h] BYREF
  __int128 v46; // [xsp+A0h] [xbp-D0h]
  __int128 v47; // [xsp+B0h] [xbp-C0h]
  __int64 v48; // [xsp+C0h] [xbp-B0h]
  _QWORD v49[2]; // [xsp+C8h] [xbp-A8h] BYREF
  __int128 *v50; // [xsp+D8h] [xbp-98h] BYREF
  __int64 (__fastcall *v51)(); // [xsp+E0h] [xbp-90h]
  size_t n; // [xsp+E8h] [xbp-88h]
  _QWORD v53[2]; // [xsp+F0h] [xbp-80h] BYREF
  __int64 v54[2]; // [xsp+100h] [xbp-70h] BYREF

  v7 = &byte_8C094;
  v33 = a1;
  v8 = sub_32C10(32, 1);
  if ( !v8 )
  {
    sub_709BC(1, 32, &off_83CC0);
    goto LABEL_68;
  }
  v9 = 0;
  v4 = (__int128 *)byte_76C8;
  *(_QWORD *)&v41 = 32;
  *((_QWORD *)&v41 + 1) = v8;
  *(_QWORD *)&v42 = 0;
  do
  {
    v11 = qword_79B8[v9];
    if ( v11 >= 0x20 )
      sub_74374();
    LODWORD(v5) = byte_6840[v11];
    v12 = byte_76C8[v9 & 7];
    if ( v9 == (_QWORD)v41 )
      sub_707D8(&v41, &off_83CF0);
    v10 = v9 == 31;
    *(_BYTE *)(*((_QWORD *)&v41 + 1) + v9++) = v12 ^ v5;
    *(_QWORD *)&v42 = v9;
  }
  while ( !v10 );
  sub_70E70(&fd, *((_QWORD *)&v41 + 1), 32);
  v1 = (const void *)*((_QWORD *)&fd + 1);
  v2 = v46;
  if ( (v46 & 0x8000000000000000LL) != 0 )
  {
    v3 = 0;
    goto LABEL_66;
  }
  if ( (_QWORD)v46 )
  {
    v3 = 1;
    v13 = sub_32C10(v46, 1);
    if ( v13 )
    {
      v14 = (void *)v13;
      goto LABEL_12;
    }
LABEL_66:
    sub_709BC(v3, v2, &off_83A68);
    goto LABEL_68;
  }
  v14 = &dword_0 + 1;
LABEL_12:
  memcpy(v14, v1, v2);
  v34 = v2;
  v35 = v14;
  v36 = v2;
  if ( (_QWORD)v41 )
    sub_32C14(*((_QWORD *)&v41 + 1), v41, 1);
  if ( ((unsigned __int64)fd | 0x8000000000000000LL) != 0x8000000000000000LL )
    sub_32C14(v1, fd, 1);
  *(_QWORD *)&v41 = &v34;
  *((_QWORD *)&v41 + 1) = sub_2C0C4;
  *(_QWORD *)&fd = &off_83D40;
  *((_QWORD *)&fd + 1) = 2;
  *(_QWORD *)&v46 = &v41;
  *((_QWORD *)&v46 + 1) = 1;
  *(_QWORD *)&v47 = 0;
  sub_70CE4(v37, &fd);
  v1 = (const void *)v37[0];
  v2 = v37[1];
  v3 = v37[2];
  v49[0] = aSdcardAndroidD;
  v49[1] = 52;
  LODWORD(fd) = 511;
  BYTE4(fd) = 1;
  v15 = sub_538D4(&fd);
  if ( v15 )
  {
    if ( (v15 & 3) == 1 )
    {
      v16 = v15 - 1;
      v17 = *(_QWORD *)(v15 - 1);
      v4 = *(__int128 **)(v15 + 7);
      if ( *(_QWORD *)v4 )
        (*(void (__fastcall **)(__int64))v4)(v17);
      v18 = *((_QWORD *)v4 + 1);
      if ( v18 )
        sub_32C14(v17, v18, *((_QWORD *)v4 + 2));
      sub_32C14(v16, 24, 8);
    }
  }
  else
  {
    *(_QWORD *)&v41 = v49;
    *((_QWORD *)&v41 + 1) = sub_30F08;
    LODWORD(v5) = 1;
    *(_QWORD *)&fd = &unk_83D08;
    *((_QWORD *)&fd + 1) = 2;
    *(_QWORD *)&v46 = &v41;
    *((_QWORD *)&v46 + 1) = 1;
    *(_QWORD *)&v47 = 0;
    sub_70CE4(&v50, &fd);
    v4 = v50;
    v7 = (char *)v51;
    v19 = n;
    DWORD2(fd) = 16777472;
    *(_QWORD *)&fd = 0x1B600000000LL;
    WORD6(fd) = 1;
    sub_536A4((__int64 *)&v41, (int)&fd, (int)v51, n);
    v6 = (unsigned int)v41;
    if ( (v41 & 1) == 0 )
    {
      LODWORD(fd) = DWORD1(v41);
      v23 = sub_30C48(&fd, v2, v3);
      v3 = v23;
      if ( (v23 & 3) == 1 )
      {
        v3 = v23 - 1;
        v5 = *(_QWORD *)(v23 - 1);
        v24 = *(_QWORD *)(v23 + 7);
        if ( *(_QWORD *)v24 )
          (*(void (__fastcall **)(__int64))v24)(v5);
        v25 = *(_QWORD *)(v24 + 8);
        if ( v25 )
          sub_32C14(v5, v25, *(_QWORD *)(v24 + 16));
        sub_32C14(v3, 24, 8);
      }
      close(fd);
      if ( v4 == (__int128 *)0x8000000000000000LL )
        goto LABEL_45;
      goto LABEL_59;
    }
    if ( v4 )
      sub_32C14(v7, v4, 1);
    sub_2BFFC(&v41);
  }
  v53[0] = &aSdcardAndroidD[52];
  v53[1] = 42;
  LODWORD(fd) = 511;
  BYTE4(fd) = 1;
  v20 = sub_538D4(&fd);
  v7 = (char *)v20;
  if ( v20 )
  {
    if ( (v20 & 3) == 1 )
    {
      v7 = (char *)(v20 - 1);
      v3 = *(_QWORD *)(v20 - 1);
      v21 = *(_QWORD *)(v20 + 7);
      if ( *(_QWORD *)v21 )
        (*(void (__fastcall **)(__int64))v21)(v3);
      v22 = *(_QWORD *)(v21 + 8);
      if ( v22 )
        sub_32C14(v3, v22, *(_QWORD *)(v21 + 16));
      sub_32C14(v7, 24, 8);
    }
LABEL_45:
    sub_2D560(&v33, &unk_7BBC, 35);
    goto LABEL_46;
  }
  v54[0] = (__int64)v53;
  v54[1] = (__int64)sub_30F08;
  v4 = (_OWORD *)(&dword_0 + 1);
  *(_QWORD *)&fd = &unk_83D08;
  *((_QWORD *)&fd + 1) = 2;
  *(_QWORD *)&v46 = v54;
  *((_QWORD *)&v46 + 1) = 1;
  *(_QWORD *)&v47 = 0;
  sub_70CE4(&v41, &fd);
  v7 = (char *)*((_QWORD *)&v41 + 1);
  v19 = v42;
  DWORD2(fd) = 16777472;
  *(_QWORD *)&fd = 0x1B600000000LL;
  WORD6(fd) = 1;
  sub_536A4(v54, (int)&fd, SDWORD2(v41), v42);
  LODWORD(v5) = v54[0];
  if ( (v54[0] & 1) != 0 )
  {
    if ( (_QWORD)v41 )
      sub_32C14(v7, v41, 1);
    sub_2BFFC(v54);
    goto LABEL_45;
  }
  LODWORD(fd) = HIDWORD(v54[0]);
  v28 = sub_30C48(&fd, v2, v3);
  v3 = v28;
  if ( (v28 & 3) == 1 )
  {
    v3 = v28 - 1;
    v29 = *(_QWORD *)(v28 - 1);
    v6 = *(_QWORD *)(v28 + 7);
    if ( *(_QWORD *)v6 )
      (*(void (__fastcall **)(__int64))v6)(v29);
    v30 = *(_QWORD *)(v6 + 8);
    if ( v30 )
      sub_32C14(v29, v30, *(_QWORD *)(v6 + 16));
    sub_32C14(v3, 24, 8);
  }
  v4 = (__int128 *)v41;
  close(fd);
  if ( v4 == (__int128 *)0x8000000000000000LL )
    goto LABEL_45;
LABEL_59:
  *(_QWORD *)&v41 = v4;
  *((_QWORD *)&v41 + 1) = v7;
  v50 = &v41;
  v51 = sub_2C0C4;
  *(_QWORD *)&fd = &off_83D60;
  *((_QWORD *)&fd + 1) = 1;
  *(_QWORD *)&v42 = v19;
  *((_QWORD *)&v46 + 1) = 1;
  *(_QWORD *)&v47 = 0;
  *(_QWORD *)&v46 = &v50;
  sub_70CE4(&v38, &fd);
  v7 = v38;
  v3 = v39;
  sub_2D560(&v33, v39, v40);
  if ( v7 )
    sub_32C14(v3, v7, 1);
  if ( (_QWORD)v41 )
    sub_32C14(*((_QWORD *)&v41 + 1), v41, 1);
LABEL_46:
  sub_201AC(&v41, &v33, &unk_7BDF, 10);
  if ( (unsigned __int8)v41 != 15 )
  {
    fd = v41;
    v46 = v42;
    v47 = v43;
    v48 = v44;
    sub_74534(aCalledResultUn, 43, &fd, &off_83A48, &off_83D70);
LABEL_68:
    __break(1u);
    v31 = *(_QWORD *)(v6 + 8);
    if ( v31 )
      sub_32C14(v4, v31, *(_QWORD *)(v6 + 16));
    sub_32C14(v3, 24, 8);
    close(fd);
    if ( (_QWORD)v41 )
      sub_32C14(v7, v41, 1);
    if ( (_DWORD)v5 )
      sub_2BFFC(v54);
    if ( v1 )
      sub_32C14(v2, v1, 1);
    v32 = sub_2BDD4(&v34);
    sub_743C4(v32);
  }
  v26 = *((_QWORD *)&v41 + 1);
  if ( v1 )
    sub_32C14(v2, v1, 1);
  if ( v34 )
    sub_32C14(v35, v34, 1);
  return v26;
}
```

Objective function: `Java_com_tinygame_app_Engine_getFlagPartB`

Decoding logic: The author uses the XOR algorithm with 3 components:

- qword_79B8: Array containing the indices.
![image](https://hackmd.io/_uploads/H19gy8IwZl.png)
- byte_6840: Array of encrypted data.
![image](https://hackmd.io/_uploads/rJOZJI8PWe.png)
- byte_76C8: XOR key consisting of 8 bytes.
![image](https://hackmd.io/_uploads/H1hfJ88Dbg.png)

Solution: Write a Python script to simulate the XOR loop to extract the Part B string.

```python=
byte_76C8 = [
    0xA5, 0x1F, 0xC3, 0x77,
    0x19, 0xEE, 0x42, 0x58
]

byte_6840 = [
    0xF5,0xDB,0xA6,0x60,0x2F,0x45,0x74,0x21,0x12,
    0x2C,0xC4,0x7D,0x2B,0x23,0x90,0x28,0x7A,0x2E,
    0xD6,0xF0,0xF4,0x26,0xDD,0x4E,0xDA,0x7A,0x11,
    0x3E,0xC3,0x3E,0xC7,0x39
]

qword_79B8 = [
    0x1E,0x11,0x14,0x17,0x19,0x01,0x07,0x1F,
    0x1C,0x0B,0x02,0x05,0x09,0x12,0x0D,0x03,
    0x0A,0x15,0x13,0x08,0x0F,0x16,0x06,0x1D,
    0x0E,0x04,0x00,0x1A,0x0C,0x18,0x10,0x1B
]

out = []
for i in range(32):
    out.append(byte_76C8[i % 8] ^ byte_6840[qword_79B8[i]])

flag_part_b = bytes(out)
print(flag_part_b.decode(errors="ignore"))

#b179c5cafbe258a8a93e136f506f248f
```

PartA: Dynamic Loading

Since the main code `ui.UiFactory` is not in the original APK but is decoded and loaded into RAM, we need to use dynamic parsing. This place was shit at first. I spent a lot of time trying to hook natively into `ui.UIFactory`, but it didn't work.

Technique used: `Frida-dexdump`

Execution: Run the application on the emulator, press the `Start button` to activate the code loading trigger.
![image](https://hackmd.io/_uploads/HyH2xIUDZg.png)

Use `frida-dexdump -U -p 15054 -d` to scan all DEX files in memory.
![image](https://hackmd.io/_uploads/HJJTl8LPZx.png)

Analyze the dumped file: Search for the classes `ui.UiFactory` and `ui.BunnyArcade`.
![image](https://hackmd.io/_uploads/HJCaxLLvWe.png)
`Get-ChildItem -Recurse | Select-String "UiFactory"`
![image](https://hackmd.io/_uploads/S1lZWLUDWg.png)
Look at this line: `classes03.dex:28512:Lui/R$string; Lui/R$style; Lui/R; Lui/UiFactory; MM::MAGIC`. And especially: `classes03.dex:31028:UiFactory.kt`. This is the DEX file containing the UI logic that the application loads dynamically.
![image](https://hackmd.io/_uploads/SJI8-UUw-l.png)

Exploitation: In the `BunnyArcade class`, the `FLAG_B64` variable is detected.
![image](https://hackmd.io/_uploads/r1iu-8LvWx.png)
![image](https://hackmd.io/_uploads/HkqtZL8wZl.png)

Decode Base64 obtain the string containing PartA `Congrats: First part of the flag: dctf{60087b379564677d411af3b7bb7ef0d0...}` 