Home>

Foreword

Under normal circumstances,By analyzing the interface and the header files from class-dump, you can guess the implementation of a certain function.But the special type of block can't see its declaration in the header file.Some method callbacks with block callbacks are dumped like this:

-(void) fm_getsubscribelist:(long long) arg1 pagesize:(long long) arg2 callback:(cdunknownblocktype) arg3;

Because this callback doesn't see its method signature,We cannot know how many parameters this block has,I don't know the specific address of its function body,Therefore, it is also difficult to use lldb for dynamic debugging.I was once blocked by this difficulty,I thought the method that called the block was in a dead end,There is no way to keep track of it.I have also given up on the analysis of a function several times,Especially frustrated.

Fortunately, we also have the powerful weapon of google.There is no problem that Google cannot solve at one time.If so, then twice.

This article talks about how to analyze the function body address of a block through its memory model.And function signatures.

Block memory structure

In the llvm documentation, you can seeBlock implementation specificationsThe most critical part is the definition of the block memory structure:

struct block_literal_1 {
 void * isa;//initialized to & _nsconcretestackblock or & _nsconcreteglobalblock
 int flags;
 int reserved;
 void (* invoke) (void *, ...);
 struct block_descriptor_1 {
 unsigned long int reserved;//null
 unsigned long int size;//sizeof (struct block_literal_1)
 //optional helper functions
 void (* copy_helper) (void * dst, void * src);//iff (1<25)
 void (* dispose_helper) (void * src);//iff (1<25)
 //required abi.2010.3.16
 const char * signature;//iff (1<30)
 } * descriptor;
 //imported variables
};

You can see that the first member is isa, which means that block is also an object in objective-c.What we are focusing on isvoid (* invode) (void *, ...); andin descriptor const char * signature , the former points to the address of the block implementation,The latter is a string representing the signature of the block function.

Actual combat

Note:This article is analyzed on a 64-bit system.For 32-bit systems, the sizes of integer and pointer types are inconsistent with 64-bit.Please modify it yourself.

After knowing the memory model of the block,You can directly open hopper and lldb for debugging.

Here I use the logical app to get the app as an example of analysis.by the way,Getting the above content is pretty good,Many paid columns have great content,Worth a look.

ready

Device:iphone 5s ios 8.2 jailbreak

usbmuxd

$tcprelay -t 22:2222 1234:1234
forwarding local port 2222 to remote port 22
forwarding local port 1234 to remote port 1234
...

ssh to the ios device and start debugserver:

