├── 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
--------------------------------------------------------------------------------