Jump to content

ARM Patching (ADVANCED)


XEKEX
 Share

Recommended Posts

Quick Notes:

Low Registers (R0 to R7): Accessible by all instructions using general-purpose registers.

High Registers (R8 to R12): Accessible by 32-bit instructions specifying a general-purpose register, not all 16-bit instructions.

Stack Pointer (R13): Used as the Stack Pointer (SP). Autoaligned to a word, four-byte boundary, ignoring writes to bits [1:0].

Link Register (R14): Subroutine Link Register (LR). Receives return address from PC during Branch and Link (BL) or Branch and Link with Exchange (BLX). Also used for exception return. Treat as a general-purpose register.

Program Counter (R15): PC.

FPU (Floating Point Unit): Supports single-precision operations - add, subtract, multiply, divide, multiply and accumulate, and square root. Also handles conversions between fixed-point and floating-point formats, and floating-point constant instructions.

FPU Registers:

Sixteen 64-bit doubleword registers: D0-D15.

Thirty-two 32-bit single-word registers: S0-S31.

63a087f0-60a5-4512-9d3b-f92196424e68.thumb.png.f10a70bb1b82bbb2ecbc25e240b12b80.png
->Source <-
---------------------------------------------------------------------------------------------------------------------------------
In Arm Patching we are using only Low Registers and the FPU.

True and false Editing.
~A MOV R0, #1
MOV means Move , by this instruction we are telling the proccessor to move the value 1 to register R0 similar when you assign a variable name :
R0 = 1 
in most programing languages the true statment always = 1  and the false statment = 0 so #1 = true and #0 = false
~A BX LR
BX Means branch exit LR or in another way return the value we stored to the caller.

Int Editing :
we can use MOV R0, # aswell for the int value but you need to know the integral data types.
byte : Signed: From −128 to 127

­ ­ ­ ­ ­ ­   ­  ­ ­: Unsigned: From 0 to 255

we can use MOV here if the int value we want is between -128 and 255 so the instruction will be : ~A MOV R0, #-128 or #255 at max

short : Signed: From −32,768 to 32,767
               : Unsigned: From 0 to 65,535

in this case we use MOVW the W stands for Word so same as above the instruction will be : ~A MOVW R0, #−32,768 or #65,535 at max

NOTE :
• Don't forget to return (~A BX LR) 
• We can Use MVN which mean Move Negative so the Max Negative Value will be #255 for Byte and MVNW for Short #65,535 (Don't add "-" since we already telling the proccessor we are dealing with negative number)
• #value will be converted automatically to hex value in the Register means #8 will be 0x00000008 and so on

Int 32 : Signed: From −2,147,483,648 to 2,147,483,647
                : Unsigned: From 0 to 4,294,967,295

the typical DWORD in GG : here we move to the advanced Part of this guide:
as I said in the Note above the values are converted in the register automatically to hex so the max value in short in hex will be 0x0000FFFF

so we have 4 zero's we can't change in the int 32, in this case we use one more instructon MOVT  T stands for Top 
example : MOVW R0, #
22136 -> R0 will be : 0X00005678
                     MOVT R0 , #4660 -> R0 will be : 0x12345678
So in case of INT32 we need 2 things
• Convert the value we want to change to hex value 
• 3 instruction in total
the Same concept here work for QWORD aswell (64 bit) 0x0000000000000001
Note : MVN R0, #2 will change to 0xFFFFFFF2 in hex

MOV R0, #2 or MOV R0, #0x2 are the same


Float and Double:
• Float and Double are IEEE 754 Floating-Point: We need the FPU here and things will get a little bit complicated,
• we need 2 or 3 registers in this case R0 , R1 and S0(for float) or D0(for double)

Suppose the hex value of this float 12.6 is : 0x4149999A
same as the int 32 : 
~A MOVW R0, #0x999A (R0 = 0x0000999A)
~A MOVT R0, #0x4149 (R0 now = 0x4149999A)
now R0 is set but if we return the value (~A BX LR) the result will be : 1095342490 and we don't want that value we want 12.6 as float (This Doesn't Work Because we didn't tell the proccessor that is a float number)
the right way is to use FPU
VMOV S15, R0 ( VMOV is the instruction MOV in the FPU : by that instruction we mean move the register value of R0 to the FPU register R15 )
VMOV.F32 S0, S15 (here we are telling the FPU we are dealing with Float number (F32) and move the value from S15 to S0 )
for double we use the same concept except we use F64 instead and register D16 and D0

