Tutorial How To Hack Using IDA (Mega Tutorial)

Alex Zander

Solid & Active Platinian
Original poster
Feb 9, 2018
Behind You.
✻ Requirements
IDA (demo works fine)
Hex Editor
ARM-to-Hex Converter
Cracked App Binary

✻ ARM Architecture
I assume you already know how to crack an app and get the binary into IDA with the proper settings.

The programming language that we'll be working with is called ARM. Each line is formatted like this:

Header Rn, Operand2
What you need to remember is that each line (called an "instruction") is read from right to left. Here's some examples:

[B]MOV R0, R1
/* This is read as move (MOV) the value of R1 to R0 */

LDR R3, R0
/* Load (LDR) the value of R0 into R3 */[/B]
There are many other complicated instructions, such as vectors, that I will not be covering in this tutorial. In this tutorial I'll show examples including ADD, SUB, MOV, LDR, STR, NOP, BX LR, CMP, and branches. 99% of the time those will be the only headers you'll use when hacking. At first look IDA can seem complex, but the actual hacking portion is repetitive, and usually doesn't take more than the 8-10 headers I'll explain here.

The following examples are real. I wanted to include the whole function so you get an idea of how many instructions you have to sort through. Some are quite long, but I hope you learn from them.

✻ Addition and Subtracting (ADD, SUB)
These headers are probably the easiest to comprehend because I assume you’ve already been acquainted with basic arithmetic.

Have a look at the example below. It is sub_X and GDB dropped me off at address 0x003B9156 (marked with <<<). The thing we are trying to hack is ammo. When you shoot a gun, it subtracts a bullet. We should be looking for a SUB that has 1 associated with it. 0x003B9150 seems to fit the description (marked with ~~~). It can either be changed to a ADDS R0, #1 or NOP. A NOP is an instruction that tells the code to skip the instruction - it’s a nothing or a null.

You don’t have to worry about memorizing the S-suffix. ADD and ADDS are written the same in hex. Operand2 dictates if the S-suffix is used or not. If Operand2 is a register, there is no S-suffix. If Operand2 is a number, there is an S-suffix. This applies to all headers.

