Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

analysis-offensive 300

Disclaimer : This is not the smart way to do this, but its easy if you have an android development setup.

So we have an apk file.

$ file TrendGacha.apk
TrendGacha.apk: Zip archive data, at least v2.0 to extract
$ unzip -qq TrendGacha.apk -d TrendGacha
$ ls TrendGacha
AndroidManifest.xml  classes.dex  lib  META-INF  res  resources.arsc
$ cd TrendGacha
$ ~/tools/dex2jar/d2j-dex2jar.sh classes.dex
dex2jar classes.dex -> ./classes-dex2jar.jar

I also installed the apk on a device to test it.

Main

The functions are quite simple. When you click on GET GACHA! it'll choose a character of the flag and give it to you. All your leaked chars can then be seen later in the HISTORY.

Looking in the output of jd-gui on the jar. The code for GET GACHA!.

public void run()
      {
        Intent localIntent = new Intent("com.tm.ctf.trendgacha.GET_GACHA");
        if (a.N() == null) {
          a.b(a.this.i().getApplicationContext());
        }
        a.N().sendBroadcast(localIntent);
        a.a(a.this, true);
      }

So the button sends a com.tm.ctf.trendgacha.GET_GACHA intent via broadcast which is then handled by GachaBroadcastReceiver.

public void onReceive(Context paramContext, Intent paramIntent)
  {
    Log.i(a, "onReceive: action=" + paramIntent.getAction());
    if (!paramIntent.getAction().equals("com.tm.ctf.trendgacha.GET_GACHA")) {
      return;
    }
    paramIntent = paramIntent.getExtras();
    if (paramIntent != null) {}
    for (int i = Integer.valueOf(paramIntent.getString("TryLoop", "1")).intValue();; i = 1)
    {
      for (int j = 0; j < i; j++)
      {
        d.b(paramContext.getApplicationContext());
        int n = d.a(paramContext.getApplicationContext());
        paramIntent = GachaAPI.getGacha(n);
        int k = paramIntent[0];
        char c1 = (char)paramIntent[1];
        d.a(paramContext.getApplicationContext(), k, c1);
        int m = d.b(paramContext.getApplicationContext(), k, c1);
        Log.i(a, "TrialCount: " + n + ", Order: " + k + ", Gacha: " + c1 + ", Count: " + m);
        paramIntent = c.iterator();
        while (paramIntent.hasNext()) {
          ((b)paramIntent.next()).b_();
        }
        Toast.makeText(paramContext, "Got \"" + k + "-" + c1 + "\" !", 0).show();
        if (n < 10) {
          Toast.makeText(paramContext, "Check History!", 0).show();
        }
      }
      break;
    }
  }
}

This will leak a byte of the flag. We also see a variable TryLoop which will repeat the leak TryLoop times. Whenever we press the GET GACHA! button TRIAL_COUNT is increased in the SharedPreferences. The GachaAPI.getGacha is called on the value of TRIAL_COUNT. Its good that we can broadcast intents via adb so that we can set TRIAL_COUNT to a very high value.

$ adb shell am broadcast -a com.tm.ctf.trendgacha.GET_GACHA -e TryLoop 100
Broadcasting: Intent { act=com.tm.ctf.trendgacha.GET_GACHA (has extras) }
Broadcast completed: result=0

Hoever this only leaks a few bytes of the flag which we can view in History. I tried to set the number to 10000 and more but the process was slow and I got bored.

History

Looking into the source of GachaAPI.getGacha we see that it loads a native library.

package com.tm.ctf.trendgacha;

public class GachaAPI
{
  static
  {
    System.loadLibrary("native-lib");
  }

  public static native int[] getGacha(int paramInt);
}

If only I could run getGacha with my parameters. I never did much development in Android, so I downloaded Android Studio, added com.tm.ctf.trendgacha in a sample app from the decompiled source and added the jniLibs to include native-lib. I added the following code in an activity to retrieve and save the flag to a file.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_card_view);
        Log.i(TAG,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
        int i = 1;
        for(i = 1 ; i < 1000000 ; ++i){
            int[] x = GachaAPI.getGacha(i);
            appendLog("Result : " +(char)x[1] + " : " + Arrays.toString(x) );
        }
    }

This succesfully gave me the flag. Just later I realized that I could have controlled the parameter to GachaAPI.getGacha from the TRIAL_COUNT in SharedPreferences. SharedPreferences are usually saved in XML format in /data/data/< appname >/shared_prefs/*.xml. But we need root on the device to pull and push these files. So to test this I set up a new AVD (already rooted!) and installed the app.

$ ~/Android/Sdk/platform-tools/adb pull /data/data/com.tm.ctf.trendgacha/shared_prefs/PREFERENCES.xml
/data/data/com.tm.ctf.trendgacha/shared_... pulled. 0.2 MB/s (1892 bytes in 0.009s)

In PREFERENCES.xml I increased the TRIAL_COUNT.

<int name="TRIAL_COUNT" value="3000863" />

And then pushed it back and restarted the application.

$  ~/Android/Sdk/platform-tools/adb push PREFERENCES.xml /data/data/com.tm.ctf.trendgacha/shared_prefs/PREFERENCES.xml
PREFERENCES.xml: 1 file pushed. 0.2 MB/s (1893 bytes in 0.009s)
$  ~/Android/Sdk/platform-tools/adb  shell am broadcast -a com.tm.ctf.trendgacha.GET_GACHA -e TryLoop 100
Broadcasting: Intent { act=com.tm.ctf.trendgacha.GET_GACHA (has extras) }
Broadcast completed: result=0

This will give us the flag.

Flag