Skip to content

Commit d1124b5

Browse files
author
Emery Ferrari
committed
iOS Restrictions Recovery v0.6
1 parent 420c708 commit d1124b5

File tree

6 files changed

+209
-31
lines changed

6 files changed

+209
-31
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
# iOS Restrictions Recovery
22

33
by Emery Ferrari<br/>
4-
A GUI/command-line tool that will recover the restrictions passcode from a device running iOS 7.0-11.4.1.
4+
A GUI/command-line tool that will recover the restrictions passcode from a device running iOS 7.0-11.4.1, either jailbroken or unjailbroken.
55

66
## Credit
77

88
slf4j Copyright (c) 2004-2017 QOS.ch<br/>
99
bc-java Copyright (c) 2000-2019 The Legion of the Bouncy Castle Inc.<br/>
10-
sshj Copyright (c) 2010-2012 sshj contributors
10+
sshj Copyright (c) 2010-2012 sshj contributors<br/><br/>
11+
The idea for the iTunes backup feature was given to me by:<br/>
12+
[u/Starwarsfan2099](https://reddit.com/user/Starwarsfan2099) and<br/>
13+
[u/KuroAMK](https://reddit.com/user/KuroAMK)<br/>
14+
The code for the iTunes backup feature was loosely based on [this GitHub project](https://github.com/Starwarsfan2099/iOS-Restriction-Key-Cracker) by [u/Starwarsfan2099](https://reddit.com/user/Starwarsfan2099)
1115

1216
## Dependencies
1317

@@ -16,7 +20,7 @@ slf4j (slf4j-api-1.7.2 and slf4j-jdk14-1.7.2 are used for compilation of the rel
1620
sshj (sshj-0.27.0 is used for compilation of the release jars)<br/>
1721
ed25519-java (eddsa-0.3.0 is used for compilation of the release jars)<br/>
1822
bc-java (bcprov-jdk15on-1.64 is used for compilation of the release jars)<br/><br/>
19-
Note: The iproxy feature currently does not work.
23+
Note: The iproxy feature currently does not work.<br/>
2024
To use the iproxy feature:<br/>
2125
-macOS: homebrew, libimobiledevice<br/>
2226
-Unix-based operating systems: libusbmuxd-tools<br/>
@@ -26,10 +30,6 @@ To use the iproxy feature:<br/>
2630

2731
This tool can either be run from the .jar executable of the latest release in the Releases tab, or can be compiled using javac.
2832

29-
## Known issues
30-
31-
There are many performance and consistency issues in the source code. None of these will affect your experience with the program in practice, however these are planned to be fixed in a future pre-release update nonetheless.
32-
3333
## Contacting me
3434

3535
I will respond to any PM I receive on Reddit.<br/>
Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,162 @@
11
package com.emeryferrari.iosrr;
22
import javax.swing.*;
33
import java.awt.event.*;
4+
import java.io.*;
45
public class Display {
56
private Display() {}
67
private static final Display CLASS_OBJ = new Display();
7-
private static JFrame FRAME = new JFrame(RRConst.NAME + " - v" + RRConst.VERSION);
8+
private static JFrame FRAME = new JFrame(RRConst.NAME + " " + RRConst.VERSION);
89
private static JLabel TITLE = new JLabel(RRConst.TITLE);
10+
private static JLabel DESC = new JLabel(RRConst.DESC);
911
private static JButton KEY_SALT_BUTTON = new JButton(RRConst.KEY_SALT_BUTTON);
1012
private static JButton FILE_BUTTON = new JButton(RRConst.FILE_BUTTON);
1113
private static JButton SSH_BUTTON = new JButton(RRConst.SSH_BUTTON);
1214
private static JButton IPROXY_BUTTON = new JButton(RRConst.IPROXY_BUTTON);
15+
private static JButton ITUNES_BACKUP = new JButton(RRConst.ITUNES_BACKUP);
16+
private static InfoPlist[] plists = null;
17+
private static boolean initialized = false;
1318
public static void createDisplay() {
14-
Display.FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
15-
Display.FRAME.setSize(800, 600);
16-
Display.FRAME.setLayout(new BoxLayout(FRAME.getContentPane(), BoxLayout.Y_AXIS));
17-
Display.KEY_SALT_BUTTON.addActionListener(Display.CLASS_OBJ.new KeySaltButtonListener());
18-
Display.FILE_BUTTON.addActionListener(Display.CLASS_OBJ.new FileButtonListener());
19-
Display.SSH_BUTTON.addActionListener(Display.CLASS_OBJ.new SSHButtonListener());
20-
Display.IPROXY_BUTTON.addActionListener(Display.CLASS_OBJ.new IproxyButtonListener());
21-
Display.IPROXY_BUTTON.setEnabled(false);
19+
if (!initialized) {
20+
Display.FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
21+
Display.FRAME.setSize(800, 600);
22+
Display.FRAME.setLayout(new BoxLayout(FRAME.getContentPane(), BoxLayout.Y_AXIS));
23+
Display.KEY_SALT_BUTTON.addActionListener(Display.CLASS_OBJ.new KeySaltButtonListener());
24+
Display.FILE_BUTTON.addActionListener(Display.CLASS_OBJ.new FileButtonListener());
25+
Display.SSH_BUTTON.addActionListener(Display.CLASS_OBJ.new SSHButtonListener());
26+
Display.IPROXY_BUTTON.addActionListener(Display.CLASS_OBJ.new IproxyButtonListener());
27+
Display.IPROXY_BUTTON.setEnabled(false);
28+
Display.ITUNES_BACKUP.addActionListener(Display.CLASS_OBJ.new ItunesBackupListener());
29+
initialized = true;
30+
}
2231
Display.FRAME.getContentPane().add(Display.TITLE);
32+
Display.FRAME.getContentPane().add(Display.DESC);
2333
Display.FRAME.getContentPane().add(Display.KEY_SALT_BUTTON);
2434
Display.FRAME.getContentPane().add(Display.FILE_BUTTON);
2535
Display.FRAME.getContentPane().add(Display.SSH_BUTTON);
26-
Display.FRAME.getContentPane().add(Display.IPROXY_BUTTON);
36+
Display.FRAME.getContentPane().add(Display.ITUNES_BACKUP);
2737
Display.FRAME.setVisible(true);
2838
}
39+
public class ItunesBackupListener implements ActionListener {
40+
public void actionPerformed(ActionEvent ev) {
41+
try {
42+
OperatingSystem currentOS = RestrictionsRecovery.identifyHostOS();
43+
String backupPath = System.getProperty("user.home") + "\\";
44+
if (currentOS == OperatingSystem.WINDOWS) {
45+
String y = JOptionPane.showInputDialog("Are you using the Microsoft Store version of iTunes? (y/n)");
46+
if (y.equalsIgnoreCase("y") || y.equalsIgnoreCase("yes")) {
47+
backupPath += "Apple\\MobileSync\\Backup\\";
48+
} else {
49+
backupPath += "AppData\\Roaming\\Apple Computer\\MobileSync\\Backup\\";
50+
}
51+
} else if (currentOS == OperatingSystem.MACOSMOJAVE || currentOS == OperatingSystem.MACOSCATALINA) {
52+
53+
} else {
54+
throw new Exception("Your OS is not supported because it is not possible to install iTunes on your OS.");
55+
}
56+
File backupLocation = new File(backupPath);
57+
String[] backups = backupLocation.list(new FilenameFilter() {
58+
@Override
59+
public boolean accept(File current, String name) {
60+
return new File(current, name).isDirectory();
61+
}
62+
});
63+
Display.FRAME.getContentPane().removeAll();
64+
Display.FRAME.getContentPane().add(new JLabel(RRConst.ITUNES_BACKUPS));
65+
plists = new InfoPlist[backups.length];
66+
for (int i = 0; i < backups.length; i++) {
67+
InfoPlist plist = InfoPlist.getInstance(backupPath + backups[i] + "\\");
68+
plists[i] = plist;
69+
if (plist != null) {
70+
JButton button = new JButton(plist.getDeviceName() + " / " + plist.getiOSVersion() + " / " + plist.getBackupDate() + " / " + plist.getDisplayName());
71+
if (plist.getiOSRelease() < 7 || plist.getiOSRelease() > 11) {
72+
button.setEnabled(false);
73+
}
74+
button.addActionListener(Display.CLASS_OBJ.new BackupListener(i));
75+
Display.FRAME.getContentPane().add(button);
76+
}
77+
}
78+
JButton back = new JButton("Back");
79+
back.addActionListener(new BackListener());
80+
Display.FRAME.getContentPane().add(back);
81+
Display.refresh();
82+
} catch (Exception ex) {
83+
Display.handleException(ex, true);
84+
}
85+
}
86+
}
87+
public class BackupListener implements ActionListener {
88+
private int index;
89+
public BackupListener(int index) {
90+
this.index = index;
91+
}
92+
public void actionPerformed(ActionEvent ev) {
93+
try {
94+
KeySaltPair pair = PropertyListReader.getKeyAndSaltFromPlist(plists[index].getFile() + "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b");
95+
Display.FRAME.getContentPane().add(new JLabel("<html><body><strong>Calculating passcode...</strong></body></html>"));
96+
Display.refresh();
97+
String passcode = RestrictionsRecovery.calculate(pair.getKey(), pair.getSalt(), false);
98+
if (passcode == null) {
99+
throw new Exception("Passcode could not be found. Key and salt does not correspond to any passcode between 0000 and 9999.");
100+
} else {
101+
JOptionPane.showMessageDialog(null, "Passcode: " + passcode);
102+
}
103+
Display.FRAME.getContentPane().removeAll();
104+
Display.createDisplay();
105+
Display.refresh();
106+
} catch (Exception ex) {
107+
Display.handleException(ex, true);
108+
}
109+
}
110+
}
111+
public static void refresh() {
112+
Display.FRAME.revalidate();
113+
Display.FRAME.repaint();
114+
}
115+
public static class BackListener implements ActionListener {
116+
public void actionPerformed(ActionEvent ev) {
117+
Display.FRAME.getContentPane().removeAll();
118+
Display.createDisplay();
119+
Display.refresh();
120+
}
121+
}
29122
public class KeySaltButtonListener implements ActionListener {
30123
public void actionPerformed(ActionEvent ev) {
31124
try {
32125
String key = JOptionPane.showInputDialog("Key?");
33126
String salt = JOptionPane.showInputDialog("Salt?");
127+
Display.DESC.setText("<html><b>Calculating passcode...</b></html>");
128+
Display.refresh();
34129
String passcode = RestrictionsRecovery.calculate(key, salt, false);
35130
if (passcode == null) {
36-
throw new Exception();
131+
throw new Exception("Passcode could not be found. Key and salt does not correspond to any passcode between 0000 and 9999.");
37132
} else {
38133
JOptionPane.showMessageDialog(null, "Passcode: " + passcode);
39134
}
40135
} catch (Exception ex) {
41-
Display.handleException(ex);
136+
Display.handleException(ex, true);
42137
}
138+
Display.DESC.setText(RRConst.DESC);
139+
Display.refresh();
43140
}
44141
}
45142
public class FileButtonListener implements ActionListener {
46143
public void actionPerformed(ActionEvent ev) {
47144
try {
48-
String file = JOptionPane.showInputDialog("Property list path?");
145+
String file = JOptionPane.showInputDialog("Property list path? (Do not include quotes)");
49146
KeySaltPair pair = PropertyListReader.getKeyAndSaltFromPlist(file);
147+
Display.DESC.setText("<html><b>Calculating passcode...</b></html>");
148+
Display.refresh();
50149
String passcode = RestrictionsRecovery.calculate(pair.getKey(), pair.getSalt(), false);
51150
if (passcode == null) {
52-
throw new Exception();
151+
throw new Exception("Passcode could not be found. Key and salt does not correspond to any passcode between 0000 and 9999.");
53152
} else {
54153
JOptionPane.showMessageDialog(null, "Passcode: " + passcode);
55154
}
56155
} catch (Exception ex) {
57-
Display.handleException(ex);
156+
Display.handleException(ex, true);
58157
}
158+
Display.DESC.setText(RRConst.DESC);
159+
Display.refresh();
59160
}
60161
}
61162
public class SSHButtonListener implements ActionListener {
@@ -64,26 +165,32 @@ public void actionPerformed(ActionEvent ev) {
64165
String ip = JOptionPane.showInputDialog("Device IP address?");
65166
String password = JOptionPane.showInputDialog("Device root password?");
66167
String port = JOptionPane.showInputDialog("Device SSH port?");
168+
Display.DESC.setText("<html><b>Calculating passcode...</b></html>");
169+
Display.refresh();
67170
RestrictionsRecovery.downloadViaSSH(ip, Integer.parseInt(port), password, false);
68171
KeySaltPair pair = PropertyListReader.getKeyAndSaltFromPlist("password.plist");
69172
String passcode = RestrictionsRecovery.calculate(pair.getKey(), pair.getSalt(), false);
70173
if (passcode == null) {
71-
throw new Exception();
174+
throw new Exception("Passcode could not be found. Key and salt does not correspond to any passcode between 0000 and 9999.");
72175
} else {
73176
JOptionPane.showMessageDialog(null, "Passcode: " + passcode);
74177
}
75178
} catch (Exception ex) {
76-
Display.handleException(ex);
179+
Display.handleException(ex, true);
77180
}
181+
Display.DESC.setText(RRConst.DESC);
182+
Display.refresh();
78183
}
79184
}
80185
public class IproxyButtonListener implements ActionListener {
81186
public void actionPerformed(ActionEvent ev) {
82187
// not implemented yet
83188
}
84189
}
85-
private static void handleException(Exception ex) {
190+
private static void handleException(Exception ex, boolean message) {
86191
ex.printStackTrace();
87-
JOptionPane.showMessageDialog(null, "Error occurred while calculating the passcode. Check the console for details.");
192+
if (message) {
193+
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().toString().split(" ")[1] + ": " + ex.getMessage());
194+
}
88195
}
89196
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.emeryferrari.iosrr;
2+
import javax.xml.parsers.*;
3+
import org.w3c.dom.*;
4+
public class InfoPlist {
5+
private final String deviceName;
6+
private final String displayName;
7+
private final String backupDate;
8+
private final String iOSVersion;
9+
private final String file;
10+
private InfoPlist(String deviceName, String displayName, String backupDate, String iOSVersion, String file) {
11+
this.deviceName = deviceName;
12+
this.displayName = displayName;
13+
this.backupDate = backupDate;
14+
this.iOSVersion = iOSVersion;
15+
this.file = file;
16+
}
17+
public String getDeviceName() {
18+
return deviceName;
19+
}
20+
public String getDisplayName() {
21+
return displayName;
22+
}
23+
public String getBackupDate() {
24+
return backupDate;
25+
}
26+
public String getiOSVersion() {
27+
return iOSVersion;
28+
}
29+
public String getFile() {
30+
return file;
31+
}
32+
public int getiOSRelease() {
33+
String version = iOSVersion.split(" ")[1].substring(0, 2);
34+
if (version.endsWith(".")) {
35+
return Integer.parseInt(version.substring(0, 1));
36+
}
37+
return Integer.parseInt(version);
38+
}
39+
public static InfoPlist getInstance(String file) {
40+
try {
41+
String fileTemp = file + "Info.plist";
42+
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
43+
DocumentBuilder builder = factory.newDocumentBuilder();
44+
Document document = builder.parse(fileTemp);
45+
document.getDocumentElement().normalize();
46+
NodeList nodes = document.getElementsByTagName("string");
47+
String displayName = nodes.item(1).getTextContent();
48+
boolean stop = false;
49+
String deviceName = null;
50+
String iOSVersion = null;
51+
for (int i = 4; !stop; i++) {
52+
if (nodes.item(i).getTextContent().startsWith("iP")) {
53+
deviceName = nodes.item(i+1).getTextContent();
54+
iOSVersion = "iOS " + nodes.item(i+2).getTextContent();
55+
stop = true;
56+
}
57+
}
58+
String backupDate = document.getElementsByTagName("date").item(0).getTextContent();
59+
String backupDate1 = backupDate.substring(0, 10);
60+
String backupDate2 = backupDate.substring(14, 19);
61+
backupDate = backupDate1 + "@" + backupDate2;
62+
return new InfoPlist(deviceName, displayName, backupDate, iOSVersion, file);
63+
} catch (Exception ex) {
64+
return null;
65+
}
66+
}
67+
}

src/com/emeryferrari/iosrr/PropertyListReader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
public class PropertyListReader {
77
private PropertyListReader() {}
88
public static KeySaltPair getKeyAndSaltFromPlist(String plist) throws ParserConfigurationException, IOException, SAXException {
9-
File plistFile = new File(plist);
109
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1110
DocumentBuilder builder = factory.newDocumentBuilder();
12-
Document document = builder.parse(plistFile);
11+
Document document = builder.parse(plist);
1312
document.getDocumentElement().normalize();
1413
String key = document.getElementsByTagName("data").item(0).getTextContent().split(" ")[1].split("\n")[0];
1514
String salt = document.getElementsByTagName("data").item(1).getTextContent().split(" ")[1].split("\n")[0];

src/com/emeryferrari/iosrr/RRConst.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
public class RRConst {
33
private RRConst() {}
44
public static final String NAME = "iOS-Restrictions-Recovery";
5-
public static final String VERSION = "0.5";
6-
7-
public static final String TITLE = "<html><body><font size=\"10\">iOS-Restrictions-Recovery</font></body></html>";
5+
public static final String VERSION = "v0.6";
6+
public static final String TITLE = "<html><body><font size=\"6\">" + NAME + " " + VERSION + "</font></body></html>";
7+
public static final String DESC = "<html><body>Compatible <strong>only</strong> with <strong>iOS 7.0</strong> through <strong>iOS 11.4.1</strong></body></html>";
88
public static final String KEY_SALT_BUTTON = "From key and salt";
99
public static final String FILE_BUTTON = "From property list file";
1010
public static final String SSH_BUTTON = "From device via SSH";
1111
public static final String IPROXY_BUTTON = "From device via iproxy over USB";
12+
public static final String ITUNES_BACKUP = "From unencrypted iTunes backup";
13+
public static final String ITUNES_BACKUPS = "iTunes Backups:";
14+
public static final String ITUNES_BACKUP_12 = "From encrypted iTunes backup (iOS 12 only)";
1215
}

src/com/emeryferrari/iosrr/RestrictionsRecovery.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public static OperatingSystem identifyHostOS() {
8989
} else if (os.indexOf("mac") >= 0) {
9090
if (System.getProperty("os.version").indexOf("10.15") >= 0) {
9191
return OperatingSystem.MACOSCATALINA;
92+
} else if (System.getProperty("os.version").indexOf("11.") >= 0) {
93+
return OperatingSystem.MACOSCATALINA;
9294
} else {
9395
return OperatingSystem.MACOSMOJAVE;
9496
}

0 commit comments

Comments
 (0)