$ssh [email protected] -p 2222
iphone $debugserver *:1234 -a "luojifm-ios"
[email protected] (#) program:debugserver project:debugserver-320.2.89
 for arm64.
attaching to process luojifm-ios ...
listening to port 1234 for a connection from * ...

Open lldb locally and attach the process remotely,For dynamic debugging:

$lldb
(lldb) process connect connect://localhost:1234

Find the offset address:

(lldb) image list -o -f
[0] 0x0000000000074000 /private/var/mobile/containers/bundle/application/d106c0e3-d874-4534-aed6-a7104131b31d/luojifm-ios.app/luojifm-ios(0x0000000100074000)
[1] 0x000000000002c000/users/wordbeyond/library/developer/xcode/ios devicesupport/8.2 (12d508)/symbols/usr/lib/dyld

Find the address of the breakpoint under hopper:

Breakpoint:

(lldb) br s -a 0x0000000000074000 + 0x0000000100069700
breakpoint 2:where=luojifm-ios`_mh_execute_header + 407504, address=0x00000001000dd700

Then click on the subscription tab in the app, and the breakpoint will be hit at this time (if there is no hit,Manual pull down to refresh).

As we all know,Objective-c method calls are converted into objc_msgsend calls, so when you step into it, you can stop when you see objc_msgsend:

->0x1000dd71c<+ 431900> ;:bl 0x100daa2bc;symbol stub for:objc_msgsend
 0x1000dd720<+ 431904> ;:mov x0, x20
 0x1000dd724<+ 431908> ;:bl 0x100daa2ec;symbol stub for:objc_release
 0x1000dd728<+ 431912> ;:mov x0, x21
(lldb) po $x0
<dataservicev2:0x17400cea0>
(lldb) po (char *) $x1
"fm_getsubscribelist:pagesize:callback:"
(lldb) po $x4
<__ nsstackblock__:0x16fd88f88>

can be seen,The fourth parameter is a stackblock object, but lldb just prints its address for us.Next, we have to find out its function body address and function signature by ourselves.

Find the function body address of a block

To find out the function body address of a block is simple,According to the memory model above,We just find the address of the function pointer called invoke,It points to the implementation of this block.

On 64-bit systems,The size of a pointer type is 8 bytes, while int is 4 bytes, as follows:

Therefore, the address of the invoke function pointer is after the 16th byte.We can use lldb's memory command to print out the memory at the specified address.We have obtained the address of the block above, and now print out its memory content:

(lldb) memory read --size 8 --format x 0x16fd88f88
0x16fd88f88:0x000000019b4d8088 0x00000000c2000000
0x16fd88f98:0x00000001000dd770 0x0000000100fc6610
0x16fd88fa8:0x000000017444c510 0x0000000000000001
0x16fd88fb8:0x000000017444c510 0x0000000000000008

As mentioned before,The address of the function pointer is after the 16th byte.It takes 8 bytes, so the address of the function can be 0x00000001000dd770.

With the function address,You can disassemble this address:

(lldb) disassemble --start-address 0x00000001000dd770
luojifm-ios`_mh_execute_header:
->0x1000dd770<+ 431984> ;:stp x28, x27, [sp, #-96]!
 0x1000dd774<+ 431988> ;:stp x26, x25, [sp, #16]
 0x1000dd778<+ 431992> ;:stp x24, x23, [sp, #32]
 0x1000dd77c<+ 431996> ;:stp x22, x21, [sp, #48]
 0x1000dd780<+ 432000> ;:stp x20, x19, [sp, #64]
 0x1000dd784<+ 432004> ;:stp x29, x30, [sp, #80]
 0x1000dd788<+ 432008> ;:add x29, sp, #80;= 80
 0x1000dd78c<+ 432012> ;:mov x22, x3

You can also set breakpoints directly in lldb:

(lldb) br s -a 0x00000001000dd770
breakpoint 3:where=luojifm-ios`_mh_execute_header + 407616, address=0x00000001000dd770

Run the function again,You can enter the callback block body.

However, in most cases,We don't need to go into the body of the block function.When writing tweak, what we need more is to know what parameters this block callback gives us.

Next, we continue to explore.

Finding the function signature of a block

To find the function signature of a block,You need to pass the signature member in the descriptor structure, and then get an nsmethodsignature object through it.

First, you need to find the descriptor structure. This structure is held in the block through pointers,It's right behind the invoke member,Occupies 8 bytes. You can see from the memory print above that the address of the descriptor pointer is 0x0000000100fc6610.

Next, you can find the signature by the address of the descriptor. However, the documentation states that not every block has a method signature,We need to make the judgment through the enumeration masks defined in flags and blocks.Still in the llvm document just now, we can see that the mask is defined as follows:

enum {
 block_has_copy_dispose=(1<25), block_has_ctor=(1<26), //helpers have c++ code
 block_is_global=(1<28), block_has_stret=(1<29), //iff block_has_signature
 block_has_signature=(1<30),};

Use the memory command again to print the value of flags:

(lldb) memory read --size 4 --format x 0x16fd8a958
0x16fd8a958:0x9b4d8088 0x00000001 0xc2000000 0x00000000
0x16fd8a968:0x000dd770 0x00000001 0x00fc6610 0x00000001

Since ((0xc2000000&(1<30))!=0), we can be sure that this block is signed.

Although it is pointed out in the documentation that not every block has a function signature.But we can seein cgblocks.cpp in clang source codegenfunction ::emitblockliteral and buildglobalblock methods, you can see that the flags member of each block is set to block_has_signature by default. So we can infer thatBlocks in all code compiled with clang are signed.In order to find the address of the signature, we also need to confirm whether the block has two optional function pointers, copy_helper and disponse_helper.Since((0xc2000000&(1<25))!=0) , so we can confirm that this block has the two function pointers just mentioned.

Now we can summarize:the address of the signature is the address offset two unsiged long and two pointers under the descriptor,After 32 bytes.Now let's find out its address,And print out its string contents:

(lldb) memory read --size 8 --format x 0x0000000100fc6610
0x100fc6610:0x0000000000000000 0x0000000000000029
0x100fc6620:0x00000001000ddb64 0x00000001000ddb70
0x100fc6630:0x0000000100dfec18 0x0000000000000001
0x100fc6640:0x0000000000000000 0x0000000000000048
(lldb) p (char *) 0x0000000100dfec18
(char *) $4=0x0000000100dfec18 "[email protected]& #63;0q8 @" nsdictionary "16b24"

Seeing this garbled code, do you feel a little broken?Toss for a long time,How to print such a bunch of ghosts,Although there is a familiar nsdictionary in it, other things are completely incomprehensible.

Don't panic, this is really a function signature,Just we need to find out its parameter type through nsmethodsignature:

(lldb) po [nsmethodsignature signaturewithobjctypes:"[email protected]& #63;[email protected]\" nsdictionary \ "16b24"]
<nsmethodsignature:0x174672940>
 number of arguments=4
 frame size=224
 is special struct return?no
 return value:-------- -------- -------- --------
 type encoding (v) "v"
 flags {}
 modifiers {}
 frame {offset=0, offset adjust=0, size=0, size adjust=0}
 memory {offset=0, size=0}
 argument 0:-------- -------- -------- --------
 type encoding (@) "@ & #63;"
 flags {isobject, isblock}
 modifiers {}
 frame {offset=0, offset adjust=0, size=8, size adjust=0}
 memory {offset=0, size=8}
 argument 1:-------- -------- -------- --------
 type encoding (q) "q"
 flags {issigned}
 modifiers {}
 frame {offset=8, offset adjust=0, size=8, size adjust=0}
 memory {offset=0, size=8}
 argument 2:-------- -------- -------- --------
 type encoding (@) "@" nsdictionary ""
 flags {isobject}
 modifiers {}
 frame {offset=16, offset adjust=0, size=8, size adjust=0}
 memory {offset=0, size=8}
  class "nsdictionary"
 argument 3:-------- -------- -------- --------
 type encoding (b) "b"
 flags {}
 modifiers {}
 frame {offset=24, offset adjust=0, size=8, size adjust=-7}
 memory {offset=0, size=1}

note, double quotes in the string need to be escaped.

For our most useful type encoding field, the corresponding explanation of these symbols can refer to the official documentation of type encoding.

So, in summary:this method has no return value,It accepts four parameters,The first is a block (a reference to our own block), the second is of type (long long), the third is an nsdictionary object, and the fourth is a bool value.

Finally, we got the function parameters for this block.The full version of the method signature originally mentioned is:

-(void) fm_getsubscribelist:(long long) arg1 pagesize:(long long) arg2 callback:(void (^) (long long, nsdictionary *, bool) arg3;

summary

Because I want to use real examples for demonstration,Therefore, this article uses the reverse dynamic analysis directly for illustration.Actually all the processes mentioned above,You can do it directly in xcode through your own code.By doing it yourself,More effective than reading ten articles.If someone asks about the block implementation and memory model next time,You can talk to it.

ios
  • Previous iOS custom back button retains system sliding back function
  • Next Docker explained in detail setting up a container firewall
  • Trends