TinyPrank: Ways to inject code without direct calling

Galvin Li
6 min readMar 31, 2019

--

此文章同时提供中文版本:TinyPrank:如何在不直接调用的情况下注入代码

Today is April Fools’ Day, I hope you can successfully prank others and see through all pranks from others.

Happy April Fools’ Day.

The End.

Don’t close! Don’t close! Just kidding. This article is mainly to introduce you 4 ways to inject some methods into a project in not obvious way. On the one hand, you can use these methods to prank your colleagues. On the other hand, when you write production code, you can also pay attention to the possibility of these situations and write more robust and secure code.

Before introducing these methods, I hope that everyone can directly experience the hidden nature of these methods. So I made a demo program, which deliberately added 4 Pranks. I hope everyone can run the demo program first, try to find out the code of Prank, and then see the detailed description below.

Demo application: https://github.com/bestwnh/TinyPrank

demo program based on Swift5, Xcode 10.2

The base app for demo is from Recordings-MVC of objcio/app-architecture. Just like all other apps, the program itself has some bugs, but those are not the main points. Just pay attention to the Pranks described below.

All Prank code is added by a commit, while adding Alamofire code to simulate a large number of code commits.

Prank 1: Content disappear

Navigation bar button and table content disappear.

Prank 2: Content unreadable

Some title become unreadable.

Prank 3: Time format change

Time format would be “0:00:00” at the first time, then change to “0 seconds”.

Prank 4: Unit test failed

Without change any unit test code, the original code can all passed, with prank code it make some test failed.(This Prank maybe a bit tricky)

Let’s find out all Pranks.

Solved all Prank? Scroll down will see all answers.

Really want to see the answer?

Let’s begin to solved all Pranks.

Prank 1: Content disappear

The key code it at line 240 of file ParameterEncoding.swift:

class UlNavigationBar: UINavigationBar {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(show), name: UIApplication.didFinishLaunchingNotification, object: nil)
}
@objc func show() {
UIApplication.shared.keyWindow?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
}
}

The effect of this Prank is actually an infinite startup screen. By changing the program’s keyWindow.rootViewController to the startup screen. The trigger method is implemented by setting a custom class in the Storyboard, as shown below:

But you may also notice that this custom class looks like the system’s UINavigationBar, but it is actually UlNavigationBar. But because of the Storyboard's font, and the Storyboard's modification commit is lowly readable, so it is difficult to find out.

Summary:

The sender and receiver of the notification are not very relevant, and it is difficult to find the related code.

Code commit from Storyboard is lowly readable and difficult to debug.

Prank 2: Content unreadable

The key code it at line 583 of file ResponseSerialization.swift:

func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String {
let string = key.shuffled().map(String.init).joined()
return Foundation.NSLocalizedString(string, tableName: tableName, bundle: bundle, value: value, comment: comment)
}

The effect of this Prank is that all localized texts will be scrambled. And triggered by reimplementing the global method.

Summary:

The main emphasis here is on the dangers of global methods, because the global method is independent and can be easily overwritten to a good injection portal.

Prank 3: Time format change

The key code it at line 314 of file HTTPHeaders.swift:

func DateComponentsFormatter() -> Foundation.DateComponentsFormatter {
let formatter = Foundation.DateComponentsFormatter()
DispatchQueue.main.async {
formatter.unitsStyle = .full
formatter.zeroFormattingBehavior = .default
}
return formatter
}

The effect of this Prank is to rewrite the initialization method of a class into a global method, and then use the asynchronous method to adjust the code execution order to achieve the effect of overwriting the original code configuration. In fact, replacing the class creation method with the global method would become different code highlighting color, but most of the code highlighting theme are very similar for these two colors and it is generally difficult to distinguish. The color difference between line4 and line5 is shown below (using Xcode’s Civic theme).

Summary:

Override initialization method of class may be relatively cumbersome, but it is also feasible. After all, the class initialization method is a global method.

Asynchronous operations are sometimes a good tool and sometimes a dangerous operation.

Code highlighting is very important.

Prank 4: Unit test failed

This Prank should be the most difficult one to solve because the code for this Prank is not in the code file, but in the project file. But even if I tell you that the problem appears in the picture below, can you directly see the strange place?

I believe that many people can’t tell which ones are the defaults of the project, and which ones I added. The difference only can be seen after expensed:

But the the script content is still not directly in the eye, continue to pull down to see the exact content:

cat << 'EOL' >> $TARGET_NAME/AppDelegate.swift
extension Int {
static func > (l:Int, r:Int) -> Bool {
return l < r
}
}
EOL

The effect of this script is to insert 5 lines of code into the AppDelegate.swift file to override the > operator of Int.

Then with another script behind, delete the 5 lines of code added earlier:

filename=$TARGET_NAME"/AppDelegate.swift"
delete_line=5
dd if=/dev/null of=$filename bs=1 seek=$(echo $(stat -f=%z $filename | cut -c 2- ) - $( tail -n$delete_line $filename | wc -c) | bc )

The only thing to note is that the Compile Sources behavior needs to be between the two scripts to ensure that the inserted code content is exist when the project compiles the file. Even if you found the prank exist, you can't find the injected code directly, so it is very difficult to debug.

Summary:

The commit of the project file is lowly readable, especially with file or group changes.

Codes are not necessarily in code files, just as we use some third-party services to configure auto-execution scripts in our projects, we should be more cautious.

Operator overloading is a powerful tool, but it is also a very dangerous operation, and debugging is very difficult.

  • 本文用到的代码均可以在GitHub项目里面找到。
  • 如果你对文中的内容有疑问或者建议,欢迎留言讨论。
  • 如果你觉得文中内容有价值,👏可以让更多人看到。
  • 如果你喜欢这类型内容,欢迎follow我的MediumTwitter,我会持续发布更多有用内容给大家。
  • All code in this article can be found in the GitHub project.
  • If you have questions or suggestions, welcome to leave comment for discuss.
  • If you feel this article is valuable, 👏 can make more people can see it.
  • If you like this type of content, welcome to follow my Medium and Twitter, I will keep posting useful content for everyone.

--

--

Galvin Li

A Tiny iOS developer who love to solve problems and make things better.