├── .gitignore ├── LICENSE.md ├── Makefile ├── NSTask.h ├── README.md ├── entitlements.xml ├── main.m ├── setup_on_iOS.sh └── updateEntitlements.sh /.gitignore: -------------------------------------------------------------------------------- 1 | toolchain 2 | sdk 3 | main.o 4 | .DS_Store 5 | keychain_dumper 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2011, Neohapsis, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GCC_BIN="`xcrun --sdk iphoneos --find gcc`" 2 | SDK="`xcrun --sdk iphoneos --show-sdk-path`" 3 | #support iPhone 3GS and above, delete armv6 to avoid SDK error 4 | ARCH_FLAGS=-arch armv7 -arch armv7s -arch arm64 5 | 6 | LDFLAGS =\ 7 | -F$(SDK)/System/Library/Frameworks/\ 8 | -framework UIKit\ 9 | -framework CoreFoundation\ 10 | -framework Foundation\ 11 | -framework CoreGraphics\ 12 | -framework Security\ 13 | -lobjc\ 14 | -lsqlite3\ 15 | -bind_at_load 16 | 17 | GCC_ARM = $(GCC_BIN) -Os -Wimplicit -isysroot $(SDK) $(ARCH_FLAGS) 18 | 19 | default: main.o list 20 | @$(GCC_ARM) $(LDFLAGS) main.o -o keychain_dumper 21 | 22 | main.o: main.m 23 | $(GCC_ARM) -c main.m 24 | 25 | clean: 26 | rm -f keychain_dumper *.o 27 | 28 | list: 29 | security find-identity -pcodesigning 30 | @printf '\nTo codesign, please run: \n\tCER="<40 character hex string for certificate>" make codesign\n' 31 | 32 | codesign: 33 | codesign -fs "$(CER)" --entitlements entitlements.xml keychain_dumper 34 | -------------------------------------------------------------------------------- /NSTask.h: -------------------------------------------------------------------------------- 1 | /* NSTask.h 2 | Copyright (c) 1996-2007, Apple Inc. All rights reserved. 3 | */ 4 | 5 | #import 6 | 7 | @class NSString, NSArray, NSDictionary; 8 | 9 | @interface NSTask : NSObject 10 | 11 | // Create an NSTask which can be run at a later time 12 | // An NSTask can only be run once. Subsequent attempts to 13 | // run an NSTask will raise. 14 | // Upon task death a notification will be sent 15 | // { Name = NSTaskDidTerminateNotification; object = task; } 16 | // 17 | 18 | - (instancetype)init; 19 | 20 | // set parameters 21 | // these methods can only be done before a launch 22 | - (void)setLaunchPath:(NSString *)path; 23 | - (void)setArguments:(NSArray *)arguments; 24 | - (void)setEnvironment:(NSDictionary *)dict; 25 | // if not set, use current 26 | - (void)setCurrentDirectoryPath:(NSString *)path; 27 | // if not set, use current 28 | 29 | // set standard I/O channels; may be either an NSFileHandle or an NSPipe 30 | - (void)setStandardInput:(id)input; 31 | - (void)setStandardOutput:(id)output; 32 | - (void)setStandardError:(id)error; 33 | 34 | // get parameters 35 | - (NSString *)launchPath; 36 | - (NSArray *)arguments; 37 | - (NSDictionary *)environment; 38 | - (NSString *)currentDirectoryPath; 39 | 40 | // get standard I/O channels; could be either an NSFileHandle or an NSPipe 41 | - (id)standardInput; 42 | - (id)standardOutput; 43 | - (id)standardError; 44 | 45 | // actions 46 | - (void)launch; 47 | 48 | - (void)interrupt; // Not always possible. Sends SIGINT. 49 | - (void)terminate; // Not always possible. Sends SIGTERM. 50 | 51 | - (BOOL)suspend; 52 | - (BOOL)resume; 53 | 54 | // status 55 | - (int)processIdentifier; 56 | - (BOOL)isRunning; 57 | 58 | - (int)terminationStatus; 59 | 60 | @end 61 | 62 | @interface NSTask (NSTaskConveniences) 63 | 64 | + (NSTask *)launchedTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments; 65 | // convenience; create and launch 66 | 67 | - (void)waitUntilExit; 68 | // poll the runLoop in defaultMode until task completes 69 | 70 | @end 71 | 72 | FOUNDATION_EXPORT NSString * const NSTaskDidTerminateNotification; 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keychain Dumper 2 | 3 | ## Usage 4 | 5 | All that should be needed to use keychain_dumper is the binary that is checked in to the Keychain-Dumper Git repository. This binary has been signed with a self-signed certificate with a "wildcard" entitlement. The entitlement allowed keychain_dumperaccess to all Keychain items in older iOS released. That support seems to have been removed in more recent releases of iOS. Instead, you must now add explicit entitlements that exist on a given device (entitlements can be app-specific). To help with that, this repository includes a `updateEntitlements.sh` shell script that can be run on-device to grant `keychain_dumper` all of the entitlements available on the device. Finally, if you either don't trust this binary or are having trouble dumping Keychain items using the below steps, you may can build the tool from source and manually sign the appropriate entitlments into your build of the keychain_dumper binary. 6 | 7 | As an aside, the following directions assume the target device has already been jailbroken. 8 | 9 | Upload keychain_dumper to a directory of your choice on the target device (I have used /tmp during testing). Also, once uploaded, be sure to validate that keychain_dumper is executable (chmod +x ./keychain_dumper if it isn't) and validate that /private/var/Keychains/keychain-2.db is world readable (chmod +r /private/var/Keychains/keychain-2.db if it isn't). 10 | 11 | Note: iOS 11 devices using Electra (or other jailbreaks) may still require a trick to bypass the native sandbox. Compile the binary with the included _entitlements.xml_, sign it with the developer account certificate/priv_key and copy the binary to _/bin_ or _/sbin_ (which already allows execution). 12 | 13 | If you are using the binary from Git you can attempt to dump all of the accessible password Keychain entries by simply running the tool with now flags 14 | 15 | ./keychain_dumper 16 | 17 | Some keychain entries are available regardless of whether the iOS is locked or not, while other entries will only be accessible if the iOS device is unlocked (i.e. a user has entered their pin). If no Keychain entries are displayed, or if you don't want to trust the provided binary, you may need to rerun the tool after building the application from source. Please see the Build section below for details on how to build and sign the application. 18 | 19 | By default keychain_dumper only dumps "Generic" and "Internet" passwords. This is generally what you are interested in, as most application passwords are stored as "Generic" or "Internet" passwords. However, you can also pass optional flags to dump additional information from the Keychain. If you run keychain_dumper with the `-h` option you will get the following usage string: 20 | 21 | Usage: keychain_dumper [-e]|[-h]|[-agnick] 22 | : Dump Password Keychain Items (Generic Password, Internet Passwords) 23 | -s: Dump All Keychain Items of a selected entitlement group 24 | -a: Dump All Keychain Items (Generic Passwords, Internet Passwords, Identities, Certificates, and Keys) 25 | -e: Dump Entitlements 26 | -g: Dump Generic Passwords 27 | -n: Dump Internet Passwords 28 | -i: Dump Identities 29 | -c: Dump Certificates 30 | -k: Dump Keys 31 | 32 | By default passing no option flags is equivalent to running keychain_dumper with the `-gn` flags set. The other flags largely allow you to dump additional information related to certificates that are installed on the device. 33 | 34 | ## Building 35 | 36 | ### Create a Self-Signed Certificate 37 | 38 | Open up the Keychain Access app located in /Applications/Utilties/Keychain Access 39 | 40 | From the application menu open Keychain Access -> Certificate Assistant -> Create a Certificate 41 | 42 | Enter a name for the certificate, and make note of it, as you will need it later when you sign `keychain_dumper`. Make sure the Identity Type is “Self Signed Root” and the Certificate Type is “Code Signing”. You don’t need to check the “Let me override defaults” unless you want to change other properties on the certificate (name, email, etc). 43 | 44 | ### Build It 45 | 46 | You should be able to compile the project using the included makefile. 47 | 48 | make 49 | 50 | If all goes well you should have a binary `keychain_dumper` placed in the same directory as all of the other project files. 51 | 52 | If you are not able to compile with default Apple SDK, try to replace SDK path in Makefile with Theos SDK: 53 | 54 | SDK="/path/to/theos/sdks/iPhoneOS14.5.sdk" 55 | 56 | 57 | ### Sign It 58 | 59 | First we need to find the certificate to use for signing. 60 | 61 | make list 62 | 63 | Find the 40 character hex string corresponding to the certificate you generated above. You can then sign `keychain_dumper`. 64 | 65 | CER=<40 character hex string for certificate> make codesign 66 | 67 | You should now be able to follow the directions specified in the Usage section above. If you don't want to use the wildcard entitlment file that is provided (or you are runnig more modern versions of iOS that don't support a wildcafrd entitlement), you can also sign specific entitlements into the binary. Using the unsigned Keychain Dumper you can get a list of entitelments that exist on your specific iOS device by using the `-e` flag. For example, you can run Keychain Dumper as follows: 68 | 69 | ./keychain_dumper -e > /var/tmp/entitlements.xml 70 | 71 | The resulting file can be used in place of the included entitlements.xml file. 72 | 73 | Large amount of access groups in entitlements.xml may result in tool not dumping any keys. Include only access groups that you want to dump keys from. Example of correct entitlements.xml file: 74 | 75 | ```xml 76 | 77 | 78 | 79 | 80 | keychain-access-groups 81 | 82 | groupName 83 | 84 | platform-application 85 | com.apple.private.security.no-container 86 | 87 | 88 | ``` 89 | 90 | ## Contact & Help 91 | 92 | If you find a bug you can [open an issue](http://github.com/ptoomey3/Keychain-Dumper/issues). 93 | -------------------------------------------------------------------------------- /entitlements.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | * 8 | 9 | platform-application 10 | com.apple.private.security.no-container 11 | 12 | 13 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Neohapsis, Inc. 3 | * All rights reserved. 4 | * 5 | * Implementation by Patrick Toomey 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * - Redistributions of source code must retain the above copyright notice, this list 11 | * of conditions and the following disclaimer. 12 | * - Redistributions in binary form must reproduce the above copyright notice, this 13 | * list of conditions and the following disclaimer in the documentation and/or 14 | * other materials provided with the distribution. 15 | * - Neither the name of Neohapsis nor the names of its contributors may be used to 16 | * endorse or promote products derived from this software without specific prior 17 | * written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #import 32 | #import 33 | #import 34 | #import "./NSTask.h" 35 | #import "sqlite3.h" 36 | #include "stdio.h" 37 | 38 | #define KNRM "\x1B[0m" 39 | #define KRED "\x1B[31m" 40 | #define KGRN "\x1B[32m" 41 | #define KYEL "\x1B[33m" 42 | #define KBLU "\x1B[34m" 43 | #define KMAG "\x1B[35m" 44 | #define KCYN "\x1B[36m" 45 | #define KWHT "\x1B[37m" 46 | 47 | //static NSString *selectedEntitlementConstant = @"none"; 48 | static NSArray *selectedEntitlementConstantList = nil; 49 | static NSString *databasePath = @"/var/Keychains/keychain-2.db"; 50 | 51 | 52 | void printToStdOut(NSString *format, ...) { 53 | va_list args; 54 | va_start(args, format); 55 | NSString *formattedString = [[NSString alloc] initWithFormat: format arguments: args]; 56 | va_end(args); 57 | [[NSFileHandle fileHandleWithStandardOutput] writeData: [formattedString dataUsingEncoding: NSUTF8StringEncoding]]; 58 | [formattedString release]; 59 | } 60 | 61 | /// Prints a `NSData` to stdout, with either the format "\(name): \(data)\n\n" 62 | /// if the data is valid UTF-8, or "\(name) (Hex): \(data)\n\n" if it isn't 63 | void printDataToStdOut(const char *name, NSData *data) { 64 | NSString *utf8Str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 65 | if (utf8Str) { 66 | printToStdOut(@"%s: %@\n\n", name, utf8Str); 67 | [utf8Str release]; 68 | } 69 | else { 70 | NSUInteger length = [data length]; 71 | char *hexStr = malloc(length * 2 + 1); 72 | const uint8_t *ptr = [data bytes]; 73 | for (int i = 0; i < length; i++) { 74 | sprintf(hexStr + i*2, "%02x", ptr[i]); 75 | } 76 | printToStdOut(@"%s (Hex): 0x%s\n\n", name, hexStr); 77 | free(hexStr); 78 | } 79 | } 80 | 81 | NSString *runProcess(NSString *executablePath, NSArray *args) { 82 | NSPipe *pipe = [NSPipe pipe]; 83 | NSFileHandle *file = pipe.fileHandleForReading; 84 | 85 | NSTask *task = [[NSTask alloc] init]; 86 | task.launchPath = executablePath; 87 | task.arguments = args; 88 | task.standardOutput = pipe; 89 | task.standardError = [NSPipe pipe]; 90 | [task launch]; 91 | 92 | NSData *data = [file readDataToEndOfFile]; 93 | [file closeFile]; 94 | 95 | return [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; 96 | } 97 | 98 | NSString *saveDataTemporarily(NSData *data) { 99 | NSString *tmpPath = @"/tmp/data"; 100 | [data writeToFile:tmpPath atomically:YES]; 101 | return tmpPath; 102 | } 103 | 104 | NSString *runOpenSSLWithArgs(NSArray *args) { 105 | NSFileManager *fileManager = [NSFileManager defaultManager]; 106 | NSString *pathForFile = @"/usr/bin/openssl"; 107 | if ([fileManager fileExistsAtPath:pathForFile]) { 108 | return runProcess(@"/usr/bin/openssl", args); 109 | } 110 | else { 111 | printToStdOut(@"%s[ERROR] Cannot dump certificates, please install \"openssl\" with Cydia.\n%s",KRED, KWHT); 112 | exit(0); 113 | } 114 | } 115 | 116 | NSString *runOpenSSLForConversion(NSString *prog, NSData *data) { 117 | NSArray *args = @[prog, @"-inform", @"der", @"-in", saveDataTemporarily(data), @"-outform", @"pem"]; 118 | return runOpenSSLWithArgs(args); 119 | } 120 | 121 | NSString *runOpenSSLForPublicConversion(NSString *prog, NSData *data) { 122 | NSArray *args = @[prog, @"-RSAPublicKey_in", @"-inform", @"der", @"-in", saveDataTemporarily(data), @"-outform", @"pem"]; 123 | return runOpenSSLWithArgs(args); 124 | } 125 | 126 | void printPrivateKeyPEM(NSData *data) { 127 | printToStdOut(@"%@\n", runOpenSSLForConversion(@"rsa", data)); 128 | } 129 | 130 | void printPublicKeyPEM(NSData *data) { 131 | printToStdOut(@"%@\n", runOpenSSLForPublicConversion(@"rsa", data)); 132 | } 133 | 134 | void printCertPEM(NSData *data) { 135 | printToStdOut(@"%@\n", runOpenSSLForConversion(@"x509", data)); 136 | } 137 | 138 | 139 | 140 | void printUsage() { 141 | printToStdOut(@"Usage: keychain_dumper [-e]|[-h]|[-agnick]\n"); 142 | printToStdOut(@": Dump Password Keychain Items (Generic Password, Internet Passwords)\n"); 143 | printToStdOut(@"-a: Dump All Keychain Items (Generic Passwords, Internet Passwords, Identities, Certificates, and Keys)\n"); 144 | printToStdOut(@"-e: Dump Entitlements\n"); 145 | printToStdOut(@"-g: Dump Generic Passwords\n"); 146 | printToStdOut(@"-n: Dump Internet Passwords\n"); 147 | printToStdOut(@"-i: Dump Identities\n"); 148 | printToStdOut(@"-c: Dump Certificates\n"); 149 | printToStdOut(@"-k: Dump Keys\n"); 150 | printToStdOut(@"-s: Dump Selected Entitlement Group\n"); 151 | } 152 | 153 | void dumpKeychainEntitlements() { 154 | const char *dbpath = [databasePath UTF8String]; 155 | sqlite3 *keychainDB; 156 | sqlite3_stmt *statement; 157 | NSMutableString *entitlementXML = [NSMutableString stringWithString:@"\n" 158 | "\n" 159 | "\n" 160 | "\t\n" 161 | "\t\tkeychain-access-groups\n" 162 | "\t\t\n"]; 163 | if (sqlite3_open(dbpath, &keychainDB) == SQLITE_OK) { 164 | const char *query_stmt = "select distinct agrp from genp union select distinct agrp from inet union select distinct agrp from cert union select distinct agrp from keys;"; 165 | 166 | if (sqlite3_prepare_v2(keychainDB, query_stmt, -1, &statement, NULL) == SQLITE_OK) { 167 | while(sqlite3_step(statement) == SQLITE_ROW) { 168 | NSString *group = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)]; 169 | 170 | [entitlementXML appendFormat:@"\t\t\t%@\n", group]; 171 | [group release]; 172 | } 173 | sqlite3_finalize(statement); 174 | } 175 | else { 176 | printToStdOut(@"Unknown error querying keychain database\n"); 177 | } 178 | 179 | [entitlementXML appendString:@"\t\t\n" 180 | "\t\n" 181 | "\n"]; 182 | sqlite3_close(keychainDB); 183 | printToStdOut(@"%@", entitlementXML); 184 | } 185 | else { 186 | printToStdOut(@"Unknown error opening keychain database\n"); 187 | } 188 | } 189 | 190 | void listEntitlements() { 191 | NSMutableArray *entitlementsArray = [[NSMutableArray alloc] init]; 192 | const char *dbpath = [databasePath UTF8String]; 193 | sqlite3 *keychainDB; 194 | sqlite3_stmt *statement; 195 | if (sqlite3_open(dbpath, &keychainDB) == SQLITE_OK) { 196 | const char *query_all = "select distinct agrp from genp union select distinct agrp from inet union select distinct agrp from cert union select distinct agrp from keys;"; 197 | if (sqlite3_prepare_v2(keychainDB, query_all, -1, &statement, NULL) == SQLITE_OK) { 198 | printToStdOut(@"%s[INFO] Listing available Entitlement Groups:\n%s", KGRN, KWHT); 199 | int index = 0; 200 | while(sqlite3_step(statement) == SQLITE_ROW) { 201 | NSString *group = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)]; 202 | printToStdOut(@"Entitlement Group [%i]: %@\n",index, group); 203 | [entitlementsArray addObject:group]; 204 | [group release]; 205 | index += 1; 206 | } 207 | sqlite3_finalize(statement); 208 | } 209 | else { 210 | printToStdOut(@"%s[ERROR] Unknown error querying keychain database\n%s", KRED, KWHT); 211 | return; 212 | } 213 | 214 | sqlite3_close(keychainDB); 215 | } 216 | else { 217 | printToStdOut(@"%s[ERROR] Unknown error opening keychain database\n%s", KRED, KWHT); 218 | return; 219 | } 220 | char userSelectionBuf[128] = {0}; 221 | printToStdOut(@"%s[ACTION] Select Entitlement Group by Number, split with ',': %s", KGRN, KWHT); 222 | scanf("%s", userSelectionBuf); 223 | NSMutableArray* userSelectionList = [NSMutableArray array]; 224 | NSString* userSelectInfo = [NSString stringWithUTF8String:userSelectionBuf]; 225 | NSArray* userSelectList = [userSelectInfo componentsSeparatedByString:@","]; 226 | [userSelectList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 227 | int number = [obj intValue]; 228 | if(number >= 0 && number < [entitlementsArray count]){ 229 | NSString* item = [entitlementsArray objectAtIndex:number]; 230 | [userSelectionList addObject:item]; 231 | } 232 | }]; 233 | selectedEntitlementConstantList = userSelectionList; 234 | } 235 | 236 | NSMutableArray *getCommandLineOptions(int argc, char **argv) { 237 | NSMutableArray *arguments = [[NSMutableArray alloc] init]; 238 | int argument; 239 | if (argc == 1) { 240 | [arguments addObject:(id)kSecClassGenericPassword]; 241 | [arguments addObject:(id)kSecClassInternetPassword]; 242 | return [arguments autorelease]; 243 | } 244 | while ((argument = getopt (argc, argv, "aegnickhs")) != -1) { 245 | switch(argument) { 246 | case 's': 247 | listEntitlements(); 248 | [arguments addObject:(id)kSecClassGenericPassword]; 249 | [arguments addObject:(id)kSecClassInternetPassword]; 250 | [arguments addObject:(id)kSecClassIdentity]; 251 | [arguments addObject:(id)kSecClassCertificate]; 252 | [arguments addObject:(id)kSecClassKey]; 253 | return [arguments autorelease]; 254 | case 'a': 255 | [arguments addObject:(id)kSecClassGenericPassword]; 256 | [arguments addObject:(id)kSecClassInternetPassword]; 257 | [arguments addObject:(id)kSecClassIdentity]; 258 | [arguments addObject:(id)kSecClassCertificate]; 259 | [arguments addObject:(id)kSecClassKey]; 260 | return [arguments autorelease]; 261 | case 'e': 262 | [arguments addObject:@"dumpEntitlements"]; 263 | return [arguments autorelease]; 264 | case 'g': 265 | [arguments addObject:(id)kSecClassGenericPassword]; 266 | break; 267 | case 'n': 268 | [arguments addObject:(id)kSecClassInternetPassword]; 269 | break; 270 | case 'i': 271 | [arguments addObject:(id)kSecClassIdentity]; 272 | break; 273 | case 'c': 274 | [arguments addObject:(id)kSecClassCertificate]; 275 | break; 276 | case 'k': 277 | [arguments addObject:(id)kSecClassKey]; 278 | break; 279 | case 'h': 280 | printUsage(); 281 | break; 282 | case '?': 283 | printUsage(); 284 | exit(EXIT_FAILURE); 285 | default: 286 | continue; 287 | } 288 | } 289 | return [arguments autorelease]; 290 | } 291 | 292 | NSArray * getKeychainObjectsForSecClass(CFTypeRef kSecClassType) { 293 | NSMutableDictionary *genericQuery = [[NSMutableDictionary alloc] init]; 294 | [genericQuery setObject:(id)kSecClassType forKey:(id)kSecClass]; 295 | [genericQuery setObject:(id)kSecMatchLimitAll forKey:(id)kSecMatchLimit]; 296 | [genericQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; 297 | [genericQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnRef]; 298 | [genericQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; 299 | NSArray *keychainItems = nil; 300 | if (SecItemCopyMatching((CFDictionaryRef)genericQuery, (CFTypeRef *)&keychainItems) != noErr) { 301 | keychainItems = nil; 302 | } 303 | [genericQuery release]; 304 | return keychainItems; 305 | } 306 | 307 | NSString * getEmptyKeychainItemString(CFTypeRef kSecClassType) { 308 | if (kSecClassType == kSecClassGenericPassword) { 309 | return @"[INFO] No Generic Password Keychain items found.\n[HINT] You should unlock your device!\n"; 310 | } else if (kSecClassType == kSecClassInternetPassword) { 311 | return @"[INFO] No Internet Password Keychain items found.\n[HINT] You should unlock your device!\n"; 312 | } else if (kSecClassType == kSecClassIdentity) { 313 | return @"[INFO] No Identity Keychain items found.\n[HINT] You should unlock your device!\n"; 314 | } else if (kSecClassType == kSecClassCertificate) { 315 | return @"[INFO] No Certificate Keychain items found.\n[HINT] You should unlock your device!\n"; 316 | } else if (kSecClassType == kSecClassKey) { 317 | return @"[INFO] No Key Keychain items found.\n[HINT] You should unlock your device!\n"; 318 | } else { 319 | return @"[INFO] Unknown Security Class\n[HINT] You should unlock your device!\n"; 320 | } 321 | } 322 | 323 | void printAccessibleAttribute(NSString *accessibleString) { 324 | if ([accessibleString isEqualToString:@"dk"]) { 325 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleAlways, protection level 0\n%s", KRED, KWHT); 326 | } else if ([accessibleString isEqualToString:@"ak"]) { 327 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleWhenUnlocked, protection level 2 (default)\n%s", KYEL, KWHT); 328 | } else if ([accessibleString isEqualToString:@"ck"]) { 329 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleAfterFirstUnlock, protection level 1\n%s", KRED, KWHT); 330 | } else if ([accessibleString isEqualToString:@"dku"]) { 331 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleAlwaysThisDeviceOnly, protection level 3\n%s", KBLU, KWHT); 332 | } else if ([accessibleString isEqualToString:@"aku"]) { 333 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, protection level 5\n%s", KBLU, KWHT); 334 | } else if ([accessibleString isEqualToString:@"cku"]) { 335 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, protection level 4\n%s", KBLU, KWHT); 336 | } else if ([accessibleString isEqualToString:@"akpu"]) { 337 | printToStdOut(@"%sAccessible Attribute: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, protection level 6\n%s",KGRN, KWHT); 338 | } else { 339 | printToStdOut(@"%sUnknown Accessible Attribute: %@\n%s", KRED, accessibleString, KWHT); 340 | } 341 | } 342 | 343 | void printSecAccessControl(NSObject *accessControlObject){ 344 | NSString* accessControlString = accessControlObject.debugDescription; 345 | if ([accessControlString rangeOfString:@"pbioc"].location != NSNotFound && [accessControlString rangeOfString:@"pbioh"].location != NSNotFound) { 346 | printToStdOut(@"%sSecAccessControl flag: .biometryCurrentSet%s\n", KGRN, KWHT); 347 | }else if ([accessControlString rangeOfString:@"pbioc"].location != NSNotFound && [accessControlString rangeOfString:@"pbioh"].location == NSNotFound) { 348 | printToStdOut(@"%sSecAccessControl flag: .biometryAny%s\n", KYEL, KWHT); 349 | }else if ([accessControlString rangeOfString:@"cpo(DeviceOwnerAuthentication)"].location != NSNotFound) { 350 | printToStdOut(@"%sSecAccessControl flag: .userPresence%s\n", KRED, KWHT); 351 | }else if ([accessControlString rangeOfString:@"cup(true));odel(true)"].location != NSNotFound) { 352 | printToStdOut(@"%sSecAccessControl flag: .devicePasscode%s\n", KRED, KWHT); 353 | }; 354 | } 355 | 356 | void printGenericPassword(NSDictionary *passwordItem) { 357 | printToStdOut(@"Generic Password\n"); 358 | printToStdOut(@"----------------\n"); 359 | printToStdOut(@"Service: %@\n", [passwordItem objectForKey:(id)kSecAttrService]); 360 | printToStdOut(@"Account: %@\n", [passwordItem objectForKey:(id)kSecAttrAccount]); 361 | printToStdOut(@"Entitlement Group: %@\n", [passwordItem objectForKey:(id)kSecAttrAccessGroup]); 362 | printToStdOut(@"Label: %@\n", [passwordItem objectForKey:(id)kSecAttrLabel]); 363 | NSString* accessibleString = [passwordItem objectForKey:(id)kSecAttrAccessible]; 364 | printAccessibleAttribute(accessibleString); 365 | printToStdOut(@"Description: %@\n", [passwordItem objectForKey:(id)kSecAttrDescription]); 366 | printToStdOut(@"Comment: %@\n", [passwordItem objectForKey:(id)kSecAttrComment]); 367 | printToStdOut(@"Synchronizable: %@\n", [passwordItem objectForKey:(id)kSecAttrSynchronizable]); 368 | printToStdOut(@"Generic Field: %@\n", [[passwordItem objectForKey:(id)kSecAttrGeneric] description]); 369 | NSData* passwordData = [passwordItem objectForKey:(id)kSecValueData]; 370 | printDataToStdOut("Keychain Data", passwordData); 371 | } 372 | 373 | void printInternetPassword(NSDictionary *passwordItem) { 374 | printToStdOut(@"Internet Password\n"); 375 | printToStdOut(@"-----------------\n"); 376 | printToStdOut(@"Server: %@\n", [passwordItem objectForKey:(id)kSecAttrServer]); 377 | printToStdOut(@"Account: %@\n", [passwordItem objectForKey:(id)kSecAttrAccount]); 378 | printToStdOut(@"Entitlement Group: %@\n", [passwordItem objectForKey:(id)kSecAttrAccessGroup]); 379 | printToStdOut(@"Label: %@\n", [passwordItem objectForKey:(id)kSecAttrLabel]); 380 | NSString* accessibleString = [passwordItem objectForKey:(id)kSecAttrAccessible]; 381 | printAccessibleAttribute(accessibleString); 382 | printSecAccessControl([passwordItem objectForKey:(id)kSecAttrAccessControl]); 383 | NSData* passwordData = [passwordItem objectForKey:(id)kSecValueData]; 384 | printDataToStdOut("Keychain Data", passwordData); 385 | } 386 | 387 | void printCertificate(NSDictionary *certificateItem) { 388 | SecCertificateRef certificate = (SecCertificateRef)[certificateItem objectForKey:(id)kSecValueRef]; 389 | CFStringRef summary; 390 | summary = SecCertificateCopySubjectSummary(certificate); 391 | printToStdOut(@"Certificate\n"); 392 | printToStdOut(@"-----------\n"); 393 | printToStdOut(@"Summary: %@\n", (NSString *)summary); 394 | CFRelease(summary); 395 | printToStdOut(@"Entitlement Group: %@\n", [certificateItem objectForKey:(id)kSecAttrAccessGroup]); 396 | printToStdOut(@"Label: %@\n", [certificateItem objectForKey:(id)kSecAttrLabel]); 397 | NSString* accessibleString = [certificateItem objectForKey:(id)kSecAttrAccessible]; 398 | printAccessibleAttribute(accessibleString); 399 | printSecAccessControl([certificateItem objectForKey:(id)kSecAttrAccessControl]); 400 | printToStdOut(@"Serial Number: %@\n", [certificateItem objectForKey:(id)kSecAttrSerialNumber]); 401 | printToStdOut(@"Subject Key ID: %@\n", [certificateItem objectForKey:(id)kSecAttrSubjectKeyID]); 402 | printToStdOut(@"Subject Key Hash: %@\n\n", [certificateItem objectForKey:(id)kSecAttrPublicKeyHash]); 403 | printCertPEM(certificateItem[@"certdata"]); 404 | } 405 | 406 | void printKey(NSDictionary *keyItem) { 407 | NSString *keyClass = @"Unknown"; 408 | //NSLog(@"%@", keyItem); //Debugging purposes 409 | CFTypeRef _keyClass = [keyItem objectForKey:(id)kSecAttrKeyClass]; 410 | CFTypeRef _keyType = [keyItem objectForKey:(id)kSecAttrKeyType]; 411 | int keySize = [[keyItem objectForKey:(id)kSecAttrKeySizeInBits] intValue]; 412 | int effectiveKeySize = [[keyItem objectForKey:(id)kSecAttrEffectiveKeySize] intValue]; 413 | if ([[(id)_keyClass description] isEqual:(id)kSecAttrKeyClassPublic]) { 414 | keyClass = @"Public"; 415 | } else if ([[(id)_keyClass description] isEqual:(id)kSecAttrKeyClassPrivate]) { 416 | keyClass = @"Private"; 417 | } else if ([[(id)_keyClass description] isEqual:(id)kSecAttrKeyClassSymmetric]) { 418 | keyClass = @"Symmetric"; 419 | } 420 | printToStdOut(@"Key\n"); 421 | printToStdOut(@"---\n"); 422 | printToStdOut(@"Entitlement Group: %@\n", [keyItem objectForKey:(id)kSecAttrAccessGroup]); 423 | printToStdOut(@"Label: %@\n", [keyItem objectForKey:(id)kSecAttrLabel]); 424 | NSString* accessibleString = [keyItem objectForKey:(id)kSecAttrAccessible]; 425 | printAccessibleAttribute(accessibleString); 426 | printSecAccessControl([keyItem objectForKey:(id)kSecAttrAccessControl]); 427 | printToStdOut(@"Application Label: %@\n", [keyItem objectForKey:(id)kSecAttrApplicationLabel]); 428 | printToStdOut(@"Application Tag: %@\n", [keyItem objectForKey:(id)kSecAttrApplicationTag]); 429 | printToStdOut(@"Key Class: %@\n", keyClass); 430 | printToStdOut(@"Key Size: %@\n", [keyItem objectForKey:(id)kSecAttrKeySizeInBits]); 431 | printToStdOut(@"Effective Key Size: %@\n", [keyItem objectForKey:(id)kSecAttrEffectiveKeySize]); 432 | if ((keySize == effectiveKeySize) && (keySize != 0)) { 433 | printToStdOut(@"Permanent Key: %@\n", [keyItem objectForKey:(id)kSecAttrIsPermanent] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrIsPermanent]) == true ? @"True" :@"False"); 434 | printToStdOut(@"For Encryption: %@\n", [keyItem objectForKey:(id)kSecAttrCanEncrypt] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanEncrypt]) == true ? @"True" :@"False"); 435 | printToStdOut(@"For Decryption: %@\n", [keyItem objectForKey:(id)kSecAttrCanDecrypt] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanDecrypt]) == true ? @"True" :@"False"); 436 | printToStdOut(@"For Key Derivation: %@\n", [keyItem objectForKey:(id)kSecAttrCanDerive] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanDerive]) == true ? @"True" :@"False"); 437 | printToStdOut(@"For Signatures: %@\n", [keyItem objectForKey:(id)kSecAttrCanSign] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanSign]) == true ? @"True" :@"False"); 438 | printToStdOut(@"For Signature Verification: %@\n", [keyItem objectForKey:(id)kSecAttrCanVerify] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanVerify]) == true ? @"True" :@"False"); 439 | printToStdOut(@"For Key Wrapping: %@\n", [keyItem objectForKey:(id)kSecAttrCanWrap] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanWrap]) == true ? @"True" :@"False"); 440 | printToStdOut(@"For Key Unwrapping: %@\n\n", [keyItem objectForKey:(id)kSecAttrCanUnwrap] == nil ? @"Empty" : CFBooleanGetValue((CFBooleanRef)[keyItem objectForKey:(id)kSecAttrCanUnwrap]) == true ? @"True" :@"False"); 441 | if (([[(id)_keyType description]isEqual:(id)kSecAttrKeyTypeRSA])&&([[(id)_keyClass description] isEqual:(id)kSecAttrKeyClassPublic])) { 442 | printToStdOut(@"RSA public key data:\n"); 443 | printPublicKeyPEM(keyItem[@"v_Data"]); 444 | } else if (([[(id)_keyType description]isEqual:(id)kSecAttrKeyTypeRSA])&&([[(id)_keyClass description] isEqual:(id)kSecAttrKeyClassPrivate])) { 445 | printToStdOut(@"RSA private key data:\n"); 446 | printPrivateKeyPEM(keyItem[@"v_Data"]); 447 | } else { 448 | printToStdOut(@"[INFO] Key data (EC, Symmetric, ...) output not implemented yet. Stay tuned.\n"); 449 | } 450 | } else { 451 | printToStdOut(@"[INFO] Malformed key data detected. Check/Cleanup KeyChain manually.\n"); 452 | } 453 | printToStdOut(@"\n"); 454 | } 455 | 456 | void printIdentity(NSDictionary *identityItem) { 457 | SecIdentityRef identity = (SecIdentityRef)[identityItem objectForKey:(id)kSecValueRef]; 458 | SecCertificateRef certificate; 459 | SecIdentityCopyCertificate(identity, &certificate); 460 | NSMutableDictionary *identityItemWithCertificate = [identityItem mutableCopy]; 461 | [identityItemWithCertificate setObject:(id)certificate forKey:(id)kSecValueRef]; 462 | printToStdOut(@"Identity\n"); 463 | printToStdOut(@"--------\n"); 464 | printCertificate(identityItemWithCertificate); 465 | printKey(identityItemWithCertificate); 466 | [identityItemWithCertificate release]; 467 | } 468 | 469 | void printResultsForSecClass(NSArray *keychainItems, CFTypeRef kSecClassType) { 470 | if (keychainItems == nil) { 471 | printToStdOut(getEmptyKeychainItemString(kSecClassType)); 472 | return; 473 | } 474 | NSDictionary *keychainItem; 475 | for (keychainItem in keychainItems) { 476 | if (kSecClassType == kSecClassGenericPassword) { 477 | if ([selectedEntitlementConstantList count] == 0) { 478 | printGenericPassword(keychainItem); 479 | } else { 480 | if ([selectedEntitlementConstantList containsObject:[keychainItem objectForKey:(id)kSecAttrAccessGroup]]) { 481 | printGenericPassword(keychainItem); 482 | } 483 | } 484 | } else if (kSecClassType == kSecClassInternetPassword) { 485 | if ([selectedEntitlementConstantList count] == 0) { 486 | printInternetPassword(keychainItem); 487 | } else { 488 | if ([selectedEntitlementConstantList containsObject:[keychainItem objectForKey:(id)kSecAttrAccessGroup]]) { 489 | printInternetPassword(keychainItem); 490 | } 491 | } 492 | } else if (kSecClassType == kSecClassIdentity) { 493 | if ([selectedEntitlementConstantList count] == 0) { 494 | printIdentity(keychainItem); 495 | } else { 496 | if ([selectedEntitlementConstantList containsObject:[keychainItem objectForKey:(id)kSecAttrAccessGroup]]) { 497 | printIdentity(keychainItem); 498 | } 499 | } 500 | } else if (kSecClassType == kSecClassCertificate) { 501 | if ([selectedEntitlementConstantList count] == 0) { 502 | printCertificate(keychainItem); 503 | } else { 504 | if ([selectedEntitlementConstantList containsObject:[keychainItem objectForKey:(id)kSecAttrAccessGroup]]) { 505 | printCertificate(keychainItem); 506 | } 507 | } 508 | } else if (kSecClassType == kSecClassKey) { 509 | if ([selectedEntitlementConstantList count] == 0) { 510 | printKey(keychainItem); 511 | } else { 512 | if ([selectedEntitlementConstantList containsObject:[keychainItem objectForKey:(id)kSecAttrAccessGroup]]) { 513 | printKey(keychainItem); 514 | } 515 | } 516 | } 517 | } 518 | return; 519 | } 520 | 521 | int main(int argc, char **argv) { 522 | id pool=[NSAutoreleasePool new]; 523 | NSArray* arguments; 524 | arguments = getCommandLineOptions(argc, argv); 525 | NSArray *passwordItems; 526 | if ([arguments indexOfObject:@"dumpEntitlements"] != NSNotFound) { 527 | dumpKeychainEntitlements(); 528 | exit(EXIT_SUCCESS); 529 | } 530 | NSArray *keychainItems = nil; 531 | for (id kSecClassType in (NSArray *) arguments) { 532 | keychainItems = getKeychainObjectsForSecClass((CFTypeRef)kSecClassType); 533 | printResultsForSecClass(keychainItems, (CFTypeRef)kSecClassType); 534 | [keychainItems release]; 535 | } 536 | [pool drain]; 537 | } 538 | -------------------------------------------------------------------------------- /setup_on_iOS.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm /usr/bin/keychain_dumper 3 | ldid -Sentitlements.xml keychain_dumper 4 | mv keychain_dumper /usr/bin 5 | -------------------------------------------------------------------------------- /updateEntitlements.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Original keychain_dumper by Patrick Toomey 3 | #Scrpt by @ReverseThatApp and @vocaeq 4 | 5 | KEYCHAIN_DUMPER_FOLDER=/usr/bin 6 | if [ ! -d "$KEYCHAIN_DUMPER_FOLDER" ] ; then 7 | mkdir "$KEYCHAIN_DUMPER_FOLDER" ; 8 | fi 9 | 10 | if [ ! -f "$KEYCHAIN_DUMPER_FOLDER/keychain_dumper" ]; then 11 | echo "The file \"$KEYCHAIN_DUMPER_FOLDER/keychain_dumper\" does not exist. " \ 12 | "Move the binary into the folder \"$KEYCHAIN_DUMPER_FOLDER/\" and run the script again." 13 | exit 1 14 | fi 15 | 16 | # set -e ; 17 | 18 | ENTITLEMENT_PATH=$KEYCHAIN_DUMPER_FOLDER/ent.xml 19 | dbKeychainArray=() 20 | declare -a invalidKeychainArray=("com.apple.bluetooth" 21 | "com.apple.cfnetwork" 22 | "com.apple.cloudd" 23 | "com.apple.continuity.encryption" 24 | "com.apple.continuity.unlock" 25 | "com.apple.icloud.searchpartyd" 26 | "com.apple.ind" 27 | "com.apple.mobilesafari" 28 | "com.apple.rapport" 29 | "com.apple.sbd" 30 | "com.apple.security.sos" 31 | "com.apple.siri.osprey" 32 | "com.apple.telephonyutilities.callservicesd" 33 | "ichat" 34 | "wifianalyticsd" 35 | ) 36 | 37 | echo "" > $ENTITLEMENT_PATH 38 | echo "" >> $ENTITLEMENT_PATH 39 | echo "" >> $ENTITLEMENT_PATH 40 | echo " " >> $ENTITLEMENT_PATH 41 | echo " keychain-access-groups" >> $ENTITLEMENT_PATH 42 | echo " " >> $ENTITLEMENT_PATH 43 | 44 | sqlite3 /var/Keychains/keychain-2.db "SELECT DISTINCT agrp FROM genp" > ./allgroups.txt 45 | sqlite3 /var/Keychains/keychain-2.db "SELECT DISTINCT agrp FROM cert" >> ./allgroups.txt 46 | sqlite3 /var/Keychains/keychain-2.db "SELECT DISTINCT agrp FROM inet" >> ./allgroups.txt 47 | sqlite3 /var/Keychains/keychain-2.db "SELECT DISTINCT agrp FROM keys" >> ./allgroups.txt 48 | 49 | while IFS= read -r line; do 50 | dbKeychainArray+=("$line") 51 | if [[ ! " ${invalidKeychainArray[@]} " =~ " ${line} " ]]; then 52 | echo " ${line}">> $ENTITLEMENT_PATH 53 | else 54 | echo "Skipping ${line}" 55 | fi 56 | done < ./allgroups.txt 57 | 58 | # cat ./allgroups.txt | sed 's/.*/\ \ \ \ \ \ \ \ \&\<\/string\>/' >> $ENTITLEMENT_PATH 59 | rm ./allgroups.txt 60 | 61 | echo " ">> $ENTITLEMENT_PATH 62 | echo " platform-application ">> $ENTITLEMENT_PATH 63 | echo " com.apple.private.security.no-container ">> $ENTITLEMENT_PATH 64 | echo " ">> $ENTITLEMENT_PATH 65 | echo "">> $ENTITLEMENT_PATH 66 | 67 | cd $KEYCHAIN_DUMPER_FOLDER 68 | ldid -Sent.xml keychain_dumper 69 | rm ent.xml 70 | echo "Entitlements updated" 71 | --------------------------------------------------------------------------------