Float
so the final code will be : 
~A MOVW R0, #0x999A (R0 = 0x0000999A)
~A MOVT R0, #0x4149 (R0 = 0x4149999A)
~A VMOV S15, R0
~A VMOV.F32 S0, S15
~A BX LR
-----------------

Double
For double the hex value of 12.6 is : 0x4029333333333333 (Same Concept for Big Float Number)
• Here we use R0, R1 , D0 and D16
• divide the hex value 0x4029333333333333 into 2 part 0x40293333 and 0x33333333 one goes for R0 and the other one goes for R1
Be carful of the placement of the hex value we start from the last 4 to the 1st 4 means we start with 0x3333 -> 0x4029
Use same concept of MOVW and  MOVT to get the result.
Result:
~A MOVW R0, #0x3333 (R0 = 0x00003333)
~A MOVT R0, #0x3333 (R0 = 0x33333333)
~A MOVW R1, # 0x3333 (R1 = 0x00003333)
~A MOVT R1,  #0x4029 (R1 = 0x40293333)
~A VMOV D16, R0, R1 (Move value Of R0 and R1 to register D16 Be Careful here R0 last 8 hex 1st then R1 the top 8 hex)
~A VMOV.F64 D0, D16 (here we use F64 and D0 , and D16 instead of F32 , S0 and S15 because the hex value is 64 bit)
~A BX LR

------

This is How you arm patch bool / int / float / double
NOTE : When it comes to function args and returns the only register that give return or args are R0,R1,R2,R3 (and SP)
this is why we use R0 and VMOV S15/D16 to S0/D0


ARMv8 :
image.thumb.png.9410801bb14282fe697e235311094abc.png


In ARMv8, LSL stands for "Logical Shift Left". It is an instruction used to shift the bits in a register to the left by a specified number of bits, and the bits that are shifted off the left-hand end are discarded. LSL can be used with immediate values or with a register value. The immediate value specifies the number of bits to shift, which can be between 0 and 63. When using a register value, the bottom byte of the register specifies the number of bits to shift
Example : 
Level 1 )

   LSL X1, X2, #3 --> Shift the contents of X2 left by 3 bits and store the result in X1
   -> In this example, X2 is being multiplied by 8 (since 8 is 2 to the power of 3), and the result is stored in X1.

Level 2)

   MOV and LSL example:
   MOV X1, #0x10 -->Move the value 0x10 into register X1
   LSL X1, X1, #3    --> Shift the contents of X1 left by 3 bits (multiply by 8 )

Level 3) Float Value 3.14159 / Hex 0x40490FD0

--Load the value 0x0FD00000 into bits 16-31 of W0
• MOVK W0, #0x0FD0, LSL #16  --> W0 = 0x00000FD0
-- Load the value 0x40490000 into bits 32-47 of W0
• MOVK W0, #0x4049, LSL #32 -> W0 = 0x40490FD0
-- Move the value of W0 into single-precision floating-point register S0
• FMOV S0, W0 --> S0 = 0x40490FD0 (interpreted as a floating-point value)

Note : 4 bytes hex (32) value we use register W and for float we use S

Level 4 ) Double value 3.14159 / Hex 0x400921F9F01B866E


MOVK X0, #0xF01B866E, LSL #16      -->X0 = 0x00000000F01B866E
MOVK X0, #0x400921F9, LSL #48   -->X0 = 0x400921F9F01B866E
FMOV D0, X0


Note: 8 bytes hex (64) value we use register X and for Double we use D

NOTE: SAME CONCEPT IN AARCH32 WITH (INT, BOOL, FLOAT, AND DOUBLE)
 LSL and MOV(Z/K) is the diffrences.


image.thumb.png.2f6587a5233b40da26030009033e6b56.png

 


image.thumb.png.030583678359cfef79a3ce46794add2e.png

PART II (LDR / STR): [STRING] ( NON UNITY GAMES )
Little-endian / Big-endians : 

