Table of Contents

Related Blogs

September 12, 2024

Client-Side vs. Server-Side Security: What’s the Difference?

Learn how to choose the right security approach for your web applications. Explore client-side and server-side security measures to enhance your defenses.

Learn More
September 9, 2024

Grass Valley Triumphs Over Application Piracy with Digital.ai Application Security

Grass Valley combats piracy with Digital.ai Application Security, boosting revenue, innovation, and customer trust in the media industry.

Learn More
September 4, 2024

How to Obfuscate Dart Code in Flutter Applications

Safeguard Flutter applications by mastering Dart code obfuscation. Our guide covers everything from setup to best practices for maximum security.

Learn More

Binary Modification Introduction: Motivation Behind This Article

This article presents the second part of our iOS cyber offense series. You can find the first article here, which explains iOS IPA package formatting and application sideloading. We recommend familiarizing yourself with this background information before proceeding with the current article.

Threat actors who attack applications usually want to modify the binary they are attacking. However, various steps exist to reverse engineer and debug the binary before modifying it. Threat actors can use many techniques to learn about a binary and many more techniques to create clever modifications that bypass functionality or add malicious behavior. Even simple modifications that only change a few bytes of an application can have a significant impact.

This article will show a step-by-step guide for how to find and modify a simple 4-byte instruction in an iOS application to bypass an authentication check altogether. I recommend learning the basics of many different attack vectors and thinking like a threat actor, as understanding the threat actors will make it much easier to defend against them. These techniques can be used to verify Digital.ai protections or to learn and understand why application security is needed to protect against these attacks.

Threat Actors’ Motives

Threat actors could be modifying an application for many reasons, and this goes over a single example. Here is a list of realistic, real-world motives:

  • Bypass a Digital Rights Management (DRM) or license check.
  • Replace a function with malware, spyware, bitcoin farming, or any other malicious activity.
  • Disable a thread or disable a watchdog mechanism.
  • Attempt to disable protections inserted from application security products.

Tools Needed to Follow This Guide

  • A disassembly tool: This guide uses Ghidra 11.1.2. Alternatives could be IDA, Hopper, or Binary Ninja.
  • Xcode: This guide uses Xcode 15.4, but a wider range is supported.
  • Git: The source code used for the iOS application is located here.
  • Python 3: Used to script unpacking and modifying the iOS binary.
  • iOS: Application signing or sideloading setup.
  • iPhone for testing the application: This guide uses a physical iPhone, but you should be able to use an iPad or an Apple silicon Mac with PlayCover or adjust the guide to build and attack an Apple silicon macOS application instead.
  • Optional: XXD command line tool to check your work.

Job Dispatcher Sample Application

Job Dispatcher is an example of an iOS application that is useful for demonstrating attacks against iOS applications and testing application security mechanisms. Job Dispatcher is an application technicians might use to receive and complete tasks. For this guide, we imagine we are threat actors trying to bypass the authentication checks so that any password is accepted.

The first step is to build the sample application by downloading it from the GitHub link above and using Xcode to build an IPA.

Job Dispatch

Finding the Target to Modify

This guide will show how to bypass the login screen of the Job Dispatcher application. More specifically, we will bypass the password check to accept any password.

To start, we need a tool that can help us understand the compiled binary file. Ghidra can disassemble a compiled application back into a human-readable assembly and decompile it into a C-like programming language. This guide uses Ghidra to find the relevant code section for the login screen within a binary and analyze which assembly instructions can be changed to bypass the login screen.

Note that the login function in Job Dispatcher is not realistic. It uses a hard-coded username and password for the check. While this login method is not realistic, many functions follow this exact design where they check open-ended input against known constraints. The idea is to learn how to bypass a function check, which is a function where the effects are visible, so the guide is using it.

Threat actors will not have the source code for an application, but let’s take a quick peek anyway so we know what we are dealing with and simplify our steps.

think like a threat actor pic

