
Breaking the iOS app! Part 2
- Transfer
- Tutorial
In the first part we examined some of the issues of storage and transmission of the security data . Now we turn to the protection of the executable code . We will modify the functionality of the iOS application at runtime and do reverse engineering. And again, remember! Our goal is not to become an ugly cracker, but to protect your application and users from malicious actions. To do this, you need to understand what a cracker can do.

To successfully complete this lesson, you must understand what assembler is. The author of the article advises you to go through the tutorial on ARM (in English).
In fact, in order to understand the meaning of the lesson, the level of necessary knowledge is a couple of minutes to google along the way. Well, near the end of the article, you decide whether you need to learn assembler or not. :) - Note. trans.
We will need:
I hope you learn a lot about these tools!
In the previous series, we modified the .plist files to change the balance in your account. Now let's see how to manipulate variables and methods right at runtime (what is called runtime). To do this, use the LLDB debugger.
Open the main bundle folder (
We occupy the starting position: the simulator is running, the application is installed, but not running.
In the terminal, type:
The debugger is running, great. On the next line we see an invitation from him: We type the
command for the debugger:
I will not write characters
The command
So the debugger is waiting. Let's move on to the iOS Simulator and perform the traditional (in the last part of the lesson) uninstalling the application from multitasking, and then restarting it (launching it from the simulator, not from the IDE) - hereinafter we will call it “restart”.
If everything is done correctly, LLDB will begin tohave fun together with the process in the simulator. The debugger will connect to the process, pause its execution and say:
And an invitation to enter a new command:
Let's add a breakpoint before each ViewController is displayed. This is exactly the place where a lot of interesting things usually happen. Often, a significant part of the application logic is determined there. For example, let's add a breakpoint to each method call
Run in terminal:
Method names are case sensitive, so the option
This sets breakpoints on all methods that are called
So LLDB tells us that he found 15 suitable places for breakpoints:
Excellent. Let's see where he put them. Enter the command:
(This is short for
Well, here they are:
In fact, it is clear here that we need to leave only one breakpoint:
Let's get back to launching our application! Enter the command:
which in full version looks like
“And now ... e-yer ... it’s time for us to have fun: otherwise I don’t play!”

We stopped the process on the frame of the class
Yes, singletones. If you carefully studied this point in the first part , you might notice a “interesting” class called
Type in our
We called the method! The debugger will output the result of execution:
If we saw the answer
LLDB also repeats the previous command by pressing Enter. Therefore, press Enter several times to rob MikhailMikhalkov a little more :
Purchasingfree content has never been so easy! Now enter the command a couple of times
... so that all the breakpoints that we set up end and evaluate the result in the simulator:

Not bad, right? Well, let's see what we can do about it.
To pause the application and return to the command line again, switch to the terminal and press Ctrl + C there. The LLDB debugger is ready to execute our commands again.
Let's finish the debugging session for now: enter the command
We return to the side of the developer. Is it possible to outwit those who want to manipulate your application through the debugger?
Fortunately, there is a way to find out if the debugger is connected to our code! But there is one problem. This check determines if the debugger is connected at this particular time. A hacker (cracker, cheater, ...) can connect to the application after this check, when the application is no longer aware of the danger. This problem can be solved in at least two ways:
The first option is usually undesirable. Its price is a waste of precious processor timefor heating the device . Let's go the second way.
One elegant solution is to check for debugger activity in a singleton
Well, finally, let's work with the code! Open our project (in your favorite IDE or in Xcode) and go to the MoneyManager.m file. Here's what we will do: add a preprocessor macro that will check in which configuration our application is built, and if it is Release, it will check whether the debugger is running. If launched, will return
Add 3 lines to the top of the
Now the method should look like this:

Please note: this macro is only available in the Release configuration. If you followed us in the first part, you should already have switched to the release.
Now start our application from the IDE (didn’t forget to choose the Release configuration?)
Xcode: Run (⌘R)
AppCode: Debug (Ctrl + D)
Xcode automatically connects the LLDB debugger when choosing the Run command. Result: account balance is not displayed! Indeed, somewhere there

And in AppCode there are two different commands: the Run command does not connect the debugger, and the Debug command connects. Conveniently.
To finally make sure that our protection works, check: can you buy something now?
Stop the application by clicking the Stop button in the IDE(with a square). Also stop the LLDB debugger. Switch to the simulator and run the application from there. The application will display the currency, as debugger is not connected.
As we already said, you can connect the debugger to the process not only at startup, but generally at an arbitrary point in time. Run in terminal:
The output of this command will contain a list of all processes in the name of which the phrase "Meme Collector" appears:
You can see that the second line corresponds to the application folder in the simulator. Pay attention to the number of this process (second column). In my case, this is the number 2001.
From the terminal, run LLDB with the key
For example, I need to type “lldb -p 2001”.
LLDB will start and report a successful connection to the process:
When LLDB is running, try accessing the singleton
The attempt to “buy” the currency now returns
And try to print a description of the object
And what is in this description?
What was required to achieve! Our singleton does not return at least any intelligible result, nor does it show an error message during the purchase process. Simple and incomprehensible to a cracker
We continue the execution of the application with the command:
Try to legally replenish your account using the "Purchase Currency" button. Nothing will come of it! After all, LLDB is still connected to the process.

Disconnect the debugger from the process: press Ctrl + C and then enter the q command . The Purchase Currency button works again.
In addition to checking for a debugger, you can take a more rigorous approach. The featureto resist the connection of GDB / LLDB to your application whenever possible .
To do this, go back to the IDE and open main.m . Add one header file:
And three lines to the beginning of the function
The function is
Now run the application from the IDE.
Xcode : the application does not seem to start. What's happening? We see for a moment a black screen that immediately disappears - this application is loaded into memory and starts to run. At the same time, Xcode wants to connect LLDB to it, but iOS does not allow it and ends the debugger process. “Once the debugger is complete,” Xcode thinks, “the application has finished, so stop it.” The last phrase sounds wild, but it works that way.- Note trans.
AppCode : with the Run (⌘R) command, the application starts normally, and with the Debug command (Ctrl + D) it crashes like Xcode.
And from the simulator it starts correctly. Try connecting a debugger to it now, as discussed above:
The result is predictable:
This is a good tool to stop young children who have read habr from playing with your application. But that will not stop bearded hackers. They will stop your process when calling a function

In general, do not feel too comfortable. Hackers like to use Cycript - a scripting language (reminiscent of JavaScript) - specifically for manipulating ObjC applications at run time. The debugger protection we made does not protect you from Cycript. Remember how we started the conversation in the previous article:
Before proceeding with the modification of binary files, let's find out how to disassemble it into parts, and what’s what.
I will periodically refer to specific addresses in the binary to illustrate certain concepts. If you do not have the version of the compiler like mine (for example, that comes with the newer Xcode), either you compile the Debug configuration instead of Release, or you yourself made changes to the project - the addresses may be different. This does not bother you - just follow the presentation to understand the idea.
The executable file format in OS X and iOS is called Mach-O . As a rule, a binary starts with a header containing all the information about where and what data is in the binary. This information is followed by download commands.(load commands), which will tell you about marking up the file by segments. In addition, these commands define special flags: for example, whether binary data in the file is encrypted.
In each segment (segment) there is one or several sections (sections). Two types of sections are worth noting:
Apple also has an excellent Mach-O format reference in English. - Note trans.
Now we will examine the binary
This command prints the header of the Meme Collector binary executable. Something like this:
Note: the file has 25 load commands (
(Before that, you can clear the terminal window by pressing ⌘K. It will be more convenient to scroll through. - Note per. )
You will get many, many lines. From these lines (even without preliminary preparation) you can see a lot of interesting things about the order of loading segments and sections into memory. But this study is beyond the scope of this tutorial, let us leave it to the most curious readers for independent study.
And we continue our lesson. Find the ( ⌘F ) section under the name
Here, the section offset
Go to the terminal. Open a new terminal window ( ⌘N ) and from the same folder “Meme Collector.app” do:
The command
Well, what is located at 159942? Class names! (Highlighted in red.) Logically, we were looking for a section

Directly above this section we see a section
Where method names end, class names begin immediately. The section
So, we see how load commands allow you to organize the chaos that the Mach-O binary is. With this knowledge we proceed ... tadaaam! to the modification of the code section.
Are you ready to launch serious guns? Finally, we will learn how to modify the application binary file!
You probably often hear in your life the phrase: the application is “hacked”. This means that someone modified the application so that it works ... mmm ... differently than the developer intended. For example, does not ask to register. Therefore, we (the author and translator) sincerely hope that our work will serve you well. Only to protect your applications.
Download IDA Demo and some HEX editor, for example, Hex Fiend . IDA is a tool that hackers use most often when studying binary. This is an incredibly powerful disassembler, debugger and decompiler. And the full version is not so expensive.
But if you are not ready to buy the program that you heard about 15 seconds ago, IDA offers a demo version with limited functionality. The demo version has limited types of assembler files that can be studied in it. Also, the ability to modify the code is disabled.
But it has our x86 assembler type. And we will do all the modifications manually in another program - Hex Fiend.
Install and run IDA. We are welcomed by Ada Lovelace, the first programmer in the world:

Press the Go button . In the terminal, we (yes, yes) are still in the bundle folder (
Do not forget about the point at the end. The dot symbol here means “current folder”.
Then in the Finder window that opens: right-click> Show package contents :

(OS Mavericks calls the bundle “package” in Russian, but this name seems uninformative to me. - Note per. )
Inside the bundle package you will find an executable file

Indeed, the IDA has determined that this binary is an i386 architecture executable.
Your settings should correspond to those shown above (I think you will not have to change anything) - and click"Let's go!"OK The disassembler will parse the file into small pieces and draw up its scheme (mapping) - what we did above, but ... how to say ... more professionally. :)
If you ask "Objective-C 2.0 structures detected. Do you want to parse them and rename methods? ” - answer Yes. If you ask something about the “proximity view”, answer No.
When the IDA finishes processing the binary file, you will ofcourse come in shock to see the main screen. If the IDA window is not very similar to the one below, then in the left panel, find the function name

(In my case, one space was not enough, I had to press Enter one more time or another. Well, you quickly figure out what's what. ”- Approx.)
And open the project in Xcode or AppCode. In order to reduce the presentation, we will look a little into the code.
Open MoneyManager.m and take a look at the method
Learn the algorithm, it is quite simple. If the instance variable does
If you circumvent this test (“jump over”, in assembler terms) - you could buy anything you like, then the value
Now we find the same code in the disassembler. Go back to the IDA, click on any function in the Functions panel (just to activate this panel) and then press Ctrl + F(or from the menu: Edit> Quick Filter ). An input box appears to search for our function. We need to find

Yeah, find, double-click on the name of the method. The IDA will show the disassembler window, which perfectly demonstrates the conditional statement and code branching:

Even if you don’t know anything fromthe assembler school course , from the source code
I will load you with assembler a little. Let's look at the conditional operator (“jump”), the lowest in the upper block, from which two arrows go - red and green. it
To replace the instruction, you need to find it. Double-click on the operand
Here is the same information, but in a linear form. Find the line number with the command

In my case, the address turned out to be 0x00018D88. Let me remind you, you can have any other address.
The operand code "
Download (if you haven’t downloaded yet) Hex Fiend . Install it, for example, by copying it to a folder
A window will open with our binary. Beautiful, is not it? Here they are - our friends:

Now type in the terminal:
As we saw earlier, it
Here we see the start address of the section (
Excellent! Time to do arithmetic: you need to recalculate the offset of the instruction
So as not to be distracted, I have prepared a formula for you:
{absolute position of the command in a binary file} =
{command address} - {start address of the text section} + {shift of the text section}
In my case:
Command address
Start address of the text section = 0x2970 (from
Shift of the text section = decimal 6512 (from
Take a calculator, switch from the menu: View> For the programmer ... (do not forget to switch to the desired number system when entering decimal and hexadecimal numbers).
I got:
0x18D88 - 0x2970 + 6512 = 0x17D88
If your calculations were correct, this will be the address of the instruction
Hmm, for some reason, the line numbers are in decimal. Well, let's recalculate: 0x17D88 = 97672, i.e. from position 97664 you need to count another 8 bytes to the right. 8 bytes = 16 hexadecimal digits = two 4-byte words. You see, Hex Fiend groups a binary “text” according to the words: We

skip the first two words, and at the beginning of the third word - here it is - our operation code
Correct
Yes, we really threw out the verification of the condition “does the user have money?” Even without money, the transaction is completed. And a small bonus: unsigned value
How do we protect ourselves? Remember, I said: "nothing is safe." This statement also works here. Reverse engineering can be very difficult, but you cannot stop an attacker if he is serious. Your only hope is to confuse the attackers so much that they give up this business and go to break other applications.
One way is to change the names of important classes and methods through a preprocessor. Open the project in the IDE and find the file "Meme Collector-Prefix.pch" in it. Add a line to it:
This code will replace all occurrences of "
This approach should be used with great care so as not to break anything. You need to make sure 100% that the selected new name is not found anywhere else in your application. Otherwise, you will confuse yourself, inexplicable things will begin to happen with the application.
Usually in the executable file there is a symbol table in which the mapping of addresses into readable names of functions and methods is stored. And now, another way to confuse the code is to delete the character table after building the project. This is more suitable for hiding C and C ++ functions, because Objective-C messages are handled by a single function
Open MoneyManager.m again and add the following C function to the beginning:
Then compile the application again. Check the existence of this function in the symbol table. From the terminal:
The command
An easy way to delete a character table from an iOS application is to find two options in the project settings: Deployment Postprocessing and Strip Linked Product, and set them to Yes:

Then you need to “clean” the project (Xcode: Product> Clean or in AppCode: Run> Clean ) and recompile. Then go to the terminal and run the same command:
Fine! We successfully deleted the character that referred to
We made sure that an attacker can:
When creating an application, it is important to remember these things. Think about how much effort you are willing to make to make the application more secure. What is security? It is always a balance between your resources (time), the level of problems for your users, and the probability of hacking.
IOS app security is a serious topic. You can still learn a lot. So far we have only scratched the surface slightly. The whole range of capabilities of the debugger and other analysis tools lies much deeper. If you are interested in this topic, I advise you to think about jailbreaking a test device. The file system will provide rich food for research.
If you have no problems with English, be sure to check out Hacking and Securing iOS Applications(author Jonathan Zdziarski). Although it is slightly outdated (you have to google for changes in the encryption mechanism of Apple applications), but the author of the article is one of his favorite books on iOS and security.
A couple more books:
Hacking: The Art of Exploitation, 2nd Edition by Jon Erickson
Mac OS X and iOS Internals: To the Apple's Core by Jonathan Levin
Forums:
http://www.woodmann.com
http://www.reddit.com / r / ReverseEngineering
Article on injection of code:
http://blog.timac.org/?p=761 The
author can be written in comments , and by translation write to dev

To successfully complete this lesson, you must understand what assembler is. The author of the article advises you to go through the tutorial on ARM (in English).
In fact, in order to understand the meaning of the lesson, the level of necessary knowledge is a couple of minutes to google along the way. Well, near the end of the article, you decide whether you need to learn assembler or not. :) - Note. trans.
Let's start
We will need:
- Test project Meme Collector from the previous part.
- Utility class-dump-z .
- Open source HEX editor for OS X: Hex Fiend .
- The IDA demo is a well-known multiprocessor disassembler and debugger. The demo version has limitations that are not significant for this lesson.
I hope you learn a lot about these tools!
Runtime Manipulation
In the previous series, we modified the .plist files to change the balance in your account. Now let's see how to manipulate variables and methods right at runtime (what is called runtime). To do this, use the LLDB debugger.
In the original article, all examples with GDB, but
after upgrading Xcode to 5.0.1 (the current version at the time of translation), dancing with a tambourine is required to run GDB. Therefore, in order not to burden the tutorial, I redid it according to the command correspondence table between LLDB and GDB . - Note trans.
Open the main bundle folder (
Meme Collector.app
) installed on the iOS simulator in the terminal . If you are at a loss to do this, take a look at the first part . We occupy the starting position: the simulator is running, the application is installed, but not running.
In the terminal, type:
lldb
The debugger is running, great. On the next line we see an invitation from him: We type the
(lldb)
command for the debugger:
I will not write characters
(lldb)
at the beginning of the line so that you don’t mix anything when copyingattach --name "Meme Collector" --waitfor
The command
attach
is used to connect to a specific process. Here we ask LLDB to wait for a new process called " Meme Collector
" to start and connect to it. So the debugger is waiting. Let's move on to the iOS Simulator and perform the traditional (in the last part of the lesson) uninstalling the application from multitasking, and then restarting it (launching it from the simulator, not from the IDE) - hereinafter we will call it “restart”.
If everything is done correctly, LLDB will begin to
Process 1427 stopped
Executable module set to "/Users/dmitriy/Library/Application Support/iPhone Simulator/7.0.3/Applications/9A72F266-8851-4A25-84E4-9CF8EFF95CD4/Meme Collector.app/Meme Collector".
Architecture set to: i486-apple-macosx.
And an invitation to enter a new command:
(lldb)
Let's add a breakpoint before each ViewController is displayed. This is exactly the place where a lot of interesting things usually happen. Often, a significant part of the application logic is determined there. For example, let's add a breakpoint to each method call
viewDidLoad
, since in iOS subclasses are UIViewController
almost always overridden viewDidLoad
. Run in terminal:
b viewDidLoad
Method names are case sensitive, so the option
viewdidload
will fail. This sets breakpoints on all methods that are called
viewDidLoad
(including the C ++ and Objective-C methods). If desired, for specific ObjC selectors you can enter their names, for example -[UIViewController viewDidLoad]
, but note that this option will not work for class descendants UIViewController
. So LLDB tells us that he found 15 suitable places for breakpoints:
Breakpoint 1: 15 locations.
Excellent. Let's see where he put them. Enter the command:
br l
(This is short for
breakpoint list
- if you want, you can write the full version of the command.) Well, here they are:
Current breakpoints:
1: name = 'viewDidLoad', locations = 15, resolved = 15
1.1: where = Meme Collector`-[ViewController viewDidLoad] + 18 at ViewController.m:27, address = 0x0001f482, resolved, hit count = 0
1.2: where = UIKit`-[UIViewController viewDidLoad], address = 0x005d3db5, resolved, hit count = 0
1.3: where = UIKit`-[_UIModalItemsPresentingViewController viewDidLoad], address = 0x0065ab4b, resolved, hit count = 0
1.4: where = UIKit`-[UIKeyboardCandidateGridCollectionViewController viewDidLoad], address = 0x00680729, resolved, hit count = 0
1.5: where = UIKit`-[UIActivityGroupViewController viewDidLoad], address = 0x008d2b6b, resolved, hit count = 0
1.6: where = UIKit`-[UIPrintPanelTableViewController viewDidLoad], address = 0x009be80f, resolved, hit count = 0
1.7: where = UIKit`-[UIPrintStatusViewController viewDidLoad], address = 0x009c8828, resolved, hit count = 0
1.8: where = UIKit`-[UIPrintRangeViewController viewDidLoad], address = 0x009d29ae, resolved, hit count = 0
1.9: where = UIKit`-[_UILongDefinitionViewController viewDidLoad], address = 0x00a10cf4, resolved, hit count = 0
1.10: where = UIKit`-[_UINoDefinitionViewController viewDidLoad], address = 0x00a1249d, resolved, hit count = 0
1.11: where = UIKit`-[UIReferenceLibraryViewController viewDidLoad], address = 0x00a13bd4, resolved, hit count = 0
1.12: where = UIKit`-[_UIFallbackPresentationViewController viewDidLoad], address = 0x00a77877, resolved, hit count = 0
1.13: where = UIKit`-[_UIViewServiceViewControllerOperator viewDidLoad], address = 0x00aba23b, resolved, hit count = 0
1.14: where = UIKit`-[UIActivityViewController viewDidLoad], address = 0x00b4f296, resolved, hit count = 0
1.15: where = UIKit`-[_UITextEditingController viewDidLoad], address = 0x00b9a6ec, resolved, hit count = 0
In fact, it is clear here that we need to leave only one breakpoint:
-[ViewController viewDidLoad]
since the rest belongs to the Apple Private API. But we are interested, so leave them on. Let's get back to launching our application! Enter the command:
c
which in full version looks like
continue
. The application will continue to execute the code until the first call viewDidLoad
:Process 1427 resuming
Process 1427 stopped
* thread #1: tid = 0x83c4, 0x0001f482 Meme Collector`-[ViewController viewDidLoad](self=0x08f7c620, _cmd=0x00c50587) + 18 at ViewController.m:27, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
frame #0: 0x0001f482 Meme Collector`-[ViewController viewDidLoad](self=0x08f7c620, _cmd=0x00c50587) + 18 at ViewController.m:27
24
25 - (void)viewDidLoad
26 {
-> 27 [super viewDidLoad];
28 self.memeDescriptionTextView.clipsToBounds = YES;
29 self.memeDescriptionTextView.layer.cornerRadius = 20.0f;
30 [self.moneyLabel sizeToFit];
“And now ... e-yer ... it’s time for us to have fun: otherwise I don’t play!”

We stopped the process on the frame of the class
ViewController
(file ViewController.m). So we have access to its instance variables and methods. Cool? And further! The code section is already loaded into memory. Therefore, we have access to all other classes, including attention! - singletones. Yes, singletones. If you carefully studied this point in the first part , you might notice a “interesting” class called
MoneyManager
. He has a method purchaseCurrency
that he wants to test, huh? :) Type in our
(lldb)
-terminal:call [[MoneyManager sharedManager] purchaseCurrency]
We called the method! The debugger will output the result of execution:
(BOOL) $0 = YES
If we saw the answer
YES
, it means that we have successfully “acquired” virtual currency. (Here the author blabbed, this is insider information. We, crackers, should not know it. - Approx. Per. ) LLDB also repeats the previous command by pressing Enter. Therefore, press Enter several times to rob Mikhail
(lldb) call [[MoneyManager sharedManager] purchaseCurrency]
(BOOL) $0 = YES
(lldb)
(BOOL) $1 = YES
(lldb)
(BOOL) $2 = YES
(lldb)
(BOOL) $3 = YES
(lldb)
(BOOL) $4 = YES
(lldb)
(BOOL) $5 = YES
(lldb)
(BOOL) $6 = YES
(lldb)
(BOOL) $7 = YES
(lldb)
Purchasing
c
... so that all the breakpoints that we set up end and evaluate the result in the simulator:

Not bad, right? Well, let's see what we can do about it.
To pause the application and return to the command line again, switch to the terminal and press Ctrl + C there. The LLDB debugger is ready to execute our commands again.
Let's finish the debugging session for now: enter the command
q
and then to confirm y
:(lldb) q
Quitting LLDB will detach from one or more processes. Do you really want to proceed: [Y/n] y
We return to the side of the developer. Is it possible to outwit those who want to manipulate your application through the debugger?
Protection against runtime manipulation
Fortunately, there is a way to find out if the debugger is connected to our code! But there is one problem. This check determines if the debugger is connected at this particular time. A hacker (cracker, cheater, ...) can connect to the application after this check, when the application is no longer aware of the danger. This problem can be solved in at least two ways:
- Include a check in the run loop, so that the check will be performed continuously.
- Put a check in the most critical parts of the code where we are most concerned about security.
The first option is usually undesirable. Its price is a waste of precious processor time
One elegant solution is to check for debugger activity in a singleton
MoneyManager
. For example: if we determined that debugging is occurring, then return nil
instead of a static instance of the class.Advanced mode
In Objective-C, you can easily do this because methods in Objective-C are inherently not methods, but messages . This means that sending a message to an empty object is absolutely safe - it does nothing, i.e. The code does not crash.
Well, finally, let's work with the code! Open our project (in your favorite IDE or in Xcode) and go to the MoneyManager.m file. Here's what we will do: add a preprocessor macro that will check in which configuration our application is built, and if it is Release, it will check whether the debugger is running. If launched, will return
nil
. Otherwise, everything is done as usual. Add 3 lines to the top of the
sharedManager
class method MoneyManager
:#ifndef DEBUG
SEC_IS_BEING_DEBUGGED_RETURN_NIL();
#endif
Now the method should look like this:

SEC_IS_BEING_DEBUGGED_RETURN_NIL()
- this is a call to the standard preprocessor macro, which returns nil
if a debugger is connected to the application. Please note: this macro is only available in the Release configuration. If you followed us in the first part, you should already have switched to the release.
(just in case, let me remind you)
Xcode: Product> Scheme> Edit scheme ... ( ⌘ < ) - select Run ... on the left, Info> Build Configuration: Release tab on the right.
AppCode: Run> Edit configurations ... > Configuration: Release.
AppCode: Run> Edit configurations ... > Configuration: Release.
Advanced mode
Some might say that it’s better to write an ObjC method or C function instead of a preprocessor macro. But! There is a very specific reason to use a macro. Since we already learned that anyone can sneak the names of all the methods, as well as change their behavior (looking ahead: this is what we will do next) - knowing this, we want to hide our check (for example, here, inside the singleton method). In general, it will be much more difficult for crackers to find and patch a security check, in the case of a macro, they will have to tinker in assembler.
Now start our application from the IDE (didn’t forget to choose the Release configuration?)
Xcode: Run (⌘R)
AppCode: Debug (Ctrl + D)
Xcode automatically connects the LLDB debugger when choosing the Run command. Result: account balance is not displayed! Indeed, somewhere there
nil
: 
And in AppCode there are two different commands: the Run command does not connect the debugger, and the Debug command connects. Conveniently.
To finally make sure that our protection works, check: can you buy something now?
MoneyManager
unavailable - then you can’t. Stop the application by clicking the Stop button in the IDE(with a square). Also stop the LLDB debugger. Switch to the simulator and run the application from there. The application will display the currency, as debugger is not connected.
As we already said, you can connect the debugger to the process not only at startup, but generally at an arbitrary point in time. Run in terminal:
ps aux | grep "Meme Collector"
The output of this command will contain a list of all processes in the name of which the phrase "Meme Collector" appears:
dmitriy 2008 0,0 0,0 2432784 636 s001 S+ 1:05 0:00.00 grep Meme Collector
dmitriy 2001 0,0 0,4 857416 32240 ?? S 1:04 0:00.65 /Users/dmitriy/Library/Application Support/iPhone Simulator/7.0.3/Applications/9A72F266-8851-4A25-84E4-9CF8EFF95CD4/Meme Collector.app/Meme Collector
You can see that the second line corresponds to the application folder in the simulator. Pay attention to the number of this process (second column). In my case, this is the number 2001.
From the terminal, run LLDB with the key
-p
so that it connects to the process by number:lldb -p {ваш номер процесса}
For example, I need to type “lldb -p 2001”.
LLDB will start and report a successful connection to the process:
Attaching to process with:
process attach -p 2001
Process 2001 stopped
Executable module set to "/Users/dmitriy/Library/Application Support/iPhone Simulator/7.0.3/Applications/9A72F266-8851-4A25-84E4-9CF8EFF95CD4/Meme Collector.app/Meme Collector".
Architecture set to: i486-apple-macosx.
When LLDB is running, try accessing the singleton
MoneyManager
:call [[MoneyManager sharedManager] purchaseCurrency]
The attempt to “buy” the currency now returns
NO
, that is, it does not pass. And try to print a description of the object
sharedManager
. Type the command:po [MoneyManager sharedManager]
And what is in this description?
nil
What was required to achieve! Our singleton does not return at least any intelligible result, nor does it show an error message during the purchase process. Simple and incomprehensible to a cracker
nil
. We continue the execution of the application with the command:
c
Try to legally replenish your account using the "Purchase Currency" button. Nothing will come of it! After all, LLDB is still connected to the process.

Disconnect the debugger from the process: press Ctrl + C and then enter the q command . The Purchase Currency button works again.
In addition to checking for a debugger, you can take a more rigorous approach. The feature
ptrace
helps To do this, go back to the IDE and open main.m . Add one header file:
#include
And three lines to the beginning of the function
main
:#ifndef DEBUG
ptrace(PT_DENY_ATTACH, 0, 0, 0);
#endif
The function is
ptrace
commonly used in debuggers to connect to a process, as GDB and LLDB do, as we saw. We added a call ptrace
that, with a special parameter, PT_DENY_ATTACH
asks the operating system to prevent other processes (that is, debuggers) from connecting to our application. Now run the application from the IDE.
Xcode : the application does not seem to start. What's happening? We see for a moment a black screen that immediately disappears - this application is loaded into memory and starts to run. At the same time, Xcode wants to connect LLDB to it, but iOS does not allow it and ends the debugger process. “Once the debugger is complete,” Xcode thinks, “the application has finished, so stop it.” The last phrase sounds wild, but it works that way.- Note trans.
AppCode : with the Run (⌘R) command, the application starts normally, and with the Debug command (Ctrl + D) it crashes like Xcode.
And from the simulator it starts correctly. Try connecting a debugger to it now, as discussed above:
lldb -p {номер процесса Meme Collector}
The result is predictable:
Attaching to process with:
process attach -p 3435
error: attach failed: process did not stop (no such process or permission problem?)
This is a good tool to stop young children who have read habr from playing with your application. But that will not stop bearded hackers. They will stop your process when calling a function
ptrace
and modify it before continuing. 
In general, do not feel too comfortable. Hackers like to use Cycript - a scripting language (reminiscent of JavaScript) - specifically for manipulating ObjC applications at run time. The debugger protection we made does not protect you from Cycript. Remember how we started the conversation in the previous article:
No application is safe!
We prepare the binary
Before proceeding with the modification of binary files, let's find out how to disassemble it into parts, and what’s what.
I will periodically refer to specific addresses in the binary to illustrate certain concepts. If you do not have the version of the compiler like mine (for example, that comes with the newer Xcode), either you compile the Debug configuration instead of Release, or you yourself made changes to the project - the addresses may be different. This does not bother you - just follow the presentation to understand the idea.
The executable file format in OS X and iOS is called Mach-O . As a rule, a binary starts with a header containing all the information about where and what data is in the binary. This information is followed by download commands.(load commands), which will tell you about marking up the file by segments. In addition, these commands define special flags: for example, whether binary data in the file is encrypted.
In each segment (segment) there is one or several sections (sections). Two types of sections are worth noting:
- Text section . Mostly for read-only data. For example, source code, C lines, constants, etc. The peculiarity of read-only data is that if the system runs out of RAM, it can easily free data from these sections and later (if necessary) load it again from the file.
- Data section . Mostly for data that can be modified from code. They include BSS sections for static variables, a common section for global variables , etc.
Apple also has an excellent Mach-O format reference in English. - Note trans.
Now we will examine the binary
Meme Collector
to see all this in action. Let's start with the headline. In the terminal, still in the folder of the main bundle “Meme Collector.app”, enter:otool -h "Meme Collector"
This command prints the header of the Meme Collector binary executable. Something like this:
Meme Collector:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedface 7 3 0x00 2 25 3372 0x01000085
Advanced mode
0xfeedface (0xFEEDFACE) is a hexadecimal address or ... some phrase in English, don't you think? Wikipedia answers . - Note trans.
Note: the file has 25 load commands (
cmds
), and they occupy 3372 bytes ( sizeofcmds
). Let's look at these commands:otool -l "Meme Collector"
(Before that, you can clear the terminal window by pressing ⌘K. It will be more convenient to scroll through. - Note per. )
You will get many, many lines. From these lines (even without preliminary preparation) you can see a lot of interesting things about the order of loading segments and sections into memory. But this study is beyond the scope of this tutorial, let us leave it to the most curious readers for independent study.
And we continue our lesson. Find the ( ⌘F ) section under the name
__objc_classname
, pay attention to offset
- this is the "position" or "shift" of this section relative to the beginning of the virtual memory occupied by the application.Advanced mode
About
offset
. The most curious may have already understood the difference between addr
and offset
why is this difference everywhere equal to 0x1000 = 4096 bytes? If not yet, read more about a __PAGEZERO
very interesting page.Here, the section offset
__objc_classname
is 159942 bytes (in decimal). In the image below, on the left side - underlined in red. Go to the terminal. Open a new terminal window ( ⌘N ) and from the same folder “Meme Collector.app” do:
strings -o "Meme Collector"
The command
strings
searches for lines in a binary file, and the flag -o
will write at each line its position relative to the beginning of the file. Well, what is located at 159942? Class names! (Highlighted in red.) Logically, we were looking for a section
__objc_classname
: 
Directly above this section we see a section
__objc_methname
, it starts from 140887 - here we have the names of the methods (highlighted in blue), starting with the method init
.Advanced mode
I wonder why the method
init
comes first?Where method names end, class names begin immediately. The section
__objc_classname
goes right after the section __objc_methname
. In the boot commands, they went one after another - and are loaded into memory sequentially. So, we see how load commands allow you to organize the chaos that the Mach-O binary is. With this knowledge we proceed ... tadaaam! to the modification of the code section.
Heavy artillery: disassembler and reverse engineering
Are you ready to launch serious guns? Finally, we will learn how to modify the application binary file!
You probably often hear in your life the phrase: the application is “hacked”. This means that someone modified the application so that it works ... mmm ... differently than the developer intended. For example, does not ask to register. Therefore, we (the author and translator) sincerely hope that our work will serve you well. Only to protect your applications.
Download IDA Demo and some HEX editor, for example, Hex Fiend . IDA is a tool that hackers use most often when studying binary. This is an incredibly powerful disassembler, debugger and decompiler. And the full version is not so expensive.
But if you are not ready to buy the program that you heard about 15 seconds ago, IDA offers a demo version with limited functionality. The demo version has limited types of assembler files that can be studied in it. Also, the ability to modify the code is disabled.
But it has our x86 assembler type. And we will do all the modifications manually in another program - Hex Fiend.
Advanced mode
“Assembler type ... how is x86? Why not ARM? ” - you ask. Indeed, the iOS application is an ARM binary. But for the simulator, executable files are compiled for the x86 architecture. Code compiled for one architecture will not work correctly on another.
Install and run IDA. We are welcomed by Ada Lovelace, the first programmer in the world:

Press the Go button . In the terminal, we (yes, yes) are still in the bundle folder (
Meme Collector.app
). Enter the following command to show this folder in Finder:open -R .
Do not forget about the point at the end. The dot symbol here means “current folder”.
Then in the Finder window that opens: right-click> Show package contents :

(OS Mavericks calls the bundle “package” in Russian, but this name seems uninformative to me. - Note per. )
Inside the bundle package you will find an executable file
Meme Collector
, drag it into the IDA window and get a dialog box: 
Indeed, the IDA has determined that this binary is an i386 architecture executable.
Your settings should correspond to those shown above (I think you will not have to change anything) - and click
If you ask "Objective-C 2.0 structures detected. Do you want to parse them and rename methods? ” - answer Yes. If you ask something about the “proximity view”, answer No.
When the IDA finishes processing the binary file, you will of
start
, click on it and then press the spacebar until you see such a beautiful flowchart: 
(In my case, one space was not enough, I had to press Enter one more time or another. Well, you quickly figure out what's what. ”- Approx.)
And open the project in Xcode or AppCode. In order to reduce the presentation, we will look a little into the code.
Open MoneyManager.m and take a look at the method
buyObject:
- (BOOL)buyObject:(id)object
{
NSUInteger totalMoney = self.money.unsignedIntegerValue;
NSUInteger cost = [object cost].unsignedIntegerValue;
if (totalMoney < cost) {
return NO;
}
_money = @(totalMoney - cost);
return [self saveState];
}
Learn the algorithm, it is quite simple. If the instance variable does
_money
not have enough to pay, then the function will return NO
and the transaction will not be completed. This conditional statement allowing / prohibiting a purchase relies on a single Boolean value: does the user have enough money? (Does it remind you of the behavior of some people in a store? - Approx. Per. ) If you circumvent this test (“jump over”, in assembler terms) - you could buy anything you like, then the value
_money
would no longer be considered a factor when buying. Now we find the same code in the disassembler. Go back to the IDA, click on any function in the Functions panel (just to activate this panel) and then press Ctrl + F(or from the menu: Edit> Quick Filter ). An input box appears to search for our function. We need to find
buyObject:

Yeah, find, double-click on the name of the method. The IDA will show the disassembler window, which perfectly demonstrates the conditional statement and code branching:

Even if you don’t know anything from
buyObject:
we can assume that the green “right” arrow is exactly the place we want to go hacker, many actions are performed there. A short code under the red arrow "left" is more like a concise " return nil
". I will load you with assembler a little. Let's look at the conditional operator (“jump”), the lowest in the upper block, from which two arrows go - red and green. it
jnb
, which means "jump if not below" (go if "not less"). Apparently, we should replace this instruction with "jump always" - the instruction jmp
. To replace the instruction, you need to find it. Double-click on the operand
jnb
. It will be highlighted in yellow. Now press the spacebar to switch to text mode. Here is the same information, but in a linear form. Find the line number with the command
jnb
(this line is highlighted): 
In my case, the address turned out to be 0x00018D88. Let me remind you, you can have any other address.
The operand code "
jnb short
" is 0x73??
where the question marks indicate the relative offset in bytes where we want to go. We need to change the operand code to 0xEB??
- unconditional jump code "jmp short
"(for the same number of bytes). Where did I get the operand codes? For example, from Intel Software Developer's Manual (by the way, an exciting read!) Download (if you haven’t downloaded yet) Hex Fiend . Install it, for example, by copying it to a folder
/Applications
. From the terminal (assuming that we are still in the bundle folder “Meme Collector.app”) type the command:open -a "/Applications/Hex Fiend.app/" "Meme Collector"
A window will open with our binary. Beautiful, is not it? Here they are - our friends:
__objc_classname
and other sections. Before us is clearly the header of the executable file. 
Now type in the terminal:
otool -l "Meme Collector" | grep -a10 "sectname __text"
As we saw earlier, it
otool -l
displays commands to load a binary file into memory. We are interested in the code section (“text” section), so we narrow the search area with the command grep
. We got something like this: segname __TEXT
vmaddr 0x00001000
vmsize 0x0002e000
fileoff 0
filesize 188416
maxprot 0x00000007
initprot 0x00000005
nsects 11
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x00002970
size 0x0001dec3
offset 6512
align 2^4 (16)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
Here we see the start address of the section (
addr
) 0x00002970, and the shift ( offset
) - 6512 (decimal number). You can take the IDA and make sure that the starting address from which the code starts is exactly 0x2970, for this you need to scroll (in a "linear" form) to the very very top. (I remind you, your specific values may differ, but the meaning is the same). Excellent! Time to do arithmetic: you need to recalculate the offset of the instruction
jnb
(found for the "text" section) to the absolute value inside the binary file. If you try to change the bytes at the address found in the IDA, you probably catch a crash somewhere, because they do not match. So as not to be distracted, I have prepared a formula for you:
{absolute position of the command in a binary file} =
{command address} - {start address of the text section} + {shift of the text section}
In my case:
Command address
jnb
= 0x18D88 (from IDA) Start address of the text section = 0x2970 (from
otool
) Shift of the text section = decimal 6512 (from
otool
) Take a calculator, switch from the menu: View> For the programmer ... (do not forget to switch to the desired number system when entering decimal and hexadecimal numbers).
I got:
0x18D88 - 0x2970 + 6512 = 0x17D88
Advanced mode
Readers of previous Advanced Mode spoilers have already understood why the position of the command in the file differs from the position of the command in the RAM by 0x1000 (0x18D88 - 0x17D88).
If your calculations were correct, this will be the address of the instruction
jnb
that we saw in the IDA. Now in Hex Fiend, press ⌘L (or from the Edit> Jump To Offset menu ) to open the address input field. Enter your address value (if you enter in hexadecimal format, do not forget 0x
at the beginning). Hmm, for some reason, the line numbers are in decimal. Well, let's recalculate: 0x17D88 = 97672, i.e. from position 97664 you need to count another 8 bytes to the right. 8 bytes = 16 hexadecimal digits = two 4-byte words. You see, Hex Fiend groups a binary “text” according to the words: We

skip the first two words, and at the beginning of the third word - here it is - our operation code
0x7304
. 0x73
- instruction code, and0x04
- offset by how many bytes the processor should “jump” forward. Correct
0x73
on 0xEB
(gently: a single click on Backspace immediately removes 1 byte = two hexadecimal characters). Save ( ⌘S ) and close the file. Open the simulator, delete the application from memory and run it again (from the simulator, and not from the IDE, so as not to be compiled again). Buy memes until you run out of money. What happened when you tried to buy a product that costs more than your “money”? Yes, we really threw out the verification of the condition “does the user have money?” Even without money, the transaction is completed. And a small bonus: unsigned value
_money
"Loops", due to the peculiarities of the representation of numbers in memory, instead of negative it becomes a little less than 10 32 (about 4 billion).Reverse Engineering Protection
How do we protect ourselves? Remember, I said: "nothing is safe." This statement also works here. Reverse engineering can be very difficult, but you cannot stop an attacker if he is serious. Your only hope is to confuse the attackers so much that they give up this business and go to break other applications.
One way is to change the names of important classes and methods through a preprocessor. Open the project in the IDE and find the file "Meme Collector-Prefix.pch" in it. Add a line to it:
#define MoneyManager DS_UIColor_Theme
This code will replace all occurrences of "
MoneyManager
" with a name that seems less interesting to crackers: " DS_UIColor_Theme
". This approach should be used with great care so as not to break anything. You need to make sure 100% that the selected new name is not found anywhere else in your application. Otherwise, you will confuse yourself, inexplicable things will begin to happen with the application.
Usually in the executable file there is a symbol table in which the mapping of addresses into readable names of functions and methods is stored. And now, another way to confuse the code is to delete the character table after building the project. This is more suitable for hiding C and C ++ functions, because Objective-C messages are handled by a single function
objc_msgSend()
. Open MoneyManager.m again and add the following C function to the beginning:
BOOL aSecretFunction(void) {
return YES;
}
Then compile the application again. Check the existence of this function in the symbol table. From the terminal:
nm "Meme Collector" | grep aSecretFunction
The command
nm
displays a symbol table, and grep
filters by function name. Here it is:00018b8f t _aSecretFunction
An easy way to delete a character table from an iOS application is to find two options in the project settings: Deployment Postprocessing and Strip Linked Product, and set them to Yes:

Then you need to “clean” the project (Xcode: Product> Clean or in AppCode: Run> Clean ) and recompile. Then go to the terminal and run the same command:
nm "Meme Collector" | grep aSecretFunction
Fine! We successfully deleted the character that referred to
aSecretFunction()
. Now, the cracker will have to spend more time to find critical points in the code.What's next?
We made sure that an attacker can:
- easy to see the names of Objective-C selectors;
- Manipulate files accessed by your application
- intercept and modify network interaction;
- Manage the runtime
- change the executable file of your application.
When creating an application, it is important to remember these things. Think about how much effort you are willing to make to make the application more secure. What is security? It is always a balance between your resources (time), the level of problems for your users, and the probability of hacking.
IOS app security is a serious topic. You can still learn a lot. So far we have only scratched the surface slightly. The whole range of capabilities of the debugger and other analysis tools lies much deeper. If you are interested in this topic, I advise you to think about jailbreaking a test device. The file system will provide rich food for research.
If you have no problems with English, be sure to check out Hacking and Securing iOS Applications(author Jonathan Zdziarski). Although it is slightly outdated (you have to google for changes in the encryption mechanism of Apple applications), but the author of the article is one of his favorite books on iOS and security.
A couple more books:
Hacking: The Art of Exploitation, 2nd Edition by Jon Erickson
Mac OS X and iOS Internals: To the Apple's Core by Jonathan Levin
Forums:
http://www.woodmann.com
http://www.reddit.com / r / ReverseEngineering
Article on injection of code:
http://blog.timac.org/?p=761 The
author can be written in comments , and by translation write to dev
@
x128.ru mail .