image.png.d3be22b606472a12b21eae97a2e0fdd2.png

 

LDR and STR are instructions used in ARMv7 and ARMv8 architectures to load and store data from memory.

LDR stands for "Load Register" and is used to load a value from memory into a register. The syntax for LDR in ARMv7 and ARMv8 is
 LDR <Register>, [<Address>]

STR stands for "Store Register" and is used to store a value from a register into memory. The syntax for STR in ARMv7 and ARMv8 is 
STR <Register>, [<Address>]

where <Register> is the name of the register to load the value into, and <Address> is the memory address from which to load the value.

In both cases, the square brackets around <Address> indicate that the value inside the brackets is a memory address, rather than a register.
 

To load the string 'GG TESTING' into a register, you can use the LDR instruction. Assume the pointer to 'G' is 0x00000004 we can use this address as the base address for the LDR instruction.

The instruction for loading the first four characters of the string into a 32-bit register (e.g., R1/X1) would be:
• LDR R1/X1, [0x00000004] -- R1/X1 = 'GG T'
This instruction loads the 32-bit value at memory address 0x00000004 into R1/X1.

Note: Use the Move instructions above (PART I) to assign the value (address) to a register BEFOR USING LDR
--> LDR R1/X1, [R0] -- R0 = 0x123456789 ( use MOV to assign the correct address to R0 or X0)

To load the entire string into a register, you can use the LDR instruction with a register offset. Assuming the string is stored in consecutive memory locations, we can use the following instruction to load the entire string into a register (e.g., R1/X1)


LDR R1/X1, [0x00000004], #10
This instruction loads the 32-bit value at memory address 0x00000004 into R1 and increments the base address by 10 (the length of the string). As a result, the entire string 'GG TESTING' will be loaded into R1.

ADVANCED : 

If 'GG TESTING' is a half-word (i.e., each character is 2 bytes or 16 bits) and the pointer to 'G' is located at memory address 0x0000004 + 0x8, then the instructions for loading the string into a register would be different
Dummy memory:
0x0000004 (<-- pointer )= 123 
0x0000008 = 21
0x000000C = 9999999
0x0000010 = 'GG'
0x0000014 = ' T' -- with space at the start.
0x0000018 = 'ES'
etc..

--> between every byte value ( character ) there is 0 
[ example in memory
0x00000010 = 71 (G) <-- byte
0x00000011 = 0 <-- byte
0x00000012 = 71 (G) <-- byte
0x00000013 = 0 <-- byte
0x00000014 = 32 (space) <- byte
]


To load the half-word 'GG' into a 32-bit register (e.g., R0/X0), we can use the LDRH instruction as follows:
LDRH R0, [0x00000004, 0x8] 

This instruction loads the 16-bit value at memory address 0x00000010 into the lower 16 bits of R0/X0. Since we want to load the first two characters of the string, we add an offset of 0x8 to the base address.
Read more about LDR
To load the entire string into a register, we can use the LDRH instruction with a register offset as follows:
LDRH R0, [0x00000004, 0x8], #0xC


This instruction loads the 16-bit value at memory address 0x00000010 into the lower 16 bits of R1, and increments the base address by 0xC (or 12 bytes) to load the remaining characters of the string. The 'GG TESTING' string has a length of 10 characters, which corresponds to 20 bytes (11 characters x 2 bytes per character), so we need to load 12 bytes in addition to the first 2 bytes to load the entire string.

AARCH64 :

LDRH --> LDURH (
Load Unsigned Halfword with a 64-bit offset) or LDSRH (signed)

