I try to modify the branch of this disassembly in a binary:

SUB             SP, SP, #0x60
STP             X26, X25, [SP,#0x50+var_40]
STP             X24, X23, [SP,#0x50+var_30]
STP             X22, X21, [SP,#0x50+var_20]
STP             X20, X19, [SP,#0x50+var_10]
STP             X29, X30, [SP,#0x50+var_s0]
ADD             X29, SP, #0x50
MOV             X20, X0
MOV             X0, X2
BL              _objc_retain
MOV             X19, X0
ADRP            X8, #selRef_shouldCheckForUpdate@PAGE
LDR             X1, [X8,#selRef_shouldCheckForUpdate@PAGEOFF] ; char *
MOV             X0, X20 ; void *
BL              _objc_msgSend
CBZ             W0, loc_ADCC

Basically, the binary are checking for update and will prompt if it should, and for the sake of learning, I wanted to achieve on how to:

  1. Always go to loc_ADCC and;

  2. Always ignore loc_ADCC (Skip from going to loc_ADCC)

This ARM64 really got me confused, I could however understand it in 32bit, but not in 64bit. It's like a new world. You could see the screenshot here for a better visualization.

Thanks in advance!

  • 2
    what does "Always ignore loc_ADCC" mean?
    – Igor Skochinsky
    Jul 18 '18 at 17:08
  • @IgorSkochinsky I suppose it implies "always skip branching to loc_ADCC" (and the compare before it I think) Jul 18 '18 at 23:10
  • You're right @ElianKamal Jul 19 '18 at 2:50
  • Just for reference: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… - CBZ and CBNZ definition. Just NOP it out (put NOP(s) of the same size instead of it) to avoid jump.
    – w s
    Jul 19 '18 at 6:24
  • by the way, thank you for putting disassembly as text and not just a screenshot! I hope those who downvoted the question leave a comment why they did it so it can be improved.
    – Igor Skochinsky
    Jul 19 '18 at 18:57

So, there are these options:

  1. to always take the branch, you need to convert CBZ (compare and branch if zero) to simple B (branch always). Let's have a look at the encodings of both:

B: 31 30 29 28 27 26 25 .. 0 0 0 0 1 0 1 imm26


31 30 29 28 27 26 25 24 23..5 4..0 sf 0 1 1 0 1 0 0 imm19 Rt

To convert CBZ to B, you need to patch the opcode part (bits 31..26) and move the imm19 field (branch offset) to the imm26 field of the B opcode (bits 25..0). Since both opcodes interpret the offset in the same way (multiply by 4 and add to PC), you don't need to do any conversion besides sign extension.

For example, let's take this instruction from a random sample I had:

05088 E0 01 00 34  CBZ W0, loc_50C4

Opcode as a 32-bit value: 0x340001E0 (AArch64 always uses little-endian instructions)

In binary: 00110100000000000000000111100000.

Split by fields:

0 0110100 0000000000000001111 00000 sf op imm19 Rt

Let's assemble the B opcode (sign-extending imm19 to 26 bits):

000101 00000000000000000000001111 op imm26

Or, as hex: 0x1400000F

After patching:

5088 0F 00 00 14                 B               loc_50C4
  1. To skip the branch, you can patch CBZ to a NOP (no operation). The NOP encoding for ARM64 is 0xD503201F or 1F 20 03 D5
  • Thank you for the very detailed answer! It helps a lot! If you don't mind, I would like to read more about the explanation you did in 'Split by fields and assemble' section, could you point me a good learning head-start (basically how you split it and sign-extending)? :) Jul 20 '18 at 4:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.