Friday, January 9, 2015

Unlimited moves in Candy Crush Soda Saga

Hi! As the first article of the current blog, today we’ll try to get unlimited moves on King’s Candy Crush Soda Saga. Although my main language is SpanishI’ll try to publish every article in both languagesSpanish and English; I’lltry my bestsorry for my bad English!

Spanish version / Versión en español: http://reversingyourcode.blogspot.com.es/2015/01/movimientos-ilimitados-en-candy-crush.html
Let’s get startedWe first inspect the APK with Java Decompiler (dex2jar -> Java Decompiler)
There are many interesting classes:
//com.king.candycrushsodasaga.StritzActivity
public void onCreate(Bundle paramBundle)
  {
    System.loadLibrary("stritz");
    super.setHasSplashScreen(true);
    this.mSplashView = new SplashView(this);
    super.setMinimizeInsteadOfForceQuit(true);
    super.onCreate(paramBundle, PlatformProxy.createNativeInstance(this));
    this.mFrameLayout = new FrameLayout(this);
    this.mFrameLayout.addView(this.mSplashView, new FrameLayout.LayoutParams(-1, -1));
    this.mFrameLayout.addView(getGameView(), 0, new FrameLayout.LayoutParams(-1, -1));
    setContentView(this.mFrameLayout);
    addListener(new GameActivityFacebookListener(getGameView()));
    handleIntent(getIntent());
  }

com.king.candycrushsodasaga.PlatformProxy
public static native int createNativeInstance(StritzActivity paramStritzActivity);

//com.king.core.GameActivity.onCreate
  protected void onCreate(Bundle paramBundleint paramInt)
  {
    this.mUncaughtExceptionWriter = new UncaughtExceptionWriter(getApplicationContext());
    super.onCreate(paramBundle);
    this.mDisplay = getWindow().getWindowManager().getDefaultDisplay();
    GameLib.mContext = this;
    WebViewHelper.mActivity = this;
    this.mView = new GameView(thisthis.mForceQuitWhenDonethis.mUseSleepInLoop);
    this.mView.setFocusable(true);
    this.mView.setFocusableInTouchMode(true);
    if (!this.mHasSplashScreen)
      setContentView(this.mView);
    addListener(new GameActivityDeepLinkListener(this.mView));
    addCustomGameListeners();
    this.mSensorManager = ((SensorManager)getSystemService("sensor"));
    this.mAccelerometer = this.mSensorManager.getDefaultSensor(1);
    getWindow().addFlags(1152);
    if (Build.VERSION.SDK_INT >= 9);
    this.mRotationCompensator = new RotationCompensatedListener(thisthis.mDisplay);
    this.mNativeApplication = new NativeApplication();
    this.mNativeApplication.create(paramIntthisgetApplicationContext());
    handleIntent(getIntent());
  }

//com.king.core.NativeApplication
public class NativeApplication
{
  public native void create(int paramInt, Activity paramActivity, Context paramContext);
 
  public native void destroy();
 
  public native void init(int paramInt1, int paramInt2, int paramInt3, int paramInt4);
 
  public native void onAccelerometer(float paramFloat1, float paramFloat2, float paramFloat3);
 