Finding the target through reverse engineering a binary can be time-consuming, especially if the application is obfuscated. In this case, the target function should be near the strings “tech” and “secret,” so let’s search for those in the compiled binary. In many applications, reverse engineers can use error messages or logging strings to find the sections of code they are interested in. There is also a string for “Invalid Username or Password,” which is a realistic string an attacker will use to find relevant code segments. Due to how Swift handles alert calls, the “Invalid Username or Password” string will be better used for a dynamic analysis technique instead of the static analysis approach we will use.

Reverse Engineering Job Dispatcher

Now, let’s open the Job Dispatcher binary Mach-O file in Ghidra. You will need to make a new non-shared Ghidra project. Once you reach the CodeBrowser screen, we can import the Job Dispatcher file and use all the default analysis options. This will take a few minutes to run.

This is a good point to mention that we did not provide the precompiled application. When you build the application using different Xcode versions, build settings, or, potentially, modifications to the source code, the addresses, registers, and even the assembly itself can be different values. You might need to adjust the addresses and registers used to follow along.

We know the code we want is near the strings “tech” and “secret.” Let’s search for those strings. To do this, use the menu options “Search” –> “For Strings…”

A typical threat actor could start guessing potentially interesting strings until they find something worth investigating. We will save the guesswork here and just search for the string “secret.”

Clicking on the string will shift the code browser to the location of the string. It will take a few steps to find the relevant code. Now, let’s follow the string references until we find the code.

It looks like “tech” and “secret” are stored next to each other in the constant strings section of the binary. Now, follow the cross-references (XREF) to another data location provided.

The constant strings are wrapped in CFString objects, which we can see here. Below, we can continue following the XREFs of the CFString objects and see where the code uses these strings.

This looks like it could be the code we’re looking for. In a stripped binary, the function names for internal functions do not exist. Ghidra helpfully named this function FUN_10004000 to indicate the function at address 10004000. We’ll need to read through this disassembly and decompilation to see if it matches the much simpler source code from earlier. Assembly can be challenging to read, so first, analyze the decompilation to the right. There is a function that takes two arguments and then makes a call to FUN_10001e2ec to do some comparisons. Next (below), we will check this support function to see what it does.

It’s a string comparison function. This is exactly what we’re looking for. These functions look a bit different in Ghidra compared to the source code. You can look at the source on Git Hub to better understand what kind of information is removed when reverse engineering.

According to docs, it will return YES if the strings match and NO otherwise. We want this always to return YES to allow any password.

This is the relevant portion of Assembly. The “password” string is referenced using register x2. Then, we call the “isEqualToString” function. The return value of a function call using the ARM64v8 calling convention would be stored in register x0. We can see that the return value from the string comparison is processed in the line directly after the string comparison function call. It is “mov x20, x0”, which copies the value in the x0 register into the x20 register. We can easily modify this MOV assembly instruction to always place YES in x20, regardless of the provided password.

To do this, we want to replace this line of assembly in the binary file:

MOV x20, x0

Which is represented as “f4 03 00 aa” in hexadecimal.

With this line of assembly, which uses a constant value that represents YES and completely ignores what the string comparison returned:

MOV x20, #01

We can use an online ARM64v8 Assembly to Binary converter to determine that Mov x20, #01 is represented by the hex values:

34 00 80 D2

Ghidra shows that this assembly line is stored at the location:

0x100004040

In this case, 0x10000000 is the image offset into the TEXT section where binary instructions are stored. The application loader uses this offset, but the address of the contents in the file on the disc starts at 0x0.

So, the offset of the binary instruction we want to replace will be at 0x4040 in the Job Dispatcher binary file. Now we know what we need to change and where in the file we need to change it.

Let’s Get Hacking

While we could manually open the binary file in some hex editor and replace the bytes, that is a little impractical. Here is a convenient Python script that will unzip, modify a line of assembly, and zip the modified binary back together. You might need to modify the pathing to match the directories you chose.

