# 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).

It splits the flag into two parts, and you have to find both to put them back together.
PartB
Drop the file into jadx

`MainActivity`: The logic code found is not here. The application uses `Engine.findAndBuildLoader` to load a class named `ui.UiFactory` at runtime.

`Engine.java`: Noticed the presence of the native library `libtinymedia.so` and native functions. Specifically, the `getFlagPartB()` function.

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.

- byte_6840: Array of encrypted data.

- byte_76C8: XOR key consisting of 8 bytes.

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.

Use `frida-dexdump -U -p 15054 -d` to scan all DEX files in memory.

Analyze the dumped file: Search for the classes `ui.UiFactory` and `ui.BunnyArcade`.

`Get-ChildItem -Recurse | Select-String "UiFactory"`

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.

Exploitation: In the `BunnyArcade class`, the `FLAG_B64` variable is detected.


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