Skip to content

Instantly share code, notes, and snippets.

@johnzaro
Last active November 26, 2023 13:15
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save johnzaro/fdffd93c2ee0bc7db56d7959a67b4235 to your computer and use it in GitHub Desktop.
Save johnzaro/fdffd93c2ee0bc7db56d7959a67b4235 to your computer and use it in GitHub Desktop.
Guide to using libundirect and finding direct methods on iOS 14
(I used hopper disassembler)
1) Open the Safari binary from iOS 13.7 [binary 1] and also open a Safari binary from iOS 14+ [binary 2] which has the selector you are interested in converted to direct method.
2) Search in the processes tab of the [binary 1] for the selector you are interested in.
3) Open it and have a look at the code inside it by using the 'pseudo-code mode'.
4) We now arrive to the 1st way to find a direct method:
4a) Look at the selector (step 3) and check to see if it uses any ivar inside it.
If it uses any ivar try to search that ivar in the [binary 2] by using the 'Labels' tab and if you find it there, check the 'DATA XREF=...' section to see in which selectors you can find that ivar (we are interested for methods with names similar to sub_123456789).
Hopefully one of them is the one you are looking for and you can check which one is the correct by searching their names in the search of processes tab and looking the implementation of each one by using the 'pseudo-code mode'.
If the selector on [binary 1] doesn't use any ivar,
or the ivar it uses cannot be found in the newer binary,
or it can be found but none of the implementations of the methods in which it exists look similar to the selector from [binary 1] then you have one more similar way to find the method you are looking for.
4b) Look at the selector (step 3) and check to see if it uses any other selectors inside it.
If you can find any copy its name and paste it in the search of 'String' tab of the [binary 2] and see if that selector name still exists or it has been converted to direct method too.
If you can find it click it and check the 'DATA XREF=...' section again to see in which selectors it is used (we are interested for methods with names similar to sub_123456789).
Hopefully one of them is the one you are looking for and you can check which one is the correct by searching their names in the search of processes tab and looking the implementation of each one by using the 'pseudo-code mode'.
If You still haven't found the method you are looking for:
5) Use the 'Assembly Mode' to select all of the lines of the selector [binary 1].
6) Change to 'Hexadecimal Mode' and use 'Edit' -> 'Copy hex string...' to copy all the bytes of the selector.
7) Paste them in a txt and add new line every 4 bytes to make the bytes more readable and comparable.
An example of that would be:
...
F8 5F BC A9
F6 57 01 A9
F4 4F 02 A9
FD 7B 03 A9
...
8) Try to select 2 or 3 lines of bytes (8 or 12 bytes) from that txt randomly, paste them in a separate file, remove the spaces and new lines between them and copy them.
9) Use the 'Hexadecimal Mode' in [binary 2] and open find menu with cmd+F, make sure its in the 'Hexadecimal Value' type, paste the bytes there and press enter to search.
There are 2 possible cases now:
10a) It cannot find those bytes anywhere in the [binary 2] so just repeat steps 8 and 9.
10b) It finds those bytes somewhere in the binary. In that case:
11) Open again the find menu and press enter again to check if those bytes exist in another area too.
11a) If they exist in multiple places just repeat steps 8 and 9.
11b) If the bytes only exist in one place inside the [binary 2] then:
12) Open the 'pseudo-code mode' and see how the implementation of that method looks like.
12a) If the implementation looks completely different to the selector of [binary 1] then you probably found a wrong method so repeat steps 8 and 9.
12b) If it looks like the selector from [binary 1] then we found the name of the direct method on that binary take a note of that.
We now have the name of the direct method on our hands.
To make sure that the unique bytes will work in other iOS versions its best to use steps 5 - 7 to make a txt file of the method you have found in [binary 2] and also repeat the process for other binaries in other iOS versions.
Its really easy to find unique bytes that stay unchanged between all iOS version by opening side by side those txt files that contain the bytes of the same method in different iOS versions.
Notes:
1) Make sure to include these 2 imports inside your code:
Add this inside the file that uses libundirect_find, libundirect_rebind etc.
#import <libundirect/libundirect.h> (you need to link your tweak with libundirect in that case so add that lib in your Makefile) OR #import <libundirect/libundirect_dynamic.h> (you DON'T need to link your tweak with libundirect in that case).
Add this in the file you do the %init of direct methods (I forgot to add that and spent way too much time wondering what I am doing wrong).
#import <libundirect/libundirect_hookoverwrite.h>
2) The 1st byte of the methods (which is needed by libundirect_find) is almost always different between the arm64 and arm64e version of the binary and sometimes it also changes between iOS versions so you should pay attention to that too.
3) If the starting byte of the method is repeated between the start byte and the bytes that you have used as unique ones, then libundirect_find will return a wrong pointer pointing to that intermediate byte and not the real starting byte.
That is caused because libundirect_find first tries to find the unique bytes you have provided and then takes steps back until it reaches the starting byte you provide.
the solution to that is to NOT provide the 1st byte but the 2nd or 3rd etc which you will make sure that are unique bytes and not being repeated inside the method.
Then subtract the offset from the 1st byte from the final pointer.
(Hopefully that will be fixed in a future libundirect update).
An example to that is the following (0x5F is the 2nd byte of the method and not the 1st):
methodPtr = (void*)(((intptr_t)libundirect_find(@"MobileSafari", (unsigned char[]){0x12, 0x23, 0x34, 0x45, 0x56}, 5, 0x5F)) - 1);
or (0x4B is the 3nd byte of the method and not the 1st):
methodPtr = (void*)(((intptr_t)libundirect_find(@"MobileSafari", (unsigned char[]){0x12, 0x23, 0x34, 0x45, 0x56}, 5, 0x4B)) - 2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment