11package machomap
22
3+ import "errors"
4+
35// Helpers for driving the target after a map: calling an exported function, and
4- // checking whether a class is already registered (used to keep injection
6+ // asking a class that may already be registered (used to keep injection
57// idempotent — mapping the same image twice fails on duplicate class pairs).
68
9+ // ErrClassMethodNotFound means the class is registered but lacks the class method.
10+ var ErrClassMethodNotFound = errors .New ("class method not found" )
11+
12+ var classMethodHelpers = []string {
13+ "mmap" , "objc_getClass" , "sel_registerName" , "class_getClassMethod" , "method_getImplementation" ,
14+ }
15+
716// CallFunction borrows a thread and calls fn(args...) once, returning x0. Suitable
817// for simple exported getters/setters; it uses the default return breakpoint, so
918// callers should not use it for re-entrant functions (Map sets up a trampoline
@@ -17,36 +26,63 @@ func CallFunction(c *RSP, fn uint64, args ...uint64) (uint64, error) {
1726 return t .Call (fn , args ... )
1827}
1928
20- // ClassIsRegistered reports whether objc_getClass(name) is non-nil in the target.
21- func ClassIsRegistered (c * RSP , res Resolver , name string ) (bool , error ) {
22- h , err := res .Resolve ([]string {"mmap" , "objc_getClass" })
29+ // CallClassMethod calls +[class selector] in the target and returns x0. The second
30+ // result is false when the class is not registered.
31+ func CallClassMethod (c * RSP , res Resolver , class , selector string ) (uint64 , bool , error ) {
32+ h , err := res .Resolve (classMethodHelpers )
2333 if err != nil {
24- return false , err
34+ return 0 , false , err
2535 }
2636 t , err := NewTarget (c )
2737 if err != nil {
28- return false , err
38+ return 0 , false , err
2939 }
3040 defer func () { _ = t .Restore () }()
3141 scratch , err := t .Call (h ["mmap" ], 0 , 0x1000 , protRead | protWrite , mapAnon | mapPrivate , neg1 , 0 )
3242 if err != nil {
33- return false , err
43+ return 0 , false , err
3444 }
35- if err := c .WriteMem (scratch , append ([]byte (name ), 0 ), 0 ); err != nil {
36- return false , err
45+ names := append (append ([]byte (class ), 0 ), append ([]byte (selector ), 0 )... )
46+ if err := c .WriteMem (scratch , names , 0 ); err != nil {
47+ return 0 , false , err
3748 }
38- // objc_getClass can wait on a lock until every thread runs, and the default
49+ // the objc runtime can wait on a lock until every thread runs, and the default
3950 // return breakpoint sits in shared code that another thread could then hit
4051 landingPage , err := t .Call (h ["mmap" ], 0 , 0x1000 , protRead | protExec , mapAnon | mapPrivate , neg1 , 0 )
4152 if err != nil {
42- return false , err
53+ return 0 , false , err
4354 }
4455 if err := t .SetLanding (landingPage ); err != nil {
45- return false , err
56+ return 0 , false , err
57+ }
58+ return callClassMethod (t , h , scratch , scratch + uint64 (len (class ))+ 1 )
59+ }
60+
61+ // callClassMethod looks the method up before calling it, because sending an
62+ // unknown selector would crash the app.
63+ func callClassMethod (t * Target , h map [string ]uint64 , className , selectorName uint64 ) (uint64 , bool , error ) {
64+ cls , err := t .Call (h ["objc_getClass" ], className )
65+ if err != nil || cls == 0 {
66+ return 0 , false , err
67+ }
68+ sel , err := t .Call (h ["sel_registerName" ], selectorName )
69+ if err != nil {
70+ return 0 , false , err
71+ }
72+ method , err := t .Call (h ["class_getClassMethod" ], cls , sel )
73+ if err != nil {
74+ return 0 , false , err
75+ }
76+ if method == 0 {
77+ return 0 , true , ErrClassMethodNotFound
78+ }
79+ implementation , err := t .Call (h ["method_getImplementation" ], method )
80+ if err != nil {
81+ return 0 , false , err
4682 }
47- cls , err := t .Call (h [ "objc_getClass" ], scratch )
83+ value , err := t .Call (implementation , cls , sel )
4884 if err != nil {
49- return false , err
85+ return 0 , false , err
5086 }
51- return cls != 0 , nil
87+ return value , true , nil
5288}
0 commit comments