In the last article we talked about using shell scripts to re-check and debug other people’s apps, so why do we re-check and attach debugging to other people’s apps? Is it too full? Of course not… Our next task is code injection

What code does iOSAPP execute?

Before we start code injection, let’s take a look at what code is executed when an iOS APP is running, and where do we start to inject code

  1. MachO(binary of APP, all of the code we wrote will be here, it will be covered in a later article)
  2. Framework(can be written by us or third-party code)
  3. System library (system supplied)

We can’t change the system library on a non-jailbroken phone. We can modify MachO, but it is troublesome. We need to be able to write assembly. The Framework is the easiest one for us to start with, we just write one ourselves…

So now the question is how does someone else’s APP implement the Framework that we wrote?

The MachO file has a Load Commands section, and DYLD reads this Load The contents of Commands are loaded into memory. If we can plug our own Framework into someone else’s MachO Load Commands, then our code will be executed naturally

So now the question becomes how do we plug our Framework into someone else’s App’s MachO file?

Yololib this is a terminal command line tool, is used to write the Framework into the MachO file, code is not much about 200 lines, interested can look at the source, use is yololib parameter 1 Parameter 2, where parameter 1 is the MachO file and parameter 2 is the path of our Framework relative to the MachO file. To facilitate the use of this tool, it is recommended to put it in the system’s usr/local/bin directory, remember to give it executable permission (terminal chmod +x yololib), so that we can open any terminal can use this command, also easy to use the script; All right, now we’re done, we just need to do it… Remember from the last article, using shell script to re-sign APP, we can continue, or create a new project to perform the following code injection, but the premise is still complete shell script re-sign APP steps; I’ll create a new project here to demonstrate the code injection that follows

Steps for code injection

The new Framework

After completing the steps of the previous shell script re-checkout, let’s first create a new Framework and get our Framework readyJust call the Framework FrankyHook(it doesn’t matter, but you’ll need to remember it when you use Yololib), create a CodeInject class inside it (it doesn’t matter, inherits NSObject, mainly load), and create a lo The AD method prints some content

Add code to the script

Remember the name of the Framework in the red box, you created it yourself… (If you copy from me when I didn’t sayOne minor detail is that your scripts must be run before the Frameworks are embedded, otherwise your Framework will be deleted every time the scripts are run… (I just added the Framework path you wrote to MachO with yololib. If the script is put at the end of MachO, it will immediately kill your Framework. The image not found will not work.)To be honest, I’ve been doing iOS development for more than four years, and I didn’t know this place could drag until today.

Command + R looks at the console output

So, do you find that we can already execute our code in WeChat? Let’s implement two small requirements

Some knowledge points you need to have

For those of you who are familiar with Objective-C rumTime, skip this section and move on to the next section

Method Swizzle Method swap

The Runtime provides some APIS to implement method swapping, but suppose we have this code:When NSURL initializes a URL, if the URL contains Chinese, the initialization fails and returns nil. The students who worked in iOS development should be met, the solution is to a URL percent encoding, now suppose a scene, you have just hired a new company, found the problem, and then search in engineering, I found my day, everywhere is with Chinese in the URL, and without coding, so this time So, do you choose to change it one by one, or do you think of a better way?

It is better to use method exchange, create a new class of NSURL, in the load method of the class, implement our method exchangeAn OC method can be divided into two parts, one is the method name SEL, and the other is the method implementation IMP. Normally, the name of a method corresponds to its implementation, and sometimes we swap the method implementation through runtime, as shown aboveloadThe code inside the method, the default methodoneAnd methodstwoImplementations are all pointing to their own IMP, throughmethod_exchangeImplementations()After the function swap, the methodoneThe implementation of thetwoThe implementation, while the methodtwoThe implementation of theoneWhen the code is calledURLWithString:It will come to usHK_URLWithString:Method when the code callsHK_URLWithString:“Will be executedURLWithString:Methods; So all of the original projectURLWithString:Method, can perform to our code logic, the first call an initialization of an original URL of the method, the success of the URL, if is nil, it means we may need to the encoding of the string STR, we’ll use after encoding STR initializes the URL, so you don’t have to waste time and energy to one by one to modify the generation in the engineering The code

Block registration clicks on wechat

Use Debug View Hierarchy to View the View Hierarchy of wechat’s login and registration page, find the registered button, and View the target and action of the buttonWe know that when the register button is clicked, it callsWCAccountLoginControlLogictheonFirstViewRegisterMethod, and we can now execute our injection code in our Framework, so how do we implement the interception of wechat registration button click? This can be done using Method Swizzle

Before we start writing the code to implement the method exchange, there is a small problem. Although we know from experience that the onFirstViewRegister should be an object method, probably with no return value and no parameters, this is just an educated guess… We programmers should be more reliable, so how to verify our guesses?

Class-dumpandyololibUsr /local/bin is a terminal command line tool that can be used to export MachO headers from MachO files. This tool can be used to export MachO headers from MachO files

class-dump -H WeChat -o ./headers/

Direct all its headers files to a headers folder. This process takes a while, so be patientThis file is quite large. After the guide, we can cut it to the project root directory, which can save some space for our mobile phones. Indeed, there is no need to put it in the APP packageAs you can see, there are 15,074 items in it, which is quite a lot of header files on wechat… Folders of this size would be very painful to open with Xcode, sublime… It’s a little lighter, and it’s faster to find the header file, as shown hereSo that makes sure of thisonFirstViewRegisterThe method has no return value and no arguments, so you can start writing code to intercept it

Now we can go to our Framework’s CodeInject. M file and write some code

This is the code. Now when Command + R is running, click the wechat registration button again.

Steal user account passwords

The above requirements only break the functionality, making the original functionality unusable; The next requirement is that the user’s account password be stolen without anyone knowing; So when do we get the user’s account and password, of course, when we click on the login button, so you know, using viewDebug to see the login buttonLogin button click target:WCAccountMainLoginViewController,action:onNext;

The onNext method has no arguments. Where is the password we need? Haha, those of you who have done development may find these two names strangely familiar

WCAccountTextFieldItem *_textFieldUserNameItem;
WCAccountTextFieldItem *_textFieldUserPwdItem;
Copy the code

The variable names are familiar, but this oneWCAccountTextFieldItemWe don’t know what it is. What do we do? What if I already have the header file? Keep searching…

WCAccountTextFieldItemIt doesn’t look like there’s anything in there, so look at its parent classWCBaseTextFieldItem WCBaseTextFieldItemOne was found in the classWXUITextFieldThings that are familiar to usUITextFieldLooks like it. We think it might be this onem_textFieldWe have what we want, so we don’t have to write code

Now we can debug again using the viewDebug tool to see where our account and password are and use LLDB to dynamically debug and verify our guessFirst, the address of the controller can be obtained from the target of the login button. After obtaining the controller, the KVC method is used to obtain the member variables_textFieldUserPwdItemThe password can be obtained, the account number is the same operation) and print it; Access to the_textFieldUserPwdItemAfter the address of, use the KVC method again to get its member variablesm_textFieldAnd print the object, good boy, the plain text password is here!!

Next we implement the requirements in code:This code looks similar to the code used to intercept wechat registration, so let’s run command + R to see the resultWTF? The account password is indeed obtained, but the APP is broken? Why did the crash occur? The crash message is a classic error that we often encounterunrecognized selector sent to instance 0x10b15b400That’s the controllerWCAccountMainLoginViewControllerUnable to identifyFK_onNextThis method;

For a moment, why is it recommended to write method exchanges in the same category as the classes that want to exchange methods? Because in the classification of the classes that want to swap methods, we will add a new method to implement our logic, and because it is in the classification, so the current class also added our new method, so that the exchange will not be unable to find the method error; And the above code, our intention is also hope in WCAccountMainLoginViewController our logic, a new method for processing and exchange onNext method, but the question now is whether we in CodeInject load method of this class, so is there any way to solve What about this problem?

Dynamic addition method

BOOL class_addMethod(Class _Nullable CLS, SEL _Nonnull NAME, IMP _Nonnull IMP, Const char * _Nullable types) parameter 1: to which class method parameters are added. 2: method name parameter 3: method implementation parameter 4: description of method parameters and return value

So what solution can we come up with using this API? HereWCAccountMainLoginViewControllerController adds ourFK_onNextMethod, letFK_onNextandonNextMethods are swapped, and the final code looks like thisAfter command + R is run again, it is found that the account password entered by the user can be successfully obtained, and the login logic of wechat can be successfully invoked

Dynamic substitution method

Now, what else can we do besides add new methods? IMP _Nullable class_replaceMethod(Class _Nullable CLS, SEL _Nonnull name, IMP _Nonnull IMP, const char * _Nullable types) parameter 1: which class parameter to replace 2: method name parameter 3: method implementation parameter 4: method parameter and return value description, with some special symbols, or can not write the return value: IMP, the original method implementation

If we use this approach, then we should be rawonNextI’m going to replace it with our methodFK_onNextMethod, so how do we call wechat originalonNextAnd the method is to take the original before you replace itonNextMethod implementation IMP is recorded and then in ourFK_onNextMethod uses the IMP of this record to implement the originalonNext, the specific code is as follows:Comparing the code implementation of method interchange, we find that method substitution requires one more variableoriginalIMPTo record the originalonNextMethod, but also with a warning, although there is a little bit of missing code, but also not so easy to understand… Of course, using method substitution is just a matter of learning about the runtime API and getting a feel for the power of RumTime. It’s up to you to decide which method to use

Dynamic access method implementation and set method implementation

So, is there a sluttier operation? Of course, there are…

IMP _Nonnull method_getImplementation(Method _Nonnull m)

Obtain method M implementation IMP

IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)

Set method M to implement IMP

The principle is not so different from substitution, you get the original firstonNextImplement and record, and then set the new IMP(we wroteFK_onNext) toWCAccountMainLoginViewControllertheonNextMethod, the specific code is as follows:

Ok, so here we have used three methods provided by the Runtime to steal the user’s account password… It’s really gone at this point

The next article begins with the MachO file we’ve been talking about lately, what a MachO file is, what it contains, what it does…