iOS debug session: Direct field offset (Part 1)

Programming passion
5 min readMar 14, 2023

--

This is a deep dive into the low-level debugging technique. This topic is not what we do in our regular debugging sessions on a daily basis.

I don’t suggest you to read it.

What is Direct field offset?

This is a terminology in low level. Its name has tell all

Direct field offset

Basically, when you access a certain property of an object, it jumps to the memory address of the object in heap memory. However, how We know which is the address of the property in the memory region? To solve this, the direct field offset approach is introduced. It tells the direct offset of property from the object base address. The property can be read/write randomly in no time.

Look through an example

Let’s make a simple program and examine it

class ViewController: UIViewController {

@IBOutlet weak var contentLabel: UILabel!

var patched = false

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}

@IBAction func onBtnTapped(_ sender: Any) {

if patched {
contentLabel.text = "Program is patched"
} else {
contentLabel.text = "Program is loaded"
}
}

}

Set the breakpooint in the func onBtnTapped, and see how it accesses the patched property

Extract the field offset value

In the capture above, We see how the program extract the direct field offset value in line 7

0x1056347d4 <+20>: mov rax, qword ptr [rip + 0xa59d]

In brief explanation:

  1. rip is the program counter, it’s point to the latest instruction address at where it’s currently executing. In this context, rip is 0x1056347d4.
  2. rip + 0xa59d. The constant 0xa59d is calculated and fixed at compile time, to give a direct access to a constant in runtime. This instruction is pointing at the address where the direct offset value locates. The actual address is 0x1056347d4 + 0xa59d = 0x10563ed71
  3. qword ptr [rip + 0xa59d], this instruction make reference to the address at 0x10563ed71, and retrieve the actual value.
  4. move rax, finally it copies the value to the register rax

Access direct field offset

There are a few ways to find out the direct field offset value.

Just read the register

In debugger, set the breakpoint at the successive instruction address, 0x1056347db, and read the actual value in the register rax in debugger

(lldb)register read rax
rax = 0x0000000000000368

Read it from memory

As above, we know the address of the direct field offset in the payload. It is in the bracket, rip + 0xa59d, or 0x1056347d4 + 0xa59d = 0x10563ed71

in debugger, navigate to the address 0x10563ed71 by the following command

(lldb) memory read 0x000000010563ed71 -fA
0x10563ed71: 0x6800000000000003
0x10563ed79: 0xc000000000000003
0x10563ed81: 0x6000007fff862bc6
0x10563ed89: 0x0000007fff864add
0x10563ed91: 0x0f0000600003fe24
0x10563ed99: 0x200007c035000000
0x10563eda1: 0x0000006000003829
0x10563eda9: 0x0000000000000000

It seems to reveal nothing meaningful. Something is not seemingly right.
First, the address 0x10563ed71 is an odd number, it should’ve been an even

Second, The value of each address is shifted. Look at both sides of it.

Correct the address

We need to shift the address back or forth, to have to correct location of the direct field offset. In this case, I give it an offset of 7

(lldb) memory read 0x000000010563ed71+7 -fA
0x10563ed78: 0x0000000000000368
0x10563ed80: 0x00007fff862bc6c0 (void *)0x00007fff862bc6c0: NSObject
0x10563ed88: 0x00007fff864add60 (void *)0x00007fff862bc6c0: NSObject
0x10563ed90: 0x0000600003fe2400
0x10563ed98: 0x0007c0350000000f
0x10563eda0: 0x0000600000382920
0x10563eda8: 0x0000000000000000
0x10563edb0: 0x0000000000000000

After this, the meaningful info is uncovered. The actual address of the direct field offset is 0x10563ed78, and its value is 0x0000000000000368

Look it up from the MACH-O

This is a direct way to look up the actual value of the direct field offset without debugging and breakpoint. However to keep the focus on this study, I’m not going to introduce it here

Access the property

Access the property of the object by direct field offset

In the capture above, We notice this

 0x105634803 <+67>: mov    al, byte ptr [r13 + rax]

In brief explanation:

  1. r13 is the register that keeps the address of the object.
  2. rax keeps the direct field offset value
  3. r13 + rax tells the actual memory address the property locates at
  4. The whole instruction basically retrieve the value of the property at the target address, and store it in the register al

ur turn

Now, its our turn to access that by ourselves, How are We going to do it?

Calculate the property address

# Read the address of the object in the register r13
(lldb) register read r13
r13 = 0x00007fad0f81a170
# Read the value of direct field offset
(lldb) register read rax
rax = 0x0000000000000368
# Calculate the actual address of the property of the object.
(lldb) p/x 0x00007fad0f81a170 + 0x0000000000000368
(Int) $R3 = 0x00007fad0f81a4d8
# The actual address of the property is 0x00007fad0f81a4d8

Access the property

(lldb) memory read 0x00007fad0f81a4d8 -f A
0x7fad0f81a4d8: 0x0000000000000000

The value of 0x7fad0f81a4d8 is 0x0, it is false.

Just have some fun

Now, you may know how We gain a direct access to a property of an object. We can apply a change on it at will. Remember, not every property is made equal. There are some kinds of property that don’t have direct field offset.

Actually, you can just jump in Xcode debugger console, and set the value of the property, no sweat. That is the most effective way to do it.

The way, by using direct field offset, is just a narrow path. This path is not for everyone.

If you’re interted in this topic, there are following parts of direct field offset available.

--

--

Programming passion

Been working on iOS, tvOS, Swift, and Objective-C since once upon a time. I'm just a little iOS developer, but like to mess with everything.