Author Topic: Android Reverse Engineering: Quizduell cheating  (Read 4753 times)

p

  • Global Moderator
  • Apache
  • *****
  • Posts: 110
  • Karma: +27/-0
    • View Profile
Android Reverse Engineering: Quizduell cheating
« on: March 03, 2014, 09:41:39 am »
Hey,

ich wollte mich etwas mit Android Reverse Engineering beschäftigen und habe mir dazu Quizduell ausgesucht.
Nach etwas normalem Spielen und googeln fällt auf: Die Fragen und Antworten werden auf das Gerät gezogen.
Test mit: Flugmodus. Ein umständlicher Trick ist auch: Fragen starten, Flugmodus an, richtige Antworten notieren, lokal gespeicherte Daten der App löschen, Flugmodus aus und die gleichen Fragen nocheinmal richtig beantworten.

Nach meinen ersten Analysen des Codes mit der Hilfe von apktool, dex2jar und JD-GUI kam ich auch in die richtige Richtung und fand zusätzlich einen Blogpost von easysurfer, welcher genau darüber geschrieben hat - leider aber ist der Blog down.

Seine Vorgangsweise ist ca. die selbe gewesen, ich kam zum Beispiel durch das Aufsuchen von "state_correct" schneller zu dem Code mit der auch von ihm genanten "b()"-Funktion.
Seinen smali-Code habe ich übernommen, nachdem ich seinen Blogpost über den Google Cache einsehen konnte - die Syntax sollte man sich bei Gelegenheit auch genauer anschauen.

Ich werde jetzt nicht genauer auf die Vorgehensweise eingehen, diese hat easysurfer echt wunderbar erklärt: Klick

Nach dem erneuten packen und signieren(mit z.B. APK-Signer, hier gibt es auch "SignAPK" mit Certificate und Key, checkt das *.jar aber besser selber. Wer weiß was da drin steckt) des manipulierten Codes, werden in den Quizbildschirmen die falschen Antworten rot und die richtige Antwort grün hinterlegt. Funktioniert einwandfrei!

Es hat unglaublich viel Spaß gemacht und ich möchte noch easysurfer danken, falls er sich jemals hierher verirrt.
Ich werde mich aufjedenfall weiter mit Android Reverse Engineering beschäftigen.  :ugly2;

-p

Edit:
Ich quote hier mal den rohen Blogpost von Easysurfer, just in case dass der Cache geleert wird.
Quote
Android RE – Quizduell

Die Android Applikation “Quizduell” testet in abwechselnden Spielrunden das eigene und gegnerische Wissen diverser Themengebiete. Wer nach 6 Runden die meisten der Multiple Choice Fragen richtig beantworten konnte, gewinnt und steigt im Ranking auf.

Der eigentliche Fragebildschrim sieht dabei so aus:

Screenshot_2013-11-19-16-24-22

Ziel ist es, schon vor dem Auswählen einer Antwort zu wissen, ob diese richtig ist oder nicht. Nach der Auswahl wird die richtige Antwort Grün markiert, alle anderen Rot. Wir müssen also die Activity untersuchen, was nach dem Klicken auf eine Antwort passiert und den Code zum setzen der Button-Hintergrundfarbe schon beim Laden der Activity aufrufen.

Wie schon beim Billiard RE decompilen wir die App zunächst mit apktool, um das Manifest, Strings und Smali-Code zu erhalten. Dann entpacken wir die App und wandeln den .dex-Code in eine jar-Datei um, welche in JD-Gui geladen wird. Doch welch Überraschung, die Klassen sind obfuscated und nur ein paar Activites erkennbar:

Temp

Der eigentliche Quizbildschirm ist gut versteckt und nicht unter den benannten Activities zu finden. Also wählen wir einen anderen Weg: Wir suchen einen (statischen) String, der nur in dieser gesuchten Activity zu finden ist. Die Fragen und Antworten sind dafür ungeeignet (dynamisch geladen), aber was ist mit “Runde X gegen Y”? In der Strings.xml sind alle Strings mit internem Namen zu finden. Durch diesen internen Namen erhalten wir über die Public.xml die “Namen -> Token” Verknüpfung. Die Suche nach “Runde” liefert Erfolg!

    <string name=”game_round_vs_xx” formatted=”false”>Runde %d gegen %s</string>
    <public type=”string” name=”game_round_vs_xx” id=”0x7f0b0053″ />

 

Also konvertieren wir die Hex Value in unser Dezimalsystem (JD-Gui stellt diese nur als 10er Basis dar -.-) und durchsuchen den zuvor erstellten Sourcedump. Voila! Wir finden die Klasse “aC”, welche von RelativeLayout erbt. Also ist diese auch darstellbar. Folgenden Source ist für uns interessant:

// Der String game_round_vs_xx
String str = getResources().getString(2131427411);
// Neues Object mit 2 Einträgen
Object[] arrayOfObject = new Object[2];
// Das ist also die Rundennummer in param4
arrayOfObject[0] = Integer.valueOf(paramInt4);
// paramD.c() liefert wohl den gegnerischen Namen
arrayOfObject[1] = paramD.c();
// Formatiere und setze den Text
localTextView.setText(String.format(str, arrayOfObject));

