Description

I asked for the challenge from other people so I have no idea what the description is. EmojiGachaRPG.apk

Static Analysis

I started out by decompiling it using jadx-gui and looked into the AndroidManifest.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<uses-permission android:name="android.permission.INTERNET"/>

<activity
    android:theme="@style/Theme.UltraAddictiveGachaGame"
    android:name="com.honque.ultraaddictivegachagame.MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
<activity
    android:name="androidx.compose.ui.tooling.PreviewActivity"
    android:exported="true"/>
<activity
    android:theme="@android:style/Theme.Material.Light.NoActionBar"
    android:name="androidx.activity.ComponentActivity"
    android:exported="true"/>

Based on the information found, the application requires internet connection and the only interesting activity is the MainActivity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    EdgeToEdge.enable$default(this, null, null, 3, null);
    Context applicationContext = getApplicationContext();
    Intrinsics.checkNotNullExpressionValue(applicationContext, "getApplicationContext(...)");
    GameRepositoryKt.setGameRepository(new GameRepository(applicationContext));
    CharacterMasterList.INSTANCE.loadCharactersFromXml(this);
    BuildersKt__Builders_commonKt.launch$default(LifecycleOwnerKt.getLifecycleScope(this), null, null, new MainActivity$onCreate$1(null), 3, null);
    ComponentActivityKt.setContent$default(this, null, ComposableSingletons$MainActivityKt.INSTANCE.m7438getLambda2$app_debug(), 1, null);
}

@Override // android.app.Activity
protected void onStop() {
    super.onStop();
    GameRepositoryKt.getGameRepository().savePlayerData();
    Log.i("GachaGame", "Game data saved onStop.");
}

After looking into it, it seems to run some other functions. I then decide to play around with the application since Im too lazy o read the code.

Dynamic analysis

alt text

So this is the main page of the application. After clicking around, I noticed something important.

alt text

So basically, I need the exact gold to buy the flag. The first thing that came into my mind was manipulating the gold value. To perform that, I tried looking into the code again.

1
2
3
4
5
6
7
8
9
public final int getCurrency() {
    IntState $this$getValue$iv = currency;
    return $this$getValue$iv.getIntValue();
}

public final void setCurrency(int i) {
    MutableIntState $this$setValue$iv = currency;
    $this$setValue$iv.setIntValue(i);
}

When I looked into Player, I noticed this code which I assume it is related to the money or player gold. I then just use frida to change my player gold to the required ammount will do.

1
2
3
4
5
6
7
8
9
Java.perform(()=>{
    let Player = Java.use("com.honque.ultraaddictivegachagame.Player");
    Player["getCurrency"].implementation = function () {
        console.log(`Player.getCurrency is called`);
        let result = this["getCurrency"]();
        console.log(`Player.getCurrency result=${result}`);
        return result;
    };
})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 frida -U -N com.honque.ultraaddictivegachagame -l test.js
     ____
    / _  |   Frida 16.5.9 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to POCOPHONE F1 (id=d211a91c)
[POCOPHONE F1::com.honque.ultraaddictivegachagame ]-> Player.getCurrency is called
Player.getCurrency result=90
Player.getCurrency is called
Player.getCurrency result=90
Player.getCurrency is called
Player.getCurrency result=80
Player.getCurrency is called
Player.getCurrency result=80
Player.getCurrency is called
Player.getCurrency result=80

alt text

Now that I can read the result, I can just modify the return value will do.

1
2
3
4
5
6
7
8
9
Java.perform(()=>{
    let Player = Java.use("com.honque.ultraaddictivegachagame.Player");
    Player["getCurrency"].implementation = function () {
        console.log(`Player.getCurrency is called`);
        let result = this["getCurrency"]();
        console.log(`Player.getCurrency result=${result}`);
        return 214783647;
    };
})

alt text

by using this simple method, I can easily buy the flag. since the server is down when I am writing this, I could not get the correct flag.

Things I learned from this challenge

  • frida intercepting and changing return value