  public native void onBackKeyDown();

We see many references to native methods, and an interesting LoadLibrary(stritz”); besides thisthere isn’t any other interesting thingnor any game logic controller, so we assume  that the game logic must be outside ofthe dex file, maybe in the referenced native libraryNext we unpack the APK with apktool, and we see that in the directory “lib/armeabi-v7a” there’s a cool native library named “libstritz.so”. We get IDA Pro to startexamining it.
At first glancewe see a enormous number of functions, as many statically linked libraries; no obfuscation and every method are with their symbol in the export table!!!

We see some interesting classesSwitcherSwitcher::GameCommunicatorSugarCrushViewSwitcher::GameMode (abstract), CStritzGameModeFactory, and various xxxxxxGameMode.
In an initial analysis we can see a complex and hierarchycal structure of classes and interfaces, and many design patterns (Factory, observer…). We also see a event driven designwith many interesting functions in the formof “OnXXXX”.
In a more detailed analysiswe see that one of the main controllers is the Switch classwhere the different GameMode are managed. At first glanceit looks that many of the game logic is implemented in the GameModeclasses.
We search for interesting methods of GameMode classes:
IsCompleted(void)
IsFailed(void)
GetFailReason(void)
GetWinReason(void) 
IsSugarCrushAllowed(void)
IsSugarCrushCompleted(void)
OnSuccessfulSwitch(Switcher::SwapInfo *)
OnUnsuccessfulSwitch(Switcher::Item *,Switcher::Item *)

We also search for references to “MovesLeft”, “DecreaseMovesLeft” in the function names:
CSpecialCandiesCreationState::GetNumExpectedMovesLeft(int,int,int) 00293C94 
Switcher::GameCommunicator::OnSugarCrushDecreaseMovesLeft(int) 00465E84 
CStritzGameModeHudPresenter::DecreaseNumberOfMovesLeft(int) 002FD374
CStritzGameModeHudPresenter::IncreaseNumberOfMovesLeft(int,float) 002FD38C
CStritzGameModeHudView::OnSugarCrushDecreaseMovesLeft(int) 002FE654
CStritzGameModeHudView::IncreaseNumberOfMovesLeft(int,float) 002FE690 
CStritzGameModeHudView::DecreaseNumberOfMovesLeft(int) 002FE910 

We see logic game states defined:
Switcher::LogicState::INIT 007606CC 
Switcher::LogicState::SUGAR_CRUSH 007606D0 
Switcher::LogicState::SHUFFLE 007606D8 
Switcher::LogicState::COMPLETE 007606DC 
Switcher::LogicState::FAIL 007606E0

We starting with an interesting function with the name “DecreaseNumberOfMovesLeft”. We disassemble the function CStritzGameModeHudView::DecreaseNumberOfMovesLeft(int):
LDR             R2, [R0,#0x58]
RSB             R1, R1, R2
STR             R1, [R0,#0x58]
B               _ZN22CStritzGameModeHudView9ShowMovesEv ; CStritzGameModeHudView::ShowMoves(void)
; End of function CStritzGameModeHudView::DecreaseNumberOfMovesLeft(int
 
We see that this function is very simple: it loads in R2 the value at (this+0x58) (R0 = this), next add the value of R1 (int parameter of the function) and nextstores the result at the same place (this+0x58). Before finishingitcalls this->ShowMoves(). It looks interesting, so we try to replace the subtract instruction (RSB R1,R1,R2by one equivalent to NOP. We run the game, and see that the counter is not decreasing, so it looks that weaccomplished our target. Butwhen we make the allowed moveswe lose, so we assume that there must be other internal counter besides the GUI counter we just killed. As this counter is not the ourswe undo the changes.
In the next stagewe focus on the GameMode objectsthat looks that contain the game logicThere aren’t many methods into them, so we focus in the following:
IsSugarCrushAllowed(void)
IsSugarCrushCompleted(void)
OnSuccessfulSwitch(Switcher::SwapInfo *)
OnUnsuccessfulSwitch(Switcher::Item *,Switcher::Item *)

IsSugarCrushAllowed must have any check to determine if we can move or not, so we disassemble SodaToTheBrimGameMode::IsSugarCrushAllowed:
; SodaToTheBrimGameMode::IsSugarCrushAllowed(void)const
EXPORT _ZNK21SodaToTheBrimGameMode19IsSugarCrushAllowedEv
_ZNK21SodaToTheBrimGameMode19IsSugarCrushAllowedEv
LDR             R0, [R0,#8]
CMP             R0, #0
MOVLE           R0, #0
MOVGT           R0, #1
BX              LR
; End of function SodaToTheBrimGameMode::IsSugarCrushAllowed(void)
 
We see that this function only checks that the value at (this+8) is greater than 0, where it returns 1; else it returns 0. This is probably the counter that we are looking for, so we’ll find any reference in the methods abovementioned.
We found a very interesting one at OnSuccessfulSwitch:
; SodaToTheBrimGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
EXPORT _ZN21SodaToTheBrimGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
_ZN21SodaToTheBrimGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
LDR             R3, [R0,#8]
CMP             R3, #0
SUBGT           R3, R3, #1
STRGT           R3, [R0,#8]
LDR             R0, [R0,#0x28]
CMP             R0, #0
BXEQ            LR
B               _ZN16CLemonadeSeaTask18OnSuccessfulSwitchEv ; CLemonadeSeaTask::OnSuccessfulSwitch(void)
; End of function SodaToTheBrimGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
 
It loads the value, compares it with 0, and if it’s greater than 0, subtracts 1 to it and stores it in the same place. We look for the offset of the SUBGT instructionthat’s 0x323FAC, and we replace that 1 by a 0 so it subtracts 0:

We save the binaryrepack the APK, sign itinstall it and run itEffectivelynow we have infinite moves! As the GUI counter and the internal counter are independentwe see the counter decreasingbut when it reaches 0, itcomes to -1 and so on xD
We continue with every other game modespatching every xxxxxGameMode::OnSuccessfulSwitch function.
We go with BubbleGumGameMode:
; BubbleGumGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
EXPORT _ZN17BubbleGumGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
_ZN17BubbleGumGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
LDR             R3, [R0,#8]
CMP             R3, #0
SUBGT           R3, R3, #1
STRGT           R3, [R0,#8]
BX              LR
; End of function BubbleGumGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
 
The same as beforeThis time the offset is 0x326AAC, and we need to replace the 1 byte by a 0.
GiantBearsGameMode is identical to the previous, so I skip the detailssearch the SUBGT offset, replace 1 by 0 and so on.
We repeat the same procedure with HoneyGameModeCFloatingNutsMode and CChocolateNemesisGameMode;
And we finished!! Enjoy Candy Crush Soda with infinite moves!!

1 comment:

  1. Cool post. Thanls for sharing.
    how did you packed and signed the game?
    Also what approach would you take for games that requiere Internet?

    Thanks

    ReplyDelete