├── Entitlements.plist ├── IOKit.c ├── IOKit.h ├── IOReturn.h ├── LICENSE ├── Makefile ├── README.md ├── SecDbKeychainSerializedAKSWrappedKey.proto ├── SecDbKeychainSerializedAKSWrappedKey_pb2.py ├── SecDbKeychainSerializedItemV7.proto ├── SecDbKeychainSerializedItemV7_pb2.py ├── SecDbKeychainSerializedMetadata.proto ├── SecDbKeychainSerializedMetadata_pb2.py ├── SecDbKeychainSerializedSecretData.proto ├── SecDbKeychainSerializedSecretData_pb2.py ├── ccl_bplist.py ├── keychain_decrypt.py ├── keyclass_unwrapper ├── keyclass_unwrapper.c └── requirements.txt /Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.keystore.access-keychain-keys 6 | 7 | com.apple.keystore.device 8 | 9 | platform-application 10 | com.apple.private.security.no-container 11 | 12 | 13 | -------------------------------------------------------------------------------- /IOKit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "IOKit.h" 5 | 6 | struct ioconnectCache { 7 | const char* serviceName; 8 | io_connect_t conn; 9 | }; 10 | #define IOCONNECT_CACHE_ENTRIES 10 11 | 12 | struct ioconnectCache cache[IOCONNECT_CACHE_ENTRIES]={{NULL, 0}}; 13 | 14 | void __attribute__((destructor)) IOKit_destruct() 15 | { 16 | int i; 17 | for (i=0; i < IOCONNECT_CACHE_ENTRIES && cache[i].conn != 0; i++) { 18 | //printf("Closing %s\n", cache[i].serviceName); 19 | IOServiceClose(cache[i].conn); 20 | } 21 | } 22 | 23 | io_connect_t IOKit_getConnect(const char* serviceName) 24 | { 25 | IOReturn ret; 26 | io_connect_t conn = 0; 27 | int i; 28 | 29 | for (i=0; i < IOCONNECT_CACHE_ENTRIES && cache[i].serviceName != NULL; i++) { 30 | if (!strcmp(serviceName, cache[i].serviceName)) 31 | { 32 | //printf("got cache for %s\n", serviceName); 33 | return cache[i].conn; 34 | } 35 | } 36 | 37 | CFMutableDictionaryRef dict = IOServiceMatching(serviceName); 38 | io_service_t dev = IOServiceGetMatchingService(kIOMasterPortDefault, dict); 39 | 40 | if(!dev) { 41 | fprintf(stderr, "FAIL: Could not get %s service\n", serviceName); 42 | return -1; 43 | } 44 | 45 | ret = IOServiceOpen(dev, mach_task_self(), 0, &conn); 46 | 47 | IOObjectRelease(dev); 48 | if(ret != kIOReturnSuccess) { 49 | fprintf(stderr, "FAIL: Cannot open service %s\n", serviceName); 50 | return -1; 51 | } 52 | 53 | if (i < 10) { 54 | cache[i].serviceName = serviceName; 55 | cache[i].conn = conn; 56 | } 57 | 58 | return conn; 59 | } 60 | 61 | IOReturn IOKit_call(const char* serviceName, 62 | uint32_t selector, 63 | const uint64_t *input, 64 | uint32_t inputCnt, 65 | const void *inputStruct, 66 | size_t inputStructCnt, 67 | uint64_t *output, 68 | uint32_t *outputCnt, 69 | void *outputStruct, 70 | size_t *outputStructCnt) 71 | { 72 | IOReturn ret; 73 | io_connect_t conn = IOKit_getConnect(serviceName); 74 | 75 | ret = IOConnectCallMethod(conn, 76 | selector, 77 | input, 78 | inputCnt, 79 | inputStruct, 80 | inputStructCnt, 81 | output, 82 | outputCnt, 83 | outputStruct, 84 | outputStructCnt); 85 | 86 | if (ret != kIOReturnSuccess) 87 | { 88 | fprintf(stderr, "IOConnectCallMethod returned %x\n", ret); 89 | } 90 | 91 | return ret; 92 | } 93 | -------------------------------------------------------------------------------- /IOKit.h: -------------------------------------------------------------------------------- 1 | //#include 2 | #ifndef __IOKIT_H 3 | #define __IOKIT_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include "IOReturn.h" 9 | 10 | typedef mach_port_t io_connect_t; 11 | typedef mach_port_t io_service_t; 12 | typedef mach_port_t io_object_t; 13 | const mach_port_t kIOMasterPortDefault; 14 | 15 | kern_return_t 16 | IOServiceOpen( 17 | io_service_t service, 18 | task_port_t owningTask, 19 | uint32_t type, 20 | io_connect_t * connect ); 21 | 22 | kern_return_t 23 | IOServiceClose( 24 | io_connect_t connect ); 25 | 26 | CFMutableDictionaryRef 27 | IOServiceMatching( 28 | const char * name ); 29 | 30 | io_service_t 31 | IOServiceGetMatchingService( 32 | mach_port_t masterPort, 33 | CFDictionaryRef matching ); 34 | 35 | kern_return_t 36 | IOObjectRelease( 37 | io_object_t object ); 38 | 39 | kern_return_t 40 | IOConnectCallMethod( 41 | mach_port_t connection, // In 42 | uint32_t selector, // In 43 | const uint64_t *input, // In 44 | uint32_t inputCnt, // In 45 | const void *inputStruct, // In 46 | size_t inputStructCnt, // In 47 | uint64_t *output, // Out 48 | uint32_t *outputCnt, // In/Out 49 | void *outputStruct, // Out 50 | size_t *outputStructCnt); // In/Out 51 | 52 | kern_return_t 53 | IOConnectCallStructMethod( 54 | mach_port_t connection, // In 55 | uint32_t selector, // In 56 | const void *inputStruct, // In 57 | size_t inputStructCnt, // In 58 | void *outputStruct, // Out 59 | size_t *outputStructCnt); // In/Out 60 | 61 | //iokit wrappers 62 | io_connect_t IOKit_getConnect(const char* serviceName); 63 | 64 | IOReturn IOKit_call(const char* serviceName, 65 | uint32_t selector, 66 | const uint64_t *input, 67 | uint32_t inputCnt, 68 | const void *inputStruct, 69 | size_t inputStructCnt, 70 | uint64_t *output, 71 | uint32_t *outputCnt, 72 | void *outputStruct, 73 | size_t *outputStructCnt); 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /IOReturn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1998-2002 Apple Computer, Inc. All rights reserved. 3 | * 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ 5 | * 6 | * This file contains Original Code and/or Modifications of Original Code 7 | * as defined in and that are subject to the Apple Public Source License 8 | * Version 2.0 (the 'License'). You may not use this file except in 9 | * compliance with the License. The rights granted to you under the License 10 | * may not be used to create, or enable the creation or redistribution of, 11 | * unlawful or unlicensed copies of an Apple operating system, or to 12 | * circumvent, violate, or enable the circumvention or violation of, any 13 | * terms of an Apple operating system software license agreement. 14 | * 15 | * Please obtain a copy of the License at 16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. 17 | * 18 | * The Original Code and all software distributed under the License are 19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 23 | * Please see the License for the specific language governing rights and 24 | * limitations under the License. 25 | * 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ 27 | */ 28 | /* 29 | * HISTORY 30 | */ 31 | 32 | /* 33 | * Core IOReturn values. Others may be family defined. 34 | */ 35 | 36 | #ifndef __IOKIT_IORETURN_H 37 | #define __IOKIT_IORETURN_H 38 | 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | #include 44 | 45 | typedef kern_return_t IOReturn; 46 | 47 | #ifndef sys_iokit 48 | #define sys_iokit err_system(0x38) 49 | #endif /* sys_iokit */ 50 | #define sub_iokit_common err_sub(0) 51 | #define sub_iokit_usb err_sub(1) 52 | #define sub_iokit_firewire err_sub(2) 53 | #define sub_iokit_block_storage err_sub(4) 54 | #define sub_iokit_graphics err_sub(5) 55 | #define sub_iokit_networking err_sub(6) 56 | #define sub_iokit_bluetooth err_sub(8) 57 | #define sub_iokit_pmu err_sub(9) 58 | #define sub_iokit_acpi err_sub(10) 59 | #define sub_iokit_smbus err_sub(11) 60 | #define sub_iokit_ahci err_sub(12) 61 | #define sub_iokit_powermanagement err_sub(13) 62 | #define sub_iokit_hidsystem err_sub(14) 63 | #define sub_iokit_scsi err_sub(16) 64 | //#define sub_iokit_pccard err_sub(21) 65 | #define sub_iokit_thunderbolt err_sub(29) 66 | 67 | #define sub_iokit_audio_video err_sub(0x45) 68 | #define sub_iokit_hsic err_sub(0x147) 69 | #define sub_iokit_sdio err_sub(0x174) 70 | #define sub_iokit_wlan err_sub(0x208) 71 | 72 | #define sub_iokit_vendor_specific err_sub(-2) 73 | #define sub_iokit_reserved err_sub(-1) 74 | 75 | #define iokit_common_err(return) (sys_iokit|sub_iokit_common|return) 76 | #define iokit_family_err(sub,return) (sys_iokit|sub|return) 77 | #define iokit_vendor_specific_err(return) (sys_iokit|sub_iokit_vendor_specific|return) 78 | 79 | #define kIOReturnSuccess KERN_SUCCESS // OK 80 | #define kIOReturnError iokit_common_err(0x2bc) // general error 81 | #define kIOReturnNoMemory iokit_common_err(0x2bd) // can't allocate memory 82 | #define kIOReturnNoResources iokit_common_err(0x2be) // resource shortage 83 | #define kIOReturnIPCError iokit_common_err(0x2bf) // error during IPC 84 | #define kIOReturnNoDevice iokit_common_err(0x2c0) // no such device 85 | #define kIOReturnNotPrivileged iokit_common_err(0x2c1) // privilege violation 86 | #define kIOReturnBadArgument iokit_common_err(0x2c2) // invalid argument 87 | #define kIOReturnLockedRead iokit_common_err(0x2c3) // device read locked 88 | #define kIOReturnLockedWrite iokit_common_err(0x2c4) // device write locked 89 | #define kIOReturnExclusiveAccess iokit_common_err(0x2c5) // exclusive access and 90 | // device already open 91 | #define kIOReturnBadMessageID iokit_common_err(0x2c6) // sent/received messages 92 | // had different msg_id 93 | #define kIOReturnUnsupported iokit_common_err(0x2c7) // unsupported function 94 | #define kIOReturnVMError iokit_common_err(0x2c8) // misc. VM failure 95 | #define kIOReturnInternalError iokit_common_err(0x2c9) // internal error 96 | #define kIOReturnIOError iokit_common_err(0x2ca) // General I/O error 97 | //#define kIOReturn???Error iokit_common_err(0x2cb) // ??? 98 | #define kIOReturnCannotLock iokit_common_err(0x2cc) // can't acquire lock 99 | #define kIOReturnNotOpen iokit_common_err(0x2cd) // device not open 100 | #define kIOReturnNotReadable iokit_common_err(0x2ce) // read not supported 101 | #define kIOReturnNotWritable iokit_common_err(0x2cf) // write not supported 102 | #define kIOReturnNotAligned iokit_common_err(0x2d0) // alignment error 103 | #define kIOReturnBadMedia iokit_common_err(0x2d1) // Media Error 104 | #define kIOReturnStillOpen iokit_common_err(0x2d2) // device(s) still open 105 | #define kIOReturnRLDError iokit_common_err(0x2d3) // rld failure 106 | #define kIOReturnDMAError iokit_common_err(0x2d4) // DMA failure 107 | #define kIOReturnBusy iokit_common_err(0x2d5) // Device Busy 108 | #define kIOReturnTimeout iokit_common_err(0x2d6) // I/O Timeout 109 | #define kIOReturnOffline iokit_common_err(0x2d7) // device offline 110 | #define kIOReturnNotReady iokit_common_err(0x2d8) // not ready 111 | #define kIOReturnNotAttached iokit_common_err(0x2d9) // device not attached 112 | #define kIOReturnNoChannels iokit_common_err(0x2da) // no DMA channels left 113 | #define kIOReturnNoSpace iokit_common_err(0x2db) // no space for data 114 | //#define kIOReturn???Error iokit_common_err(0x2dc) // ??? 115 | #define kIOReturnPortExists iokit_common_err(0x2dd) // port already exists 116 | #define kIOReturnCannotWire iokit_common_err(0x2de) // can't wire down 117 | // physical memory 118 | #define kIOReturnNoInterrupt iokit_common_err(0x2df) // no interrupt attached 119 | #define kIOReturnNoFrames iokit_common_err(0x2e0) // no DMA frames enqueued 120 | #define kIOReturnMessageTooLarge iokit_common_err(0x2e1) // oversized msg received 121 | // on interrupt port 122 | #define kIOReturnNotPermitted iokit_common_err(0x2e2) // not permitted 123 | #define kIOReturnNoPower iokit_common_err(0x2e3) // no power to device 124 | #define kIOReturnNoMedia iokit_common_err(0x2e4) // media not present 125 | #define kIOReturnUnformattedMedia iokit_common_err(0x2e5)// media not formatted 126 | #define kIOReturnUnsupportedMode iokit_common_err(0x2e6) // no such mode 127 | #define kIOReturnUnderrun iokit_common_err(0x2e7) // data underrun 128 | #define kIOReturnOverrun iokit_common_err(0x2e8) // data overrun 129 | #define kIOReturnDeviceError iokit_common_err(0x2e9) // the device is not working properly! 130 | #define kIOReturnNoCompletion iokit_common_err(0x2ea) // a completion routine is required 131 | #define kIOReturnAborted iokit_common_err(0x2eb) // operation aborted 132 | #define kIOReturnNoBandwidth iokit_common_err(0x2ec) // bus bandwidth would be exceeded 133 | #define kIOReturnNotResponding iokit_common_err(0x2ed) // device not responding 134 | #define kIOReturnIsoTooOld iokit_common_err(0x2ee) // isochronous I/O request for distant past! 135 | #define kIOReturnIsoTooNew iokit_common_err(0x2ef) // isochronous I/O request for distant future 136 | #define kIOReturnNotFound iokit_common_err(0x2f0) // data was not found 137 | #define kIOReturnInvalid iokit_common_err(0x1) // should never be seen 138 | 139 | #ifdef __cplusplus 140 | } 141 | #endif 142 | 143 | #endif /* ! __IOKIT_IORETURN_H */ 144 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GCC_BIN="`xcrun --sdk iphoneos --find clang`" 2 | SDK="`xcrun --sdk iphoneos --show-sdk-path`" 3 | ARCH_FLAGS=-arch arm64 4 | 5 | LDFLAGS =\ 6 | -F$(SDK)/System/Library/Frameworks/\ 7 | -framework Foundation\ 8 | -framework Security\ 9 | -framework IOKit\ 10 | -I. 11 | 12 | GCC_ARM = $(GCC_BIN) -Os -Wimplicit -isysroot $(SDK) $(ARCH_FLAGS) 13 | 14 | keyclass_unwrapper: keyclass_unwrapper.c IOKit.c 15 | @$(GCC_ARM) $(LDFLAGS) -o $@ $^ 16 | codesign -s - --entitlements Entitlements.plist $@ 17 | 18 | clean: 19 | rm -f keyclass_unwrapper *.o 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS Keychain Decrypter 2 | Small script to decrypt keychains on iOS. 3 | Needs an agent on the iDevice to unwrap keys. Rest of decryption/parsing id done on host 4 | 5 | Tested on an iPhone 7 iOS 14.0 6 | 7 | This works on MacOS Catalina and should work on Linux 8 | Windows support may require to adapt ssh commandlines 9 | 10 | ## Requirements 11 | ``` 12 | pip install requirements.txt 13 | ``` 14 | Jailbroken device accessible via ssh (default checkra1n behaviour) 15 | `sshpass`, `ssh` and `iproxy` configured in your PATH 16 | 17 | To compile phone agent, Xcode should be installed. 18 | 19 | ## Dump my keychain 20 | 1. jailbreak your device 21 | 2. run iproxy in a terminal mapping localport 2222 - for checkra1ned devices: 22 | ``` 23 | iproxy 2222 44 24 | ``` 25 | 4. open a new terminal window 26 | 5. Upload the agent on your device 27 | ``` 28 | sshpass -p alpine scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -P2222 keyclass_unwrapper root@localhost: 29 | ``` 30 | 6. Download keychain database from your device 31 | ``` 32 | sshpass -p alpine scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -P2222 root@localhost:/private/var/Keychains/keychain-2.db . 33 | ``` 34 | 7. **unlock your device and keep it unlocked** until the dump is finished 35 | 8. run the python script 36 | ``` 37 | python3 keychain_decrypt.py 38 | ``` 39 | 9. You should obtain a keychain_decrypted.plist file 40 | If an error occure, try again, sometimes it is a timing problem 41 | 42 | 10. Clean 43 | ``` 44 | sshpass -p alpine ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p2222 root@localhost "rm /var/root/keyclass_unwrapper; shutdown -h now" 45 | ``` 46 | 47 | ## Self compile the agent 48 | You should have an identity to sign the code 49 | ``` 50 | make 51 | ``` 52 | 53 | ## Credits 54 | - [iChainbreaker](https://github.com/n0fate/iChainbreaker) 55 | - [iphone-dataprotection.keychainviewer]( )https://github.com/nabla-c0d3/iphone-dataprotection.keychainviewer/tree/master/Keychain) 56 | - [Apple Open Sources]( )https://opensource.apple.com/source/Security/Security-59306.80.4/keychain/securityd/) 57 | 58 | ## Licence 59 | GPL V2 60 | 61 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedAKSWrappedKey.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | message SecDbKeychainSerializedAKSWrappedKey { 4 | required bytes wrappedKey = 1; 5 | optional bytes refKeyBlob = 2; 6 | required uint32 type = 3; 7 | } 8 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedAKSWrappedKey_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: SecDbKeychainSerializedAKSWrappedKey.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='SecDbKeychainSerializedAKSWrappedKey.proto', 18 | package='', 19 | syntax='proto2', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n*SecDbKeychainSerializedAKSWrappedKey.proto\"\\\n$SecDbKeychainSerializedAKSWrappedKey\x12\x12\n\nwrappedKey\x18\x01 \x02(\x0c\x12\x12\n\nrefKeyBlob\x18\x02 \x01(\x0c\x12\x0c\n\x04type\x18\x03 \x02(\r' 23 | ) 24 | 25 | 26 | 27 | 28 | _SECDBKEYCHAINSERIALIZEDAKSWRAPPEDKEY = _descriptor.Descriptor( 29 | name='SecDbKeychainSerializedAKSWrappedKey', 30 | full_name='SecDbKeychainSerializedAKSWrappedKey', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | create_key=_descriptor._internal_create_key, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='wrappedKey', full_name='SecDbKeychainSerializedAKSWrappedKey.wrappedKey', index=0, 38 | number=1, type=12, cpp_type=9, label=2, 39 | has_default_value=False, default_value=b"", 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 43 | _descriptor.FieldDescriptor( 44 | name='refKeyBlob', full_name='SecDbKeychainSerializedAKSWrappedKey.refKeyBlob', index=1, 45 | number=2, type=12, cpp_type=9, label=1, 46 | has_default_value=False, default_value=b"", 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 50 | _descriptor.FieldDescriptor( 51 | name='type', full_name='SecDbKeychainSerializedAKSWrappedKey.type', index=2, 52 | number=3, type=13, cpp_type=3, label=2, 53 | has_default_value=False, default_value=0, 54 | message_type=None, enum_type=None, containing_type=None, 55 | is_extension=False, extension_scope=None, 56 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 57 | ], 58 | extensions=[ 59 | ], 60 | nested_types=[], 61 | enum_types=[ 62 | ], 63 | serialized_options=None, 64 | is_extendable=False, 65 | syntax='proto2', 66 | extension_ranges=[], 67 | oneofs=[ 68 | ], 69 | serialized_start=46, 70 | serialized_end=138, 71 | ) 72 | 73 | DESCRIPTOR.message_types_by_name['SecDbKeychainSerializedAKSWrappedKey'] = _SECDBKEYCHAINSERIALIZEDAKSWRAPPEDKEY 74 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 75 | 76 | SecDbKeychainSerializedAKSWrappedKey = _reflection.GeneratedProtocolMessageType('SecDbKeychainSerializedAKSWrappedKey', (_message.Message,), { 77 | 'DESCRIPTOR' : _SECDBKEYCHAINSERIALIZEDAKSWRAPPEDKEY, 78 | '__module__' : 'SecDbKeychainSerializedAKSWrappedKey_pb2' 79 | # @@protoc_insertion_point(class_scope:SecDbKeychainSerializedAKSWrappedKey) 80 | }) 81 | _sym_db.RegisterMessage(SecDbKeychainSerializedAKSWrappedKey) 82 | 83 | 84 | # @@protoc_insertion_point(module_scope) 85 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedItemV7.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | message SecDbKeychainSerializedItemV7 { 4 | required bytes encryptedSecretData = 1; 5 | required bytes encryptedMetadata = 2; 6 | 7 | enum Keyclass { 8 | KEYCLASS_AK = 6; 9 | KEYCLASS_CK = 7; 10 | KEYCLASS_DK = 8; 11 | KEYCLASS_AKU = 9; 12 | KEYCLASS_CKU = 10; 13 | KEYCLASS_DKU = 11; 14 | KEYCLASS_AKPU = 12; 15 | } 16 | required Keyclass keyclass = 3 [default = KEYCLASS_AKPU]; 17 | } 18 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedItemV7_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: SecDbKeychainSerializedItemV7.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='SecDbKeychainSerializedItemV7.proto', 18 | package='', 19 | syntax='proto2', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n#SecDbKeychainSerializedItemV7.proto\"\xaa\x02\n\x1dSecDbKeychainSerializedItemV7\x12\x1b\n\x13\x65ncryptedSecretData\x18\x01 \x02(\x0c\x12\x19\n\x11\x65ncryptedMetadata\x18\x02 \x02(\x0c\x12H\n\x08keyclass\x18\x03 \x02(\x0e\x32\'.SecDbKeychainSerializedItemV7.Keyclass:\rKEYCLASS_AKPU\"\x86\x01\n\x08Keyclass\x12\x0f\n\x0bKEYCLASS_AK\x10\x06\x12\x0f\n\x0bKEYCLASS_CK\x10\x07\x12\x0f\n\x0bKEYCLASS_DK\x10\x08\x12\x10\n\x0cKEYCLASS_AKU\x10\t\x12\x10\n\x0cKEYCLASS_CKU\x10\n\x12\x10\n\x0cKEYCLASS_DKU\x10\x0b\x12\x11\n\rKEYCLASS_AKPU\x10\x0c' 23 | ) 24 | 25 | 26 | 27 | _SECDBKEYCHAINSERIALIZEDITEMV7_KEYCLASS = _descriptor.EnumDescriptor( 28 | name='Keyclass', 29 | full_name='SecDbKeychainSerializedItemV7.Keyclass', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | create_key=_descriptor._internal_create_key, 33 | values=[ 34 | _descriptor.EnumValueDescriptor( 35 | name='KEYCLASS_AK', index=0, number=6, 36 | serialized_options=None, 37 | type=None, 38 | create_key=_descriptor._internal_create_key), 39 | _descriptor.EnumValueDescriptor( 40 | name='KEYCLASS_CK', index=1, number=7, 41 | serialized_options=None, 42 | type=None, 43 | create_key=_descriptor._internal_create_key), 44 | _descriptor.EnumValueDescriptor( 45 | name='KEYCLASS_DK', index=2, number=8, 46 | serialized_options=None, 47 | type=None, 48 | create_key=_descriptor._internal_create_key), 49 | _descriptor.EnumValueDescriptor( 50 | name='KEYCLASS_AKU', index=3, number=9, 51 | serialized_options=None, 52 | type=None, 53 | create_key=_descriptor._internal_create_key), 54 | _descriptor.EnumValueDescriptor( 55 | name='KEYCLASS_CKU', index=4, number=10, 56 | serialized_options=None, 57 | type=None, 58 | create_key=_descriptor._internal_create_key), 59 | _descriptor.EnumValueDescriptor( 60 | name='KEYCLASS_DKU', index=5, number=11, 61 | serialized_options=None, 62 | type=None, 63 | create_key=_descriptor._internal_create_key), 64 | _descriptor.EnumValueDescriptor( 65 | name='KEYCLASS_AKPU', index=6, number=12, 66 | serialized_options=None, 67 | type=None, 68 | create_key=_descriptor._internal_create_key), 69 | ], 70 | containing_type=None, 71 | serialized_options=None, 72 | serialized_start=204, 73 | serialized_end=338, 74 | ) 75 | _sym_db.RegisterEnumDescriptor(_SECDBKEYCHAINSERIALIZEDITEMV7_KEYCLASS) 76 | 77 | 78 | _SECDBKEYCHAINSERIALIZEDITEMV7 = _descriptor.Descriptor( 79 | name='SecDbKeychainSerializedItemV7', 80 | full_name='SecDbKeychainSerializedItemV7', 81 | filename=None, 82 | file=DESCRIPTOR, 83 | containing_type=None, 84 | create_key=_descriptor._internal_create_key, 85 | fields=[ 86 | _descriptor.FieldDescriptor( 87 | name='encryptedSecretData', full_name='SecDbKeychainSerializedItemV7.encryptedSecretData', index=0, 88 | number=1, type=12, cpp_type=9, label=2, 89 | has_default_value=False, default_value=b"", 90 | message_type=None, enum_type=None, containing_type=None, 91 | is_extension=False, extension_scope=None, 92 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 93 | _descriptor.FieldDescriptor( 94 | name='encryptedMetadata', full_name='SecDbKeychainSerializedItemV7.encryptedMetadata', index=1, 95 | number=2, type=12, cpp_type=9, label=2, 96 | has_default_value=False, default_value=b"", 97 | message_type=None, enum_type=None, containing_type=None, 98 | is_extension=False, extension_scope=None, 99 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 100 | _descriptor.FieldDescriptor( 101 | name='keyclass', full_name='SecDbKeychainSerializedItemV7.keyclass', index=2, 102 | number=3, type=14, cpp_type=8, label=2, 103 | has_default_value=True, default_value=12, 104 | message_type=None, enum_type=None, containing_type=None, 105 | is_extension=False, extension_scope=None, 106 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 107 | ], 108 | extensions=[ 109 | ], 110 | nested_types=[], 111 | enum_types=[ 112 | _SECDBKEYCHAINSERIALIZEDITEMV7_KEYCLASS, 113 | ], 114 | serialized_options=None, 115 | is_extendable=False, 116 | syntax='proto2', 117 | extension_ranges=[], 118 | oneofs=[ 119 | ], 120 | serialized_start=40, 121 | serialized_end=338, 122 | ) 123 | 124 | _SECDBKEYCHAINSERIALIZEDITEMV7.fields_by_name['keyclass'].enum_type = _SECDBKEYCHAINSERIALIZEDITEMV7_KEYCLASS 125 | _SECDBKEYCHAINSERIALIZEDITEMV7_KEYCLASS.containing_type = _SECDBKEYCHAINSERIALIZEDITEMV7 126 | DESCRIPTOR.message_types_by_name['SecDbKeychainSerializedItemV7'] = _SECDBKEYCHAINSERIALIZEDITEMV7 127 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 128 | 129 | SecDbKeychainSerializedItemV7 = _reflection.GeneratedProtocolMessageType('SecDbKeychainSerializedItemV7', (_message.Message,), { 130 | 'DESCRIPTOR' : _SECDBKEYCHAINSERIALIZEDITEMV7, 131 | '__module__' : 'SecDbKeychainSerializedItemV7_pb2' 132 | # @@protoc_insertion_point(class_scope:SecDbKeychainSerializedItemV7) 133 | }) 134 | _sym_db.RegisterMessage(SecDbKeychainSerializedItemV7) 135 | 136 | 137 | # @@protoc_insertion_point(module_scope) 138 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedMetadata.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | message SecDbKeychainSerializedMetadata { 4 | required bytes ciphertext = 1; 5 | required bytes wrappedKey = 2; 6 | required string tamperCheck = 3; 7 | } 8 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedMetadata_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: SecDbKeychainSerializedMetadata.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='SecDbKeychainSerializedMetadata.proto', 18 | package='', 19 | syntax='proto2', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n%SecDbKeychainSerializedMetadata.proto\"^\n\x1fSecDbKeychainSerializedMetadata\x12\x12\n\nciphertext\x18\x01 \x02(\x0c\x12\x12\n\nwrappedKey\x18\x02 \x02(\x0c\x12\x13\n\x0btamperCheck\x18\x03 \x02(\t' 23 | ) 24 | 25 | 26 | 27 | 28 | _SECDBKEYCHAINSERIALIZEDMETADATA = _descriptor.Descriptor( 29 | name='SecDbKeychainSerializedMetadata', 30 | full_name='SecDbKeychainSerializedMetadata', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | create_key=_descriptor._internal_create_key, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='ciphertext', full_name='SecDbKeychainSerializedMetadata.ciphertext', index=0, 38 | number=1, type=12, cpp_type=9, label=2, 39 | has_default_value=False, default_value=b"", 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 43 | _descriptor.FieldDescriptor( 44 | name='wrappedKey', full_name='SecDbKeychainSerializedMetadata.wrappedKey', index=1, 45 | number=2, type=12, cpp_type=9, label=2, 46 | has_default_value=False, default_value=b"", 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 50 | _descriptor.FieldDescriptor( 51 | name='tamperCheck', full_name='SecDbKeychainSerializedMetadata.tamperCheck', index=2, 52 | number=3, type=9, cpp_type=9, label=2, 53 | has_default_value=False, default_value=b"".decode('utf-8'), 54 | message_type=None, enum_type=None, containing_type=None, 55 | is_extension=False, extension_scope=None, 56 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 57 | ], 58 | extensions=[ 59 | ], 60 | nested_types=[], 61 | enum_types=[ 62 | ], 63 | serialized_options=None, 64 | is_extendable=False, 65 | syntax='proto2', 66 | extension_ranges=[], 67 | oneofs=[ 68 | ], 69 | serialized_start=41, 70 | serialized_end=135, 71 | ) 72 | 73 | DESCRIPTOR.message_types_by_name['SecDbKeychainSerializedMetadata'] = _SECDBKEYCHAINSERIALIZEDMETADATA 74 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 75 | 76 | SecDbKeychainSerializedMetadata = _reflection.GeneratedProtocolMessageType('SecDbKeychainSerializedMetadata', (_message.Message,), { 77 | 'DESCRIPTOR' : _SECDBKEYCHAINSERIALIZEDMETADATA, 78 | '__module__' : 'SecDbKeychainSerializedMetadata_pb2' 79 | # @@protoc_insertion_point(class_scope:SecDbKeychainSerializedMetadata) 80 | }) 81 | _sym_db.RegisterMessage(SecDbKeychainSerializedMetadata) 82 | 83 | 84 | # @@protoc_insertion_point(module_scope) 85 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedSecretData.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | message SecDbKeychainSerializedSecretData { 4 | required bytes ciphertext = 1; 5 | required bytes wrappedKey = 2; 6 | required string tamperCheck = 3; 7 | optional bytes SecDbBackupWrappedItemKey = 4; 8 | } 9 | -------------------------------------------------------------------------------- /SecDbKeychainSerializedSecretData_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: SecDbKeychainSerializedSecretData.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='SecDbKeychainSerializedSecretData.proto', 18 | package='', 19 | syntax='proto2', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n\'SecDbKeychainSerializedSecretData.proto\"\x83\x01\n!SecDbKeychainSerializedSecretData\x12\x12\n\nciphertext\x18\x01 \x02(\x0c\x12\x12\n\nwrappedKey\x18\x02 \x02(\x0c\x12\x13\n\x0btamperCheck\x18\x03 \x02(\t\x12!\n\x19SecDbBackupWrappedItemKey\x18\x04 \x01(\x0c' 23 | ) 24 | 25 | 26 | 27 | 28 | _SECDBKEYCHAINSERIALIZEDSECRETDATA = _descriptor.Descriptor( 29 | name='SecDbKeychainSerializedSecretData', 30 | full_name='SecDbKeychainSerializedSecretData', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | create_key=_descriptor._internal_create_key, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='ciphertext', full_name='SecDbKeychainSerializedSecretData.ciphertext', index=0, 38 | number=1, type=12, cpp_type=9, label=2, 39 | has_default_value=False, default_value=b"", 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 43 | _descriptor.FieldDescriptor( 44 | name='wrappedKey', full_name='SecDbKeychainSerializedSecretData.wrappedKey', index=1, 45 | number=2, type=12, cpp_type=9, label=2, 46 | has_default_value=False, default_value=b"", 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 50 | _descriptor.FieldDescriptor( 51 | name='tamperCheck', full_name='SecDbKeychainSerializedSecretData.tamperCheck', index=2, 52 | number=3, type=9, cpp_type=9, label=2, 53 | has_default_value=False, default_value=b"".decode('utf-8'), 54 | message_type=None, enum_type=None, containing_type=None, 55 | is_extension=False, extension_scope=None, 56 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 57 | _descriptor.FieldDescriptor( 58 | name='SecDbBackupWrappedItemKey', full_name='SecDbKeychainSerializedSecretData.SecDbBackupWrappedItemKey', index=3, 59 | number=4, type=12, cpp_type=9, label=1, 60 | has_default_value=False, default_value=b"", 61 | message_type=None, enum_type=None, containing_type=None, 62 | is_extension=False, extension_scope=None, 63 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 64 | ], 65 | extensions=[ 66 | ], 67 | nested_types=[], 68 | enum_types=[ 69 | ], 70 | serialized_options=None, 71 | is_extendable=False, 72 | syntax='proto2', 73 | extension_ranges=[], 74 | oneofs=[ 75 | ], 76 | serialized_start=44, 77 | serialized_end=175, 78 | ) 79 | 80 | DESCRIPTOR.message_types_by_name['SecDbKeychainSerializedSecretData'] = _SECDBKEYCHAINSERIALIZEDSECRETDATA 81 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 82 | 83 | SecDbKeychainSerializedSecretData = _reflection.GeneratedProtocolMessageType('SecDbKeychainSerializedSecretData', (_message.Message,), { 84 | 'DESCRIPTOR' : _SECDBKEYCHAINSERIALIZEDSECRETDATA, 85 | '__module__' : 'SecDbKeychainSerializedSecretData_pb2' 86 | # @@protoc_insertion_point(class_scope:SecDbKeychainSerializedSecretData) 87 | }) 88 | _sym_db.RegisterMessage(SecDbKeychainSerializedSecretData) 89 | 90 | 91 | # @@protoc_insertion_point(module_scope) 92 | -------------------------------------------------------------------------------- /ccl_bplist.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2012-2016, CCL Forensics 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the CCL Forensics nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL CCL FORENSICS BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | """ 27 | 28 | import sys 29 | import os 30 | import struct 31 | import datetime 32 | 33 | __version__ = "0.21" 34 | __description__ = "Converts Apple binary PList files into a native Python data structure" 35 | __contact__ = "Alex Caithness" 36 | 37 | _object_converter = None 38 | def set_object_converter(function): 39 | """Sets the object converter function to be used when retrieving objects from the bplist. 40 | default is None (which will return objects in their raw form). 41 | A built in converter (ccl_bplist.NSKeyedArchiver_common_objects_convertor) which is geared 42 | toward dealling with common types in NSKeyedArchiver is available which can simplify code greatly 43 | when dealling with these types of files.""" 44 | if not hasattr(function, "__call__"): 45 | raise TypeError("function is not a function") 46 | global _object_converter 47 | _object_converter = function 48 | 49 | class BplistError(Exception): 50 | pass 51 | 52 | class BplistUID: 53 | def __init__(self, value): 54 | self.value = value 55 | 56 | def __repr__(self): 57 | return "UID: {0}".format(self.value) 58 | 59 | def __str__(self): 60 | return self.__repr__() 61 | 62 | def __decode_multibyte_int(b, signed=True): 63 | if len(b) == 1: 64 | fmt = ">B" # Always unsigned? 65 | elif len(b) == 2: 66 | fmt = ">h" 67 | elif len(b) == 3: 68 | if signed: 69 | return ((b[0] << 16) | struct.unpack(">H", b[1:])[0]) - ((b[0] >> 7) * 2 * 0x800000) 70 | else: 71 | return (b[0] << 16) | struct.unpack(">H", b[1:])[0] 72 | elif len(b) == 4: 73 | fmt = ">i" 74 | elif len(b) == 8: 75 | fmt = ">q" 76 | elif len(b) == 16: 77 | # special case for BigIntegers 78 | high, low = struct.unpack(">QQ", b) 79 | result = (high << 64) | low 80 | if high & 0x8000000000000000 and signed: 81 | result -= 0x100000000000000000000000000000000 82 | return result 83 | else: 84 | raise BplistError("Cannot decode multibyte int of length {0}".format(len(b))) 85 | 86 | if signed and len(b) > 1: 87 | return struct.unpack(fmt.lower(), b)[0] 88 | else: 89 | return struct.unpack(fmt.upper(), b)[0] 90 | 91 | def __decode_float(b, signed=True): 92 | if len(b) == 4: 93 | fmt = ">f" 94 | elif len(b) == 8: 95 | fmt = ">d" 96 | else: 97 | raise BplistError("Cannot decode float of length {0}".format(len(b))) 98 | 99 | if signed: 100 | return struct.unpack(fmt.lower(), b)[0] 101 | else: 102 | return struct.unpack(fmt.upper(), b)[0] 103 | 104 | def __decode_object(f, offset, collection_offset_size, offset_table): 105 | # Move to offset and read type 106 | #print("Decoding object at offset {0}".format(offset)) 107 | f.seek(offset) 108 | # A little hack to keep the script portable between py2.x and py3k 109 | if sys.version_info[0] < 3: 110 | type_byte = ord(f.read(1)[0]) 111 | else: 112 | type_byte = f.read(1)[0] 113 | #print("Type byte: {0}".format(hex(type_byte))) 114 | if type_byte == 0x00: # Null 0000 0000 115 | return None 116 | elif type_byte == 0x08: # False 0000 1000 117 | return False 118 | elif type_byte == 0x09: # True 0000 1001 119 | return True 120 | elif type_byte == 0x0F: # Fill 0000 1111 121 | raise BplistError("Fill type not currently supported at offset {0}".format(f.tell())) # Not sure what to return really... 122 | elif type_byte & 0xF0 == 0x10: # Int 0001 xxxx 123 | int_length = 2 ** (type_byte & 0x0F) 124 | int_bytes = f.read(int_length) 125 | return __decode_multibyte_int(int_bytes) 126 | elif type_byte & 0xF0 == 0x20: # Float 0010 nnnn 127 | float_length = 2 ** (type_byte & 0x0F) 128 | float_bytes = f.read(float_length) 129 | return __decode_float(float_bytes) 130 | elif type_byte & 0xFF == 0x33: # Date 0011 0011 131 | date_bytes = f.read(8) 132 | date_value = __decode_float(date_bytes) 133 | try: 134 | result = datetime.datetime(2001,1,1) + datetime.timedelta(seconds = date_value) 135 | except OverflowError: 136 | result = datetime.datetime.min 137 | return result 138 | elif type_byte & 0xF0 == 0x40: # Data 0100 nnnn 139 | if type_byte & 0x0F != 0x0F: 140 | # length in 4 lsb 141 | data_length = type_byte & 0x0F 142 | else: 143 | # A little hack to keep the script portable between py2.x and py3k 144 | if sys.version_info[0] < 3: 145 | int_type_byte = ord(f.read(1)[0]) 146 | else: 147 | int_type_byte = f.read(1)[0] 148 | if int_type_byte & 0xF0 != 0x10: 149 | raise BplistError("Long Data field definition not followed by int type at offset {0}".format(f.tell())) 150 | int_length = 2 ** (int_type_byte & 0x0F) 151 | int_bytes = f.read(int_length) 152 | data_length = __decode_multibyte_int(int_bytes, False) 153 | return f.read(data_length) 154 | elif type_byte & 0xF0 == 0x50: # ASCII 0101 nnnn 155 | if type_byte & 0x0F != 0x0F: 156 | # length in 4 lsb 157 | ascii_length = type_byte & 0x0F 158 | else: 159 | # A little hack to keep the script portable between py2.x and py3k 160 | if sys.version_info[0] < 3: 161 | int_type_byte = ord(f.read(1)[0]) 162 | else: 163 | int_type_byte = f.read(1)[0] 164 | if int_type_byte & 0xF0 != 0x10: 165 | raise BplistError("Long ASCII field definition not followed by int type at offset {0}".format(f.tell())) 166 | int_length = 2 ** (int_type_byte & 0x0F) 167 | int_bytes = f.read(int_length) 168 | ascii_length = __decode_multibyte_int(int_bytes, False) 169 | return f.read(ascii_length).decode("ascii") 170 | elif type_byte & 0xF0 == 0x60: # UTF-16 0110 nnnn 171 | if type_byte & 0x0F != 0x0F: 172 | # length in 4 lsb 173 | utf16_length = (type_byte & 0x0F) * 2 # Length is characters - 16bit width 174 | else: 175 | # A little hack to keep the script portable between py2.x and py3k 176 | if sys.version_info[0] < 3: 177 | int_type_byte = ord(f.read(1)[0]) 178 | else: 179 | int_type_byte = f.read(1)[0] 180 | if int_type_byte & 0xF0 != 0x10: 181 | raise BplistError("Long UTF-16 field definition not followed by int type at offset {0}".format(f.tell())) 182 | int_length = 2 ** (int_type_byte & 0x0F) 183 | int_bytes = f.read(int_length) 184 | utf16_length = __decode_multibyte_int(int_bytes, False) * 2 185 | return f.read(utf16_length).decode("utf_16_be") 186 | elif type_byte & 0xF0 == 0x80: # UID 1000 nnnn 187 | uid_length = (type_byte & 0x0F) + 1 188 | uid_bytes = f.read(uid_length) 189 | return BplistUID(__decode_multibyte_int(uid_bytes, signed=False)) 190 | elif type_byte & 0xF0 == 0xA0: # Array 1010 nnnn 191 | if type_byte & 0x0F != 0x0F: 192 | # length in 4 lsb 193 | array_count = type_byte & 0x0F 194 | else: 195 | # A little hack to keep the script portable between py2.x and py3k 196 | if sys.version_info[0] < 3: 197 | int_type_byte = ord(f.read(1)[0]) 198 | else: 199 | int_type_byte = f.read(1)[0] 200 | if int_type_byte & 0xF0 != 0x10: 201 | raise BplistError("Long Array field definition not followed by int type at offset {0}".format(f.tell())) 202 | int_length = 2 ** (int_type_byte & 0x0F) 203 | int_bytes = f.read(int_length) 204 | array_count = __decode_multibyte_int(int_bytes, signed=False) 205 | array_refs = [] 206 | for i in range(array_count): 207 | array_refs.append(__decode_multibyte_int(f.read(collection_offset_size), False)) 208 | return [__decode_object(f, offset_table[obj_ref], collection_offset_size, offset_table) for obj_ref in array_refs] 209 | elif type_byte & 0xF0 == 0xC0: # Set 1010 nnnn 210 | if type_byte & 0x0F != 0x0F: 211 | # length in 4 lsb 212 | set_count = type_byte & 0x0F 213 | else: 214 | # A little hack to keep the script portable between py2.x and py3k 215 | if sys.version_info[0] < 3: 216 | int_type_byte = ord(f.read(1)[0]) 217 | else: 218 | int_type_byte = f.read(1)[0] 219 | if int_type_byte & 0xF0 != 0x10: 220 | raise BplistError("Long Set field definition not followed by int type at offset {0}".format(f.tell())) 221 | int_length = 2 ** (int_type_byte & 0x0F) 222 | int_bytes = f.read(int_length) 223 | set_count = __decode_multibyte_int(int_bytes, signed=False) 224 | set_refs = [] 225 | for i in range(set_count): 226 | set_refs.append(__decode_multibyte_int(f.read(collection_offset_size), False)) 227 | return [__decode_object(f, offset_table[obj_ref], collection_offset_size, offset_table) for obj_ref in set_refs] 228 | elif type_byte & 0xF0 == 0xD0: # Dict 1011 nnnn 229 | if type_byte & 0x0F != 0x0F: 230 | # length in 4 lsb 231 | dict_count = type_byte & 0x0F 232 | else: 233 | # A little hack to keep the script portable between py2.x and py3k 234 | if sys.version_info[0] < 3: 235 | int_type_byte = ord(f.read(1)[0]) 236 | else: 237 | int_type_byte = f.read(1)[0] 238 | #print("Dictionary length int byte: {0}".format(hex(int_type_byte))) 239 | if int_type_byte & 0xF0 != 0x10: 240 | raise BplistError("Long Dict field definition not followed by int type at offset {0}".format(f.tell())) 241 | int_length = 2 ** (int_type_byte & 0x0F) 242 | int_bytes = f.read(int_length) 243 | dict_count = __decode_multibyte_int(int_bytes, signed=False) 244 | key_refs = [] 245 | #print("Dictionary count: {0}".format(dict_count)) 246 | for i in range(dict_count): 247 | key_refs.append(__decode_multibyte_int(f.read(collection_offset_size), False)) 248 | value_refs = [] 249 | for i in range(dict_count): 250 | value_refs.append(__decode_multibyte_int(f.read(collection_offset_size), False)) 251 | 252 | dict_result = {} 253 | for i in range(dict_count): 254 | #print("Key ref: {0}\tVal ref: {1}".format(key_refs[i], value_refs[i])) 255 | key = __decode_object(f, offset_table[key_refs[i]], collection_offset_size, offset_table) 256 | val = __decode_object(f, offset_table[value_refs[i]], collection_offset_size, offset_table) 257 | dict_result[key] = val 258 | return dict_result 259 | 260 | 261 | def load(f): 262 | """ 263 | Reads and converts a file-like object containing a binary property list. 264 | Takes a file-like object (must support reading and seeking) as an argument 265 | Returns a data structure representing the data in the property list 266 | """ 267 | # Check magic number 268 | if f.read(8) != b"bplist00": 269 | raise BplistError("Bad file header") 270 | 271 | # Read trailer 272 | f.seek(-32, os.SEEK_END) 273 | trailer = f.read(32) 274 | offset_int_size, collection_offset_size, object_count, top_level_object_index, offest_table_offset = struct.unpack(">6xbbQQQ", trailer) 275 | 276 | # Read offset table 277 | f.seek(offest_table_offset) 278 | offset_table = [] 279 | for i in range(object_count): 280 | offset_table.append(__decode_multibyte_int(f.read(offset_int_size), False)) 281 | 282 | return __decode_object(f, offset_table[top_level_object_index], collection_offset_size, offset_table) 283 | 284 | 285 | def NSKeyedArchiver_common_objects_convertor(o): 286 | """Built in converter function (suitable for submission to set_object_converter()) which automatically 287 | converts the following common data-types found in NSKeyedArchiver: 288 | NSDictionary/NSMutableDictionary; 289 | NSArray/NSMutableArray; 290 | NSSet/NSMutableSet 291 | NSString/NSMutableString 292 | NSDate 293 | $null strings""" 294 | # Conversion: NSDictionary 295 | if is_nsmutabledictionary(o): 296 | return convert_NSMutableDictionary(o) 297 | # Conversion: NSArray 298 | elif is_nsarray(o): 299 | return convert_NSArray(o) 300 | elif is_isnsset(o): 301 | return convert_NSSet(o) 302 | # Conversion: NSString 303 | elif is_nsstring(o): 304 | return convert_NSString(o) 305 | # Conversion: NSDate 306 | elif is_nsdate(o): 307 | return convert_NSDate(o) 308 | # Conversion: "$null" string 309 | elif isinstance(o, str) and o == "$null": 310 | return None 311 | # Fallback: 312 | else: 313 | return o 314 | 315 | def NSKeyedArchiver_convert(o, object_table): 316 | if isinstance(o, list): 317 | #return NsKeyedArchiverList(o, object_table) 318 | result = NsKeyedArchiverList(o, object_table) 319 | elif isinstance(o, dict): 320 | #return NsKeyedArchiverDictionary(o, object_table) 321 | result = NsKeyedArchiverDictionary(o, object_table) 322 | elif isinstance(o, BplistUID): 323 | #return NSKeyedArchiver_convert(object_table[o.value], object_table) 324 | result = NSKeyedArchiver_convert(object_table[o.value], object_table) 325 | else: 326 | #return o 327 | result = o 328 | 329 | if _object_converter: 330 | return _object_converter(result) 331 | else: 332 | return result 333 | 334 | 335 | class NsKeyedArchiverDictionary(dict): 336 | def __init__(self, original_dict, object_table): 337 | super(NsKeyedArchiverDictionary, self).__init__(original_dict) 338 | self.object_table = object_table 339 | 340 | def __getitem__(self, index): 341 | o = super(NsKeyedArchiverDictionary, self).__getitem__(index) 342 | return NSKeyedArchiver_convert(o, self.object_table) 343 | 344 | def get(self, key, default=None): 345 | return self[key] if key in self else default 346 | 347 | def values(self): 348 | for k in self: 349 | yield self[k] 350 | 351 | def items(self): 352 | for k in self: 353 | yield k, self[k] 354 | 355 | class NsKeyedArchiverList(list): 356 | def __init__(self, original_iterable, object_table): 357 | super(NsKeyedArchiverList, self).__init__(original_iterable) 358 | self.object_table = object_table 359 | 360 | def __getitem__(self, index): 361 | o = super(NsKeyedArchiverList, self).__getitem__(index) 362 | return NSKeyedArchiver_convert(o, self.object_table) 363 | 364 | def __iter__(self): 365 | for o in super(NsKeyedArchiverList, self).__iter__(): 366 | yield NSKeyedArchiver_convert(o, self.object_table) 367 | 368 | 369 | def deserialise_NsKeyedArchiver(obj, parse_whole_structure=False): 370 | """Deserialises an NSKeyedArchiver bplist rebuilding the structure. 371 | obj should usually be the top-level object returned by the load() 372 | function.""" 373 | 374 | # Check that this is an archiver and version we understand 375 | if not isinstance(obj, dict): 376 | raise TypeError("obj must be a dict") 377 | if "$archiver" not in obj or obj["$archiver"] not in ("NSKeyedArchiver", "NRKeyedArchiver"): 378 | raise ValueError("obj does not contain an '$archiver' key or the '$archiver' is unrecognised") 379 | if "$version" not in obj or obj["$version"] != 100000: 380 | raise ValueError("obj does not contain a '$version' key or the '$version' is unrecognised") 381 | 382 | object_table = obj["$objects"] 383 | if "root" in obj["$top"] and not parse_whole_structure: 384 | return NSKeyedArchiver_convert(obj["$top"]["root"], object_table) 385 | else: 386 | return NSKeyedArchiver_convert(obj["$top"], object_table) 387 | 388 | # NSMutableDictionary convenience functions 389 | def is_nsmutabledictionary(obj): 390 | if not isinstance(obj, dict): 391 | return False 392 | if "$class" not in obj.keys(): 393 | return False 394 | if obj["$class"].get("$classname") not in ("NSMutableDictionary", "NSDictionary"): 395 | return False 396 | if "NS.keys" not in obj.keys(): 397 | return False 398 | if "NS.objects" not in obj.keys(): 399 | return False 400 | 401 | return True 402 | 403 | def convert_NSMutableDictionary(obj): 404 | """Converts a NSKeyedArchiver serialised NSMutableDictionary into 405 | a straight dictionary (rather than two lists as it is serialised 406 | as)""" 407 | 408 | # The dictionary is serialised as two lists (one for keys and one 409 | # for values) which obviously removes all convenience afforded by 410 | # dictionaries. This function converts this structure to an 411 | # actual dictionary so that values can be accessed by key. 412 | 413 | if not is_nsmutabledictionary(obj): 414 | raise ValueError("obj does not have the correct structure for a NSDictionary/NSMutableDictionary serialised to a NSKeyedArchiver") 415 | keys = obj["NS.keys"] 416 | vals = obj["NS.objects"] 417 | 418 | # sense check the keys and values: 419 | if not isinstance(keys, list): 420 | raise TypeError("The 'NS.keys' value is an unexpected type (expected list; actual: {0}".format(type(keys))) 421 | if not isinstance(vals, list): 422 | raise TypeError("The 'NS.objects' value is an unexpected type (expected list; actual: {0}".format(type(vals))) 423 | if len(keys) != len(vals): 424 | raise ValueError("The length of the 'NS.keys' list ({0}) is not equal to that of the 'NS.objects ({1})".format(len(keys), len(vals))) 425 | 426 | result = {} 427 | for i,k in enumerate(keys): 428 | if k in result: 429 | raise ValueError("The 'NS.keys' list contains duplicate entries") 430 | result[k] = vals[i] 431 | 432 | return result 433 | 434 | # NSArray convenience functions 435 | def is_nsarray(obj): 436 | if not isinstance(obj, dict): 437 | return False 438 | if "$class" not in obj.keys(): 439 | return False 440 | if obj["$class"].get("$classname") not in ("NSArray", "NSMutableArray"): 441 | return False 442 | if "NS.objects" not in obj.keys(): 443 | return False 444 | 445 | return True 446 | 447 | def convert_NSArray(obj): 448 | if not is_nsarray(obj): 449 | raise ValueError("obj does not have the correct structure for a NSArray/NSMutableArray serialised to a NSKeyedArchiver") 450 | 451 | return obj["NS.objects"] 452 | 453 | # NSSet convenience functions 454 | def is_isnsset(obj): 455 | if not isinstance(obj, dict): 456 | return False 457 | if "$class" not in obj.keys(): 458 | return False 459 | if obj["$class"].get("$classname") not in ("NSSet", "NSMutableSet"): 460 | return False 461 | if "NS.objects" not in obj.keys(): 462 | return False 463 | 464 | return True 465 | 466 | def convert_NSSet(obj): 467 | if not is_isnsset(obj): 468 | raise ValueError("obj does not have the correct structure for a NSSet/NSMutableSet serialised to a NSKeyedArchiver") 469 | 470 | return list(obj["NS.objects"]) 471 | 472 | # NSString convenience functions 473 | def is_nsstring(obj): 474 | if not isinstance(obj, dict): 475 | return False 476 | if "$class" not in obj.keys(): 477 | return False 478 | if obj["$class"].get("$classname") not in ("NSString", "NSMutableString"): 479 | return False 480 | if "NS.string" not in obj.keys(): 481 | return False 482 | return True 483 | 484 | def convert_NSString(obj): 485 | if not is_nsstring(obj): 486 | raise ValueError("obj does not have the correct structure for a NSString/NSMutableString serialised to a NSKeyedArchiver") 487 | 488 | return obj["NS.string"] 489 | 490 | # NSDate convenience functions 491 | def is_nsdate(obj): 492 | if not isinstance(obj, dict): 493 | return False 494 | if "$class" not in obj.keys(): 495 | return False 496 | if obj["$class"].get("$classname") not in ("NSDate"): 497 | return False 498 | if "NS.time" not in obj.keys(): 499 | return False 500 | 501 | return True 502 | 503 | def convert_NSDate(obj): 504 | if not is_nsdate(obj): 505 | raise ValueError("obj does not have the correct structure for a NSDate serialised to a NSKeyedArchiver") 506 | 507 | return datetime.datetime(2001, 1, 1) + datetime.timedelta(seconds=obj["NS.time"]) -------------------------------------------------------------------------------- /keychain_decrypt.py: -------------------------------------------------------------------------------- 1 | ################################################################################################ 2 | # # 3 | # iOS Keychain Decrypter # 4 | # inspired by https://github.com/n0fate/iChainbreaker # 5 | # and https://github.com/nabla-c0d3/iphone-dataprotection.keychainviewer/tree/master/Keychain # 6 | # # 7 | # Copyright Matthieu Regnery 2020 # 8 | # # 9 | # This program is free software: you can redistribute it and/or modify # 10 | # it under the terms of the GNU General Public License as published by # 11 | # the Free Software Foundation, either version 3 of the License, or # 12 | # (at your option) any later version. # 13 | # # 14 | # This program is distributed in the hope that it will be useful, # 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 17 | # GNU General Public License for more details. # 18 | # # 19 | # You should have received a copy of the GNU General Public License # 20 | # along with this program. If not, see . # 21 | ################################################################################################ 22 | 23 | 24 | import sqlite3 25 | import pandas 26 | from struct import unpack 27 | import SecDbKeychainSerializedItemV7_pb2 28 | import SecDbKeychainSerializedSecretData_pb2 29 | import SecDbKeychainSerializedMetadata_pb2 30 | import SecDbKeychainSerializedAKSWrappedKey_pb2 31 | import subprocess 32 | import sys 33 | import os 34 | from binascii import hexlify, unhexlify 35 | import ccl_bplist 36 | from io import BytesIO 37 | from Crypto.Cipher import AES 38 | from pyasn1.codec.der.decoder import decode 39 | from base64 import b64encode 40 | import plistlib 41 | import time 42 | 43 | # path to connect to keychain db 44 | # can be found in /private/var/Keychains/keychain-2.db 45 | KEYCHAIN_DEFAULT_PAHT = "keychain-2.db" 46 | 47 | 48 | # itemv7 is encoded with protobuf. 49 | # definition taken from Apple Open Source (Security/keychain/securityd) 50 | # https://opensource.apple.com/source/Security/Security-59306.80.4/keychain/securityd/ 51 | def deserialize_data(rowitem): 52 | version = unpack('=6 : 79 | ssh = subprocess.Popen([ 80 | "sshpass", 81 | "-p", 82 | "alpine", 83 | "ssh", 84 | "-p2222", 85 | "-o", 86 | "UserKnownHostsFile=/dev/null", 87 | "-o", 88 | "StrictHostKeyChecking=no", 89 | "root@127.0.0.1", 90 | "./keyclass_unwrapper", 91 | hexlify(key).decode("ascii"), 92 | str(int(keyclass)) 93 | ], 94 | shell=False, 95 | stdout=subprocess.PIPE, 96 | stderr=subprocess.PIPE) 97 | time.sleep(0.1) 98 | out = ssh.stdout.readlines() 99 | while out == 0: 100 | out = ssh.stdout.readlines() 101 | time.sleep(1) 102 | if len(out) > 0: 103 | unwrapped_key = out[0] 104 | else: 105 | print ("error unwrapping key of keyclass {}: {} \n Trying again...".format(keyclass, hexlify(key))) 106 | unwrapped_key = unwrap_key(key, keyclass) 107 | 108 | return unwrapped_key 109 | 110 | 111 | # itemV7 has two main parts: 112 | # 1. secretData containing password or key 113 | # 2. metaData containing key/password name (ie acct) 114 | # decrypt secretData by : 115 | # - unwrapping key 116 | # - decrypting with AES GCM 117 | # - parsing resulting ASN1 DER 118 | def decrypt_secretData(item): 119 | if item['keyclass'] >=6 : 120 | unwrapped_key = unwrap_key(item['encryptedSecretData_wrappedKey'], item['keyclass']) 121 | bplist = BytesIO(item['encryptedSecretData_ciphertext']) 122 | plist = ccl_bplist.load(bplist) 123 | secretDataDeserialized = ccl_bplist.deserialise_NsKeyedArchiver(plist, parse_whole_structure=True) 124 | authCode = secretDataDeserialized['root']['SFAuthenticationCode'] 125 | iv = secretDataDeserialized['root']['SFInitializationVector'] 126 | ciphertext = secretDataDeserialized['root']['SFCiphertext'] 127 | 128 | gcm = AES.new(unhexlify(unwrapped_key)[:32], AES.MODE_GCM, iv) 129 | decrypted = gcm.decrypt_and_verify(ciphertext, authCode) 130 | 131 | der_data = decode(decrypted)[0] 132 | for k in der_data: 133 | if 'Octet' in str(type(k[1])): 134 | item['decrypted'].update({str(k[0]) : bytes(k[1])}) 135 | else: 136 | item['decrypted'].update({str(k[0]) : str(k[1])}) 137 | return item 138 | 139 | 140 | # decrypt Metadata by : 141 | # - unwrapping metadata key 142 | # - decrypting metadata key with AES GCM 143 | # - decrypting metadata with AES GCM 144 | # - parsing resulting ASN1 DER 145 | def decrypt_Metadata(item, df_meta): 146 | if item['keyclass'] >=6 : 147 | bplist = BytesIO(item['encryptedMetadata_wrappedKey']) 148 | plist = ccl_bplist.load(bplist) 149 | metaDataWrappedKeyDeserialized = ccl_bplist.deserialise_NsKeyedArchiver(plist, parse_whole_structure=True) 150 | authCode = metaDataWrappedKeyDeserialized['root']['SFAuthenticationCode'] 151 | iv = metaDataWrappedKeyDeserialized['root']['SFInitializationVector'] 152 | ciphertext = metaDataWrappedKeyDeserialized['root']['SFCiphertext'] 153 | unwrapped_metadata_key = unwrap_key( 154 | df_meta[df_meta.keyclass == int(item['keyclass'])].iloc[0].data, 155 | item['keyclass'] 156 | ) 157 | gcm = AES.new(unhexlify(unwrapped_metadata_key)[:32], AES.MODE_GCM, iv) 158 | metadata_key = gcm.decrypt_and_verify(ciphertext, authCode) 159 | 160 | bplist = BytesIO(item['encryptedMetadata_ciphertext']) 161 | plist = ccl_bplist.load(bplist) 162 | metaDataDeserialized = ccl_bplist.deserialise_NsKeyedArchiver(plist, parse_whole_structure=True) 163 | authCode = metaDataDeserialized['root']['SFAuthenticationCode'] 164 | iv = metaDataDeserialized['root']['SFInitializationVector'] 165 | ciphertext = metaDataDeserialized['root']['SFCiphertext'] 166 | 167 | gcm = AES.new(metadata_key[:32], AES.MODE_GCM, iv) 168 | decrypted = gcm.decrypt_and_verify(ciphertext, authCode) 169 | der_data = decode(decrypted)[0] 170 | item['decrypted'] = {} 171 | for k in der_data: 172 | if 'Octet' in str(type(k[1])): 173 | item['decrypted'][str(k[0])] = bytes(k[1]) 174 | else: 175 | item['decrypted'][str(k[0])] = str(k[1]) 176 | 177 | 178 | return item 179 | 180 | def main(): 181 | keychain_path = KEYCHAIN_DEFAULT_PAHT 182 | 183 | if len(sys.argv) > 1: 184 | keychain_path = sys.argv[1] 185 | 186 | if not os.path.exists(keychain_path): 187 | raise IOError("Can not find keychain database in {}".format(keychain_path)) 188 | 189 | db = sqlite3.connect(keychain_path) 190 | 191 | # extract data from generic password table 192 | df_genp = pandas.read_sql_query( 193 | """ 194 | SELECT * FROM genp; 195 | """, db) 196 | 197 | # extract data from internet password table 198 | df_inet = pandas.read_sql_query( 199 | """ 200 | SELECT * FROM inet; 201 | """, db) 202 | 203 | 204 | # extract metadata class keys to decrypt metadata 205 | df_meta = pandas.read_sql_query( 206 | """ 207 | SELECT * FROM metadatakeys; 208 | """, db) 209 | df_meta['keyclass'] = df_meta['keyclass'].astype(int) 210 | 211 | 212 | # decrypt 213 | df_genp = df_genp.apply(lambda r: deserialize_data(r), axis=1) 214 | df_genp = df_genp.apply(lambda r: decrypt_Metadata(r, df_meta), axis=1) 215 | df_genp = df_genp.apply(lambda r: decrypt_secretData(r), axis=1) 216 | 217 | df_inet = df_inet.apply(lambda r: deserialize_data(r), axis=1) 218 | df_inet = df_inet.apply(lambda r: decrypt_Metadata(r, df_meta), axis=1) 219 | df_inet = df_inet.apply(lambda r: decrypt_secretData(r), axis=1) 220 | 221 | res_dict = { 222 | 'genp': df_genp['decrypted'].to_list(), 223 | 'inet': df_inet['decrypted'].to_list() 224 | } 225 | 226 | 227 | # Exporting result 228 | 229 | with open("keychain_decrypted.plist","wb") as out: 230 | plistlib.dump(res_dict, out, sort_keys=False ) 231 | 232 | if __name__ == "__main__": 233 | main() 234 | 235 | 236 | -------------------------------------------------------------------------------- /keyclass_unwrapper: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xperylabhub/ios_keychain_decrypter/f7fead6db6cc4f5ec1c8de20ffe1d4ee39af6b9f/keyclass_unwrapper -------------------------------------------------------------------------------- /keyclass_unwrapper.c: -------------------------------------------------------------------------------- 1 | /*############################################################################################### 2 | # # 3 | # iOS Keychain Decrypter # 4 | # inspired by https://github.com/n0fate/iChainbreaker # 5 | # and https://github.com/nabla-c0d3/iphone-dataprotection.keychainviewer/tree/master/Keychain # 6 | # # 7 | # Copyright Matthieu Regnery 2020 # 8 | # # 9 | # This program is free software: you can redistribute it and/or modify # 10 | # it under the terms of the GNU General Public License as published by # 11 | # the Free Software Foundation, either version 3 of the License, or # 12 | # (at your option) any later version. # 13 | # # 14 | # This program is distributed in the hope that it will be useful, # 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 17 | # GNU General Public License for more details. # 18 | # # 19 | # You should have received a copy of the GNU General Public License # 20 | # along with this program. If not, see . # 21 | ###############################################################################################*/ 22 | 23 | 24 | #include 25 | #include 26 | #include "IOKit.h" 27 | 28 | #define kAppleKeyStoreInitUserClient 0 29 | #define kAppleKeyStoreKeyUnwrap 11 30 | 31 | CFStringRef keychain_protectionClassIdToString(uint32_t protection_class) 32 | { 33 | static CFStringRef protectionClasses[] = { 34 | CFSTR("WhenUnlocked"), 35 | CFSTR("AfterFirstUnlock"), 36 | CFSTR("Always"), 37 | CFSTR("WhenUnlockedThisDeviceOnly"), 38 | CFSTR("AfterFirstUnlockThisDeviceOnly"), 39 | CFSTR("AlwaysThisDeviceOnly") 40 | }; 41 | protection_class &= 0xF; 42 | 43 | if (protection_class >= 6 && protection_class <= 11) 44 | return protectionClasses[protection_class - 6]; 45 | return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("Unknown protection class %d"), protection_class); 46 | } 47 | 48 | int AppleKeyStoreKeyBagInit() 49 | { 50 | uint64_t out = 0; 51 | uint32_t one = 1; 52 | return IOKit_call("AppleKeyStore", 53 | kAppleKeyStoreInitUserClient, 54 | NULL, 55 | 0, 56 | NULL, 57 | 0, 58 | &out, 59 | &one, 60 | NULL, 61 | NULL); 62 | } 63 | 64 | IOReturn AppleKeyStore_keyUnwrap(uint32_t protection_class, const uint8_t* buffer, size_t bufferLen, uint8_t* out) 65 | { 66 | size_t outputStructCnt = bufferLen+8; 67 | uint64_t input[2]={0, protection_class}; 68 | 69 | return IOKit_call("AppleKeyStore", 70 | kAppleKeyStoreKeyUnwrap, 71 | input, 72 | 2, 73 | buffer, 74 | bufferLen, 75 | NULL, 76 | NULL, 77 | out, 78 | &outputStructCnt); 79 | } 80 | 81 | int main(int argc, char* argv[]) 82 | { 83 | AppleKeyStoreKeyBagInit(); 84 | if (argc == 3) { 85 | uint32_t keylen = strlen(argv[1])/2; 86 | unsigned char wrappedKey [keylen]; 87 | const char *pos = argv[1]; 88 | 89 | uint32_t keyclass = atoi(argv[2]); 90 | 91 | for (size_t count = 0; count < keylen; count++) { 92 | sscanf(pos, "%2hhx", &wrappedKey[count]); 93 | pos += 2; 94 | } 95 | uint8_t unwrappedKey [48]; 96 | AppleKeyStore_keyUnwrap(keyclass, &wrappedKey, 40, unwrappedKey); 97 | 98 | for(size_t count = 0; count < keylen; count++) 99 | printf("%02x", unwrappedKey[count]); 100 | } 101 | else{ 102 | printf("Usage : keychain key keyclass"); 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | pyasn1 3 | pycrypto 4 | plistlib --------------------------------------------------------------------------------