ADD R0, R1
ADDS R0, #1
__text:003B90EE loc_3B90EE ; CODE XREF: sub_3B8B4C+55Ej
__text:003B90EE ; sub_3B8B4C+564j ...
__text:003B90EE MOV R0, #(off_4AC64C - 0x3B90FA) ; off_4AC64C
__text:003B90F6 ADD R0, PC
__text:003B90F8 LDR R0, [R0] ; byte_6FFAFC
__text:003B90FA LDRB.W R0, [R0,#0x50]
__text:003B90FE CMP R0, #0
__text:003B9100 BNE loc_3B9156
__text:003B9102 LDR.W R0, [R10,#8]
__text:003B9106 MOVW R2, #0x61A8
__text:003B910A CMP R0, #1
__text:003B910C ITT GE
__text:003B910E SUBGE R0, #1
__text:003B9110 STRGE.W R0, [R10,#8]
__text:003B9114 LDR.W R1, [R10,#0xC]
__text:003B9118 MOVS R0, #0
__text:003B911A CMP R1, R2
__text:003B911C IT LT
__text:003B911E MOVLT R0, #1
__text:003B9120 CMP R1, #1
__text:003B9122 BLT loc_3B9156
__text:003B9124 ORRS R0, R6
__text:003B9126 CMP R0, #1
__text:003B9128 BNE loc_3B9156
__text:003B912A CMP R6, #1
__text:003B912C BNE loc_3B914C
__text:003B912E BL sub_1A7F30
__text:003B9132 VLDR S0, =100.0
__text:003B9136 VMOV D1, R0, R0
__text:003B913A VCMPE.F32 S2, S0
__text:003B913E VMRS APSR_nzcv, FPSCR
__text:003B9142 BMI loc_3B914C
__text:003B9144 LDR.W R0, [R10]
__text:003B9148 CMP R0, #0x28
__text:003B914A BNE loc_3B9156
__text:003B914C loc_3B914C ; CODE XREF: sub_3B8B4C+5E0j
__text:003B914C ; sub_3B8B4C+5F6j
__text:003B914C LDR.W R0, [R10,#0xC]
__text:003B9150 SUBS R0, #1 ~~~~
__text:003B9152 STR.W R0, [R10,#0xC]
__text:003B9156 loc_3B9156 ; CODE XREF: sub_3B8B4C+5B4j
__text:003B9156 ; sub_3B8B4C+5D6j ...
__text:003B9156 MOVS R0, #1 <<<
__text:003B9158 STR.W R0, [R10,#4]
__text:003B915C LDR.W R0, [R10,#8]
__text:003B9160 CMP R0, #0
__text:003B9162 BEQ loc_3B9184
__text:003B9164 MOVW R0, #(:lower16:(off_4AC504 - 0x3B9172))
__text:003B9168 CMP R4, #1
__text:003B916A MOVT.W R0, #(:upper16:(off_4AC504 - 0x3B9172))
__text:003B916E ADD R0, PC ; off_4AC504
__text:003B9170 LDR R0, [R0] ; dword_85F8DC
__text:003B9172 BNE loc_3B9200
__text:003B9174 LDR R0, [R0]
__text:003B9176 LDR.W R1, [R10]
__text:003B917A CMP R1, #0x2B
__text:003B917C BNE loc_3B9204
__text:003B917E ADDW R0, R0, #0x44C
__text:003B9182 B loc_3B9224
__text:003B9184 ; ---------------------------------------------------------------------------[B]
✻ Data Handling (MOV, LDR, STR)

Data handling headers are probably the most common headers used when hacking. A move (MOV) simply moves a value into another register. Loaders (LDR) load a value into a register - this is the same as MOV but Operand2’s value remains. A store (STR) is the reverse of LDR - it tells the value of Rn to be stored into Operand2. STR is the only header that is read left to right (that I know of anyways). Have a look at the STR instruction in the example (marked by <<<). It says, “Store the value of R1 in R0+1C”.

It’s worth noting at this point that R7 holds the value of 803 million. I’m not sure why, but it really comes in handy. It’s also important to remember that the first Rn in the function is usually where title-name property is stored (in this case, stars). With those two things in mind, look at the example below. The function name is AwardStars - it’s job must be to load the user’s current star count (hence all the LDRs and STRs). The first instruction is hackable (marked with ~~~)! Instead of loading R0+1C (whatever that is) into our stars register, why not R7? Replace the LDR instruction with MOV R2, R7. This function can also be hacked by the STR instruction (marked by <<<). Remember, the LDR loaded R0+1C into our stars, and since LDR doesn’t wipe R0+1C’s value, R0+1C is unchanged. Therefore, both R0+1C and R2 must equal the same thing. Furthermore, R0+1C must also equal our stars. A bit confusing, but the hard part is now over. Simply change the STR to STR R7, [R0,#0x1C], which reads “Store the value of R7, or 803 million, into R0+1C”.

[B]__text:00061E0C ; Player_AwardStar(SPlayer *, int)
__text:00061E0C __Z16Player_AwardStarP7SPlayeri ; CODE XREF: L_SceneManager_AwardPlayerReward(SSceneManager *,int,ESceneRewardType,SNpcInstance *)+ECp
__text:00061E0C ; L_SceneManager_TriggerReward(SSceneManager *,SSceneReward *)+FAp ...
__text:00061E0C LDR R2, [R0,#0x1C] ~~~
__text:00061E0E ADD R1, R2
__text:00061E10 STR R1, [R0,#0x1C] <<<
__text:00061E12 LDR R0, [R0]
__text:00061E14 MOVS R1, #0
__text:00061E16 B.W __Z24SceneManager_MarkForSaveP13SSceneManageri ; SceneManager_MarkForSave(SSceneManager *,int)
__text:00061E16 ; End of function Player_AwardStar(SPlayer *,int)[/B]
Now let’s look at hacking a LDR. The example below is from the same app and has a similar name: AwardEnergy. If the function behaved itself, we could replace the first two instructions (marked with <<<) with MOV R0, R7 followed by BX LR. A BX LR tells the code to skip all the way to next function. So what we’d be doing is telling 803 million to move into R0 (energy) and skip to the next function - done.

That WOULD work except there are some branches in the function. It’s usually not appropriate to BX LR before a branch. As I explain in the next example, a branch is a boolean - if you skip that boolean the code effectively nulls both true and false, which most of the time results in a crash. If you’re familiar with Pay2Win games, you’ll know that energy is a bit more complex than a currency value because of timer checks. I’m guessing that’s why there are branches, and nulling those would crash the app.

So what we have to do is hack the LDR function (marked with ~~~). We know that R0 is our energy, and similar to the AwardStars LDR instruction, this LDR is asking for R4+30. If it’s changed to MOV R0, R7, 803 million will be loaded into energy and all is well.

__text:00061DD4 ; Player_AwardEnergy(SPlayer *, unsigned int, int)
__text:00061DD4 __Z18Player_AwardEnergyP7SPlayerji ; CODE XREF: Player_Update(SPlayer *,int)+3F2p
__text:00061DD4 ; L_SceneManager_AwardPlayerReward(SSceneManager *,int,ESceneRewardType,SNpcInstance *)+D4p ...
__text:00061DD4 PUSH {R4,R7,LR} <<<
__text:00061DD6 MOV R4, R0 <<<
__text:00061DD8 ADD R7, SP, #4
__text:00061DDA LDR R0, [R4,#0x30] ~~~
__text:00061DDC CBNZ R2, loc_61DF0
__text:00061DDE LDR R2, [R4,#0x34]
__text:00061DE0 CMP R0, R2
__text:00061DE2 BHI loc_61DEA
__text:00061DE4 ADDS R3, R0, R1
__text:00061DE6 CMP R3, R2
__text:00061DE8 BCS loc_61E06
__text:00061DEA loc_61DEA ; CODE XREF: Player_AwardEnergy(SPlayer *,uint,int)+Ej
__text:00061DEA CMP R0, R2
__text:00061DEC IT CS
__text:00061DEE POPCS {R4,R7,PC}
__text:00061DF0 loc_61DF0 ; CODE XREF: Player_AwardEnergy(SPlayer *,uint,int)+8j
__text:00061DF0 ADD R0, R1
__text:00061DF2 STR R0, [R4,#0x30]
__text:00061DF4 MOV R0, R4
__text:00061DF6 BL __Z27L_Player_ResizeEnergyButtonP7SPlayer ; L_Player_ResizeEnergyButton(SPlayer *)
__text:00061DFA LDR R0, [R4]
__text:00061DFC MOVS R1, #0
__text:00061DFE POP.W {R4,R7,LR}
__text:00061E02 B.W __Z24SceneManager_MarkForSaveP13SSceneManageri ; SceneManager_MarkForSave(SSceneManager *,int)
__text:00061E06 ; ---------------------------------------------------------------------------
__text:00061E06 loc_61E06 ; CODE XREF: Player_AwardEnergy(SPlayer *,uint,int)+14j
__text:00061E06 STR R2, [R4,#0x30]
__text:00061E08 POP {R4,R7,PC}
__text:00061E08 ; End of function Player_AwardEnergy(SPlayer *,uint,int)
✻ Branches (unconditional, conditional)

Branches do what they sound like: They tell the code to branch off in 1 or 2 directions. There are two types of branches: unconditional and conditional.

Unconditional branches are B and BL - they branch in 1 direction. Branch (B ) tells the code to jump to an address INSIDE the housing function. The last instruction in the example function is a Branch. A Branch Link (BL) is a branch that tells the code to jump to an address OUTSIDE the housing function. There are multiple BLs in the example and are beside text (this text is the address destination).

Conditional branches are everything else - BEQ, BNZ, BLT, etc. They always follow a Compare (CMP), which dictates what happens when the code comes to the branch. Let’s have a look at the first branch in the example (marked by <<<). The CMP instruction reads, “compare the number 0 with R0”. A BEQ (branch if equal) follows the CMP, saying, “If the number 0 is equal to R0, branch to address 0x1002E0”. That’s how all conditional branches work - the key is to remember instructions are read right to left. That doesn’t matter for BEQ, but when you do BLT and BGT, it is crucial.

Now that you now how branches work, let’s hack this function. The function name, CanLearnSkill, is a boolean (hence all the branches). In the game, you can only learn skills if you have skill points. We want to remove that restriction so you can learn skills regardless of skill points! Look at the first 4 conditional branches (marked with <<<). They all tell the code to branch to 0x1002E0 if their Rn equals 0. Zero you say? Sounds like our skill points. Judging by the rest of the branches, this is highly likely. Have a look at 0x1002E0 (marked with ~~~). Remember that in numeric code, 0=false and 1=true. MOV R0,#0 is a very common way of writing false, or in other words, NOPE. If however the instruction is changed to MOV R0,#1 we’ll always be able to learn skills. It all makes sense now: If R0, R3, R4 equal 0 (all checks and balances for skill points), branch to MOV R0,#1, which tells the code TRUE/YES: you CAN learn skills on 0. The following instruction (LDMFD SP!, {R4-R7,PC}) is a reset - it tells the code to pop back to the beginning of the function. Remember that the MOV R0,#1 is only called when Rn is 0, so this doesn’t affect the learning of skills with non-zero skill points.

__text:00100234 ; CMvPlayer::CanLearnSkill(CMvSkill *, bool)
__text:00100234 EXPORT __ZN9CMvPlayer13CanLearnSkillEP8CMvSkillb
__text:00100234 __ZN9CMvPlayer13CanLearnSkillEP8CMvSkillb
__text:00100234 ; CODE XREF: CMvSkill::DrawExplainPopup(bool,bool)+40p
__text:00100234 ; CMvSkill::DrawIcon(CGsDrawRect *,int,int,bool)+6Cp ...
__text:00100234 STMFD SP!, {R4-R7,LR}
__text:00100238 ADD R7, SP, #0xC
__text:0010023C LDR R3, [R0]
__text:00100240 MOV R6, R0
__text:00100244 MOV R5, R1
__text:00100248 UXTB R4, R2
__text:0010024C LDR R3, [R3,#0x24]
__text:00100250 BLX R3
__text:00100254 CMP R0, #0 <<<
__text:00100258 BEQ loc_1002E0 <<<
__text:0010025C CMP R4, #0 <<<
__text:00100260 BEQ loc_100274 <<<
__text:00100264 MOV R3, #0x69A
__text:00100268 LDRH R3, [R6,R3]
__text:0010026C CMP R3, #0 <<<
__text:00100270 BEQ loc_1002E0 <<<
__text:00100274 loc_100274 ; CODE XREF: CMvPlayer::CanLearnSkill(CMvSkill *,bool)+2Cj
__text:00100274 CMP R5, #0 <<<
__text:00100278 BEQ loc_1002E0 <<<
__text:0010027C MOV R0, R5
__text:00100280 MOV R1, #0xFFFFFFFF
__text:00100284 LDRB R4, [R5,#5]
__text:00100288 BL __ZN8CMvSkill12LoadMaxLevelEi ; CMvSkill::LoadMaxLevel(int)
__text:0010028C CMP R4, R0
__text:00100290 BGE loc_1002E0
__text:00100294 B loc_1002E8
__text:00100298 ; ---------------------------------------------------------------------------
__text:00100298 loc_100298 ; CODE XREF: CMvPlayer::CanLearnSkill(CMvSkill *,bool)+CCj
__text:00100298 MOV R1, #0xFFFFFFFF
__text:0010029C MOV R0, R5
__text:001002A0 BL __ZN8CMvSkill17LoadLimitPreSkillEi ; CMvSkill::LoadLimitPreSkill(int)
__text:001002A4 CMN R0, #1
__text:001002A8 MOV R1, R0
__text:001002AC MOVEQ R0, #1
__text:001002B0 LDMEQFD SP!, {R4-R7,PC}
__text:001002B4 MOV R0, R6
__text:001002B8 BL __ZN9CMvPlayer14SearchSkillPtrEi ; CMvPlayer::SearchSkillPtr(int)
__text:001002BC CMP R0, #0
__text:001002C0 BEQ loc_1002E0
__text:001002C4 LDRSB R3, [R0,#4]
__text:001002C8 CMN R3, #1
__text:001002CC BLE loc_1002E0
__text:001002D0 LDRB R0, [R0,#5]
__text:001002D4 SUBS R0, R0, #0
__text:001002D8 MOVNE R0, #1
__text:001002DC LDMFD SP!, {R4-R7,PC}
__text:001002E0 ; ---------------------------------------------------------------------------
__text:001002E0 loc_1002E0 ; CODE XREF: CMvPlayer::CanLearnSkill(CMvSkill *,bool)+24j
__text:001002E0 ; CMvPlayer::CanLearnSkill(CMvSkill *,bool)+3Cj ...
__text:001002E0 MOV R0, #0 ~~~
__text:001002E4 LDMFD SP!, {R4-R7,PC}
__text:001002E8 ; ---------------------------------------------------------------------------
__text:001002E8 loc_1002E8 ; CODE XREF: CMvPlayer::CanLearnSkill(CMvSkill *,bool)+60j
__text:001002E8 MOV R0, R5
__text:001002EC MOV R1, #0xFFFFFFFF
__text:001002F0 LDRB R4, [R6,#0x40F]
__text:001002F4 BL __ZN8CMvSkill18LoadLimitCharLevelEi ; CMvSkill::LoadLimitCharLevel(int)
__text:001002F8 CMP R4, R0
__text:001002FC BLT loc_1002E0
__text:00100300 B loc_100298
__text:00100300 ; End of function CMvPlayer::CanLearnSkill(CMvSkill *,bool)
✻ Special Thanks
mikeyb123 - For sharing his offsets of High School Story, which helped me with LDRs.

Credits :- Evilly GOOd
Last edited:


Oct 24, 2021
hello im new here my question is how do i determine which R means what ? as u said during energy modification that R0 represents energy .how did u come to know that specifically R0 represents that ?

About us

  • Welcome to platinmods.com! We are proud to present you the place which let's dreams come true! Focusing on quality and trust we have spend much time to build a gaming community fitting to your wishes and needs. Actually we offer you the finest MODs and Games of the Android section and we slowly expand to the iOS section as well. But games, Android MODs & iOS MODs are not the only things we can offer you. We have tutorials, tools, a very friendly, active and solid community which will help you with any problem you have =) Your happiness is our goal. We hope you enjoy!

Forum statistics

Latest member