iOS debug session: Direct field offset (Part 1)
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
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:
- 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.
- 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
- qword ptr [rip + 0xa59d], this instruction make reference to the address at 0x10563ed71, and retrieve the actual value.
- 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
In the capture above, We notice this
0x105634803 <+67>: mov al, byte ptr [r13 + rax]
In brief explanation:
- r13 is the register that keeps the address of the object.
- rax keeps the direct field offset value
- r13 + rax tells the actual memory address the property locates at
- 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.