Allerdings ist ist hier von den gesuchten Fragen/Antworten nichts zu sehen! Dieses Layout scheint nur den oberen “Header” zu beinhalten. Also suchen wir, welche Klasse die von uns gefundene instanziert. Eine Suche nach “new aC(” liefert ein Resultat in der “NewGameActivity.java” und einer ominösen “h.java”. Letzere ist der gesuchte Quizbildschirm, oder zumindest die dahinterstehende Logik.

Die Klasse ist schlimm obfuscated, Typenamen und lokale Namen sind jeweils nur mit Buchstaben bezeichnet und auch sonst gibt der Source nicht viel her. Nun heißt es, Variablen und Klassen zurück zu verfolgen und ihnen eine Bedeutung zu geben. Nach ein weiteren paar Minuten dieses analysierens konnte ich wiederrum über die schon bekannte Gegnernamen-Variable eine Zuordnung der “schon vom Gegener ausgewählte Antwort” finden. Dies passierte in folgender Loop:

for (int i3 = 0; i3 &lt; this.d.size(); i3++) // Loop throuhg all answers ?
    {
      // Hol die Antwort an dem Index i3
      b CurrentAnswer = (b)this.d.get(((Integer)localArrayList.get(i3)).intValue());
      // Initialisiere sie?
      CurrentAnswer.c();
      // Hole dir die interne Antwortklasse mit dem Index i3
      z localz = localr.c(i3);
      // Ist die Antwort richtig, dann setze die RightAnswer-Variable
      if (localz.b())
        RightAnswerQ = CurrentAnswer;
      // Initialisiere sie?
      CurrentAnswer.a(localz);
      w localw = localr.d();
      // Wenn: Enemy zuerst dran war und die Antwort ID des Enemies der aktuellen Antwort ID entspricht
      // Setze EnemySelectedAnswer auf die aktuell durchlaufene Antwort
      if ((localw.d()) &amp;&amp; (localw.a().c() == CurrentAnswer.a().c()))
        EnemySelectedAnswerQ = CurrentAnswer;
    }

Ziemlich verwirrend, nicht? Nunja, alles was uns interessiert ist die neue Klasse “b”. Die Klasse “b”, die wohl die Antwort-Klasse zu sein scheint. Wir haben dabei ein Auge auf die “z.b()”-Funktion, die wohl zurückgibt, ob die Antwort richtig ist oder nicht.

Lange Rede kurzer Sinn, hier wird also in der b-Klasse die Antwort gecheckt und dementsprechend Booleans gesetzt: (Natürlich wieder nach Analyse und umbenennen der Variablen)

 public final void b()
  {
    if (this.RealAnswerClass.CheckIsRight())
    {
      this.IsWrongAnswer = false;
      this.IsCorrectAnswer = true;
      return;
    }
    this.IsWrongAnswer = true;
    this.IsCorrectAnswer = false;
  }

Am Anfang der Klasse finden wir folgende Arrays:

public final class b extends FrameLayout
{
  private static final int[] i = { 2130772076 };
  private static final int[] j = { 2130772077 };

Wenn wir diese Zurücktracen, so lauten die Werte:

    <public type=”attr” name=”state_correct” id=”0x7f01006c” />
    <public type=”attr” name=”state_wrong” id=”0x7f01006d” />

Sieht also gut aus. Und wo werden diese gesetzt?

protected final int[] onCreateDrawableState(int paramInt)
  {
    int[] arrayOfInt = super.onCreateDrawableState(paramInt + 2);
    if (this.IsCorrectAnswer)
      mergeDrawableStates(arrayOfInt, i);
    if (this.IsWrongAnswer)
      mergeDrawableStates(arrayOfInt, j);
    return arrayOfInt;
  }

Diese States verweißen also auf eine Art Templateklasse (wie in CSS), die u.a. die Backgroundcolor des Buttons festlegt. Und genau diese obere Funktion wird einmal nach dem Init der Antwort aufgerufen, und einmal nach einem Click Event. Wir müssen es also schaffen, dass die Booleans “IsCorrect/WrongAnswer” nach dem Initalisieren nicht beide auf false stehen, sondern einen Wert annehmen. Und da hatten wir doch die b()-Funktion, die genau diese Booleans setzt!

Also fügen wir im Smali-Code einen Aufruf der b()-Funktion ein, nachdem der Text des Buttons (und somit auch die interene Antwortklasse) der Antwort gesetzt ist. Diese Funktion zum setzen der Antwort sieht wie folgt aus:

public final void a(z paramz)
  {
    this.RealAnswerClass = paramz;
    this.ButtonTextView.setText(paramz.a());
  }

Und in Smali fügen wir nun nach der setText-Instruction unseren Methodenaufruf ein:

invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
invoke-virtual {p0}, Lse/feomedia/quizkampen/views/b;->b()V

Dabei ist p0 der this-Pointer und b() die aufzurufende Funktion.

Das Resultat builden wir wieder mit apktool und signieren es noch mit einem Testkey. Nun wird die App installiert und ausprobiert. Und nunja, es geht :D

Auch wenn dieser Blogeintrag etwas trocken geworden ist, so wollte ich doch zeigen, wie man an solch unbekannte Applikationen herrangeht, sich von Klasse zu Klasse hangelt und versucht, das gesammte Konzept hinter dem verschleierten Code zu verstehen. Und ein solch simpler Patch freut einen jedes Reverserherz, denn es zeigt, dass man das man die Logik des Codes verstanden hat und sie somit auch kontrollieren kann.

Greez Easy
« Last Edit: March 03, 2014, 10:11:58 am by p »

puddy

  • Global Moderator
  • Gopher
  • *****
  • Posts: 72
  • Karma: +18/-0
    • View Profile
Re: Android Reverse Engineering: Quizduell cheating
« Reply #1 on: March 03, 2014, 12:16:54 pm »
haha sehr coole Sache!  ;D

fraggle

  • the breaker
  • Global Moderator
  • Apache
  • *****
  • Posts: 118
  • Karma: +17/-0
    • View Profile
Re: Android Reverse Engineering: Quizduell cheating
« Reply #2 on: June 26, 2015, 06:26:53 pm »
May the Force be with you.