1 from zipfile import ZipFile
2 import os
3 import shutil
4 
5 def hack_and_repack(payloadDir, hackedDir, inputIpa, movOffset, storeTrue):
6 
7 # unzip the IPA to get access to the target binary
8 with ZipFile(inputIpa, 'r') as zObject:
9 zObject.extractall(path=payloadDir)
10 
11 target = os.path.join(payloadDir, "Payload", "Job Dispatcher.app", "Job Dispatcher")
12 
13 # actual hackzorz part
14 with open(target, "r+b") as targetFile:
15 targetFile.seek(movOffset)
16 targetFile.write(storeTrue)
17 
18 # zip it up and reform the ipa
19 hackedIpa = os.path.join(hackedDir, "Job Dispatcher.ipa")
20 shutil.make_archive(os.path.join(hackedDir, "Job Dispatcher.ipa"), 'zip', payloadDir)
21 os.rename(os.path.join(hackedDir, "Job Dispatcher.ipa.zip"), hackedIpa)
22 
23 hack_and_repack("payload", "hacked_dir", "JobDispatcher/Job Dispatcher.ipa", int(b"4040", 16), b'\x34\x00\x80\xD2')

 

Run this script using: python3 hacking_script.py

Once the script is complete, a new IPA should be added. This IPA should be modified with the new MOV assembly instruction, which will cause any password to be accepted. We can also verify that the binary was properly edited by loading it back into Ghidra or just a quick check using XXD.

% xxd -s 0x4034 -c 4 -g 1 -l 16 Payload/Job\ Dispatcher.app/Job\ Dispatcher
00004034: 02 09 46 f9 ..F.
00004038: e0 03 13 aa ....
0000403c: ac 68 00 94 .h..
00004040: 34 00 80 d2 4...

We can see that the new move instruction has been inserted to location 0x4040.

To run and test the modified iOS application, we must properly resign it or sideload it onto our iOS device. This is described in the first article of this series. Run the application and test using the username “tech” and using absolutely anything in the password box; it should all be accepted, and move on to the next screen.

Take Aways

Unprotected applications increase business risk by exposing critical business logic to potential threats, allowing threat actors to make unauthorized modifications to the application’s binary code and potentially bypassing crucial security features like licensing checks, authentication checks, or other critical functions.

For games, threat actors will often create hacks or versions of games without DRM and then provide the hacks for other users. If a threat actor can modify the binary, they could also insert cryptojacking or malware into the app and then attempt to redistribute the hacked application to unsuspecting end users. Digital.ai can reduce these risks and make it so difficult and time-consuming to hack applications that even determined threat actors will choose other, lower-hanging fruit to attack. The next part of this series will discuss defending against iOS binary modification.

If you’re already using our products, it could be a good exercise to try protecting Job Dispatcher so that it detects the modifications to the authentication function and then exits the application before a user can even attempt to sign in. I recommend disabling obfuscations if you try this, or you might find it nearly impossible.

 

Check out our page for more information on how we stop binary modifications.

demo placeholder jungle

Author

Steven Friedman

Take Our Threat Assessment for Mobile/Web/Desktop Apps

Explore

What's New In The World of stg-digitalai-staging.kinsta.cloud

September 12, 2024

Client-Side vs. Server-Side Security: What’s the Difference?

Learn how to choose the right security approach for your web applications. Explore client-side and server-side security measures to enhance your defenses.

Learn More
September 9, 2024

Grass Valley Triumphs Over Application Piracy with Digital.ai Application Security

Grass Valley combats piracy with Digital.ai Application Security, boosting revenue, innovation, and customer trust in the media industry.

Learn More
September 4, 2024

How to Obfuscate Dart Code in Flutter Applications

Safeguard Flutter applications by mastering Dart code obfuscation. Our guide covers everything from setup to best practices for maximum security.

Learn More