preface

Article 10 – application signature, we use two ways CodeSign terminal instructions and Shell script, the successful implementation of the heavy signature WeChat app, can already see WeChat login UI levels of registration page, and then, we want to do your own thing, such as injection of your own code, modify the WeChat register or login process.

Code injection

Generally, the original program is modified by code injection. The injected code will choose to use the Framework or third-party libraries such as Dylib to inject.

1.1 FrameWork into

We know that after re-signing app, the shell project code will be replaced (replaced by MachO level 2 executable file), so the original project code will not be executed. IOS system loads the MachO executable through dyLD (dynamic linker provided by iOS system), and the loading process begins with the Load Commands of MachO (including _PAGEZERO, _TEXT, _DATA, _LINKEDIT, etc.) πŸ‘‡

In addition to executing its own engineering code, the App also relies on some system base libraries (UIKit, Foudation, etc.) and third-party libraries (.framwork,.A library, etc.). These libraries are ultimately a MachO executable. Analyzing the MachO file, you will find that the Frameworks library exists in Load Commands as LC_LOAD_DYLIB, and the path to the corresponding Frameworks library is πŸ‘‡

As long as the Load Command has the library LC_LOAD_DYLIB, dyld will Load the library in the corresponding path.

With this in mind, the process of code injection is clear πŸ‘‡

  1. Confirm the target AppMachOExecutable fileLoad CommandsThere is a corresponding inLC_LOAD_DYLIB
  2. Write your own code to the dynamic library

1.1.1 Injection Procedure

  1. Add one to your shell projecttarget πŸ‘‰ LGHook FrameworkThe dynamic library

⚠️ Note: The shell project here uses the shell script re-signing pattern from the previous article 10- Applying re-signing.

  1. LGHookaddLGInjectClass, and override+loadmethods

If LGHook is loaded into memory, log is printed.

  1. runTo see theProducesIn the.app

The dynamic library LGHook is already in the Framewroks of the target App, then we look at MachOπŸ‘‡

  1. Look at the MachO file

LGHook LC_LOAD_DYLIB does not exist in Load Commands, so run does not print logs in the console.

1.1.2 Yololib manual injection

At this point, you need to modify the MachO file using yololib tool to actually add LGHook to the Load Commands of the target App πŸ‘‡

  1. copyyololibAnd the target AppExecutable filetoThe same directoryπŸ‘‡ (A separate newA folder)

Then run πŸ‘‡

./yololib The name of the Framework path to add to the target executable

For example πŸ‘‡

./yololib WeChat Frameworks/LGHook.framework/LGHook
Copy the code

Look again at the executable file Load CommandsπŸ‘‡

This is where LGHook comes in.

  1. takeOriginal. Ipa package, decompress, inPayloadFound in the folder.appRight,Display package contentsTo replaceThe previous stepThe generatedBinary MachO file, back toPayloadIn the directory, enter the following command to generate.ipapackage
zip -ry xx.ipa Payload/
Copy the code

⚠️ Note: This step must replace the binary MachO files in the original.ipa package.

  • The original packageWeChat 8.0.2. Ipa, change the suffix to.zip, decompression πŸ‘‡

  • Right clickDisplay package contentsπŸ‘‡

  • Replace the one from the previous stepWeChat Mach-OπŸ‘‡

  • Return to thePayloadIn the directory, enter the commandGenerated. Ipa packageπŸ‘‡

  1. Put the. Ipa package generated in the previous step into the APP folder πŸ‘‡

CMD +shift+k clear the XCode project cache, then runπŸ‘‡

Solve πŸ‘‡

Modify the lowest version supported by lghook. framework.

The run again πŸ‘‡

And you’re done! 🍺 🍺 🍺 🍺 🍺 🍺 🍺 🍺 🍺

summary

The above steps are roughly πŸ‘‡

  1. New shell projectWeChat, the configurationRe-signing script
  2. Create a new one with XcodeLGHook.framwork(noteChange the supported version), run the real machine, install the library into the APP package
  3. throughyololibforWeChattheMachOinjectionLGHook.framworkThe library path. Command πŸ‘‰$./yololib MachO file path Library path
  4. Use the original wechat. Ipa package that will be generated in step 3MachO, replace the original package insideMachO, again packaged to generate.ipapackage
  5. Shell projectThe APP folderReplace the one generated in step 4The ipa package, run directly.

All Framwork loading is performed by DYLD loading into memory. The successfully injected library path is written to the LC_LOAD_DYLIB of the MachO file.

1.2 Dylib injection

In addition to Framwork injection, Dylib injection is also available. The principle is the same as Framwork, the process is similar to Framwork.

  1. Create the dylib library. So I chosemacOS, for the sake of libraryThe dynamic libraryπŸ‘‡