LDURH W0, [X1, #16] ; Load a halfword from the memory address X1 + 16 into W0

This loads a 16-bit unsigned halfword from the memory address X1 + 16 into the 32-bit register W0. Note that the offset value is added to the base register X1 to form the memory address. Also, because LDURH is an unsigned load instruction, the loaded halfword is zero-extended to 32 bits.

NOTE: the LDURH instruction is specific to AArch64 architecture and is not available in AArch32 architecture.image.thumb.png.c5318929031f2cc4d48ea78bf74b340a.png

 


STR:


STR is used to store the contents of a register into a memory location that is addressed using a base register and an optional offset. The contents of the register are written to the memory location, overwriting any previous data that was stored at that location.
-->STR Rd, [Rn {, #offset}]

where Rd is the source register whose contents will be stored in memory, Rn is the base register that points to the memory location where the data will be stored, and offset is an optional 32-bit offset that is added to the base register to form the memory address.

Example of using the STR instruction to store the contents of R0 register into a memory location:
--> STR R0/X0, [R1/X1, #4] ; Store the contents of R0/X1 into the memory location R1/X1 + 4.


NOTE STR Wd, [Xn, #offset], imm |  the STR instruction with the imm option is only available in AArch64.
image.png.3d282051dcb86b7fceffae543491afd9.png       |--> Wd/Xd, [Xn, #offset]




The imm option allows you to add an immediate value to the offset to form the memory address. The immediate value is sign-extended to 64 bits, shifted left by the scale factor (which is determined by the size of the data being transferred), and then added to the offset.
-> STR W0, [X1, #0x100], #0x20 -- This stores the contents of register W0 into the memory location pointed to by register X1 plus 0x100 plus 0x20, overwriting any previous data stored at that location.

In AArch32, there is no imm option for the STR instruction. However, you can achieve a similar effect by adding the immediate value to the offset before using it in the instruction. Here's an example:

ADD R2, R1, #0x120 --> R2 = R1 + 0x120
STR R0, [R2] --> Store R0 at address R2

Here, the ADD instruction adds the immediate value 0x20 to the base register R1, storing the result in R2. The STR instruction then stores the contents of register R0 into the memory location pointed to by register R2.

Note: that the immediate value is added to the offset before using it in the instruction, rather than being added as a separate operand like the imm option in AArch64.


--->FOR Using LDR / STR on values just LDR/STR R0/X0, [DESTINATION ADDRESS]


Note : Unity games use pointers for the string


----------------------------------------------> Converting Float and Double to Hex <---------------------------------

This is mainly 
IEEE Standard for Floating-Point Arithmetic. (you can skip this part by using online converter)
> You need : 
• Advanced Lua scripting Knowladge.
• Math Knowladge.
• Binary 32 and 64 Knowladge.
--------------Please read--------------

Edited by XEKEX
Correcting a mistake
Link to comment
Share on other sites

  • 2 weeks later...
  • 2 weeks later...
4 hours ago, Godiskata said:

Help me pls! I got get_movespeed(float) in dump, what arm code multiple x time?

64bit 

A8 mov w0, #4 (try 4-20 which one is better) 

And

A8 ret

 

32bit 

A mov r0, #4 ((try 4-20 which one is better) 

And 

A bx lr

5 hours ago, Godiskata said:

Help me pls! I got get_movespeed(float) in dump, what arm code multiple x time?

Maybe arm just edit dword only

Link to comment
Share on other sites

3 hours ago, Alessa- said:

Can W and X 

 

register W will hold 32 bit values and X will hold 64 values , 
 

local binary = string.pack("f", 4.0) -- this will convert the float 4 to binary form "f" = float / "d" = double
      local hex = ""
      for i = 1, #binary do -- this loop will give us the hex value of the binary above
        hex = hex .. string.format("%02X", string.byte(string.reverse(binary), i))
      end
--[[
the final arm code will be : 
~A8 MOVK X0, #0x0000, LSL #16 -- LSL will shift our hex value
~A8 MOVK X0, #0x4080, LSL #32
~A8 FMOV S0 ,X0
~A8 RET
]]
--reply if the code work for you

 

Link to comment
Share on other sites

  • 3 weeks later...
  • 4 weeks later...
1 hour ago, DARK_DEMON_SCRIPTER said:

@XEKEXcan you give me void editng value for arm and arm64 for true and false 

void functions doenst have return , also it act like : 
for key , value in pairs() in lua

Link to comment
Share on other sites

  • 1 month later...
14 hours ago, LTC said:

@XEKEXhi bro, i have 7 arm codes like in the picture how can i edit #123 to #9999?, i tried your methods but the game will be closed immediately

Screenshot_20230515-025759_Zombie Cubes 2.jpg

edit the address 9936C000 ( push instruction ) to MOVW R0, #999
edit the address below it (ADD R11 SP #16 ) to BX LR

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.