Modify theBase SDKforiOSπŸ‘‡

The Code Signing Identity is changed to iOS DeveloperπŸ‘‡

Add Copy Files to build Phases and add libLGDylibHook. Dylib πŸ‘‡

  1. LGDylibHook. M add Hook code πŸ‘‡
+ (void) load {NSLog (@ "LGDylibHook Success 🍺 🍺 🍺 🍺 🍺 🍺 🍺 🍺"); }Copy the code

Run, at this point, is still justFrameworksIn theHave libLGDylibHook dylibLibrary,MachOIn theLoad CommandsstillThere is no LC_LOAD_DYLIB.

1.2.1 Yololib automatic injection

  1. Directly in theappResign.shAdd to the scriptyololibtheModify the MachOScript instructions πŸ‘‡
# modified yololib MachO file #. / yololib "$TARGET_APP_PATH / $APP_BINARY" "Frameworks/LGHook. Framework/LGHook". / yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libLGDylibHook.dylib"Copy the code

Place the Yololib library in the project directory πŸ‘‡

  1. To observe thelibLGDylibHook.dylibWhether inFrameworksIn πŸ‘‡

The Load in the MachO Commands πŸ‘‡

  1. run

⚠️ Note: you need to compile the library libLGDylibHook. Dylib first, and then compile the shell project

summary

Injection steps

  1. Create a new one with XcodedylibLibraries (note: Dylib belongs toMacOS, so needModify the properties)
    • Base SDKforiOS
    • Code Signing IdentityModified toiOS Developer
  2. addThe Target depends onWill, let XcodeCustom DylibfilePackage into APPpackage
  3. Yololib was used for injection

Second, the example demonstration

Next, we practice, for wechat registration and login page, code HOOK registration and login process. First of all, register. We look at the UI level of the register button πŸ‘‡

Above, print the addresses, the Target is WCAccountLoginControlLogic found the sign up button, the action is onFirstViewRegister.

We then hook this method directly to intercept the registration event πŸ‘‡

#import "LGDylibHook.h" #import <objc/runtime.h> @implementation LGDylibHook + (void)load { NSLog(@"LGDylib Hook success"); / / intercept WeChat register event Method oldMethod = class_getInstanceMethod (objc_getClass (" WCAccountLoginControlLogic "), @selector(onFirstViewRegister)); Method newMethod = class_getInstanceMethod(self, @selector(hook_onFirstViewRegister)); method_exchangeImplementations(oldMethod, newMethod); } - (void)hook_onFirstViewRegister { NSLog(@"WeChat click login"); } @endCopy the code

Run it πŸ‘‡

Successful intercept! Next, we want to intercept the event, execute our own code, and then restore the original flow. How do we do that? I’m sure you can all think of πŸ‘‰ Method Swizzle.

Method Swizzle

In OC, the relationship between SEL and IMP is like the table of contents of a book. SEL is the method number, just like the title. IMP is the real address of the method implementation, just like the page number. There is a one-to-one correspondence. The Runtime provides a function to swap SEL and IMP mappings.

OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
   OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Copy the code

The technique of swapping SEL and IMP mappings using this function is called Method Swizzle.

2.2 Login Debugging Analysis

Next, let’s debug the [login] function πŸ‘‡

Above knowable, view the debug analysis can get Target is WCAccountMainLoginViewController, the action is onNext. Similarly, [login] events can be intercepted, so how to get PWD?

View debug can see that the PWD control is a WCUITextField and see the corresponding text (password). How do we get the password without view Debug?

2.2.1 Dynamic analysis

The first approach is dynamic analysis πŸ‘‰, through which control hierarchies and response relationships can be analyzed step by step through response chains πŸ‘‡

Combined with Presponder and Pviews analysis. But this way is generally more troublesome.

2.2.2 Static Analysis

πŸ‘‰ use class-dump to export headers (⚠️swift files do not work).

./class-dump -H WeChat -o ./Headers
Copy the code

After execution, all header files are in the Headers folder πŸ‘‡

Too many files 22335, not recommended to open Xcode.

  • searchWCAccountMainLoginViewControllerTo findonNextπŸ‘‡

The onNext method takes no arguments and returns no values. See if there are any other passwords associated with πŸ‘‡

We noticed that _textFieldUserPwdItem is the password field. 2. Search WCAccountTextFieldItem

WCBaseTextFieldItem, WCBaseTextFieldItem, WCBaseTextFieldItem

Found the m_textField member variable of type WCUITextField. This is not the input account password control.

  1. Debug verification

Next let’s verify, is it a password control? πŸ‘‰ KVC console print view πŸ‘‡

Related instructions πŸ‘‡

(lldb) po [(WCAccountMainLoginViewController *)0x117813000 valueForKey:@"_textFieldUserPwdItem"]
<WCAccountTextFieldItem: 0x2830f9ec0>

(lldb) po [(WCAccountTextFieldItem *)0x2830f9ec0 valueForKey:@"m_textField"]
<WCUITextField: 0x1120a4400; baseClass = UITextField; frame = (20 0; 345 44); text = 'qwer1234'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x2818beca0>; layer = <CALayer: 0x281441dc0>>

(lldb) po [(WCUITextField *)0x1120a4400 text]
qwer1234
Copy the code

Sure enough, I found the corresponding class and the desired password. 🍺 🍺 🍺 🍺 🍺 🍺 🍺 🍺

2.3 Login code injection

⚠️ Note: do not use your common account to try, may be blocked.

Finally, and importantly, inject your own code πŸ‘‡

+ (void)load { NSLog(@"LGDylib Hook success"); / / intercept WeChat login event Method oldMethod = class_getInstanceMethod (objc_getClass (" WCAccountMainLoginViewController "), @selector(onNext)); Method newMethod = class_getInstanceMethod(self, @selector(hook_onNext)); method_exchangeImplementations(oldMethod, newMethod); } - (void)hook_onNext { NSLog(@"WeChat click login"); UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];  NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); }Copy the code

Next, in the process we should call back the original method and let the login proceed πŸ‘‡

- (void)hook_onNext { NSLog(@"WeChat click login"); UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];  NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); [self hook_onNext]; }Copy the code

Run πŸ‘‡

Error: Unable to find method! Are not present in the WCAccountMainLoginViewController hook_onNext method. Because πŸ‘‡

Normally we do method swaps in categories, not in categories.

Is there a solution? Of course, there are three kinds πŸ‘‡

Class_addMethod way

Use AddMethod method, so that the original method can be called, not because can’t find SEL and crash πŸ‘‡

/ / 1. Class_addMethod way + (void) load {/ login/intercept WeChat event Class CLS = objc_getClass (" WCAccountMainLoginViewController "); Method onNext = class_getInstanceMethod(cls, @selector(onNext)); // add a new method to WC class_addMethod(CLS, @selector(new_onNext), new_onNext, "v@:"); // Swap method_exchangeImplementations(onNext, class_getInstanceMethod(CLS, @selector(new_onNext))); Void new_onNext(id self, SEL _cmd) {// Get password UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); [self performSelector:@selector(new_onNext)]; }Copy the code
Class_replaceMethod way

Replace IMPπŸ‘‡ directly with the original method, using class_replaceMethod

/ / 2. Class_replaceMethod way + (void) load {/ login/intercept WeChat event Class CLS = objc_getClass (" WCAccountMainLoginViewController "); Origin_onNext = class_replaceMethod(CLS, @selector(onNext), new_onNext, "v@:"); } // original imp imp (*origin_onNext)(id self, SEL _cmd); Void new_onNext(id self, SEL _cmd) {// Get password UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); origin_onNext(self,_cmd); }Copy the code
Method_setImplementation way

Using method_setImplementation, reassign the original IMPπŸ‘‡ directly

/ / 3. Method_setImplementation way + (void) load {/ login/intercept WeChat event Class CLS = objc_getClass (" WCAccountMainLoginViewController ");  OnNext = class_getInstanceMethod(CLS,@selector(onNext)); //get origin_onNext = method_getImplementation(onNext); //set method_setImplementation(onNext, new_onNext); } // original imp imp (*origin_onNext)(id self, SEL _cmd); Void new_onNext(id self, SEL _cmd) {// Get password UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); origin_onNext(self,_cmd); }Copy the code

The previous three methods can successfully return to the original login process. You can check for yourself.

Demo(including wechat. Ipa original package)

XFResignHook

⚠️ Note: wechat IPA package is too large to upload gitHub, if necessary, please leave a message to send email or private message to me.

conclusion

  • Code injection: Framework (recommended)/dylib
    • createFramework/dylib
    • yololibModify theMacho Load Commands
      • ./yololib Executable file to modify Framework/dylib path & name to add
  • Debug analysis
    • dynamicDebug:view debugdebuggingThe control levelIn combination withpresponderandpviews
    • staticAnalysis: byclass-dumpexportThe header fileAnalyzing code logic
  • Code Hook
    • + loadMethod corresponding method of hook corresponding class
    • hookMethod is called back to the original method
      1. class_addMethodπŸ‘‰πŸ» allows the original method to be called without the risk ofFailed to find SEL and crashed
      2. class_replaceMethodπŸ‘‰πŸ» gives the original methodReplace the IMP
      3. method_setImplementationπŸ‘‰ 🏻To assign a valueThe originalIMP

A link to the

Github AloneMonkey/MonkeyDev