├── .gitignore ├── COPYRIGHT ├── LICENSE ├── README.md ├── __init__.py ├── afc_service.py ├── afc_service_unittesting.py ├── amfi_service.py ├── amfi_service_unittesting.py ├── app.py ├── bpylist ├── __init__.py ├── archive_types.py ├── archiver.py ├── bplist.cp37-win_amd64.pyd ├── bplist.cpython-36m-x86_64-linux-gnu.so └── bplist.cpython-37m-darwin.so ├── device_manager.py ├── device_manager_unittesting.py ├── device_service.py ├── device_service_unittesting.py ├── diagnostics_relay_service.py ├── dtxlib.py ├── house_arrest_proxy_service.py ├── house_arrest_proxy_service_test.py ├── image_mounter_service.py ├── image_mounter_service_unittesting.py ├── installation_proxy_service.py ├── installation_proxy_service_unittesting.py ├── instrument_service.py ├── ios_deviceinfo_new.json ├── libimobiledevice ├── __init__.py ├── libimobiledevice-1.0.dll ├── libimobiledevice-1.0.so.6.0.0 ├── libimobiledevice-glue-1.0.dll ├── libimobiledevice.so.6.0.0 ├── libplist-2.0.dll ├── libusbmuxd-2.0.dll ├── macos │ ├── libcrypto.1.1.dylib │ ├── libcrypto.dylib │ ├── libimobiledevice.6.dylib │ ├── libimobiledevice.dylib │ ├── libplist++.3.dylib │ ├── libplist++.dylib │ ├── libplist.3.dylib │ ├── libplist.dylib │ ├── libssl.1.1.dylib │ ├── libssl.dylib │ ├── libusbmuxd.6.dylib │ └── libusbmuxd.dylib └── plistutil.exe ├── lockdown_service.py ├── lockdown_service_unittesting.py ├── pykp.cp37-win_amd64.pyd ├── pykp.cpython-36m-x86_64-linux-gnu.so ├── pykp.cpython-37m-darwin.so ├── rsd.py ├── screenshotr_service.py ├── screenshotr_service_unittesting.py ├── service.py ├── spring_board_service.py ├── spring_board_service_unittesting.py ├── syslog_relay_service.py ├── syslog_relay_service_unittesting.py ├── test.py ├── utils.py ├── utils_unittesting.py └── zeroconf ├── __init__.py ├── py.typed └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | 126 | 127 | # Logs 128 | logs 129 | *.log 130 | npm-debug.log* 131 | yarn-debug.log* 132 | yarn-error.log* 133 | lerna-debug.log* 134 | 135 | # Diagnostic reports (https://nodejs.org/api/report.html) 136 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 137 | 138 | # Runtime data 139 | pids 140 | *.pid 141 | *.seed 142 | *.pid.lock 143 | 144 | # Directory for instrumented libs generated by jscoverage/JSCover 145 | lib-cov 146 | 147 | # Coverage directory used by tools like istanbul 148 | coverage 149 | *.lcov 150 | 151 | # nyc test coverage 152 | .nyc_output 153 | 154 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 155 | .grunt 156 | 157 | # Bower dependency directory (https://bower.io/) 158 | bower_components 159 | 160 | # node-waf configuration 161 | .lock-wscript 162 | 163 | # Compiled binary addons (https://nodejs.org/api/addons.html) 164 | build/Release 165 | 166 | # Dependency directories 167 | node_modules/ 168 | jspm_packages/ 169 | 170 | # TypeScript v1 declaration files 171 | typings/ 172 | 173 | # TypeScript cache 174 | *.tsbuildinfo 175 | 176 | # Optional npm cache directory 177 | .npm 178 | 179 | # Optional eslint cache 180 | .eslintcache 181 | 182 | # Optional REPL history 183 | .node_repl_history 184 | 185 | # Output of 'npm pack' 186 | *.tgz 187 | 188 | # Yarn Integrity file 189 | .yarn-integrity 190 | 191 | # dotenv environment variables file 192 | .env 193 | .env.test 194 | 195 | # parcel-bundler cache (https://parceljs.org/) 196 | .cache 197 | 198 | # next.js build output 199 | .next 200 | 201 | # nuxt.js build output 202 | .nuxt 203 | 204 | # vuepress build output 205 | .vuepress/dist 206 | 207 | # Serverless directories 208 | .serverless/ 209 | 210 | # FuseBox cache 211 | .fusebox/ 212 | 213 | # DynamoDB Local files 214 | .dynamodb/ 215 | 216 | # IDEA 217 | .idea/ 218 | 219 | .tpt_data/ 220 | 221 | 222 | *.db 223 | *.db-journal 224 | 225 | .DS_Store -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | libimobiledevice GPLv2 2 | libplist GPLv2 3 | bpylist MIT 4 | zeroconf LGPLv2 5 | -------------------------------------------------------------------------------- /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. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS Debug Bridge 2 | 3 | iOS Debug Bridge (idb) is a versatile command-line tool that lets you communicate with a device. 4 | 5 | 6 | 7 | ## Query for devices 8 | 9 | ``` 10 | $ idb devices 11 | List of devices attached 12 | 97006ebdc8bc5daed2e354f4addae4fd2a81c52d device USB 13 | ``` 14 | 15 | 16 | ## Get device info 17 | 18 | ``` 19 | $ idb --udid 97006ebdc8bc5daed2e354f4addae4fd2a81c52d deviceinfo 20 | Device info of device(udid: 97006ebdc8bc5daed2e354f4addae4fd2a81c52d) 21 | os_type: iOS 22 | device_name: San's iPhone 23 | device_type: iPhone X 24 | product_type: iPhone10,3 25 | os: 13.3.1(17D50) 26 | cpu_type: Apple A11 Bionic 27 | cpu_arch: Apple A11 Bionic 64-bit 28 | cpu_core_num: 6 29 | cpu_freq: [0, 2390] 30 | gpu_type: Apple A10X Fusion (12-core graphics) 31 | ``` 32 | 33 | 34 | ## Get Value 35 | 36 | ``` 37 | $ idb getvalue 38 | Values of device(udid: 97006ebdc8bc5daed2e354f4addae4fd2a81c52d) 39 | BasebandCertId: 2315222105 40 | BasebandKeyHashInformation: {'AKeyStatus': 2, 'SKeyHash': b'\xbb\xef\xedp,/i\x0f\xb5c\xdbx\xd0\x8e2z\x00\x84\x98\x1d\xbc\x98\x02\xe5i\x13\xa1h\x85F\x05j', 'SKeyStatus': 0} 41 | BasebandSerialNumber: b"'C\xde\x01" 42 | BasebandVersion: 5.30.01 43 | BoardId: 6 44 | BuildVersion: 17D50 45 | CPUArchitecture: arm64 46 | ChipID: 32789 47 | DeviceClass: iPhone 48 | DeviceColor: 1 49 | DeviceName: San's iPhone 50 | DieID: 7157468793159726 51 | HardwareModel: D22AP 52 | HasSiDP: True 53 | PartitionType: GUID_partition_scheme 54 | ProductName: iPhone OS 55 | ProductType: iPhone10,3 56 | ProductVersion: 13.3.1 57 | ProductionSOC: True 58 | ProtocolVersion: 2 59 | SupportedDeviceFamilies: [1] 60 | TelephonyCapability: True 61 | UniqueChipID: 7157468793159726 62 | UniqueDeviceID: 97006ebdc8bc5daed2e354f4addae4fd2a81c52d 63 | WiFiAddress: e4:9a:dc:b4:ba:94 64 | ``` 65 | 66 | 67 | ## Query for applications 68 | 69 | 70 | ``` 71 | $ idb applications 72 | List of user applications installed: 73 | CFBundleExecutable: JD4iPhone 74 | CFBundleVersion: 8.4.6 75 | CFBundleShortVersionString: 167053 76 | CFBundleIdentifier: com.360buy.jdmobile 77 | Path: /private/var/containers/Bundle/Application/AB511C13-DD00-4B44-AD23-253C164D9215/JD4iPhone.app 78 | CFBundleName: 京东 79 | 80 | List of system applications installed: 81 | CFBundleExecutable: Contacts 82 | CFBundleVersion: 1.0 83 | CFBundleShortVersionString: 1.0 84 | CFBundleIdentifier: com.apple.MobileAddressBook 85 | Path: /private/var/containers/Bundle/Application/7FF5041A-225B-462C-9FF1-16D0A6AC571B/Contacts.app 86 | CFBundleName: 通讯录 87 | ``` 88 | 89 | ## Get Application's icon 90 | 91 | 92 | ``` 93 | $ idb geticon --bundle_id com.apple.Preferences --output icon.png 94 | Save icon file at F:\lds\project\idb\icon.png 95 | ``` 96 | 97 | 98 | ## Lookup image 99 | 100 | 101 | ``` 102 | $ idb lookupimage --image_type Developer 103 | Image mount status: Yes 104 | ``` 105 | 106 | ## Mount image 107 | 108 | ``` 109 | $ idb mountimage --image_file F:\lds\DeviceSupport\DeviceSupport\13.3\DeveloperDiskImage.dmg --sig_file F:\lds\DeviceSupport\DeviceSupport\13.3\DeveloperDiskImage.dmg.signature 110 | Mount result: True 111 | ``` 112 | 113 | 114 | ## Take screenshot 115 | 116 | ``` 117 | $ idb screenshot 118 | Save screenshot image file at F:\lds\project\idb\screenshot_20200218_112501.png 119 | ``` 120 | 121 | 122 | ## Query Running Processes 123 | 124 | ``` 125 | $ idb instrument running 126 | runningProcesses: 127 | isApplication name pid realAppName startDate 128 | False filecoordinationd 144 /usr/sbin/filecoordinationd bpylist.timestamp datetime.datetime(2020, 2, 17, 15, 12, 42, 829858, tzinfo=datetime.timezone.utc)``` 129 | ``` 130 | 131 | ## Instrument 132 | 133 | ### channels 134 | 135 | ``` 136 | $ idb instrument channels 137 | Published capabilities: 138 | com.apple.instruments.server.services.processcontrolbydictionary 4 139 | ``` 140 | 141 | ### graphics 142 | 143 | ``` 144 | $ idb instrument graphics 145 | [GRAPHICS] 146 | { 147 | 'stdTextureCreationBytes': 0, 148 | 'XRVideoCardRunTimeStamp': 4129783, // timestamp(us) 149 | 'SplitSceneCount': 0, 150 | 'Device Utilization %': 0, // GPU Usage - Device 151 | 'finishGLWaitTime': 0, 152 | 'recoveryCount': 0, 153 | 'gartUsedBytes': 45383680, 154 | 'gartMapInBytesPerSample': 0, 155 | 'IOGLBundleName': 'Built-In', 156 | 'CoreAnimationFramesPerSecond': 58, // FPS - fps 157 | 'freeToAllocGPUAddressWaitTime': 0, 158 | 'TiledSceneBytes': 118784, 159 | 'Renderer Utilization %': 0, // GPU Usage - Render 160 | 'Tiler Utilization %': 0, // GPU Usage - Tiler 161 | 'oolTextureCreationBytes': 0, 162 | 'gartMapOutBytesPerSample': 1998848, 163 | 'contextGLCount': 0, 164 | 'agpTextureCreationBytes': 0, 165 | 'CommandBufferRenderCount': 31, 166 | 'iosurfaceTextureCreationBytes': 0, 167 | 'textureCount': 976, 168 | 'hardwareWaitTime': 0, 169 | 'agprefTextureCreationBytes': 0 170 | } 171 | ``` 172 | 173 | ### sysmontap 174 | 175 | ``` 176 | $ idb instrument sysmontap 177 | [{ 178 | 'Processes': { 179 | 0: [55934222336, None, 165363256, 69663420, 192643072, 1206386688, 335527936, 0, None, 2096201728], 180 | 144: [4365975552, None, 8395, 301, 2458024, 1900544, 2310144, 144, None, 17207296], 181 | 49: [4393910272, None, 105903, 30000, 3998160, 2375680, 3833856, 49, None, 47230976], 182 | 3018: [4325965824, None, 113, 2, 966960, 655360, 868352, 3018, None, 966656], 183 | 98: [4395515904, None, 211910, 55, 3293608, 4177920, 3145728, 98, None, 494104576], 184 | 2972: [4741709824, None, 2849, 34, 23528288, 14057472, 23248896, 2972, None, 155648], 185 | 52: [4342824960, None, 283782, 29356, 1704360, 1687552, 1556480, 52, None, 5021696], 186 | 2494: [4361601024, None, 421, 18, 1311144, 475136, 1146880, 2494, None, 237568], 187 | }, 188 | 'Type': 7, 189 | 'EndMachAbsTime': 3595494138985, 190 | 'ProcessesAttributes': ['memVirtualSize', 'cpuUsage', 'ctxSwitch', 'intWakeups', 'physFootprint', 'memResidentSize', 'memAnon', 'pid', 'powerScore', 'diskBytesRead'], 191 | 'StartMachAbsTime': 3595491365204 192 | }] 193 | 194 | [{ 195 | 'PerCPUUsage': [{ 196 | 'CPU_NiceLoad': 0.0, 197 | 'CPU_SystemLoad': -1.0, 198 | 'CPU_TotalLoad': 6.930693069306926, 199 | 'CPU_UserLoad': -1.0 200 | }, { 201 | 'CPU_NiceLoad': 0.0, 202 | 'CPU_SystemLoad': -1.0, 203 | 'CPU_TotalLoad': 0.9900990099009874, 204 | 'CPU_UserLoad': -1.0 205 | }, { 206 | 'CPU_NiceLoad': 0.0, 207 | 'CPU_SystemLoad': -1.0, 208 | 'CPU_TotalLoad': 0.0, 209 | 'CPU_UserLoad': -1.0 210 | }, { 211 | 'CPU_NiceLoad': 0.0, 212 | 'CPU_SystemLoad': -1.0, 213 | 'CPU_TotalLoad': 0.0, 214 | 'CPU_UserLoad': -1.0 215 | }, { 216 | 'CPU_NiceLoad': 0.0, 217 | 'CPU_SystemLoad': -1.0, 218 | 'CPU_TotalLoad': 1.0, 219 | 'CPU_UserLoad': -1.0 220 | }, { 221 | 'CPU_NiceLoad': 0.0, 222 | 'CPU_SystemLoad': -1.0, 223 | 'CPU_TotalLoad': 8.0, 224 | 'CPU_UserLoad': -1.0 225 | }], 226 | 'EndMachAbsTime': 3595515481450, 227 | 'CPUCount': 6, 228 | 'EnabledCPUs': 6, 229 | 'SystemCPUUsage': { 230 | 'CPU_NiceLoad': 0.0, 231 | 'CPU_SystemLoad': -1.0, 232 | 'CPU_TotalLoad': 16.920792079207914, // CPU - Total Usage = SystemCPUUsage.CPU_TotalLoad / EnabledCPUs 233 | 'CPU_UserLoad': -1.0 234 | }, 235 | 'Type': 33, 236 | 'StartMachAbsTime': 3595491365204 237 | }, { 238 | 'Processes': { 239 | 0: [55934222336, 0.02943223109141613, 165363748, 69663619, 192643072, 1206353920, 335495168, 0, None, 2096201728], // 'ProcessesAttributes': ['memVirtualSize', 'cpuUsage', 'ctxSwitch', 'intWakeups', 'physFootprint', 'memResidentSize', 'memAnon', 'pid', 'powerScore', 'diskBytesRead'], 240 | 144: [4365975552, 0.0, 8395, 301, 2458024, 1900544, 2310144, 144, 0.0, 17207296], 241 | 49: [4393910272, 0.0, 105903, 30000, 3998160, 2375680, 3833856, 49, 0.0, 47230976], 242 | 3018: [4325965824, 0.0, 113, 2, 966960, 655360, 868352, 3018, 0.0, 966656], 243 | 98: [4395515904, 0.0055491588698022894, 211914, 55, 3293608, 4177920, 3145728, 98, 0.23121495260104377, 494104576], 244 | 2972: [4741709824, 0.0, 2849, 34, 23528288, 14057472, 23248896, 2972, 0.0, 155648], 245 | 52: [4342824960, 0.0, 283782, 29356, 1704360, 1687552, 1556480, 52, 0.0, 5021696], 246 | 2494: [4361601024, 0.0, 421, 18, 1311144, 475136, 1146880, 2494, 0.0, 237568], 247 | }, 248 | 'Type': 5, 249 | 'EndMachAbsTime': 3595520484394, 250 | 'StartMachAbsTime': 3595494138986 251 | }] 252 | ``` 253 | 254 | 255 | ``` 256 | enabledCPUs = data.EnabledCPUs 257 | if enabledCPUs == 0: 258 | enabledCPUs = data.CPUCount 259 | totalCPUUsage = data.SystemCPUUsage.CPU_TottatlLoad / enabledCPUs 260 | 261 | 262 | 263 | 264 | ``` 265 | 266 | 267 | ### activity(IOS < 11.0) 268 | 269 | 270 | ``` 271 | $ idb instrument activity 272 | { 273 | "CPUNiceLoad":0.0, 274 | "NetBytesIn":19742719, 275 | "DiskBytesWritten":134885376, 276 | "VMPageInBytes":789274624, 277 | "TotalThreads":507, 278 | "TotalVMSize":102345474048, 279 | "PhysicalMemoryActive":392609792, 280 | "DiskWriteOpsPerSecond":0, 281 | "DiskWriteOps":6297, 282 | "NetBytesInPerSecond":0, 283 | "PhysicalMemoryUsed":786100224, 284 | "DiskReadOpsPerSecond":0, 285 | "DiskBytesReadPerSecond":0, 286 | "NetBytesOut":18399168, 287 | "PhysicalMemoryFree":28282880, 288 | "CPUUserLoad":65.90909004211426, 289 | "XRActivityClientMachAbsoluteTime":14443144570, // absTime 290 | "DiskBytesWrittenPerSecond":0, 291 | "NetPacketsOut":4527, 292 | "DiskBytesRead":782565376, 293 | "PhysicalMemoryWired":189034496, 294 | "NetPacketsIn":8936, 295 | "TotalProcesses":0, 296 | "IndividualCPULoad":[ 297 | { 298 | "TotalLoad":"63.636364", 299 | "SystemLoad":"0.000000", 300 | "NiceLoad":"0.000000", 301 | "UserLoad":"63.636364" 302 | }, 303 | { 304 | "TotalLoad":"68.181818", 305 | "SystemLoad":"0.000000", 306 | "NiceLoad":"0.000000", 307 | "UserLoad":"68.181818" 308 | } 309 | ], 310 | "CPUTotalLoad":65.90909004211426, 311 | "Processes":[ 312 | { 313 | "CPUUsage":0.21836340937464532, // cpuUsage; appCPUUsage = cpuUsage / cpuNum; totalCPUUsage = SUM(processCPUUsage) 314 | "UnixSyscalls":91249, 315 | "Private":4284416, 316 | "VPrivate":27578368, 317 | "Faults":4771, 318 | "TotalMicroSeconds":895321, 319 | "MachSyscalls":81338, 320 | "Ports":1106, 321 | "MessagesReceived":15268, 322 | "ContextSwitches":23485, 323 | "PGID":1, 324 | "UID":0, 325 | "Threads":3, 326 | "TotalSeconds":4, 327 | "ResidentSize":6176768, // realMemory(b) 328 | "PageIns":823, 329 | "Architecture":12, 330 | "PID":1, // pid 331 | "VirtualSize":664883200, // virtualMemory(b) 332 | "Command":"launchd", 333 | "PPID":0, 334 | "MessagesSent":43868, 335 | "Shared":1126400 336 | } 337 | ], 338 | "PhysicalMemoryInactive":204455936, 339 | "NetPacketsOutPerSecond":0, 340 | "VMSwapUsed":0, 341 | "DiskReadOps":27249, 342 | "NetBytesOutPerSecond":0, 343 | "CPUSystemLoad":0.0, 344 | "NetPacketsInPerSecond":0, 345 | "VMPageOutBytes":139264 346 | } 347 | ``` 348 | 349 | 350 | 351 | ### networking(Wifi mode) 352 | 353 | ``` 354 | $ idb instrument networking 355 | connection-detected {'LocalAddress': '192.168.31.195:63993', 'RemoteAddress': '17.57.145.102:5223', 'InterfaceIndex': 8, 'Pid': -2, 'RecvBufferSize': 131072, 'RecvBufferUsed': 0, 'SerialNumber': 2, 'Kind': 1} 356 | connection-detected {'LocalAddress': '172.31.57.105:63992', 'RemoteAddress': '17.252.204.141:5223', 'InterfaceIndex': 3, 'Pid': -2, 'RecvBufferSize': 131072, 'RecvBufferUsed': 0, 'SerialNumber': 3, 'Kind': 1} 357 | connection-detected {'LocalAddress': '192.168.31.195:56950', 'RemoteAddress': '17.56.8.133:993', 'InterfaceIndex': 8, 'Pid': -2, 'RecvBufferSize': 131072, 'RecvBufferUsed': 0, 'SerialNumber': 12, 'Kind': 1} 358 | connection-detected {'LocalAddress': '0.0.0.0:59998', 'RemoteAddress': '0.0.0.0:0', 'InterfaceIndex': 8, 'Pid': -2, 'RecvBufferSize': 0, 'RecvBufferUsed': 0, 'SerialNumber': 13, 'Kind': 2} 359 | connection-detected {'LocalAddress': '[::]:59814', 'RemoteAddress': '[::]:0', 'InterfaceIndex': 8, 'Pid': -2, 'RecvBufferSize': 0, 'RecvBufferUsed': 0, 'SerialNumber': 38, 'Kind': 2} 360 | connection-detected {'LocalAddress': '[::]:5353', 'RemoteAddress': '[::]:0', 'InterfaceIndex': 8, 'Pid': -2, 'RecvBufferSize': 196724, 'RecvBufferUsed': 0, 'SerialNumber': 52, 'Kind': 2} 361 | connection-detected {'LocalAddress': '0.0.0.0:5353', 'RemoteAddress': '0.0.0.0:0', 'InterfaceIndex': 8, 'Pid': -2, 'RecvBufferSize': 196724, 'RecvBufferUsed': 0, 'SerialNumber': 53, 'Kind': 2} 362 | interface-detection {'InterfaceIndex': 8, 'Name': 'en0'} 363 | interface-detection {'InterfaceIndex': 3, 'Name': 'pdp_ip0'} 364 | connection-update { 365 | 'RxPackets': 20448, 366 | 'RxBytes': 4242060, // download 367 | 'TxPackets': 15446, 368 | 'TxBytes': 3144553, // upload 369 | 'RxDups': None, 370 | 'RxOOO': None, 371 | 'TxRetx': None, 372 | 'MinRTT': None, 373 | 'AvgRTT': None, 374 | 'ConnectionSerial': 53 375 | } 376 | ``` 377 | 378 | 379 | ### netstat(USB mode) 380 | 381 | ``` 382 | idb instrument netstat 2468 383 | start {2468.0} 384 | { 385 | 2468.0: { 386 | 'net.packets.delta': 0, 387 | 'time': 1582189905.198155, 388 | 'net.tx.bytes': 56636, // upload 389 | 'net.bytes.delta': 0, 390 | 'net.rx.packets.delta': 0, 391 | 'net.tx.packets': 303, 392 | 'net.rx.bytes': 293823, // download 393 | 'net.bytes': 350459, 394 | 'net.tx.bytes.delta': 0, 395 | 'net.rx.bytes.delta': 0, 396 | 'net.rx.packets': 416, 397 | 'pid': 2468.0, 398 | 'net.tx.packets.delta': 0, 399 | 'net.packets': 719 400 | } 401 | } 402 | ``` 403 | 404 | 405 | ### energy 406 | 407 | ``` 408 | $ idb instrument energy 2468 409 | { 410 | 2230.0: { 411 | 'energy.overhead': 490.0, // Energy - overheadEnergy 412 | 'kIDEGaugeSecondsSinceInitialQueryKey': 11, 413 | 'energy.version': 1, 414 | 'energy.gpu.cost': 0, // Energy - gpuEnergy 415 | 'energy.cpu.cost': 35.94272039376739, // Energy - cpuEnergy 416 | 'energy.networkning.overhead': 500, 417 | 'energy.appstate.cost': 8, 418 | 'energy.location.overhead': 0, 419 | 'energy.thermalstate.cost': 0, 420 | 'energy.networking.cost': 0, // Energy - networkEnergy 421 | 'energy.cost': 25.942720393767388, 422 | 'energy.cpu.overhead': 0, 423 | 'energy.location.cost': 0, // Energy - locationEnergy 424 | 'energy.gpu.overhead': 0, 425 | 'energy.appstate.overhead': 0, 426 | 'energy.inducedthermalstate.cost': -1 427 | } 428 | } 429 | ``` 430 | 431 | productVersion >= 11.0: 432 | * energy.cpu.cost: cpuEnergy 433 | * energy.gpu.cost: gpuEnergy 434 | * energy.networking.cost: networkEnergy 435 | * energy.location.cost: locationEnergy 436 | * energy.display.cost: displayEnergy 437 | * energy.overhead: overheadEnergy 438 | 439 | productVersion < 11.0: 440 | * energy.CPU: cpuEnergy 441 | * energy.GPU: gpuEnergy 442 | * energy.networking: networkEnergy 443 | * energy.location: locationEnergy 444 | * energy.overhead: overheadEnergy 445 | 446 | 447 | ## System log 448 | 449 | ``` 450 | $ idb syslog 451 | System log:(pressing Ctrl+C to exit) 452 | Feb 18 16:44:35 Sans-iPhone homed(HomeKitDaemon)[104] : Remote access health monitor timer fired, checking state for all homes 453 | Feb 18 16:44:36 Sans-iPhone symptomsd(SymptomEvaluator)[117] : NBSM: TCP metrics iteration:691 since 30.00 secs, ret=1: allflows=5/C=0/R=0/W=1/flows=0/unacked=0/rxbytes=0/txbytes=0/rxooo=0/rxdup=0/retx=0 454 | Feb 18 16:44:36 Sans-iPhone symptomsd(SymptomEvaluator)[117] : NBSM: TCP progress metrics score: 20, problem ratio: 0.20 (baseline: 0.08) 455 | Feb 18 16:44:36 Sans-iPhone CommCenter(libATCommandStudioDynamic.dylib)[81] : QMI: Svc=0xe2(BSP) Ind MsgId=0xe021 Bin=[] 456 | Feb 18 16:44:37 Sans-iPhone wifid(WiFiPolicy)[45] : __WiFiLQAMgrLogStats(helloworld:Stationary): Rssi: -65 {0 0} Snr: 0 Cca: 41 (S:0 O:3 457 | ``` 458 | 459 | 460 | ## List directory 461 | 462 | ``` 463 | $ idb ls / 464 | Downloads Books Photos Recordings DCIM iTunesRestore iTunes_Control MediaAnalysis PhotoData PublicStaging Purchases 465 | 466 | $ idb ls / --bundle="com.seasun.tmgp.jx3m" (optional )-d = 1 467 | List of directory /Documents//: 468 | whalesdk-cs debug3.json .DS_Store MSDKINFO.sqlite ss_tmp xgserialization.b 469 | 470 | ``` 471 | 472 | ## Make directory 473 | 474 | ``` 475 | $ idb mkdir /Temp 476 | Make directory /Temp Success 477 | 478 | $ idb mkdir /Temp --bundle="com.seasun.tmgp.jx3m" -d = 1 479 | Make directory /Temp Success 480 | 481 | ``` 482 | 483 | ## Delete file or directory 484 | 485 | ``` 486 | $ idb rm /Temp 487 | /Temp Deleted. 488 | 489 | $ idb rm /Temp --bundle="com.seasun.tmgp.jx3m" -d = 1 490 | /Temp Deleted. 491 | 492 | ``` 493 | 494 | ## Push file into device 495 | 496 | ``` 497 | $ idb push test.txt /Temp 498 | push file test.txt to /Temp 499 | 500 | $ idb push test.txt /Temp --bundle="com.seasun.tmgp.jx3m" -d = 1 501 | push file test.txt to /Temp 502 | 503 | ``` 504 | 505 | 506 | ## Pull file from device 507 | 508 | ``` 509 | $ idb pull /Temp/test.txt 510 | pull file F:\lds\project\idb\test.txt 511 | 512 | $ idb pull /Temp/test.txt --bundle="com.seasun.tmgp.jx3m" -d = 1 513 | pull file F:\lds\project\idb\test.txt 514 | 515 | ``` 516 | 517 | ## Install ipa into device 518 | 519 | ``` 520 | $ idb install ~/tmp.ipa 521 | 522 | ``` 523 | 524 | ## Uninstall ipa into device 525 | 526 | ``` 527 | $ idb uninstall com.seasun.jxpocket.tako 528 | 529 | ``` 530 | 531 | ## launch app with bundle id 532 | ``` 533 | $ idb instrument launch com.ksg.tako 534 | ``` 535 | 536 | ## wireless mode 537 | 538 | ``` 539 | $ idb -u 97006ebdc8bc5daed2e354f4addae4fd2a81c52d:10.11.255.115 heartbeat 540 | ``` 541 | 542 | ``` 543 | $ idb -u 97006ebdc8bc5daed2e354f4addae4fd2a81c52d:10.11.255.115 deviceinfo 544 | ``` 545 | 546 | 547 | ## diagnostics 548 | 549 | iOS >= 13.0 550 | ``` 551 | $ idb diagnostics AppleSmartBattery 552 | ``` 553 | 554 | iOS < 13.0 555 | ``` 556 | $ idb diagnostics AppleARMPMUCharger 557 | ``` 558 | 559 | ## developer mode 560 | 561 | ``` 562 | $ idb developermodestatus 563 | ``` 564 | 565 | ``` 566 | $ idb setdevelopermode -m 0 567 | ``` -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/__init__.py -------------------------------------------------------------------------------- /afc_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | from libimobiledevice import AfcError, AfcFileMode, AfcLinkType 9 | from libimobiledevice import afc_client_start_service, afc_client_free, afc_get_device_info, afc_get_file_info, afc_read_directory, afc_dictionary_free 10 | from libimobiledevice import afc_file_open, afc_file_close, afc_file_read, afc_file_write 11 | from libimobiledevice import afc_make_directory, afc_remove_path 12 | from libimobiledevice import plist_free 13 | from utils import read_data_from_plist_ptr, compare_version, read_buffer_from_pointer 14 | 15 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 16 | 17 | 18 | class AfcFile(object): 19 | 20 | def __init__(self, afc_client, handler): 21 | self._afc_client = afc_client 22 | self._handler = handler 23 | 24 | def __del__(self): 25 | if self._handler: 26 | self.close() 27 | 28 | def write(self, data:bytes): 29 | bytes_written = c_uint32() 30 | ret = afc_file_write(self._afc_client, self._handler, data, len(data), pointer(bytes_written)) 31 | if ret == AfcError.AFC_E_SUCCESS: 32 | return bytes_written.value 33 | else: 34 | raise IOError("Can not write file, error code:%d" % ret) 35 | 36 | def read(self, size = -1): 37 | data_p = create_string_buffer(size) 38 | length = size 39 | bytes_read = c_uint32() 40 | ret = afc_file_read(self._afc_client, self._handler, data_p, length, pointer(bytes_read)) 41 | if ret == AfcError.AFC_E_SUCCESS: 42 | buffer = read_buffer_from_pointer(data_p, bytes_read.value) 43 | return buffer 44 | elif ret == AfcError.AFC_E_END_OF_DATA: 45 | return None 46 | else: 47 | raise IOError("Can not read file, error code:%d" % ret) 48 | 49 | def close(self): 50 | afc_file_close(self._afc_client, self._handler) 51 | self._handler = None 52 | 53 | 54 | class AfcService(Service): 55 | """ 56 | 文件传输服务, 负责文件操作相关 57 | """ 58 | 59 | def new_client(self, device): 60 | """ 61 | 创建 afc client,用于调用其他接口 62 | :param device: 由DeviceService创建的device对象(C对象) 63 | :return: afc client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 64 | """ 65 | client = c_void_p() 66 | ret = afc_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 67 | if ret != AfcError.AFC_E_SUCCESS: 68 | return None 69 | return client 70 | 71 | def free_client(self, client): 72 | """ 73 | 释放mobile image mounter client 74 | :param client: mobile image mounter client(C对象) 75 | :return: bool 是否成功 76 | """ 77 | ret = afc_client_free(client) 78 | return ret == AfcError.AFC_E_SUCCESS 79 | 80 | def read_directory(self, client, directory:str): 81 | result = [] 82 | 83 | dir_list_p = POINTER(c_char_p)() 84 | ret = afc_read_directory(client, directory.encode("utf-8"), pointer(dir_list_p)) 85 | 86 | source_dir = directory 87 | if not source_dir.endswith("/"): 88 | source_dir += "/" 89 | 90 | if ret == AfcError.AFC_E_SUCCESS: 91 | i = 0 92 | item = dir_list_p[i] 93 | while item: 94 | filename = item.decode("utf-8") 95 | if filename != "." and filename != "..": 96 | filepath = source_dir + filename 97 | fileinfo = self.get_file_info(client, filepath) 98 | fileinfo['filename'] = filename 99 | fileinfo['filepath'] = filepath 100 | result.append(fileinfo) 101 | i += 1 102 | item = dir_list_p[i] 103 | afc_dictionary_free(dir_list_p) 104 | return result 105 | 106 | def get_file_info(self, client, filename:str): 107 | fileinfo = {} 108 | fileinfo_p = POINTER(c_char_p)() 109 | ret = afc_get_file_info(client, filename.encode("utf-8"), pointer(fileinfo_p)) 110 | if ret == AfcError.AFC_E_SUCCESS: 111 | i = 0 112 | item = fileinfo_p[i] 113 | key = None 114 | while item: 115 | if key is None: 116 | key = item.decode("utf-8") 117 | else: 118 | fileinfo[key] = item.decode("utf-8") 119 | key = None 120 | i += 1 121 | item = fileinfo_p[i] 122 | afc_dictionary_free(fileinfo_p) 123 | return fileinfo 124 | 125 | def open_file(self, client, filename, mode): 126 | handle = c_uint64() 127 | file_mode = AfcFileMode.AFC_FOPEN_RDONLY 128 | has_plus = "+" in mode 129 | if "r" in mode: 130 | if has_plus: 131 | file_mode |= AfcFileMode.AFC_FOPEN_RW 132 | else: 133 | file_mode |= AfcFileMode.AFC_FOPEN_RDONLY 134 | if "w" in mode: 135 | if has_plus: 136 | file_mode |= AfcFileMode.AFC_FOPEN_WR 137 | else: 138 | file_mode |= AfcFileMode.AFC_FOPEN_WRONLY 139 | if "a" in mode: 140 | if has_plus: 141 | file_mode |= AfcFileMode.AFC_FOPEN_RDAPPEND 142 | else: 143 | file_mode |= AfcFileMode.AFC_FOPEN_APPEND 144 | 145 | ret = afc_file_open(client, filename.encode("utf-8"), file_mode, pointer(handle)) 146 | if ret == AfcError.AFC_E_SUCCESS: 147 | return AfcFile(client, handle.value) 148 | elif ret == AfcError.AFC_E_OBJECT_NOT_FOUND: 149 | raise IOError("File not found") 150 | else: 151 | raise IOError("IOError: %d" % ret) 152 | 153 | def make_directory(self, client, dirname): 154 | ret = afc_make_directory(client, dirname.encode("utf-8")) 155 | return ret == AfcError.AFC_E_SUCCESS 156 | 157 | def remove_path(self, client, dirname): 158 | ret = afc_remove_path(client, dirname.encode("utf-8")) 159 | if ret == AfcError.AFC_E_SUCCESS: 160 | return True 161 | elif ret == AfcError.AFC_E_DIR_NOT_EMPTY: 162 | raise IOError("Directory not empty") 163 | elif ret == AfcError.AFC_E_OBJECT_NOT_FOUND: 164 | raise IOError("File not found") 165 | else: 166 | raise IOError("IOError: %d" % ret) 167 | 168 | -------------------------------------------------------------------------------- /afc_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | from tempfile import NamedTemporaryFile 5 | 6 | from afc_service import AfcService 7 | from device_service import DeviceService 8 | 9 | 10 | class AfcServiceTestCase(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.afc_service = AfcService() 14 | self.device_service = DeviceService() 15 | 16 | def _create_device(self): 17 | udid = self._get_udid() 18 | device = self.device_service.new_device(udid) 19 | print("device", device) 20 | self.assertIsNotNone(device) 21 | return device 22 | 23 | def _get_udid(self): 24 | device_service = DeviceService() 25 | device_list = device_service.get_device_list() 26 | self.assertIsNotNone(device_list) 27 | self.assertTrue(len(device_list) > 0) 28 | return device_list[0]['udid'] 29 | 30 | def test_read_directory(self): 31 | device = self._create_device() 32 | client = self.afc_service.new_client(device) 33 | self.assertIsNotNone(client) 34 | 35 | dir_list = self.afc_service.read_directory(client, ".") 36 | self.assertIsNotNone(dir_list) 37 | 38 | for file in dir_list: 39 | print(file['filename'], file['st_ifmt'], file['st_size']) 40 | if file['st_ifmt'] == "S_IFDIR": 41 | sub_dir_list = self.afc_service.read_directory(client, file['filepath']) 42 | for subfile in sub_dir_list: 43 | print("\t", subfile['filename'], subfile['st_ifmt'], subfile['st_size']) 44 | 45 | self.afc_service.free_client(client) 46 | self.device_service.free_device(device) 47 | 48 | def test_read_file(self): 49 | device = self._create_device() 50 | client = self.afc_service.new_client(device) 51 | self.assertIsNotNone(client) 52 | 53 | filename = "./Downloads/downloads.28.sqlitedb" 54 | afc_file = self.afc_service.open_file(client, filename, "r") 55 | while True: 56 | buffer = afc_file.read(1024) 57 | if not buffer: 58 | print("EOF") 59 | break 60 | print(buffer) 61 | afc_file.close() 62 | 63 | self.afc_service.free_client(client) 64 | self.device_service.free_device(device) 65 | 66 | def test_make_directory(self): 67 | device = self._create_device() 68 | client = self.afc_service.new_client(device) 69 | self.assertIsNotNone(client) 70 | 71 | dirname = "./Downloads/Test/Test1/Test2/Test3" 72 | success = self.afc_service.make_directory(client, dirname) 73 | self.assertTrue(success) 74 | 75 | self.afc_service.free_client(client) 76 | self.device_service.free_device(device) 77 | 78 | def test_remove_path(self): 79 | device = self._create_device() 80 | client = self.afc_service.new_client(device) 81 | self.assertIsNotNone(client) 82 | 83 | dirname = "./Downloads/Test/Test1/Test2/Test3" 84 | success = self.afc_service.remove_path(client, dirname) 85 | self.assertTrue(success) 86 | 87 | self.afc_service.free_client(client) 88 | self.device_service.free_device(device) 89 | 90 | def test_write_file(self): 91 | device = self._create_device() 92 | client = self.afc_service.new_client(device) 93 | self.assertIsNotNone(client) 94 | 95 | filename = "./Downloads/test.txt" 96 | afc_file = self.afc_service.open_file(client, filename, "w") 97 | afc_file.write(b"test") 98 | afc_file.close() 99 | 100 | self.afc_service.free_client(client) 101 | self.device_service.free_device(device) 102 | 103 | 104 | if __name__ == '__main__': 105 | unittest.main() -------------------------------------------------------------------------------- /amfi_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | from libimobiledevice import AMFIError 9 | from libimobiledevice import amfi_client_start_service, amfi_client_free, amfi_set_developer_mode 10 | from libimobiledevice import plist_free 11 | from utils import read_data_from_plist_ptr, compare_version 12 | 13 | 14 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 15 | 16 | 17 | 18 | class AMFIService(Service): 19 | """ 20 | AMFI 21 | """ 22 | 23 | def new_client(self, device): 24 | """ 25 | 创建amfi client, 用于调用其他接口 26 | :param device: 由DeviceService创建的device对象(C对象) 27 | :return: amfi client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 28 | """ 29 | client = c_void_p() 30 | ret = amfi_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 31 | if ret != AMFIError.AMFI_E_SUCCESS: 32 | return None 33 | return client 34 | 35 | def free_client(self, client): 36 | """ 37 | 释放amfi client 38 | :param client: amfi client(C对象) 39 | :return: bool 是否成功 40 | """ 41 | ret = amfi_client_free(client) 42 | return ret == AMFIError.AMFI_E_SUCCESS 43 | 44 | def set_developer_mode(self, client, mode): 45 | """ 46 | 设置 显示/隐藏"开发者模式"菜单 47 | :param client: amfi client(C对象) 48 | :param mode: 0: reveal toggle in settings; 49 | 1: enable developer mode (only if no passcode is set); 50 | 2: answers developer mode enable prompt post-restart ? 51 | :return: 返回码 52 | """ 53 | ret = amfi_set_developer_mode(client, mode) 54 | return ret 55 | 56 | -------------------------------------------------------------------------------- /amfi_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from amfi_service import AMFIService 4 | from device_service import DeviceService 5 | 6 | 7 | class AMFIServiceTestCase(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.amfi_service = AMFIService() 11 | self.device_service = DeviceService() 12 | 13 | def _create_device(self): 14 | udid = self._get_udid() 15 | device = self.device_service.new_device(udid) 16 | print("device", device) 17 | self.assertIsNotNone(device) 18 | return device 19 | 20 | def _get_udid(self): 21 | device_service = DeviceService() 22 | device_list = device_service.get_device_list() 23 | self.assertIsNotNone(device_list) 24 | self.assertTrue(len(device_list) > 0) 25 | return device_list[0]['udid'] 26 | 27 | def test_set_developer_mode(self): 28 | device = self._create_device() 29 | client = self.amfi_service.new_client(device) 30 | self.assertIsNotNone(client) 31 | 32 | mode = 0 # reveal toggle in settings 33 | ret = self.amfi_service.set_developer_mode(client, mode) 34 | print(ret) 35 | self.assertTrue(ret == 0) 36 | self.amfi_service.free_client(client) 37 | 38 | if __name__ == '__main__': 39 | unittest.main() -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | import io 5 | import sys 6 | import time 7 | import datetime 8 | 9 | from afc_service import AfcService 10 | from device_service import DeviceService 11 | from house_arrest_proxy_service import House_arrest_proxy_service 12 | from installation_proxy_service import InstallationProxyService 13 | from diagnostics_relay_service import DiagnosticsRelayService 14 | from libimobiledevice import IDeviceConnectionType, SbservicesInterfaceOrientation 15 | from instrument_service import instrument_main, setup_parser as setup_instrument_parser 16 | from screenshotr_service import ScreenshotrService 17 | from spring_board_service import SpringBoardService 18 | from image_mounter_service import ImageMounterService 19 | from syslog_relay_service import SyslogRelayService 20 | from lockdown_service import LockdownService 21 | from amfi_service import AMFIService 22 | 23 | try: 24 | from PIL import Image 25 | except: 26 | Image = None 27 | 28 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 29 | 30 | device_service = DeviceService() 31 | 32 | 33 | def _get_device_or_die(udid = None): 34 | device = None 35 | if udid is not None: 36 | device = device_service.new_device(udid) 37 | else: 38 | device_list = device_service.get_device_list() 39 | if len(device_list) > 0: 40 | device = device_service.new_device(device_list[0]['udid']) 41 | if device is None: 42 | print("No device attached") 43 | exit(-1) 44 | else: 45 | return device 46 | 47 | 48 | def print_devices(): 49 | print("List of devices attached") 50 | device_list = device_service.get_device_list() 51 | for device in device_list: 52 | conn_type = "USB" if device['conn_type'] == IDeviceConnectionType.CONNECTION_USBMUXD else "WIFI" 53 | print("%s device %s" % (device['udid'], conn_type)) 54 | 55 | 56 | def get_device_info_from_configs(product_type): 57 | with open(os.path.join(ROOT_DIR, "ios_deviceinfo_new.json")) as fp: 58 | device_info_map = json.load(fp) 59 | if product_type in device_info_map: 60 | return device_info_map[product_type] 61 | return None 62 | 63 | 64 | def get_device_info(udid): 65 | device = _get_device_or_die(udid) 66 | 67 | lockdown_service = LockdownService() 68 | lockdown_client = lockdown_service.new_client(device) 69 | 70 | values, error = lockdown_service.get_value(lockdown_client, key=None) 71 | if error: 72 | return None, error 73 | 74 | device_name = values['DeviceName'] 75 | product_version = values['ProductVersion'] 76 | build_version = values['BuildVersion'] 77 | product_type = values['ProductType'] 78 | unique_device_id = values['UniqueDeviceID'] 79 | os = "%s(%s)" % (product_version, build_version) 80 | 81 | device_info = get_device_info_from_configs(product_type) 82 | device_type = device_info['deviceType'] 83 | cpu_type = device_info['cpuInfo']['hwType'] 84 | cpu_arch = device_info['cpuInfo']['processor'] 85 | cpu_core_num = device_info['cpuInfo']['coreNum'] 86 | min_cpu_freq = int(int(device_info['cpuInfo']['minCpuFreq']) / 1000) 87 | max_cpu_freq = int(int(device_info['cpuInfo']['maxCpuFreq']) / 1000) 88 | cpu_freq = "[%s, %s]" % (str(min_cpu_freq), str(max_cpu_freq)) 89 | gpu_type = device_info['gpuInfo'] 90 | battery_info = device_info['batteryInfo'] # TODO: 91 | 92 | lockdown_service.free_client(lockdown_client) 93 | device_service.free_device(device) 94 | 95 | return { 96 | "os_type": "iOS", 97 | "device_name": device_name, 98 | "device_type": device_type, 99 | "product_type": product_type, 100 | "os": os, 101 | "cpu_type": cpu_type, 102 | "cpu_arch": cpu_arch, 103 | "cpu_core_num": cpu_core_num, 104 | "cpu_freq": cpu_freq, 105 | "gpu_type": gpu_type, 106 | }, None 107 | 108 | 109 | def print_device_info(udid): 110 | device_info, error = get_device_info(udid) 111 | if error: 112 | print("Error: %s" % error) 113 | return 114 | print("Device info of device(udid: %s)" % udid) 115 | for key, value in device_info.items(): 116 | print("%s: %s" % (key, value)) 117 | 118 | 119 | def print_get_value(udid, key=None): 120 | device = _get_device_or_die(udid) 121 | 122 | lockdown_service = LockdownService() 123 | lockdown_client = lockdown_service.new_client(device) 124 | 125 | values, error = lockdown_service.get_value(lockdown_client, key=key) 126 | if error: 127 | print("Error: %s" % error) 128 | return 129 | 130 | print("Values of device(udid: %s)" % udid) 131 | if type(values) == dict: 132 | for name, value in values.items(): 133 | print("%s: %s" % (name, value)) 134 | else: 135 | print("%s: %s" % (key, values)) 136 | lockdown_service.free_client(lockdown_client) 137 | device_service.free_device(device) 138 | 139 | def print_diagnostics(udid, name): # name = ["AppleSmartBattery" >= 13.0 , "AppleARMPMUCharger" < 13.0] 140 | device = _get_device_or_die(udid) 141 | 142 | diagnostics_relay_service = DiagnosticsRelayService() 143 | diagnostics_relay_client = diagnostics_relay_service.new_client(device) 144 | if diagnostics_relay_client: 145 | data = diagnostics_relay_service.query_ioregistry_entry(diagnostics_relay_client, name, "") 146 | print(data) 147 | if data and len(data) >= 2: 148 | if data[0]: 149 | if "IORegistry" in data[1]: 150 | io_registry = data[1]["IORegistry"] 151 | update_time = io_registry['UpdateTime'] if "UpdateTime" in io_registry else None 152 | voltage = io_registry['Voltage'] if "Voltage" in io_registry else None 153 | instant_amperage = io_registry['InstantAmperage'] if "InstantAmperage" in io_registry else None 154 | temperature = io_registry['Temperature'] if "Temperature" in io_registry else None 155 | current = instant_amperage * -1 156 | power = current * voltage / 1000 157 | print("[Battery] time=%d, current=%d, voltage=%d, power=%d, temperature=%d" % (update_time, current, voltage, power, temperature)) 158 | 159 | diagnostics_relay_service.goodbye(diagnostics_relay_client) 160 | diagnostics_relay_service.free_client(diagnostics_relay_client) 161 | 162 | 163 | def get_app_list(device): 164 | installation_proxy_service = InstallationProxyService() 165 | installation_proxy_client = installation_proxy_service.new_client(device) 166 | user_apps = None 167 | system_apps = None 168 | if installation_proxy_client: 169 | user_apps = installation_proxy_service.browse(installation_proxy_client, "User") 170 | system_apps = installation_proxy_service.browse(installation_proxy_client, "System") 171 | installation_proxy_service.free_client(installation_proxy_client) 172 | return user_apps, system_apps 173 | 174 | 175 | def print_applications(udid): 176 | device = _get_device_or_die(udid) 177 | 178 | user_apps, system_apps = get_app_list(device) 179 | print("List of user applications installed:") 180 | if user_apps: 181 | for app in user_apps: 182 | for key, value in app.items(): 183 | print("%s: %s" % (key, value)) 184 | print("") 185 | print("") 186 | if system_apps: 187 | print("List of system applications installed:") 188 | for app in system_apps: 189 | for key, value in app.items(): 190 | print("%s: %s" % (key, value)) 191 | print("") 192 | device_service.free_device(device) 193 | 194 | 195 | def print_icon(udid, bundle_id, output): 196 | device = _get_device_or_die(udid) 197 | 198 | spring_board_service = SpringBoardService() 199 | spring_board_client = spring_board_service.new_client(device) 200 | 201 | pngdata = spring_board_service.get_icon_pngdata(spring_board_client, bundle_id) 202 | if pngdata: 203 | with open(output, "wb") as fp: 204 | fp.write(pngdata) 205 | print("Save icon file at %s" % os.path.abspath(output)) 206 | else: 207 | print("Can not get icon of app(bundleId=%s)" % bundle_id) 208 | spring_board_service.free_client(spring_board_client) 209 | device_service.free_device(device) 210 | 211 | 212 | def enable_Wireless(udid,enable = 1): 213 | device = _get_device_or_die(udid) 214 | lockdown_service = LockdownService() 215 | client = lockdown_service.new_client(device) 216 | lockdown_service.enable_wireless(client,int(enable),"","") 217 | lockdown_service.free_client(client) 218 | 219 | def orientation_to_str(orientation): 220 | if orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_PORTRAIT_UPSIDE_DOWN: 221 | return "PORTRAIT_UPSIDE_DOWN" 222 | elif orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_LANDSCAPE_LEFT: 223 | return "LANDSCAPE_LEFT" 224 | elif orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_LANDSCAPE_RIGHT: 225 | return "LANDSCAPE_RIGHT" 226 | elif orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_PORTRAIT: 227 | return "PORTRAIT" 228 | else: 229 | return "UNKNOWN" 230 | 231 | def rotate_image(data, orientation): 232 | if Image is None: 233 | print("[WARNING] PIL is not installed, can not auto rotate image, orientation=%s!" % orientation_to_str(orientation)) 234 | return data 235 | 236 | bytes_io = io.BytesIO(data) 237 | image = Image.open(bytes_io) 238 | #print("image size=%s orientation=%s" % (str(image.size), str(orientation))) 239 | 240 | if orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_PORTRAIT_UPSIDE_DOWN: 241 | image = image.transpose(Image.ROTATE_180) 242 | elif orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_LANDSCAPE_LEFT: 243 | image = image.transpose(Image.ROTATE_270) 244 | elif orientation == SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_LANDSCAPE_RIGHT: 245 | image = image.transpose(Image.ROTATE_90) 246 | else: 247 | image = image # portrait, do nothing 248 | bytes_io = io.BytesIO() 249 | image.save(bytes_io, format="PNG") 250 | return bytes_io.getvalue() 251 | 252 | def print_screenshot(udid, output = None): 253 | device = _get_device_or_die(udid) 254 | 255 | # get orientation of device 256 | spring_board_service = SpringBoardService() 257 | spring_board_client = spring_board_service.new_client(device) 258 | orientation = spring_board_service.get_interface_orientation(spring_board_client) 259 | spring_board_service.free_client(spring_board_client) 260 | 261 | # take screenshot 262 | screenshotr_service = ScreenshotrService() 263 | screenshotr_client = screenshotr_service.new_client(device) 264 | imgdata, file_ext = screenshotr_service.take_screenshot(screenshotr_client) 265 | if imgdata: 266 | if output is None: 267 | output = os.path.join(ROOT_DIR, "screenshot_%s%s" % (datetime.datetime.now().strftime("%Y%m%d_%H%M%S"), file_ext)) 268 | imgdata = rotate_image(imgdata, orientation) 269 | with open(output, "wb") as fp: 270 | fp.write(imgdata) 271 | print("Save screenshot image file at %s(orientation: %s)" % (os.path.abspath(output), orientation_to_str(orientation))) 272 | else: 273 | print("Error: Can not take screenshot") 274 | screenshotr_service.free_client(screenshotr_client) 275 | 276 | device_service.free_device(device) 277 | 278 | def print_image_lookup(udid, image_type = None): 279 | device = _get_device_or_die(udid) 280 | 281 | lockdown_service = LockdownService() 282 | lockdown_client = lockdown_service.new_client(device) 283 | 284 | product_version, error = lockdown_service.get_value(lockdown_client, key="ProductVersion") 285 | if error: 286 | print("Error: %s" % error) 287 | return 288 | lockdown_service.free_client(lockdown_client) 289 | 290 | if image_type is None: 291 | image_type = "Developer" 292 | 293 | image_mounter_service = ImageMounterService() 294 | image_mounter_client = image_mounter_service.new_client(device) 295 | 296 | image_mounted, error = image_mounter_service.lookup_image(image_mounter_client, image_type, product_version) 297 | if error: 298 | print("Error: %s" % error) 299 | else: 300 | print("Image mount status: " + ("Yes" if image_mounted else "No")) 301 | 302 | image_mounter_service.hangup(image_mounter_client) 303 | image_mounter_service.free_client(image_mounter_client) 304 | 305 | def print_mount_image(udid, image_type, image_file, image_signature_file): 306 | device = _get_device_or_die(udid) 307 | 308 | lockdown_service = LockdownService() 309 | lockdown_client = lockdown_service.new_client(device) 310 | 311 | product_version, error = lockdown_service.get_value(lockdown_client, key="ProductVersion") 312 | if error: 313 | print("Error: %s" % error) 314 | return 315 | lockdown_service.free_client(lockdown_client) 316 | 317 | if image_type is None: 318 | image_type = "Developer" 319 | 320 | image_mounter_service = ImageMounterService() 321 | image_mounter_client = image_mounter_service.new_client(device) 322 | 323 | result = image_mounter_service.upload_image(image_mounter_client, image_type, image_file, image_signature_file) 324 | if not result: 325 | print("Error: Can not upload image") 326 | else: 327 | image_path = "/private/var/mobile/Media/PublicStaging/staging.dimage" 328 | result, error = image_mounter_service.mount_image(image_mounter_client, image_type, image_path, image_signature_file) 329 | if error: 330 | print("Error: %s" % error) 331 | else: 332 | print("Mount result: %s" % str(result)) 333 | 334 | image_mounter_service.hangup(image_mounter_client) 335 | image_mounter_service.free_client(image_mounter_client) 336 | device_service.free_device(device) 337 | 338 | 339 | line = "" 340 | def print_syslog(udid = None): 341 | device = _get_device_or_die(udid) 342 | 343 | syslog_relay_service = SyslogRelayService() 344 | syslog_relay_client = syslog_relay_service.new_client(device) 345 | def callback(char_data, user_data): 346 | global line 347 | if char_data == b"\n": 348 | print(line) 349 | line = "" 350 | else: 351 | line += char_data.decode("utf-8") 352 | 353 | result = syslog_relay_service.start_capture(syslog_relay_client, callback) 354 | if result: 355 | print("System log:(pressing Ctrl+C to exit)") 356 | try: 357 | while True: 358 | pass 359 | except KeyboardInterrupt: 360 | pass 361 | 362 | syslog_relay_service.free_client(syslog_relay_client) 363 | device_service.free_device(device) 364 | 365 | def get_house_arrest_Client(device, device_directory, bundle_id, docType): 366 | print(bundle_id) 367 | if docType == None: 368 | docType = 1 369 | 370 | house_arrest_service = House_arrest_proxy_service() 371 | client = house_arrest_service.new_client(device) 372 | afc_client = house_arrest_service.open_sandbox_with_appid(client, docType, bundle_id) 373 | 374 | if docType == 1: 375 | root = "/Documents/" 376 | if device_directory == "/": 377 | device_directory = "" 378 | elif device_directory.startswith("/"): 379 | root = "/Documents" 380 | 381 | device_directory = root + device_directory 382 | 383 | return client, afc_client, device_directory 384 | 385 | # docType = 1: sharing documents ,else: sandBox 386 | def print_list(udid, device_directory = ".", bundle_id = None, docType = 1): 387 | device = _get_device_or_die(udid) 388 | afc_service = AfcService() 389 | client = None 390 | if bundle_id == None: 391 | afc_client = afc_service.new_client(device) 392 | else: 393 | client, afc_client, device_directory = get_house_arrest_Client(device, device_directory, bundle_id, docType) 394 | 395 | file_list = afc_service.read_directory(afc_client, device_directory) 396 | print("List of directory %s:" % device_directory) 397 | if file_list: 398 | for file in file_list: 399 | print(file['filename'], end=" ") 400 | 401 | afc_service.free_client(afc_client) 402 | device_service.free_device(device) 403 | if client != None: 404 | House_arrest_proxy_service.free_client(client) 405 | 406 | 407 | def pull_file(udid, remote_file , bundle_id = None, docType = 1): # TODO: pull directory 408 | device = _get_device_or_die(udid) 409 | 410 | afc_service = AfcService() 411 | client = None 412 | if bundle_id == None: 413 | afc_client = afc_service.new_client(device) 414 | else: 415 | client, afc_client, remote_file = get_house_arrest_Client(device, remote_file, bundle_id, docType) 416 | 417 | 418 | afc_file = afc_service.open_file(afc_client, remote_file, "r") 419 | output_file = os.path.join(ROOT_DIR, os.path.basename(remote_file)) 420 | output = open(output_file, "wb") 421 | while True: 422 | buffer = afc_file.read(1024) 423 | output.write(buffer) 424 | if not buffer: 425 | break 426 | output.close() 427 | afc_file.close() 428 | afc_service.free_client(afc_client) 429 | device_service.free_device(device) 430 | print("pull file %s" % output_file) 431 | 432 | if client != None: 433 | House_arrest_proxy_service.free_client(client) 434 | 435 | 436 | def push_file(udid, local_file, device_directory, bundle_id = None, docType = 1): 437 | if not os.path.exists(local_file): 438 | print("Error: %s is not exists" % local_file) 439 | return 440 | 441 | device = _get_device_or_die(udid) 442 | afc_service = AfcService() 443 | client = None 444 | if bundle_id == None: 445 | afc_client = afc_service.new_client(device) 446 | else: 447 | client, afc_client, device_directory = get_house_arrest_Client(device, device_directory, bundle_id, docType) 448 | 449 | success = afc_service.make_directory(afc_client, device_directory) # TODO: check 450 | remote_file = device_directory + "/" + os.path.basename(local_file) 451 | afc_file = afc_service.open_file(afc_client, remote_file, "w") 452 | input_file = open(local_file, "rb") 453 | while True: 454 | buffer = input_file.read(1024) 455 | if not buffer: 456 | break 457 | afc_file.write(buffer) 458 | input_file.close() 459 | afc_file.close() 460 | afc_service.free_client(afc_client) 461 | device_service.free_device(device) 462 | 463 | if client != None: 464 | House_arrest_proxy_service.free_client(client) 465 | 466 | print("push file %s to %s" % (local_file, remote_file)) 467 | 468 | 469 | def make_directory(udid, device_directory, bundle_id = None, docType = 1): 470 | device = _get_device_or_die(udid) 471 | client = None 472 | afc_service = AfcService() 473 | if bundle_id == None: 474 | afc_client = afc_service.new_client(device) 475 | else: 476 | client, afc_client, device_directory = get_house_arrest_Client(device, device_directory, bundle_id, docType) 477 | 478 | 479 | result = afc_service.make_directory(afc_client, device_directory) 480 | print("Make directory %s %s" % (device_directory, "Success" if result else "Fail")) 481 | 482 | afc_service.free_client(afc_client) 483 | device_service.free_device(device) 484 | if client != None: 485 | House_arrest_proxy_service.free_client(client) 486 | 487 | 488 | def remove_path(udid, device_path, bundle_id = None, docType = 1): 489 | device = _get_device_or_die(udid) 490 | client = None 491 | afc_service = AfcService() 492 | if bundle_id == None: 493 | afc_client = afc_service.new_client(device) 494 | else: 495 | client, afc_client, device_path = get_house_arrest_Client(device, device_path, bundle_id, docType) 496 | 497 | result = False 498 | error = None 499 | try: 500 | result = afc_service.remove_path(afc_client, device_path) 501 | except IOError as e: 502 | error = e 503 | 504 | if result: 505 | print("%s Deleted." % device_path) 506 | else: 507 | print("Error: %s" % error) 508 | 509 | afc_service.free_client(afc_client) 510 | device_service.free_device(device) 511 | if client != None: 512 | House_arrest_proxy_service.free_client(client) 513 | 514 | def install_ipa(udid, ipa_path): 515 | 516 | device = _get_device_or_die(udid) 517 | installation_proxy_service = InstallationProxyService() 518 | client = installation_proxy_service.new_client(device) 519 | print("start install") 520 | apps = installation_proxy_service.install(device, client, ipa_path) 521 | print("finsih install") 522 | installation_proxy_service.free_client(client) 523 | 524 | def uninstall_ipa(udid, bundle_id): 525 | device = _get_device_or_die(udid) 526 | installation_proxy_service = InstallationProxyService() 527 | client = installation_proxy_service.new_client(device) 528 | print("start install") 529 | apps = installation_proxy_service.uninstall(device, client, bundle_id) 530 | print("finsih install") 531 | installation_proxy_service.free_client(client) 532 | 533 | def start_heartbeat(udid): 534 | control = DeviceService.start_heartbeat(udid) 535 | try: 536 | while control['running']: 537 | time.sleep(1) 538 | except: 539 | pass 540 | finally: 541 | control['running'] = False 542 | control['thread'].join() 543 | 544 | def print_developer_mode_status(udid): 545 | device = _get_device_or_die(udid) 546 | lockdown_service = LockdownService() 547 | client = lockdown_service.new_client(device) 548 | result = lockdown_service.get_developer_mode_status(client) 549 | print("develpoer mode status: {0}".format(result)) 550 | lockdown_service.free_client(client) 551 | 552 | def print_set_developer_mode(udid, mode): 553 | device = _get_device_or_die(udid) 554 | amfi_client_service = AMFIService() 555 | amfi_client = amfi_client_service.new_client(device) 556 | ret = amfi_client_service.set_developer_mode(amfi_client, int(mode)) 557 | if ret == 0: 558 | print("set developer mode success") 559 | else: 560 | print("error, error code: {0}".format(ret)) 561 | amfi_client_service.free_client(amfi_client) 562 | device_service.free_device(device) 563 | 564 | def main(): 565 | argparser = argparse.ArgumentParser() 566 | # argparser.add_argument("command", help="command", choices=["devices", "deviceinfo", "devicename", "instrument"]) 567 | cmd_parser = argparser.add_subparsers(dest="command") 568 | cmd_parser.add_parser("devices") 569 | cmd_parser.add_parser("applications") 570 | cmd_parser.add_parser("deviceinfo") 571 | cmd_parser.add_parser("syslog") 572 | 573 | 574 | # list 575 | list_parser = cmd_parser.add_parser("ls") 576 | list_parser.add_argument("device_directory") 577 | list_parser.add_argument("--bundle_id", required=False ) 578 | list_parser.add_argument("-d", "--docType", required=False, type=int, help='default 1,1 for sharing documents other for sandBox') 579 | 580 | # mkdir 581 | list_parser = cmd_parser.add_parser("mkdir") 582 | list_parser.add_argument("device_directory") 583 | list_parser.add_argument("--bundle_id", required=False ) 584 | list_parser.add_argument("-d", "--docType", required=False, type=int, help='default 1,1 for sharing documents other for sandBox') 585 | 586 | # rm 587 | list_parser = cmd_parser.add_parser("rm") 588 | list_parser.add_argument("device_path") 589 | list_parser.add_argument("--bundle_id", required=False ) 590 | list_parser.add_argument("-d", "--docType", required=False, type=int, help='default 1,1 for sharing documents other for sandBox') 591 | # pull file 592 | pull_parser = cmd_parser.add_parser("pull") 593 | pull_parser.add_argument("remote_file") 594 | pull_parser.add_argument("--bundle_id", required=False ) 595 | pull_parser.add_argument("-d", "--docType", required=False, type=int, help='default 1,1 for sharing documents other for sandBox') 596 | 597 | # push file 598 | push_parser = cmd_parser.add_parser("push") 599 | push_parser.add_argument("local_file") 600 | push_parser.add_argument("device_directory") 601 | push_parser.add_argument("--bundle_id", required=False ) 602 | push_parser.add_argument("-d", "--docType", required=False, type=int, help='default 1,1 for sharing documents other for sandBox') 603 | # imagemounter 604 | lookupimage_parser = cmd_parser.add_parser("lookupimage") 605 | lookupimage_parser.add_argument("-t", "--image_type", required=False) 606 | # imagemounter 607 | mountimage_parser = cmd_parser.add_parser("mountimage") 608 | mountimage_parser.add_argument("-t", "--image_type", required=False) 609 | mountimage_parser.add_argument("-i", "--image_file", required=False) 610 | mountimage_parser.add_argument("-s", "--sig_file", required=False) 611 | # screenshot 612 | screenshot_parser = cmd_parser.add_parser("screenshot") 613 | screenshot_parser.add_argument("-o", "--output", required=False) 614 | # geticon 615 | geticon_parser = cmd_parser.add_parser("geticon") 616 | geticon_parser.add_argument("--bundle_id", required=True) 617 | geticon_parser.add_argument("-o", "--output", required=True) 618 | # getvalue 619 | getvalue_parser = cmd_parser.add_parser("getvalue") 620 | getvalue_parser.add_argument("-k", "--key") 621 | # instrument 622 | instrument_parser = cmd_parser.add_parser("instrument") 623 | setup_instrument_parser(instrument_parser) 624 | # diagnostics 625 | diagnostics_parser = cmd_parser.add_parser("diagnostics") 626 | diagnostics_subcmd_parsers = diagnostics_parser.add_subparsers(dest="diagnostics_cmd") 627 | diagnostics_ioregentry_parser = diagnostics_subcmd_parsers.add_parser("ioregentry") 628 | diagnostics_ioregentry_parser.add_argument("key") 629 | # get_developer_mode_status 630 | cmd_parser.add_parser("developermodestatus") 631 | # AMFI set_developer_mode 632 | amfi_developer_mode_parser = cmd_parser.add_parser("setdevelopermode") 633 | amfi_developer_mode_parser.add_argument("-m", "--mode", required=True) 634 | # enableWireless 635 | cmd_wireless = cmd_parser.add_parser("enableWireless") 636 | cmd_wireless.add_argument("-e", "--enable", required=False) 637 | 638 | cmd_parser.add_parser("heartbeat") 639 | 640 | argparser.add_argument("-u", "--udid", help="udid") 641 | 642 | ## install 643 | install_parser = cmd_parser.add_parser("install") 644 | install_parser.add_argument("ipa_path") 645 | 646 | ## uninstall 647 | uninstall_parser = cmd_parser.add_parser("uninstall") 648 | uninstall_parser.add_argument("bundle_id") 649 | 650 | args = argparser.parse_args() 651 | if args.command == "devices": 652 | print_devices() 653 | elif args.command == "applications": 654 | print_applications(args.udid) 655 | elif args.command == "deviceinfo": 656 | print_device_info(args.udid) 657 | elif args.command == "lookupimage": 658 | print_image_lookup(args.udid, args.image_type) 659 | elif args.command == "mountimage": 660 | print_mount_image(args.udid, args.image_type, args.image_file, args.sig_file) 661 | elif args.command == "geticon": 662 | print_icon(args.udid, args.bundle_id, args.output) 663 | elif args.command == "syslog": 664 | print_syslog(args.udid) 665 | elif args.command == "screenshot": 666 | print_screenshot(args.udid, args.output) 667 | elif args.command == "getvalue": 668 | print_get_value(args.udid, args.key) 669 | elif args.command == 'instrument': 670 | instrument_main(_get_device_or_die(args.udid), args) 671 | elif args.command == 'ls': 672 | print_list(args.udid, args.device_directory, args.bundle_id, args.docType) 673 | elif args.command == 'mkdir': 674 | make_directory(args.udid, args.device_directory, args.bundle_id, args.docType) 675 | elif args.command == 'rm': 676 | remove_path(args.udid, args.device_path, args.bundle_id, args.docType) 677 | elif args.command == 'pull': 678 | pull_file(args.udid, args.remote_file, args.bundle_id, args.docType) 679 | elif args.command == 'push': 680 | push_file(args.udid, args.local_file, args.device_directory, args.bundle_id, args.docType) 681 | elif args.command == 'enableWireless': 682 | enable_Wireless(args.udid, args.enable) 683 | elif args.command == 'install': 684 | install_ipa(args.udid, args.ipa_path) 685 | elif args.command == 'uninstall': 686 | uninstall_ipa(args.udid, args.bundle_id) 687 | elif args.command == 'heartbeat': 688 | start_heartbeat(args.udid) 689 | elif args.command == 'diagnostics': 690 | if args.diagnostics_cmd == "ioregentry": 691 | print_diagnostics(args.udid, args.key) 692 | else: 693 | print("unknown diagnostics cmd:", args.diagnostics_cmd, "support cmds:", ["ioregentry"]) 694 | elif args.command == 'developermodestatus': 695 | print_developer_mode_status(args.udid) 696 | elif args.command == 'setdevelopermode': 697 | print_set_developer_mode(args.udid, args.mode) 698 | else: 699 | argparser.print_usage() 700 | 701 | 702 | if __name__ == "__main__": 703 | main() -------------------------------------------------------------------------------- /bpylist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/bpylist/__init__.py -------------------------------------------------------------------------------- /bpylist/archive_types.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | 3 | class timestamp(float): 4 | """ 5 | Represents the concept of time (in seconds) since the UNIX epoch. 6 | 7 | The topic of date and time representations in computers inherits many 8 | of the complexities of the topics of date and time representation before 9 | computers existed, and then brings its own revelations to the mess. 10 | 11 | Python seems to take a very Gregorian view of dates, but has enabled full 12 | madness for times. 13 | 14 | However, we want to store something more agnostic, something that can easily 15 | be used in computations and formatted for any particular collection of 16 | date and time conventions. 17 | 18 | Fortunately, the database we use, our API, and our Cocoa clients have made 19 | similar decisions. So to make the transmission of data to and from clients, 20 | we will use this class to store our agnostic representation. 21 | """ 22 | 23 | unix2apple_epoch_delta = 978307200.0 24 | 25 | def encode_archive(obj, archive): 26 | "Delegate for packing timestamps back into the NSDate archive format" 27 | offset = obj - timestamp.unix2apple_epoch_delta 28 | archive.encode('NS.time', offset) 29 | 30 | def decode_archive(archive): 31 | "Delegate for unpacking NSDate objects from an archiver.Archive" 32 | offset = archive.decode('NS.time') 33 | return timestamp(timestamp.unix2apple_epoch_delta + offset) 34 | 35 | def __str__(self): 36 | return f"bpylist.timestamp {self.to_datetime().__repr__()}" 37 | 38 | def to_datetime(self) -> datetime: 39 | return datetime.fromtimestamp(self, timezone.utc) 40 | 41 | 42 | class uid(int): 43 | """ 44 | An unique identifier used by Cocoa's NSArchiver to identify a particular 45 | class that should be used to map an archived object back into a native 46 | object. 47 | """ 48 | 49 | def __repr__(self): 50 | return f"uid({int(self)})" 51 | 52 | def __str__(self): 53 | return f"uid({int(self)})" 54 | -------------------------------------------------------------------------------- /bpylist/archiver.py: -------------------------------------------------------------------------------- 1 | from bpylist import bplist 2 | from bpylist.archive_types import timestamp, uid 3 | from typing import Mapping 4 | from collections import OrderedDict 5 | 6 | # The magic number which Cocoa uses as an implementation version. 7 | # I don' think there were 99_999 previous implementations, I think 8 | # Apple just likes to store a lot of zeros 9 | NSKeyedArchiveVersion = 100_000 10 | 11 | # Cached for convenience 12 | null_uid = uid(0) 13 | 14 | 15 | def unarchive(plist: bytes) -> object: 16 | "Unpack an NSKeyedArchived byte blob into a more useful object tree." 17 | return Unarchive(plist).top_object() 18 | 19 | 20 | def unarchive_file(path: str) -> object: 21 | "A convenience for unarchive(plist) which loads an archive from a file for you" 22 | with open(path, 'rb') as fd: 23 | return unarchive(fd.read()) 24 | 25 | 26 | def archive(obj: object) -> bytes: 27 | "Pack an object tree into an NSKeyedArchived blob." 28 | return Archive(obj).to_bytes() 29 | 30 | 31 | class ArchiverError(Exception): 32 | pass 33 | 34 | 35 | class UnsupportedArchiver(ArchiverError): 36 | """ 37 | Just in case we are given a regular NSArchive instead of an NSKeyedArchive, 38 | or if Apple introduces a new archiver and we are given some of its work. 39 | """ 40 | 41 | def __init__(self, alternate): 42 | super().__init__(f"unsupported encoder: `{alternate}'") 43 | 44 | 45 | class UnsupportedArchiveVersion(ArchiverError): 46 | def __init__(self, version): 47 | super().__init__(f"expected {NSKeyedArchiveVersion}, got `{version}'") 48 | 49 | 50 | class MissingTopObject(ArchiverError): 51 | def __init__(self, plist): 52 | super().__init__(f"no top object! plist dump: {plist}") 53 | 54 | 55 | class MissingTopObjectUID(ArchiverError): 56 | def __init__(self, top): 57 | super().__init__(f"top object did not have a UID! dump: {top}") 58 | 59 | 60 | class MissingObjectsArray(ArchiverError): 61 | def __init__(self, plist): 62 | super().__init__(f"full plist dump: `{plist}'") 63 | 64 | 65 | class MissingClassMetaData(ArchiverError): 66 | def __init__(self, index, result): 67 | super().__init__(f"$class had no metadata {index}: {result}") 68 | 69 | 70 | class MissingClassName(ArchiverError): 71 | def __init__(self, meta): 72 | super().__init__(f"$class had no $classname; $class = {meta}") 73 | 74 | 75 | class MissingClassUID(ArchiverError): 76 | def __init__(self, obj): 77 | super().__init__(f"object has no $class: {obj}") 78 | 79 | 80 | class CircularReference(ArchiverError): 81 | def __init__(self, index): 82 | super().__init__(f"archive has a cycle with {index}") 83 | 84 | 85 | class MissingClassMapping(ArchiverError): 86 | def __init__(self, name, mapping): 87 | super().__init__(f"no mapping for {name} in {mapping}") 88 | 89 | 90 | class DictArchive: 91 | "Delegate for packing/unpacking NS(Mutable)Dictionary objects" 92 | 93 | def decode_archive(archive): 94 | key_uids = archive.decode('NS.keys') 95 | val_uids = archive.decode('NS.objects') 96 | 97 | count = len(key_uids) 98 | d = dict() 99 | 100 | for i in range(count): 101 | key = archive._decode_index(key_uids[i]) 102 | val = archive._decode_index(val_uids[i]) 103 | d[key] = val 104 | 105 | return d 106 | 107 | class MutableStringArchive: 108 | "Delegate for packing/unpacking NSMutableString objects" 109 | 110 | def decode_archive(archive): 111 | s = archive.decode('NS.string') 112 | return s 113 | 114 | class MutableDataArchive: 115 | "Delegate for packing/unpacking NSMutableData objects" 116 | 117 | def decode_archive(archive): 118 | s = archive.decode('NS.data') 119 | return s 120 | class ListArchive: 121 | "Delegate for packing/unpacking NS(Mutable)Array objects" 122 | 123 | def decode_archive(archive): 124 | uids = archive.decode('NS.objects') 125 | return [archive._decode_index(index) for index in uids] 126 | 127 | 128 | class SetArchive: 129 | "Delegate for packing/unpacking NS(Mutable)Set objects" 130 | 131 | def decode_archive(archive): 132 | uids = archive.decode('NS.objects') 133 | return set([archive._decode_index(index) for index in uids]) 134 | 135 | class NullArchive: 136 | 137 | def decode_archive(archive): 138 | return None 139 | 140 | class TODOArchive: 141 | 142 | def decode_archive(archive): 143 | return "!!! TODO !!!" 144 | 145 | class ErrorArchive: 146 | 147 | def decode_archive(archive): 148 | domain = archive.decode('NSDomain') 149 | userinfo = archive.decode('NSUserInfo') 150 | code = archive.decode('NSCode') 151 | return {"$class": "NSError", "domain": domain, "userinfo": userinfo, "code": code} 152 | 153 | class ExceptionArchive: 154 | 155 | def decode_archive(archive): 156 | name = archive.decode('NS.name') 157 | reason = archive.decode('NS.reason') 158 | userinfo = archive.decode('userinfo') 159 | return {"$class": "NSException", "reason": reason, "userinfo": userinfo, "name": name} 160 | 161 | class ArchivedObject: 162 | """ 163 | Stateful wrapper around Unarchive for an archived object. 164 | 165 | This is the object that will be passed to unarchiving delegates 166 | so that they can construct objects. The only useful method on 167 | this class is decode(self, key). 168 | """ 169 | 170 | def __init__(self, obj, unarchiver): 171 | self._object = obj 172 | self._unarchiver = unarchiver 173 | 174 | def _decode_index(self, index: uid): 175 | return self._unarchiver.decode_object(index) 176 | 177 | def decode(self, key: str): 178 | return self._unarchiver.decode_key(self._object, key) 179 | 180 | 181 | class CycleToken: 182 | "token used in Unarchive's unpacked_uids cache to help detect cycles" 183 | pass 184 | 185 | 186 | class Unarchive: 187 | """ 188 | Capable of unpacking an archived object tree in the NSKeyedArchive format. 189 | 190 | Apple's implementation can be found here: 191 | https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation\ 192 | /NSKeyedUnarchiver.swift 193 | 194 | Note: At this time, we support only a limited subset of circular 195 | references. In general, cycles in the object tree being unarchived is 196 | be considered forbidden by this implementation. 197 | 198 | In order to properly support circular references, the unarchiver needs to 199 | separate allocation from initialization so that it can allocate an instance 200 | of a class and cache the reference before passing the instance to the 201 | decode-specific initializer. However, doing this for certain built-in types 202 | is non-trivial, and I don't want to have a mess of special cases. 203 | """ 204 | 205 | def __init__(self, input: bytes): 206 | self.input = input 207 | self.unpacked_uids = {} 208 | self.top_uid = null_uid 209 | self.objects = None 210 | 211 | def unpack_archive_header(self): 212 | plist = bplist.parse(self.input) 213 | 214 | archiver = plist.get('$archiver') 215 | if archiver != 'NSKeyedArchiver': 216 | raise UnsupportedArchiver(archiver) 217 | 218 | version = plist.get('$version') 219 | if version != NSKeyedArchiveVersion: 220 | raise UnsupportedArchiveVersion(version) 221 | 222 | top = plist.get('$top') 223 | if not isinstance(top, dict): 224 | raise MissingTopObject(plist) 225 | 226 | self.top_uid = top.get('root') 227 | if not isinstance(self.top_uid, uid): 228 | raise MissingTopObjectUID(top) 229 | 230 | self.objects = plist.get('$objects') 231 | if not isinstance(self.objects, list): 232 | raise MissingObjectsArray(plist) 233 | 234 | def class_for_uid(self, index: uid): 235 | "use the UNARCHIVE_CLASS_MAP to find the unarchiving delegate of a uid" 236 | 237 | meta = self.objects[index] 238 | if not isinstance(meta, dict): 239 | raise MissingClassMetaData(index, meta) 240 | 241 | name = meta.get('$classname') 242 | if not isinstance(name, str): 243 | raise MissingClassName(meta) 244 | 245 | klass = UNARCHIVE_CLASS_MAP.get(name) 246 | if klass is None: 247 | raise MissingClassMapping(name, UNARCHIVE_CLASS_MAP) 248 | 249 | return klass 250 | 251 | def decode_key(self, obj, key): 252 | val = obj.get(key) 253 | if isinstance(val, uid): 254 | return self.decode_object(val) 255 | return val 256 | 257 | def decode_object(self, index: uid): 258 | # index 0 always points to the $null object, which is the archive's 259 | # special way of saying the value is null/nil/none 260 | if index == 0: 261 | return None 262 | #print("decode index:", index) 263 | obj = self.unpacked_uids.get(index) 264 | if obj == CycleToken: 265 | raise CircularReference(index) 266 | 267 | if obj is not None: 268 | return obj 269 | 270 | raw_obj = self.objects[index] 271 | #print(raw_obj) 272 | # put a temp object in place, in case we have a circular 273 | # reference, which we do not really support 274 | self.unpacked_uids[index] = CycleToken 275 | 276 | # if obj is a (semi-)primitive type (e.g. str) 277 | if not isinstance(raw_obj, dict): 278 | self.unpacked_uids[index] = raw_obj 279 | return raw_obj 280 | 281 | class_uid = raw_obj.get('$class') 282 | if not isinstance(class_uid, uid): 283 | raise MissingClassUID(raw_obj) 284 | 285 | klass = self.class_for_uid(class_uid) 286 | obj = klass.decode_archive(ArchivedObject(raw_obj, self)) 287 | 288 | self.unpacked_uids[index] = obj 289 | return obj 290 | 291 | def top_object(self): 292 | "recursively decode the root/top object and return the result" 293 | 294 | self.unpack_archive_header() 295 | return self.decode_object(self.top_uid) 296 | 297 | 298 | class ArchivingObject: 299 | """ 300 | Stateful wrapper around Archive for an object being archived. 301 | 302 | This is the object that will be passed to unarchiving delegates 303 | so that they can do their part in constructing the archive. The 304 | only useful method on this class is encode(self, key, val). 305 | """ 306 | 307 | def __init__(self, archive_obj, archiver): 308 | self._archive_obj = archive_obj 309 | self._archiver = archiver 310 | 311 | def encode(self, key, val): 312 | val = self._archiver.encode(val) 313 | self._archive_obj[key] = val 314 | 315 | 316 | class Archive: 317 | """ 318 | Capable of packing an object tree into the NSKeyedArchive format. 319 | 320 | Apple's implementation can be found here: 321 | https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation\ 322 | /NSKeyedArchiver.swift 323 | 324 | Unlike our unarchiver, we are actually capable of archiving circular 325 | references...so, yeah. 326 | """ 327 | 328 | # types which do not require the "object" encoding for an archive; 329 | primitive_types = [int, float, bool, str, bytes, uid] 330 | 331 | # types which require no extra encoding at all, they can be inlined 332 | # in the archive 333 | inline_types = [int, float, bool] 334 | 335 | def __init__(self, input): 336 | self.input = input 337 | # cache/map class names (str) to uids 338 | self.class_map = {} 339 | # cache/map of already archived objects to uids (to avoid cycles) 340 | self.ref_map = {} 341 | # objects that go directly into the archive, always start with $null 342 | self.objects = ['$null'] 343 | 344 | def uid_for_archiver(self, archiver: type) -> uid: 345 | """ 346 | Ensure the class definition for the archiver is included in the arcive. 347 | 348 | Non-primitive objects are encoded as a dictionary of key-value pairs; 349 | there is always a $class key, which has a UID value...the UID is itself 350 | a pointer/index which points to the definition of the class (which is 351 | also in the archive). 352 | 353 | This method makes sure that all the metadata is included in the archive 354 | exactly once (no duplicates class metadata). 355 | """ 356 | 357 | val = self.class_map.get(archiver) 358 | if val: 359 | return val 360 | 361 | val = uid(len(self.objects)) 362 | self.class_map[archiver] = val 363 | 364 | # TODO: this is where we might need to include the full class ancestry; 365 | # though the open source code from apple does not appear to check 366 | self.objects.append({ '$classes': [archiver], '$classname': archiver }) 367 | 368 | return val 369 | 370 | def encode(self, val): 371 | cls = val.__class__ 372 | 373 | if cls in Archive.inline_types: 374 | return val 375 | 376 | return self.archive(val) 377 | 378 | def encode_list(self, objs, archive_obj): 379 | archiver_uid = self.uid_for_archiver('NSArray') 380 | archive_obj['$class'] = archiver_uid 381 | archive_obj['NS.objects'] = [self.archive(obj) for obj in objs] 382 | 383 | def encode_set(self, objs, archive_obj): 384 | archiver_uid = self.uid_for_archiver('NSSet') 385 | archive_obj['$class'] = archiver_uid 386 | archive_obj['NS.objects'] = [self.archive(obj) for obj in objs] 387 | 388 | def encode_dict(self, obj, archive_obj): 389 | archiver_uid = self.uid_for_archiver('NSDictionary') 390 | archive_obj['$class'] = archiver_uid 391 | 392 | keys = [] 393 | vals = [] 394 | for k in obj: 395 | keys.append(self.archive(k)) 396 | vals.append(self.archive(obj[k])) 397 | 398 | archive_obj['NS.keys'] = keys 399 | archive_obj['NS.objects'] = vals 400 | 401 | def encode_top_level(self, obj, archive_obj): 402 | "Encode obj and store the encoding in archive_obj" 403 | 404 | cls = obj.__class__ 405 | 406 | if cls == list: 407 | self.encode_list(obj, archive_obj) 408 | 409 | elif cls == dict: 410 | self.encode_dict(obj, archive_obj) 411 | 412 | elif cls == set: 413 | self.encode_set(obj, archive_obj) 414 | 415 | else: 416 | archiver = ARCHIVE_CLASS_MAP.get(cls) 417 | if archiver is None: 418 | raise MissingClassMapping(obj, ARCHIVE_CLASS_MAP) 419 | 420 | archiver_uid = self.uid_for_archiver(archiver) 421 | archive_obj['$class'] = archiver_uid 422 | 423 | archive_wrapper = ArchivingObject(archive_obj, self) 424 | cls.encode_archive(obj, archive_wrapper) 425 | 426 | def archive(self, obj) -> uid: 427 | "Add the encoded form of obj to the archive, returning the UID of obj." 428 | 429 | if obj is None: 430 | return null_uid 431 | 432 | # the ref_map allows us to avoid infinite recursion caused by 433 | # cycles in the object graph by functioning as a sort of promise 434 | ref = self.ref_map.get(id(obj)) 435 | if ref: 436 | return ref 437 | 438 | index = uid(len(self.objects)) 439 | self.ref_map[id(obj)] = index 440 | 441 | cls = obj.__class__ 442 | if cls in Archive.primitive_types: 443 | self.objects.append(obj) 444 | return index 445 | 446 | archive_obj = {} 447 | self.objects.append(archive_obj) 448 | self.encode_top_level(obj, archive_obj) 449 | 450 | return index 451 | 452 | def to_bytes(self) -> bytes: 453 | "Generate the archive and return it as a bytes blob" 454 | 455 | # avoid regenerating 456 | if len(self.objects) == 1: 457 | self.archive(self.input) 458 | 459 | d = { '$archiver': 'NSKeyedArchiver', 460 | '$version': NSKeyedArchiveVersion, 461 | '$objects': self.objects, 462 | '$top': { 'root': uid(1) } 463 | } 464 | d2 = OrderedDict() 465 | d2['$version'] = d['$version'] 466 | d2['$archiver'] = d['$archiver'] 467 | d2['$top'] = d['$top'] 468 | d2['$objects'] = d['$objects'] 469 | return bplist.generate(d2) 470 | 471 | 472 | UNARCHIVE_CLASS_MAP = { 473 | 'NSDictionary': DictArchive, 474 | 'NSMutableDictionary': DictArchive, 475 | 'NSArray': ListArchive, 476 | 'NSMutableArray': ListArchive, 477 | 'NSSet': SetArchive, 478 | 'NSMutableSet': SetArchive, 479 | 'NSDate': timestamp, 480 | 'NSNull': NullArchive, 481 | 'NSError': ErrorArchive, 482 | 'NSException': ExceptionArchive, 483 | 'NSMutableString': MutableStringArchive, 484 | 'DTSysmonTapMessage': TODOArchive, 485 | 'NSMutableData': MutableDataArchive, 486 | } 487 | 488 | 489 | ARCHIVE_CLASS_MAP = { 490 | dict: 'NSDictionary', 491 | list: 'NSArray', 492 | set: 'NSSet', 493 | timestamp: 'NSDate' 494 | } 495 | 496 | 497 | def update_class_map(new_map: Mapping[str, type]): 498 | UNARCHIVE_CLASS_MAP.update(new_map) 499 | ARCHIVE_CLASS_MAP.update({ v: k for k, v in new_map.items() }) 500 | -------------------------------------------------------------------------------- /bpylist/bplist.cp37-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/bpylist/bplist.cp37-win_amd64.pyd -------------------------------------------------------------------------------- /bpylist/bplist.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/bpylist/bplist.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /bpylist/bplist.cpython-37m-darwin.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/bpylist/bplist.cpython-37m-darwin.so -------------------------------------------------------------------------------- /device_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from device_service import DeviceService 5 | from libimobiledevice import IDeviceEventType 6 | from lockdown_service import LockdownService 7 | 8 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | 11 | class Device(object): 12 | 13 | def __init__(self, serial, device_name, model, device_info, connected): 14 | self.serial = serial 15 | self.device_name = device_name 16 | self.model = model 17 | self.device_info = device_info 18 | self.connected = connected 19 | 20 | def __str__(self): 21 | return "[Device] udid=%s, device_name=%s, model=%s" % (self.serial, self.device_name, self.model) 22 | 23 | class DeviceManager(object): 24 | 25 | def __init__(self): 26 | self._device_service = DeviceService() 27 | self._device_service.subscribe(self._on_device_changed) 28 | self._device_map = {} 29 | self._listeners = [] 30 | 31 | self._refresh_device_map() 32 | 33 | def __del__(self): 34 | pass 35 | 36 | def _refresh_device_map(self): 37 | devices = self._device_service.get_device_list() 38 | for device in devices: 39 | udid = device['udid'] 40 | if udid not in self._device_map: 41 | device_info, error = self.get_device_info(udid) 42 | device_name = "unknown" 43 | product_type = "unknown" 44 | if device_info: 45 | device_name = device_info['device_name'] 46 | product_type = device_info['product_type'] 47 | device = Device(udid, device_name, product_type, device_info, connected=True) 48 | self._device_map[udid] = device 49 | 50 | def _on_device_changed(self, event): 51 | print("on_device_changed", event) 52 | self._refresh_device_map() 53 | device = self._device_map[event['udid']] 54 | if event['type'] == IDeviceEventType.IDEVICE_DEVICE_ADD: 55 | device.connected = True 56 | for l in self._listeners: 57 | l.on_device_connect(device) 58 | elif event['type'] == IDeviceEventType.IDEVICE_DEVICE_REMOVE: 59 | device.connected = False 60 | for l in self._listeners: 61 | l.on_device_disconnect(device) 62 | 63 | def register_device_change_listener(self, listener): 64 | self._listeners.append(listener) 65 | 66 | def unregister_device_change_listener(self, listener): 67 | self._listeners.remove(listener) 68 | 69 | def get_device_info(self, udid): 70 | device = self._device_service.new_device(udid) 71 | if not device: 72 | return None, "No device connected with udid(%s)" % udid 73 | 74 | lockdown_service = LockdownService() 75 | lockdown_client = lockdown_service.new_client(device) 76 | 77 | values, error = lockdown_service.get_value(lockdown_client, key=None) 78 | if error: 79 | return None, error 80 | 81 | device_name = values['DeviceName'] 82 | product_version = values['ProductVersion'] 83 | build_version = values['BuildVersion'] 84 | product_type = values['ProductType'] 85 | unique_device_id = values['UniqueDeviceID'] 86 | os = "%s(%s)" % (product_version, build_version) 87 | 88 | device_info = self._get_device_info_from_configs(product_type) 89 | device_type = device_info['deviceType'] 90 | cpu_type = device_info['cpuInfo']['hwType'] 91 | cpu_arch = device_info['cpuInfo']['processor'] 92 | cpu_core_num = device_info['cpuInfo']['coreNum'] 93 | min_cpu_freq = int(int(device_info['cpuInfo']['minCpuFreq']) / 1000) 94 | max_cpu_freq = int(int(device_info['cpuInfo']['maxCpuFreq']) / 1000) 95 | cpu_freq = "[%s, %s]" % (str(min_cpu_freq), str(max_cpu_freq)) 96 | gpu_type = device_info['gpuInfo'] 97 | battery_info = device_info['batteryInfo'] # TODO: 98 | 99 | lockdown_service.free_client(lockdown_client) 100 | self._device_service.free_device(device) 101 | 102 | return { 103 | "os_type": "iOS", 104 | "device_name": device_name, 105 | "device_type": device_type, 106 | "product_type": product_type, 107 | "os": os, 108 | "cpu_type": cpu_type, 109 | "cpu_arch": cpu_arch, 110 | "cpu_core_num": cpu_core_num, 111 | "cpu_freq": cpu_freq, 112 | "gpu_type": gpu_type, 113 | }, None 114 | 115 | def _get_device_info_from_configs(self, product_type): 116 | with open(os.path.join(ROOT_DIR, "ios_deviceinfo_new.json")) as fp: # TODO: 117 | device_info_map = json.load(fp) 118 | if product_type in device_info_map: 119 | return device_info_map[product_type] 120 | return None 121 | 122 | def get_connected_devices(self): 123 | return list(self._device_map.values()) -------------------------------------------------------------------------------- /device_manager_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import time 3 | 4 | from device_manager import DeviceManager 5 | 6 | 7 | class DeviceChangeListener(object): 8 | 9 | def on_device_connect(self, device): 10 | print("connect", device, device.device_info) 11 | 12 | def on_device_disconnect(self, device): 13 | print("disconnect", device, device.device_info) 14 | 15 | 16 | class DeviceServiceTestCase(unittest.TestCase): 17 | 18 | def setUp(self): 19 | self.device_manager = DeviceManager() 20 | 21 | def test_get_connected_devices(self): 22 | devices = self.device_manager.get_connected_devices() 23 | print("devices", devices) 24 | self.assertIsNotNone(devices) 25 | self.assertTrue(len(devices) > 0) 26 | 27 | def test_listener(self): 28 | listener = DeviceChangeListener() 29 | self.device_manager.register_device_change_listener(listener) 30 | while True: 31 | pass 32 | 33 | if __name__ == '__main__': 34 | unittest.main() -------------------------------------------------------------------------------- /device_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | # device 9 | from libimobiledevice import IDeviceInfo, IDeviceEventCb, IDeviceOptions, IDeviceError 10 | from libimobiledevice import idevice_get_device_list_extended, idevice_device_list_extended_free 11 | from libimobiledevice import idevice_event_subscribe, idevice_event_unsubscribe 12 | from libimobiledevice import idevice_new_with_options, idevice_new_force_network, idevice_new_force_network_ipv6, idevice_free 13 | from libimobiledevice import heartbeat_client_start_service, heartbeat_receive_with_timeout, heartbeat_send, heartbeat_client_free 14 | from libimobiledevice import plist_new_string, plist_new_dict, plist_dict_set_item, plist_free 15 | from libimobiledevice import idevice_set_debug_level 16 | 17 | import threading 18 | import time 19 | 20 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 21 | 22 | 23 | class DeviceService(Service): 24 | """ 25 | 设备管理服务, 负责获取设备相关信息 26 | """ 27 | 28 | def __init__(self): 29 | self._callback = None 30 | self._device_changed_listeners = [] 31 | self._subscribed = False 32 | 33 | @staticmethod 34 | def set_debug_level(level): 35 | idevice_set_debug_level(level) 36 | 37 | def get_device_list(self): 38 | """ 39 | 获取设备列表 40 | :return: 设备列表,没有设备时返回空集合 41 | """ 42 | result = [] 43 | device_list_p = POINTER(POINTER(IDeviceInfo))() 44 | device_count = c_int() 45 | idevice_get_device_list_extended(pointer(device_list_p), pointer(device_count)) 46 | for i in range(device_count.value): 47 | device = device_list_p[i].contents 48 | if device.conn_type == 1: # USB 49 | result.append({ 50 | "udid": device.udid.decode("UTF-8"), 51 | "conn_type": device.conn_type, 52 | "conn_data": device.conn_data, 53 | }) 54 | idevice_device_list_extended_free(device_list_p) 55 | return result 56 | 57 | @classmethod 58 | def start_heartbeat(cls, udid): 59 | if ':' not in udid: 60 | print("no need to heartbeat") 61 | return 62 | udid, addr = udid.split(':', 1) 63 | print(f'udid={udid} addr={addr}') 64 | control = {"running": True} 65 | def start_heartbeat(): 66 | device = c_void_p() 67 | assert idevice_new_force_network(pointer(device), udid.encode('utf-8'), addr.encode('utf-8')) == 0 68 | heartbeatcli = c_void_p() 69 | 70 | resp = plist_new_dict() 71 | assert resp 72 | polo = plist_new_string(b"Polo") 73 | plist_dict_set_item(resp, b"Command", polo) 74 | 75 | assert heartbeat_client_start_service(device, pointer(heartbeatcli), b"perfcat") == 0 76 | while control['running']: 77 | recv_msg = c_void_p() 78 | if heartbeat_receive_with_timeout(heartbeatcli, pointer(recv_msg), 15000) != 0: 79 | break 80 | assert heartbeat_send(heartbeatcli, resp) == 0 81 | print("beat", recv_msg) 82 | plist_free(recv_msg) 83 | 84 | print("died") 85 | plist_free(resp) 86 | heartbeat_client_free(heartbeatcli) 87 | idevice_free(device) 88 | control['running'] = False 89 | t = threading.Thread(target = start_heartbeat) 90 | t.start() 91 | control['thread'] = t 92 | #try: 93 | # while control['running']: 94 | # time.sleep(1) 95 | #except: 96 | # pass 97 | #finally: 98 | # control['running'] = False 99 | # t.join() 100 | return control 101 | 102 | def new_device(self, udid, rsd_address=None): 103 | """ 104 | 通过udid创建新的device对象,用于调用其他接口 105 | :param udid: 106 | :return: device对象,注意该对象为C对象,请在使用完后务必调用free_device方法来回收内存 107 | """ 108 | device = c_void_p() 109 | if ':' in udid: 110 | udid, addr = udid.split(':', 1) 111 | print(f'udid={udid} addr={addr}') 112 | if idevice_new_force_network: 113 | ret = idevice_new_force_network(pointer(device), udid.encode('utf-8'), addr.encode('utf-8')) 114 | if ret != IDeviceError.IDEVICE_E_SUCCESS: 115 | return None 116 | return device 117 | 118 | if rsd_address is not None: 119 | print(f'rsd = {rsd_address}') 120 | if idevice_new_force_network_ipv6: 121 | ret = idevice_new_force_network_ipv6(pointer(device), udid.encode('utf-8'), rsd_address.ip.encode('utf-8')) 122 | if ret != IDeviceError.IDEVICE_E_SUCCESS: 123 | return None 124 | return device 125 | 126 | options = int(IDeviceOptions.IDEVICE_LOOKUP_USBMUX | IDeviceOptions.IDEVICE_LOOKUP_NETWORK) 127 | ret = idevice_new_with_options(pointer(device), udid.encode("utf-8"), options) 128 | if ret != IDeviceError.IDEVICE_E_SUCCESS: 129 | return None 130 | return device # free_device(device) 131 | 132 | def free_device(self, device): 133 | """ 134 | 释放device对象 135 | :param device: new_device的返回值,C对象 136 | :return: 是否成功 137 | """ 138 | ret = idevice_free(device) 139 | return ret == IDeviceError.IDEVICE_E_SUCCESS 140 | 141 | def subscribe(self, listener): 142 | """ 143 | 订阅设备变化事件,在设备连接或移除时会将事件回调给监听器 144 | :param listener: 事件回调监听器 145 | :return: void 146 | """ 147 | self._device_changed_listeners.append(listener) 148 | if not self._subscribed: 149 | def callback(event, user_data): 150 | event_obj = event.contents 151 | for listener in self._device_changed_listeners: 152 | listener({ 153 | "udid": event_obj.udid.decode("utf-8"), 154 | "type": event_obj.event, 155 | "conn_type": event_obj.conn_type, 156 | }) 157 | user_data = c_void_p() 158 | self._callback = IDeviceEventCb(callback) 159 | logging.info("idevice_event_subscribe") 160 | idevice_event_subscribe(self._callback, user_data) 161 | self._subscribed = True 162 | 163 | def unsubscribe(self, listener): 164 | """ 165 | 取消订阅设备变化事件 166 | :param listener: 事件回调监听器 167 | :return: void 168 | """ 169 | self._device_changed_listeners.remove(listener) 170 | if len(self._device_changed_listeners) == 0 and self._subscribed: 171 | logging.info("idevice_event_unsubscribe") 172 | idevice_event_unsubscribe() 173 | self._subscribed = False 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /device_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import time 3 | 4 | from device_service import DeviceService 5 | from lockdown_service import LockdownService 6 | 7 | 8 | class DeviceServiceTestCase(unittest.TestCase): 9 | 10 | def setUp(self): 11 | self.device_service = DeviceService() 12 | 13 | def _get_udid(self): 14 | device_service = DeviceService() 15 | device_list = device_service.get_device_list() 16 | self.assertIsNotNone(device_list) 17 | self.assertTrue(len(device_list) > 0) 18 | return device_list[0]['udid'] 19 | 20 | def test_get_device_info(self): 21 | device_list = self.device_service.get_device_list() 22 | print("device_list", device_list) 23 | self.assertIsNotNone(device_list, msg="Device List is None") 24 | self.assertTrue(len(device_list) > 0, msg="Device List is Empty") 25 | 26 | def test_subscribe(self): 27 | def on_device_changed(event): 28 | print("on_device_changed", event) 29 | self.device_service.subscribe(on_device_changed) 30 | retry = 0 31 | while retry < 20: 32 | print("wait for device event...", retry) 33 | time.sleep(1) 34 | retry += 1 35 | self.device_service.subscribe() 36 | 37 | def test_new_device(self): 38 | udid = self._get_udid() 39 | device = self.device_service.new_device(udid) 40 | print("device", device) 41 | self.assertIsNotNone(device) 42 | success = self.device_service.free_device(device) 43 | self.assertTrue(success) 44 | print("free device") 45 | 46 | 47 | if __name__ == '__main__': 48 | unittest.main() -------------------------------------------------------------------------------- /diagnostics_relay_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | from libimobiledevice import DiagnosticsRelayError 9 | from libimobiledevice import diagnostics_relay_client_start_service, diagnostics_relay_client_free, diagnostics_relay_goodbye, diagnostics_relay_query_ioregistry_entry 10 | from libimobiledevice import plist_free 11 | from utils import read_data_from_plist_ptr, compare_version, read_buffer_from_pointer 12 | 13 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 14 | 15 | 16 | class DiagnosticsRelayService(Service): 17 | """ 18 | 收集诊断信息, 负责获取电池等数据 19 | """ 20 | 21 | def new_client(self, device): 22 | """ 23 | 创建diagnostics relay client,用于调用其他接口 24 | :param device: 由DeviceService创建的device对象(C对象) 25 | :return: diagnostics relay client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 26 | """ 27 | client = c_void_p() 28 | ret = diagnostics_relay_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 29 | if ret != DiagnosticsRelayError.DIAGNOSTICS_RELAY_E_SUCCESS: 30 | return None 31 | return client 32 | 33 | def goodbye(self, client): 34 | """ 35 | 断开diagnostics relay client连接 36 | :param client: diagnostics relay client(C对象) 37 | :return: bool 是否成功 38 | """ 39 | ret = diagnostics_relay_goodbye(client) 40 | return ret == DiagnosticsRelayError.DIAGNOSTICS_RELAY_E_SUCCESS 41 | 42 | def free_client(self, client): 43 | """ 44 | 释放diagnostics relay client 45 | :param client: diagnostics relay client(C对象) 46 | :return: bool 是否成功 47 | """ 48 | ret = diagnostics_relay_client_free(client) 49 | return ret == DiagnosticsRelayError.DIAGNOSTICS_RELAY_E_SUCCESS 50 | 51 | def query_ioregistry_entry(self, client, name, clazz): 52 | plist_p = c_void_p() 53 | ret = diagnostics_relay_query_ioregistry_entry(client, name.encode("utf-8"), clazz.encode("utf-8"), pointer(plist_p)) 54 | if ret != DiagnosticsRelayError.DIAGNOSTICS_RELAY_E_SUCCESS: 55 | return False, "Can not query ioregistry entry, error code %d" % ret 56 | 57 | data = read_data_from_plist_ptr(plist_p) 58 | plist_free(plist_p) 59 | 60 | if data is None: 61 | return False, "Can not parse plist result" 62 | return True, data 63 | -------------------------------------------------------------------------------- /dtxlib.py: -------------------------------------------------------------------------------- 1 | from ctypes import CDLL, c_int, POINTER, c_char_p, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof 2 | import struct 3 | from bpylist import archiver, bplist 4 | 5 | def div_ceil(p: int, q: int) -> int: 6 | return (p + q - 1) // q 7 | 8 | def div_floor(p: int, q: int) -> int: 9 | return p // q 10 | 11 | def _get_fragment_count_by_length(length): 12 | if length <= 65504: # 2**16 - sizeof(DTXMessageHeader) 13 | return 1 14 | 15 | class DTXMessageHeader(Structure): 16 | _fields_ = [ 17 | ('magic', c_uint32), 18 | ('cb', c_uint32), 19 | ('fragmentId', c_uint16), 20 | ('fragmentCount', c_uint16), 21 | ('length', c_uint32), 22 | ('identifier', c_uint32), 23 | ('conversationIndex', c_uint32), 24 | ('channelCode', c_uint32), 25 | ('expectsReply', c_uint32) 26 | ] 27 | 28 | def __init__(self): 29 | super().__init__() 30 | self.magic = 0x1f3d5b79 31 | self.cb = 0x20 32 | self.fragmentId = 0 33 | self.fragmentCount = 1 34 | 35 | class DTXPayloadHeader(Structure): 36 | _fields_ = [ 37 | ('flags', c_uint32), 38 | ('auxiliaryLength', c_uint32), 39 | ('totalLength', c_uint64) 40 | ] 41 | def __init__(self): 42 | super().__init__() 43 | self.flags = 0x2 44 | 45 | class DTXAuxiliariesHeader(Structure): 46 | _fields_ = [ 47 | ('magic', c_uint64), 48 | ('length', c_int64) 49 | ] 50 | def __init__(self): 51 | super().__init__() 52 | self.magic = 0x1f0 53 | 54 | class DTXMessage: 55 | def __init__(self): 56 | self._buf = b'' 57 | self._message_header = DTXMessageHeader() 58 | self._payload_header = None 59 | self._auxiliaries_header = None 60 | self._selector = b'' 61 | self._auxiliaries = [] 62 | pass 63 | 64 | def _init_payload_header(self): 65 | if self._payload_header is None: 66 | self._payload_header = DTXPayloadHeader() 67 | self._payload_header.totalLength = 0 68 | self._message_header.length += sizeof(DTXPayloadHeader) 69 | 70 | def _init_auxiliaries_header(self): 71 | self._init_payload_header() 72 | if self._auxiliaries_header is None: 73 | self._auxiliaries_header = DTXAuxiliariesHeader() 74 | self._payload_header.totalLength += sizeof(DTXAuxiliariesHeader) 75 | self._payload_header.auxiliaryLength += sizeof(DTXAuxiliariesHeader) 76 | self._message_header.length += sizeof(DTXAuxiliariesHeader) 77 | 78 | def _update_auxiliary_len(self, delta): 79 | self._message_header.length += delta 80 | self._payload_header.totalLength += delta 81 | self._payload_header.auxiliaryLength += delta 82 | self._auxiliaries_header.length += delta 83 | pass 84 | 85 | def _update_selector_len(self, delta): 86 | self._message_header.length += delta 87 | self._payload_header.totalLength += delta 88 | pass 89 | 90 | 91 | @classmethod 92 | def from_bytes(self, buffer: bytes): 93 | cursor = 0 94 | ret = DTXMessage() 95 | backup_buf = buffer 96 | ret._buf = buffer 97 | payload_buf = b'' 98 | ret._message_header = DTXMessageHeader.from_buffer_copy(buffer[cursor:cursor+sizeof(DTXMessageHeader)]) 99 | cursor = sizeof(DTXMessageHeader) 100 | has_payload = ret._message_header.length > 0 101 | if not has_payload: 102 | return ret 103 | 104 | if ret._message_header.length != len(buffer) - cursor - (ret._message_header.fragmentCount - 1) * sizeof(DTXMessageHeader): 105 | raise ValueError("incorrect DTXMessageHeader->length") 106 | 107 | if ret._message_header.fragmentCount == 1: 108 | payload_buf = buffer[cursor:] 109 | else: 110 | assert ret._message_header.fragmentCount >= 3 111 | while cursor < len(buffer): 112 | subhdr = DTXMessageHeader.from_buffer_copy(buffer[cursor: cursor + sizeof(DTXMessageHeader)]) 113 | cursor += sizeof(DTXMessageHeader) 114 | assert len(buffer[cursor: cursor+subhdr.length]) == subhdr.length 115 | payload_buf += buffer[cursor: cursor + subhdr.length] 116 | cursor += subhdr.length 117 | #print(subhdr.magic, subhdr.fragmentCount, subhdr.fragmentId, subhdr.length) 118 | assert subhdr.magic == ret._message_header.magic 119 | assert cursor == len(buffer) 120 | buffer = payload_buf 121 | cursor = 0 122 | ret._payload_header = DTXPayloadHeader.from_buffer_copy(buffer[cursor:cursor + sizeof(DTXPayloadHeader)]) 123 | cursor += sizeof(DTXPayloadHeader) 124 | if ret._payload_header.totalLength == 0: 125 | return ret 126 | if ret._payload_header.totalLength != len(buffer) - cursor: 127 | raise ValueError("incorrect DTXPayloadHeader->totalLength") 128 | if ret._payload_header.auxiliaryLength: 129 | ret._auxiliaries_header = DTXAuxiliariesHeader.from_buffer_copy(buffer[cursor:cursor + sizeof(DTXAuxiliariesHeader)]) 130 | cursor += sizeof(DTXAuxiliariesHeader) 131 | i = 0 132 | while i < ret._auxiliaries_header.length: 133 | m, t = struct.unpack(" bytes: 159 | if not self._payload_header: 160 | return self._buf 161 | payload_buf = b'' 162 | payload_buf += bytes(self._payload_header) 163 | if self._auxiliaries_header: 164 | payload_buf += bytes(self._auxiliaries_header) 165 | if self._auxiliaries: 166 | payload_buf += b''.join(self._auxiliaries) 167 | payload_buf += self._selector 168 | if len(payload_buf) > 65504: 169 | parts = div_ceil(len(payload_buf), 65504) 170 | self._message_header.fragmentCount = parts + 1 171 | self._buf = bytes(self._message_header) 172 | for part in range(parts): 173 | part_len = min(len(payload_buf) - part * 65504, 65504) 174 | subhdr = DTXMessageHeader.from_buffer_copy(bytes(self._message_header)) 175 | subhdr.fragmentId = part + 1 176 | subhdr.length = part_len 177 | self._buf += bytes(subhdr) 178 | self._buf += payload_buf[part * 65504: part * 65504 + part_len] 179 | else: 180 | self._buf = bytes(self._message_header) + payload_buf 181 | return self._buf 182 | 183 | def set_selector(self, buffer:bytes): 184 | self._init_payload_header() 185 | self._update_selector_len(len(buffer) - len(self._selector)) 186 | self._selector = buffer 187 | return self 188 | 189 | def get_selector(self) -> bytes: 190 | return self._selector 191 | 192 | def add_auxiliary(self, buffer:bytes): 193 | self._init_auxiliaries_header() 194 | self._update_auxiliary_len(len(buffer)) 195 | self._auxiliaries.append(buffer) 196 | return self 197 | 198 | def get_auxiliary_count(self) -> int: 199 | return len(self._auxiliaries) 200 | 201 | def get_auxiliary_at(self, idx:int) -> bytes: 202 | return self._auxiliaries[idx] 203 | 204 | def set_auxiliary_at(self, idx:int, buffer:bytes): 205 | self._init_auxiliaries_header() # we don't need this actually, for this function only to modify existing items. 206 | self._update_auxiliary_len(len(buffer) - len(self._auxiliaries[idx])) 207 | self._auxiliaries[idx] = buffer 208 | return self 209 | 210 | def new_reply(self): 211 | ret = DTXMessage() 212 | ret.channel_code = self.channel_code 213 | ret.identifier = self.identifier 214 | ret.conversation_index = self.conversation_index + 1 215 | return ret 216 | 217 | @property 218 | def conversation_index(self): 219 | return self._message_header.conversationIndex 220 | 221 | @conversation_index.setter 222 | def conversation_index(self, idx:int): 223 | self._message_header.conversationIndex = idx 224 | return self 225 | 226 | @property 227 | def channel_code(self): 228 | return self._message_header.channelCode 229 | 230 | @channel_code.setter 231 | def channel_code(self, channel:int): 232 | self._message_header.channelCode = channel 233 | return self 234 | 235 | @property 236 | def identifier(self): 237 | return self._message_header.identifier 238 | 239 | @identifier.setter 240 | def identifier(self, identifier:int): 241 | self._message_header.identifier = identifier 242 | return self 243 | 244 | @property 245 | def expects_reply(self): 246 | return self._message_header.expectsReply 247 | 248 | @expects_reply.setter 249 | def expects_reply(self, expect:bool): 250 | self._message_header.expectsReply = 1 if expect else 0 251 | 252 | def ns_keyed_archiver(obj): 253 | return archiver.archive(obj) 254 | 255 | 256 | class AuxType(object): 257 | def __init__(self, value): 258 | self._value = value 259 | 260 | def pack(self) -> bytes: 261 | pass 262 | 263 | class AuxUInt32(AuxType): 264 | def pack(self) -> bytes: 265 | return struct.pack(' bytes: 269 | return struct.pack(' bytes: 273 | return struct.pack(' bytes: 277 | return struct.pack(' 0) 28 | return device_list[0]['udid'] 29 | 30 | def test_open_sand_box(self): 31 | device = self._create_device() 32 | 33 | client = self.house_arrest_proxy_service.new_client(device) 34 | #com.jimmy.test2 35 | afcClient = self.house_arrest_proxy_service.open_sandbox_with_appid(client, 1, "com.seasun.tmgp.jx3m") 36 | 37 | self.afc_service = AfcService() 38 | tmp = self.afc_service.new_client(device) 39 | dir_list = self.afc_service.read_directory(afcClient, "/Documents") 40 | 41 | self.assertIsNotNone(device) 42 | self.assertIsNotNone(afcClient) 43 | self.assertIsNotNone(dir_list) 44 | self.assertIsNotNone(client) 45 | 46 | for file in dir_list: 47 | print(file['filename']) 48 | 49 | self.afc_service.free_client(afcClient) 50 | self.device_service.free_device(device) 51 | self.house_arrest_proxy_service.free_client(client) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() -------------------------------------------------------------------------------- /image_mounter_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | from libimobiledevice import MobileImageMounterError, MobileImageMounterUploadCb 9 | from libimobiledevice import mobile_image_mounter_start_service, mobile_image_mounter_free, mobile_image_mounter_lookup_image, mobile_image_mounter_upload_image_file, mobile_image_mounter_mount_image_file, mobile_image_mounter_hangup 10 | from libimobiledevice import plist_free 11 | from utils import read_data_from_plist_ptr, compare_version 12 | 13 | 14 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 15 | 16 | 17 | 18 | class ImageMounterService(Service): 19 | """ 20 | 镜像加载管理服务, 负责镜像加载相关事宜 21 | """ 22 | 23 | def new_client(self, device): 24 | """ 25 | 创建mobile image mounter client,用于调用其他接口 26 | :param device: 由DeviceService创建的device对象(C对象) 27 | :return: mobile image mounter client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 28 | """ 29 | client = c_void_p() 30 | ret = mobile_image_mounter_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 31 | if ret != MobileImageMounterError.MOBILE_IMAGE_MOUNTER_E_SUCCESS: 32 | return None 33 | return client 34 | 35 | def free_client(self, client): 36 | """ 37 | 释放mobile image mounter client 38 | :param client: mobile image mounter client(C对象) 39 | :return: bool 是否成功 40 | """ 41 | ret = mobile_image_mounter_free(client) 42 | return ret == MobileImageMounterError.MOBILE_IMAGE_MOUNTER_E_SUCCESS 43 | 44 | def lookup_image(self, client, image_type, product_version): 45 | plist_p = c_void_p() 46 | 47 | ret = mobile_image_mounter_lookup_image(client, image_type.encode("utf-8"), pointer(plist_p)) 48 | if ret != MobileImageMounterError.MOBILE_IMAGE_MOUNTER_E_SUCCESS: 49 | return False, "Can not lookup image, error code %d" % ret 50 | 51 | 52 | data = read_data_from_plist_ptr(plist_p) 53 | plist_free(plist_p) 54 | if data is None: 55 | return False, "Can not parse plist result" 56 | 57 | if "Error" in data: 58 | error = data['Error'] 59 | return False, error 60 | if compare_version(product_version, "10.0") >= 0: 61 | return "ImageSignature" in data and len(data["ImageSignature"]) != 0, None 62 | else: 63 | return data['ImagePresent'] if "ImagePresent" in data else False, None 64 | 65 | def upload_image(self, client, image_type, image_file, image_signature_file): 66 | ret = mobile_image_mounter_upload_image_file(client, image_type.encode("utf-8"), image_file.encode("utf-8"), image_signature_file.encode("utf-8")) 67 | return ret == MobileImageMounterError.MOBILE_IMAGE_MOUNTER_E_SUCCESS 68 | 69 | def mount_image(self, client, image_type, image_path, image_signature_file): 70 | plist_p = c_void_p() 71 | 72 | ret = mobile_image_mounter_mount_image_file(client, image_path.encode("utf-8"), image_signature_file.encode("utf-8"), image_type.encode("utf-8"), pointer(plist_p)) 73 | if ret != MobileImageMounterError.MOBILE_IMAGE_MOUNTER_E_SUCCESS: 74 | return False, "Can not mount image, error code %d" % ret 75 | 76 | data = read_data_from_plist_ptr(plist_p) 77 | if "Error" in data: 78 | return False, data['Error'] 79 | return "Status" in data and data['Status'] == "Complete", None 80 | 81 | def hangup(self, client): 82 | """ 83 | Hangs up mobile image mounter client 84 | :param client: mobile image mounter client(C对象) 85 | :return: bool 是否成功 86 | """ 87 | ret = mobile_image_mounter_hangup(client) 88 | return ret == MobileImageMounterError.MOBILE_IMAGE_MOUNTER_E_SUCCESS 89 | 90 | 91 | -------------------------------------------------------------------------------- /image_mounter_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | from tempfile import NamedTemporaryFile 5 | 6 | from image_mounter_service import ImageMounterService 7 | from device_service import DeviceService 8 | 9 | 10 | class ImageMounterServiceTestCase(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.image_mounter_service = ImageMounterService() 14 | self.device_service = DeviceService() 15 | 16 | def _create_device(self): 17 | udid = self._get_udid() 18 | device = self.device_service.new_device(udid) 19 | print("device", device) 20 | self.assertIsNotNone(device) 21 | return device 22 | 23 | def _get_udid(self): 24 | device_service = DeviceService() 25 | device_list = device_service.get_device_list() 26 | self.assertIsNotNone(device_list) 27 | self.assertTrue(len(device_list) > 0) 28 | return device_list[0]['udid'] 29 | 30 | def test_lookup_image(self): 31 | device = self._create_device() 32 | client = self.image_mounter_service.new_client(device) 33 | self.assertIsNotNone(client) 34 | 35 | product_version = "13.2" 36 | image_type = "Developer" 37 | 38 | image_mounted, error = self.image_mounter_service.lookup_image(client, image_type, product_version) 39 | print(error) 40 | self.assertIsNone(error) 41 | self.assertTrue(image_mounted) 42 | self.image_mounter_service.hangup(client) 43 | self.image_mounter_service.free_client(client) 44 | 45 | def test_upload_and_mount_image(self): 46 | device = self._create_device() 47 | client = self.image_mounter_service.new_client(device) 48 | self.assertIsNotNone(client) 49 | 50 | image_type = "Developer" 51 | image_file = r"F:\lds\DeviceSupport\DeviceSupport\13.3\DeveloperDiskImage.dmg" 52 | image_signature_file = r"F:\lds\DeviceSupport\DeviceSupport\13.3\DeveloperDiskImage.dmg.signature" 53 | 54 | result = self.image_mounter_service.upload_image(client, image_type, image_file, image_signature_file) 55 | print("result", result) 56 | self.assertTrue(result) 57 | 58 | image_type = "Developer" 59 | image_path = "/private/var/mobile/Media/PublicStaging/staging.dimage" 60 | image_signature_file = r"F:\lds\DeviceSupport\DeviceSupport\13.3\DeveloperDiskImage.dmg.signature" 61 | 62 | result, error = self.image_mounter_service.mount_image(client, image_type, image_path, image_signature_file) 63 | print("result", result, error) 64 | self.assertIsNone(error) 65 | self.assertTrue(result) 66 | 67 | if __name__ == '__main__': 68 | unittest.main() -------------------------------------------------------------------------------- /installation_proxy_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | # lockdown 9 | from libimobiledevice import InstProxyError 10 | from libimobiledevice import instproxy_client_start_service, instproxy_client_free, instproxy_install, instproxy_uninstall, instproxy_browse, instproxy_client_options_new, instproxy_client_options_add, instproxy_client_options_set_return_attributes, instproxy_client_options_free 11 | from libimobiledevice import plist_free, plist_to_bin, plist_to_bin_free, plist_to_xml, plist_to_xml_free 12 | import plistlib 13 | import zipfile 14 | from afc_service import AfcService 15 | 16 | from utils import read_buffer_from_pointer 17 | 18 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 19 | 20 | 21 | class InstallationProxyService(Service): 22 | """ 23 | InstallationProxy服务,负责获取已安装应用列表等事宜 24 | """ 25 | 26 | def new_client(self, device): 27 | """ 28 | 创建installation proxy client,用于调用其他接口 29 | :param device: 由DeviceService创建的device对象(C对象) 30 | :return: lockdown client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 31 | """ 32 | client = c_void_p() 33 | ret = instproxy_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 34 | if ret != InstProxyError.INSTPROXY_E_SUCCESS: 35 | return None 36 | return client 37 | 38 | def free_client(self, client): 39 | """ 40 | 释放installation proxy 41 | :param client: installation proxy client(C对象) 42 | :return: bool 是否成功 43 | """ 44 | ret = instproxy_client_free(client) 45 | return ret == InstProxyError.INSTPROXY_E_SUCCESS 46 | 47 | 48 | def uninstall(self, device, client, bundleid): 49 | instproxy_uninstall(client, bundleid.encode("utf-8"), None, None, None) 50 | 51 | def install(self, device, client, ipa_path): 52 | 53 | afcService = AfcService() 54 | afcClient = afcService.new_client(device) 55 | PKG_PATH= "PublicStaging" 56 | pkgname=PKG_PATH+"/tmp" 57 | pkgpath =PKG_PATH+"/tmp/" 58 | afcService.make_directory(afcClient, pkgpath) 59 | 60 | with zipfile.ZipFile(ipa_path, 'r') as zf: 61 | for zname in zf.namelist(): 62 | if zname == None: 63 | continue 64 | 65 | if zname[len(zname)-1] == '/': 66 | ##创建文件夹 67 | afcService.make_directory(afcClient, pkgpath+zname) 68 | else: 69 | try: 70 | data = zf.read(zname) 71 | afc_file = afcService.open_file(afcClient, pkgpath+zname, "w") 72 | afc_file.write(data) 73 | afc_file.close() 74 | 75 | except KeyError: 76 | print('ERROR: Did not find {} in zip file'.format(zname)) 77 | 78 | 79 | client_opts = instproxy_client_options_new() 80 | 81 | instproxy_install(client, pkgname.encode("utf-8"), client_opts, None, None) 82 | 83 | afcService.free_client(afcClient) 84 | instproxy_client_options_free(client_opts) 85 | 86 | 87 | def browse(self, client, application_type): 88 | """ 89 | 获取已安装应用列表 90 | :param client: installation proxyclient(C对象) 91 | :param application_type: 应用类型:"User" 用户应用, "System" 系统应用 92 | :return: 应用列表 93 | """ 94 | p_list_p = c_void_p() 95 | client_opts_p = instproxy_client_options_new() 96 | instproxy_client_options_add(client_opts_p, "ApplicationType".encode("utf-8"), application_type.encode("utf-8"), None) 97 | instproxy_client_options_set_return_attributes(client_opts_p, 98 | "CFBundleIdentifier".encode("utf-8"), 99 | "CFBundleExecutable".encode("utf-8"), 100 | "CFBundleName".encode("utf-8"), 101 | "CFBundleVersion".encode("utf-8"), 102 | "CFBundleShortVersionString".encode("utf-8"), 103 | "Path".encode("utf-8"), 104 | None) # TODO: 目前暂未找到ctypes如何支持可变参数,因此这里参数个数是硬写的,如果需要增加参考需要同步修改ctypes配置 105 | instproxy_browse(client, client_opts_p, pointer(p_list_p)) 106 | # TODO: p_list_p is null? 107 | 108 | plist_bin_p = c_void_p() 109 | length = c_int() 110 | plist_to_bin(p_list_p, pointer(plist_bin_p), pointer(length)) 111 | 112 | buffer = read_buffer_from_pointer(plist_bin_p, length.value) 113 | # print("buffer.length", len(buffer)) 114 | app_list = None 115 | if buffer and len(buffer) > 0: 116 | app_list = plistlib.loads(buffer) 117 | plist_to_bin_free(plist_bin_p) 118 | plist_free(p_list_p) 119 | instproxy_client_options_free(client_opts_p) 120 | return app_list 121 | -------------------------------------------------------------------------------- /installation_proxy_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import time 3 | 4 | from installation_proxy_service import InstallationProxyService 5 | from device_service import DeviceService 6 | from lockdown_service import LockdownService 7 | 8 | 9 | class InstallationProxyServiceTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.installation_proxy_service = InstallationProxyService() 13 | self.device_service = DeviceService() 14 | 15 | def _create_device(self): 16 | udid = self._get_udid() 17 | device = self.device_service.new_device(udid) 18 | print("device", device) 19 | self.assertIsNotNone(device) 20 | return device 21 | 22 | def _get_udid(self): 23 | device_service = DeviceService() 24 | device_list = device_service.get_device_list() 25 | self.assertIsNotNone(device_list) 26 | self.assertTrue(len(device_list) > 0) 27 | return device_list[0]['udid'] 28 | 29 | def test_browse(self): 30 | device = self._create_device() 31 | client = self.installation_proxy_service.new_client(device) 32 | self.assertIsNotNone(client) 33 | 34 | apps = self.installation_proxy_service.browse(client, "User") 35 | self.assertIsNotNone(apps) 36 | self.assertTrue(len(apps) > 0) 37 | print("List of applications:") 38 | for app in apps: 39 | for key, value in app.items(): 40 | print("%s: %s" % (key, value)) 41 | print("") 42 | self.installation_proxy_service.free_client(client) 43 | 44 | def test1_install(self): 45 | device = self._create_device() 46 | 47 | client = self.installation_proxy_service.new_client(device) 48 | self.assertIsNotNone(client) 49 | print("start install") 50 | apps = self.installation_proxy_service.install(device, client, "/Users/jimmy/Downloads/tmp.ipa") 51 | print("finsih install") 52 | self.installation_proxy_service.free_client(client) 53 | 54 | def test_uninstall(self): 55 | device = self._create_device() 56 | 57 | client = self.installation_proxy_service.new_client(device) 58 | self.assertIsNotNone(client) 59 | print("start uninstall") 60 | apps = self.installation_proxy_service.uninstall(device, client, "com.seasun.jxpocket.tako") 61 | print("finsih uninstall") 62 | self.installation_proxy_service.free_client(client) 63 | 64 | 65 | if __name__ == '__main__': 66 | unittest.main() -------------------------------------------------------------------------------- /libimobiledevice/libimobiledevice-1.0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/libimobiledevice-1.0.dll -------------------------------------------------------------------------------- /libimobiledevice/libimobiledevice-1.0.so.6.0.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/libimobiledevice-1.0.so.6.0.0 -------------------------------------------------------------------------------- /libimobiledevice/libimobiledevice-glue-1.0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/libimobiledevice-glue-1.0.dll -------------------------------------------------------------------------------- /libimobiledevice/libimobiledevice.so.6.0.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/libimobiledevice.so.6.0.0 -------------------------------------------------------------------------------- /libimobiledevice/libplist-2.0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/libplist-2.0.dll -------------------------------------------------------------------------------- /libimobiledevice/libusbmuxd-2.0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/libusbmuxd-2.0.dll -------------------------------------------------------------------------------- /libimobiledevice/macos/libcrypto.1.1.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/macos/libcrypto.1.1.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libcrypto.dylib: -------------------------------------------------------------------------------- 1 | libcrypto.1.1.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libimobiledevice.6.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/macos/libimobiledevice.6.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libimobiledevice.dylib: -------------------------------------------------------------------------------- 1 | libimobiledevice.6.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libplist++.3.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/macos/libplist++.3.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libplist++.dylib: -------------------------------------------------------------------------------- 1 | libplist++.3.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libplist.3.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/macos/libplist.3.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libplist.dylib: -------------------------------------------------------------------------------- 1 | libplist.3.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libssl.1.1.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/macos/libssl.1.1.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libssl.dylib: -------------------------------------------------------------------------------- 1 | libssl.1.1.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libusbmuxd.6.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/macos/libusbmuxd.6.dylib -------------------------------------------------------------------------------- /libimobiledevice/macos/libusbmuxd.dylib: -------------------------------------------------------------------------------- 1 | libusbmuxd.6.dylib -------------------------------------------------------------------------------- /libimobiledevice/plistutil.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/libimobiledevice/plistutil.exe -------------------------------------------------------------------------------- /lockdown_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | # lockdown 9 | from libimobiledevice import LockdowndError 10 | from libimobiledevice import lockdownd_remove_value, lockdownd_client_new, lockdownd_client_new_with_handshake, lockdownd_client_free, lockdownd_get_value, lockdownd_set_value, plist_new_array 11 | from libimobiledevice import plist_free, plist_to_bin, plist_to_bin_free, plist_to_xml, plist_to_xml_free,plist_new_string,plist_new_bool,plist_array_append_item 12 | from bpylist import archiver, bplist 13 | import plistlib 14 | 15 | from utils import read_buffer_from_pointer 16 | 17 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 18 | 19 | 20 | class LockdownService(Service): 21 | """ 22 | Lockdown服务,负责获取设备属性 23 | """ 24 | 25 | def new_client(self, device, handshake = True): 26 | """ 27 | 创建lockdown client,用于调用lockdown服务的其他接口 28 | :param device: 由DeviceService创建的device对象(C对象) 29 | :return: lockdown client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 30 | """ 31 | client = c_void_p() 32 | if handshake: 33 | ret = lockdownd_client_new_with_handshake(device, pointer(client), "ideviceinfo".encode("utf-8")) 34 | else: 35 | ret = lockdownd_client_new(device, pointer(client), "ideviceinfo".encode("utf-8")) 36 | if ret != LockdowndError.LOCKDOWN_E_SUCCESS: 37 | return None 38 | return client 39 | 40 | def free_client(self, client): 41 | """ 42 | 释放lockdown client 43 | :param client: lockdown client(C对象) 44 | :return: bool 是否成功 45 | """ 46 | ret = lockdownd_client_free(client) 47 | return ret == LockdowndError.LOCKDOWN_E_SUCCESS 48 | 49 | def get_value(self, client, key): 50 | 51 | 52 | return self.get_domain_Value(client,None,key) 53 | 54 | 55 | def get_domain_Value(self,client,domain,key): 56 | 57 | """ 58 | 获取设备属性值 59 | :param client: lockdown client(C对象) 60 | :param domain: 获取域名 61 | :param key: 属性名称 62 | :return: 属性值 63 | """ 64 | values = None 65 | p_list_p = c_void_p() 66 | ret = lockdownd_get_value(client, domain.encode("utf-8") if domain else None, key.encode("utf-8") if key else None, pointer(p_list_p)) 67 | if ret != LockdowndError.LOCKDOWN_E_SUCCESS: 68 | return None, "Can not new idevice" 69 | 70 | plist_bin_p = c_void_p() 71 | length = c_int() 72 | plist_to_bin(p_list_p, pointer(plist_bin_p), pointer(length)) 73 | print("length", length.value) 74 | 75 | buffer = read_buffer_from_pointer(plist_bin_p, length.value) 76 | print("buffer.length", len(buffer)) 77 | if buffer and len(buffer) > 0: 78 | values = plistlib.loads(buffer) 79 | plist_to_bin_free(plist_bin_p) 80 | plist_free(p_list_p) 81 | return values, None 82 | 83 | 84 | def set_domain_Value(self,client,domain,key,plist_value): 85 | 86 | 87 | rel =lockdownd_set_value(client, domain.encode("utf-8") if domain else None, key.encode("utf-8") if key else None, plist_value) 88 | if rel != LockdowndError.LOCKDOWN_E_SUCCESS: 89 | print("set_domain_Value error") 90 | 91 | print("set_domain_Value "+key+" rel:"+str(rel)) 92 | 93 | return rel 94 | 95 | def enable_wireless(self,client,enable,wirelessid,buddyid): 96 | 97 | self.set_domain_Value(client,"com.apple.mobile.wireless_lockdown", "EnableWifiConnections", plist_new_bool(enable)) 98 | self.set_domain_Value(client,"com.apple.mobile.wireless_lockdown", "EnableWifiDebugging", plist_new_bool(enable)) 99 | self.set_domain_Value(client,"com.apple.mobile.wireless_lockdown", "WirelessBuddyID", plist_new_string(buddyid.encode("utf-8"))) 100 | 101 | if enable: 102 | 103 | if wirelessid !=None: 104 | node = plist_new_array() 105 | #lockdownd_get_value(client, "com.apple.xcode.developerdomain".encode("utf-8"), "WirelessHosts".encode("utf-8"), pointer(node)); 106 | plist_array_append_item(node,plist_new_string(wirelessid.encode("utf-8"))) 107 | self.set_domain_Value(client,"com.apple.xcode.developerdomain","WirelessHosts",node) 108 | else: 109 | 110 | lockdownd_remove_value(client, "com.apple.xcode.developerdomain".encode("utf-8"), "WirelessHosts".encode("utf-8")) 111 | 112 | def get_developer_mode_status(self,client): 113 | value, error = self.get_domain_Value(client, "com.apple.security.mac.amfi", "DeveloperModeStatus") 114 | return value # true or false 115 | 116 | 117 | 118 | """ 119 | get_value: 120 | { 121 | 'BasebandCertId': 2315222105, 122 | 'BasebandKeyHashInformation': { 123 | 'AKeyStatus': 2, 124 | 'SKeyHash': b'\xbb\xef\xedp,/i\x0f\xb5c\xdbx\xd0\x8e2z\x00\x84\x98\x1d\xbc\x98\x02\xe5i\x13\xa1h\x85F\x05j', 125 | 'SKeyStatus': 0 126 | }, 127 | 'BasebandSerialNumber': b"'C\xde\x01", 128 | 'BasebandVersion': '5.30.01', 129 | 'BoardId': 6, 130 | 'BuildVersion': '17C54', 131 | 'CPUArchitecture': 'arm64', 132 | 'ChipID': 32789, 133 | 'DeviceClass': 'iPhone', 134 | 'DeviceColor': '1', 135 | 'DeviceName': "San's iPhone", 136 | 'DieID': 7157468793159726, 137 | 'HardwareModel': 'D22AP', 138 | 'HasSiDP': True, 139 | 'PartitionType': 'GUID_partition_scheme', 140 | 'ProductName': 'iPhone OS', 141 | 'ProductType': 'iPhone10,3', 142 | 'ProductVersion': '13.3', 143 | 'ProductionSOC': True, 144 | 'ProtocolVersion': '2', 145 | 'SupportedDeviceFamilies': [1], 146 | 'TelephonyCapability': True, 147 | 'UniqueChipID': 7157468793159726, 148 | 'UniqueDeviceID': '97006ebdc8bc5daed2e354f4addae4fd2a81c52d', 149 | 'WiFiAddress': 'e4:9a:dc:b4:ba:94' 150 | } 151 | """ -------------------------------------------------------------------------------- /lockdown_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import time 3 | 4 | from device_service import DeviceService 5 | from lockdown_service import LockdownService 6 | 7 | from libimobiledevice import plist_new_string,plist_new_bool 8 | 9 | class LockdownServiceTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.device_service = DeviceService() 13 | self.lockdown_service = LockdownService() 14 | 15 | def _create_device(self): 16 | udid = self._get_udid() 17 | device = self.device_service.new_device(udid) 18 | print("device", device) 19 | self.assertIsNotNone(device) 20 | return device 21 | 22 | def _get_udid(self): 23 | device_service = DeviceService() 24 | device_list = device_service.get_device_list() 25 | self.assertIsNotNone(device_list) 26 | self.assertTrue(len(device_list) > 0) 27 | return device_list[0]['udid'] 28 | 29 | def test_new_client(self): 30 | device = self._create_device() 31 | client = self.lockdown_service.new_client(device) 32 | print("client", client) 33 | self.assertIsNotNone(client) 34 | self.lockdown_service.free_client(client) 35 | self.device_service.free_device(device) 36 | 37 | def test_get_value(self): 38 | device = self._create_device() 39 | client = self.lockdown_service.new_client(device, handshake=False) 40 | print("client", client) 41 | self.assertIsNotNone(client) 42 | #values = self.lockdown_service.get_value(client, "ProductVersion") 43 | values, error = self.lockdown_service.get_value(client, None) 44 | print("values", type(values), values) 45 | # self.assertTrue("DeviceName" in values) 46 | # self.assertTrue("UniqueDeviceID" in values) 47 | # self.assertTrue("ProductVersion" in values) 48 | self.lockdown_service.free_client(client) 49 | self.device_service.free_device(device) 50 | 51 | def test_get_domain_value(self): 52 | device = self._create_device() 53 | client = self.lockdown_service.new_client(device) 54 | print("client", client) 55 | self.assertIsNotNone(client) 56 | #values = self.lockdown_service.get_value(client, "ProductVersion") 57 | values = self.lockdown_service.get_domain_Value(client,"com.apple.iTunes", None) 58 | print("values", type(values), values) 59 | 60 | self.lockdown_service.free_client(client) 61 | self.device_service.free_device(device) 62 | 63 | def test_set_domain_value(self): 64 | device = self._create_device() 65 | client = self.lockdown_service.new_client(device) 66 | print("client", client) 67 | self.assertIsNotNone(client) 68 | #values = self.lockdown_service.get_value(client, "ProductVersion") 69 | values = self.lockdown_service.get_domain_Value(client,None, None) 70 | self.lockdown_service.set_domain_Value(client,None,"DeviceName",plist_new_string("DeviceName".encode("utf-8"))) 71 | values = self.lockdown_service.get_domain_Value(client,None, None) 72 | print("after values", type(values), values) 73 | self.lockdown_service.free_client(client) 74 | self.device_service.free_device(device) 75 | 76 | if __name__ == '__main__': 77 | unittest.main() -------------------------------------------------------------------------------- /pykp.cp37-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/pykp.cp37-win_amd64.pyd -------------------------------------------------------------------------------- /pykp.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/pykp.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /pykp.cpython-37m-darwin.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/pykp.cpython-37m-darwin.so -------------------------------------------------------------------------------- /rsd.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class RSDAddress: 6 | ip: str 7 | services: {} 8 | -------------------------------------------------------------------------------- /screenshotr_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | from libimobiledevice import ScreenshotrError 9 | from libimobiledevice import screenshotr_client_start_service, screenshotr_client_free, screenshotr_take_screenshot, libimobiledevice_free 10 | from libimobiledevice import plist_free 11 | from utils import read_data_from_plist_ptr, compare_version, read_buffer_from_pointer 12 | 13 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 14 | 15 | 16 | 17 | class ScreenshotrService(Service): 18 | """ 19 | 截图服务, 负责截图 20 | """ 21 | 22 | def new_client(self, device): 23 | """ 24 | 创建 screenshotr client,用于调用其他接口 25 | :param device: 由DeviceService创建的device对象(C对象) 26 | :return: screenshotr client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 27 | """ 28 | client = c_void_p() 29 | ret = screenshotr_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 30 | if ret != ScreenshotrError.SCREENSHOTR_E_SUCCESS: 31 | return None 32 | return client 33 | 34 | def free_client(self, client): 35 | """ 36 | 释放mobile image mounter client 37 | :param client: mobile image mounter client(C对象) 38 | :return: bool 是否成功 39 | """ 40 | ret = screenshotr_client_free(client) 41 | return ret == ScreenshotrError.SCREENSHOTR_E_SUCCESS 42 | 43 | def take_screenshot(self, client): 44 | imgdata_p = c_void_p() 45 | imgsize = c_uint64() 46 | 47 | ret = screenshotr_take_screenshot(client, pointer(imgdata_p), pointer(imgsize)) 48 | if ret != ScreenshotrError.SCREENSHOTR_E_SUCCESS: 49 | return None, None 50 | 51 | buffer = read_buffer_from_pointer(imgdata_p, imgsize.value) 52 | 53 | if buffer.startswith(b"\x89PNG"): 54 | file_ext = ".png" 55 | elif buffer.startswith(b"MM\x00*"): 56 | file_ext = ".tiff" 57 | else: 58 | file_ext = ".dat" # print("WARNING: screenshot data has unexpected image format.") 59 | 60 | libimobiledevice_free(imgdata_p) 61 | return buffer, file_ext 62 | 63 | -------------------------------------------------------------------------------- /screenshotr_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | from tempfile import NamedTemporaryFile 5 | 6 | from screenshotr_service import ScreenshotrService 7 | from device_service import DeviceService 8 | 9 | 10 | class ScreenshotrServiceTestCase(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.screenshotr_service = ScreenshotrService() 14 | self.device_service = DeviceService() 15 | 16 | def _create_device(self): 17 | udid = self._get_udid() 18 | device = self.device_service.new_device(udid) 19 | print("device", device) 20 | self.assertIsNotNone(device) 21 | return device 22 | 23 | def _get_udid(self): 24 | device_service = DeviceService() 25 | device_list = device_service.get_device_list() 26 | self.assertIsNotNone(device_list) 27 | self.assertTrue(len(device_list) > 0) 28 | return device_list[0]['udid'] 29 | 30 | def test_take_screenshot(self): 31 | device = self._create_device() 32 | client = self.screenshotr_service.new_client(device) 33 | self.assertIsNotNone(client) 34 | 35 | imgdata, file_ext = self.screenshotr_service.take_screenshot(client) 36 | self.assertIsNotNone(imgdata) 37 | print("imgdata:", imgdata) 38 | self.screenshotr_service.free_client(client) 39 | 40 | if file_ext == ".data": 41 | print("WARNING: screenshot data has unexpected image format.") 42 | 43 | tmpfile = NamedTemporaryFile(suffix=file_ext, delete=False) 44 | tmpfile.write(imgdata) 45 | tmpfile.close() 46 | print("png file %s" % tmpfile.name) 47 | 48 | 49 | if __name__ == '__main__': 50 | unittest.main() -------------------------------------------------------------------------------- /service.py: -------------------------------------------------------------------------------- 1 | 2 | class Service(object): 3 | pass 4 | 5 | -------------------------------------------------------------------------------- /spring_board_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | # lockdown 9 | from libimobiledevice import SbservicesError, SbservicesInterfaceOrientation 10 | from libimobiledevice import sbservices_client_start_service, sbservices_client_free, sbservices_get_icon_pngdata, sbservices_get_interface_orientation, libimobiledevice_free 11 | from libimobiledevice import plist_free, plist_to_bin, plist_to_bin_free, plist_to_xml, plist_to_xml_free 12 | import plistlib 13 | 14 | from utils import read_buffer_from_pointer 15 | 16 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | 19 | class SpringBoardService(Service): 20 | """ 21 | SprintBoard服务,负责获取应用图标 22 | """ 23 | 24 | def new_client(self, device): 25 | """ 26 | 创建SprintBoard client,用于调用其他接口 27 | :param device: 由DeviceService创建的device对象(C对象) 28 | :return: SprintBoard client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 29 | """ 30 | client = c_void_p() 31 | ret = sbservices_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 32 | if ret != SbservicesError.SBSERVICES_E_SUCCESS: 33 | return None 34 | return client 35 | 36 | def free_client(self, client): 37 | """ 38 | 释放SprintBoard Client 39 | :param client: SprintBoard client(C对象) 40 | :return: bool 是否成功 41 | """ 42 | ret = sbservices_client_free(client) 43 | return ret == SbservicesError.SBSERVICES_E_SUCCESS 44 | 45 | def get_icon_pngdata(self, client, bundle_id): 46 | """ 47 | 获取应用图标 48 | :param client: SprintBoard client(C对象) 49 | :param bundle_id: 应用ID 50 | :return: 应用图标(PNG data) 51 | """ 52 | pngdata_p = c_void_p() 53 | pngsize = c_uint64() 54 | 55 | ret = sbservices_get_icon_pngdata(client, bundle_id.encode("utf-8"), pointer(pngdata_p), pointer(pngsize)) 56 | if ret != SbservicesError.SBSERVICES_E_SUCCESS: 57 | return None 58 | 59 | buffer = read_buffer_from_pointer(pngdata_p, pngsize.value) 60 | # print("buffer.length", len(buffer)) 61 | 62 | libimobiledevice_free(pngdata_p) 63 | return buffer 64 | 65 | def get_interface_orientation(self, client): 66 | interface_orientation = c_uint() 67 | 68 | ret = sbservices_get_interface_orientation(client, pointer(interface_orientation)) 69 | if ret != SbservicesError.SBSERVICES_E_SUCCESS: 70 | return SbservicesInterfaceOrientation.SBSERVICES_INTERFACE_ORIENTATION_UNKNOWN 71 | return int(interface_orientation.value) 72 | 73 | -------------------------------------------------------------------------------- /spring_board_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | from tempfile import NamedTemporaryFile 5 | 6 | from spring_board_service import SpringBoardService 7 | from device_service import DeviceService 8 | 9 | 10 | class SpringBoardServiceTestCase(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.spring_board_service = SpringBoardService() 14 | self.device_service = DeviceService() 15 | 16 | def _create_device(self): 17 | udid = self._get_udid() 18 | device = self.device_service.new_device(udid) 19 | print("device", device) 20 | self.assertIsNotNone(device) 21 | return device 22 | 23 | def _get_udid(self): 24 | device_service = DeviceService() 25 | device_list = device_service.get_device_list() 26 | self.assertIsNotNone(device_list) 27 | self.assertTrue(len(device_list) > 0) 28 | return device_list[0]['udid'] 29 | 30 | def test_get_icon_pngdata(self): 31 | device = self._create_device() 32 | client = self.spring_board_service.new_client(device) 33 | self.assertIsNotNone(client) 34 | 35 | pngdata = self.spring_board_service.get_icon_pngdata(client, "com.apple.Preferences") 36 | self.assertIsNotNone(pngdata) 37 | print("pngdata:", pngdata) 38 | self.spring_board_service.free_client(client) 39 | 40 | tmpfile = NamedTemporaryFile(suffix=".png", delete=False) 41 | tmpfile.write(pngdata) 42 | tmpfile.close() 43 | print("png file %s" % tmpfile.name) 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() -------------------------------------------------------------------------------- /syslog_relay_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import logging 4 | 5 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 6 | 7 | from service import Service 8 | from libimobiledevice import SyslogRelayError, SyslogRelayReceiveCb 9 | from libimobiledevice import syslog_relay_client_start_service, syslog_relay_client_free, syslog_relay_start_capture, syslog_relay_stop_capture 10 | from libimobiledevice import plist_free 11 | from utils import read_data_from_plist_ptr, compare_version, read_buffer_from_pointer 12 | 13 | ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) 14 | 15 | 16 | class SyslogRelayService(Service): 17 | """ 18 | 系统日志服务, 负责获取系统日志 19 | """ 20 | 21 | def __init__(self): 22 | self._callback = None 23 | 24 | def new_client(self, device): 25 | """ 26 | 创建 syslog relay client,用于调用其他接口 27 | :param device: 由DeviceService创建的device对象(C对象) 28 | :return: syslog relay client(C对象), 在使用完毕后请务必调用free_client来释放该对象内存 29 | """ 30 | client = c_void_p() 31 | ret = syslog_relay_client_start_service(device, pointer(client), "ideviceinfo".encode("utf-8")) 32 | if ret != SyslogRelayError.SYSLOG_RELAY_E_SUCCESS: 33 | return None 34 | return client 35 | 36 | def free_client(self, client): 37 | """ 38 | 释放mobile image mounter client 39 | :param client: mobile image mounter client(C对象) 40 | :return: bool 是否成功 41 | """ 42 | ret = syslog_relay_client_free(client) 43 | return ret == SyslogRelayError.SYSLOG_RELAY_E_SUCCESS 44 | 45 | def start_capture(self, client, listener): 46 | def callback(char_data, user_data): 47 | listener(char_data, user_data) 48 | user_data = c_void_p() 49 | self._callback = SyslogRelayReceiveCb(callback) 50 | ret = syslog_relay_start_capture(client, self._callback, user_data) 51 | return ret == SyslogRelayError.SYSLOG_RELAY_E_SUCCESS 52 | 53 | def stop_capture(self, client): 54 | ret = syslog_relay_stop_capture(client) 55 | return ret == SyslogRelayError.SYSLOG_RELAY_E_SUCCESS 56 | -------------------------------------------------------------------------------- /syslog_relay_service_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | from tempfile import NamedTemporaryFile 5 | 6 | from syslog_relay_service import SyslogRelayService 7 | from device_service import DeviceService 8 | 9 | 10 | class SyslogRelayServiceTestCase(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.syslog_relay_service = SyslogRelayService() 14 | self.device_service = DeviceService() 15 | 16 | def _create_device(self): 17 | udid = self._get_udid() 18 | device = self.device_service.new_device(udid) 19 | print("device", device) 20 | self.assertIsNotNone(device) 21 | return device 22 | 23 | def _get_udid(self): 24 | device_service = DeviceService() 25 | device_list = device_service.get_device_list() 26 | self.assertIsNotNone(device_list) 27 | self.assertTrue(len(device_list) > 0) 28 | return device_list[0]['udid'] 29 | 30 | def test_start_capture(self): 31 | device = self._create_device() 32 | client = self.syslog_relay_service.new_client(device) 33 | self.assertIsNotNone(client) 34 | 35 | def callback(char_data, user_data): 36 | print(char_data, end="") 37 | result = self.syslog_relay_service.start_capture(client, callback) 38 | self.assertTrue(result) 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from ctypes import cdll, c_int, c_char, POINTER, c_char_p, c_byte, pointer, cast, c_void_p, c_uint, create_string_buffer, Structure, c_uint32, c_int32, c_uint16, c_uint64, c_int64, sizeof, create_string_buffer, string_at 3 | from threading import Thread, Event 4 | import time 5 | import traceback 6 | from service import Service 7 | import socket 8 | import uuid 9 | import threading 10 | from zeroconf import ServiceBrowser, Zeroconf 11 | from utils import aes_256_cbc_decrypt, aes_256_cbc_encrypt 12 | import _thread 13 | from device_service import DeviceService 14 | from libimobiledevice import \ 15 | instrument_client_start_service, \ 16 | instrument_client_free, \ 17 | instrument_receive, \ 18 | instrument_receive_with_timeout, \ 19 | instrument_send_command, \ 20 | InstrumentError 21 | 22 | from dtxlib import DTXMessage, DTXMessageHeader, \ 23 | auxiliary_to_pyobject, pyobject_to_auxiliary, \ 24 | pyobject_to_selector, selector_to_pyobject 25 | from bpylist import archiver, bplist 26 | from utils import parse_plist_to_xml 27 | import struct 28 | 29 | def load_byte_from_hexdump(hd): 30 | return bytes(map(lambda a:int(a, 16), filter(None, hd.split()))) 31 | 32 | reg = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 A4 01 00 00 11 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 E5 00 00 00 94 01 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 D5 00 00 00 00 00 00 00 0A 00 00 00 03 00 00 00 09 00 00 00 0A 00 00 00 02 00 00 00 BD 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 31 63 6F 6D 2E 61 70 70 6C 65 2E 69 6E 73 74 72 75 6D 65 6E 74 73 2E 73 65 72 76 65 72 2E 73 65 72 76 69 63 65 73 2E 6F 62 6A 65 63 74 61 6C 6C 6F 63 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 23 5F 72 65 71 75 65 73 74 43 68 61 6E 6E 65 6C 57 69 74 68 43 6F 64 65 3A 69 64 65 6E 74 69 66 69 65 72 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82""") 33 | 34 | # 3 35 | # launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options: 36 | # /private/var/containers/Bundle/Application/D953DAE2-33CE-45FB-9C33-5379E2633B7A/PrivatePhotoManager.app 37 | # com.jimmy.tes1 38 | # {'OAKeepAllocationStatistics': 'YES', 'DYLD_PRINT_TO_STDERR': '1', 'OAWaitForSetupByPid': '461', 'DYLD_INSERT_LIBRARIES': '/Developer/Library/PrivateFrameworks/DVTInstrumentsFoundation.framework/liboainject.dylib', 'OAAllocationStatisticsOutputMask': '0x4f9d0f00', 'OS_ACTIVITY_DT_MODE': '1', 'HIPreventRefEncoding': '1'} 39 | # [] 40 | # {'StartSuspendedKey': True, 'iODestinationKey': 0} 41 | launch = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 E4 07 00 00 15 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 02 00 00 00 F4 06 00 00 D4 07 00 00 00 00 00 00 F0 07 00 00 00 00 00 00 E4 06 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 F3 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 67 2F 70 72 69 76 61 74 65 2F 76 61 72 2F 63 6F 6E 74 61 69 6E 65 72 73 2F 42 75 6E 64 6C 65 2F 41 70 70 6C 69 63 61 74 69 6F 6E 2F 44 39 35 33 44 41 45 32 2D 33 33 43 45 2D 34 35 46 42 2D 39 43 33 33 2D 35 33 37 39 45 32 36 33 33 42 37 41 2F 50 72 69 76 61 74 65 50 68 6F 74 6F 4D 61 6E 61 67 65 72 2E 61 70 70 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C6 0A 00 00 00 02 00 00 00 98 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5E 63 6F 6D 2E 6A 69 6D 6D 79 2E 74 65 73 31 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6B 0A 00 00 00 02 00 00 00 D9 02 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 AF 10 10 0B 0C 21 22 23 24 25 26 27 28 29 2A 2B 2F 36 37 55 24 6E 75 6C 6C D3 0D 0E 0F 10 18 20 57 4E 53 2E 6B 65 79 73 5A 4E 53 2E 6F 62 6A 65 63 74 73 56 24 63 6C 61 73 73 A7 11 12 13 14 15 16 17 80 02 80 03 80 04 80 05 80 06 80 07 80 08 A7 19 1A 1B 1C 1D 1A 1A 80 09 80 0A 80 0B 80 0C 80 0E 80 0A 80 0A 80 0F 5F 10 1A 4F 41 4B 65 65 70 41 6C 6C 6F 63 61 74 69 6F 6E 53 74 61 74 69 73 74 69 63 73 5F 10 14 44 59 4C 44 5F 50 52 49 4E 54 5F 54 4F 5F 53 54 44 45 52 52 5F 10 13 4F 41 57 61 69 74 46 6F 72 53 65 74 75 70 42 79 50 69 64 5F 10 15 44 59 4C 44 5F 49 4E 53 45 52 54 5F 4C 49 42 52 41 52 49 45 53 5F 10 20 4F 41 41 6C 6C 6F 63 61 74 69 6F 6E 53 74 61 74 69 73 74 69 63 73 4F 75 74 70 75 74 4D 61 73 6B 5F 10 13 4F 53 5F 41 43 54 49 56 49 54 59 5F 44 54 5F 4D 4F 44 45 5F 10 14 48 49 50 72 65 76 65 6E 74 52 65 66 45 6E 63 6F 64 69 6E 67 53 59 45 53 51 31 53 34 36 31 D2 0F 2C 2D 2E 59 4E 53 2E 73 74 72 69 6E 67 80 0D 5F 10 59 2F 44 65 76 65 6C 6F 70 65 72 2F 4C 69 62 72 61 72 79 2F 50 72 69 76 61 74 65 46 72 61 6D 65 77 6F 72 6B 73 2F 44 56 54 49 6E 73 74 72 75 6D 65 6E 74 73 46 6F 75 6E 64 61 74 69 6F 6E 2E 66 72 61 6D 65 77 6F 72 6B 2F 6C 69 62 6F 61 69 6E 6A 65 63 74 2E 64 79 6C 69 62 D2 30 31 32 33 5A 24 63 6C 61 73 73 6E 61 6D 65 58 24 63 6C 61 73 73 65 73 5F 10 0F 4E 53 4D 75 74 61 62 6C 65 53 74 72 69 6E 67 A3 32 34 35 58 4E 53 53 74 72 69 6E 67 58 4E 53 4F 62 6A 65 63 74 5A 30 78 34 66 39 64 30 66 30 30 D2 30 31 38 39 5C 4E 53 44 69 63 74 69 6F 6E 61 72 79 A2 38 35 00 08 00 11 00 1A 00 24 00 29 00 32 00 37 00 49 00 4C 00 51 00 53 00 66 00 6C 00 73 00 7B 00 86 00 8D 00 95 00 97 00 99 00 9B 00 9D 00 9F 00 A1 00 A3 00 AB 00 AD 00 AF 00 B1 00 B3 00 B5 00 B7 00 B9 00 BB 00 D8 00 EF 01 05 01 1D 01 40 01 56 01 6D 01 71 01 73 01 77 01 7C 01 86 01 88 01 E4 01 E9 01 F4 01 FD 02 0F 02 13 02 1C 02 25 02 30 02 35 02 42 00 00 00 00 00 00 02 01 00 00 00 00 00 00 00 3A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 45 0A 00 00 00 02 00 00 00 DB 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A3 0B 0C 11 55 24 6E 75 6C 6C D2 0D 0E 0F 10 5A 4E 53 2E 6F 62 6A 65 63 74 73 56 24 63 6C 61 73 73 A0 80 02 D2 12 13 14 15 5A 24 63 6C 61 73 73 6E 61 6D 65 58 24 63 6C 61 73 73 65 73 57 4E 53 41 72 72 61 79 A2 14 16 58 4E 53 4F 62 6A 65 63 74 08 11 1A 24 29 32 37 49 4C 51 53 57 5D 62 6D 74 75 77 7C 87 90 98 9B 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 17 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A4 0A 00 00 00 02 00 00 00 69 01 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A7 0B 0C 17 18 19 1A 1B 55 24 6E 75 6C 6C D3 0D 0E 0F 10 13 16 57 4E 53 2E 6B 65 79 73 5A 4E 53 2E 6F 62 6A 65 63 74 73 56 24 63 6C 61 73 73 A2 11 12 80 02 80 03 A2 14 15 80 04 80 05 80 06 5F 10 11 53 74 61 72 74 53 75 73 70 65 6E 64 65 64 4B 65 79 5F 10 10 69 4F 44 65 73 74 69 6E 61 74 69 6F 6E 4B 65 79 09 10 00 D2 1C 1D 1E 1F 5A 24 63 6C 61 73 73 6E 61 6D 65 58 24 63 6C 61 73 73 65 73 5F 10 13 4E 53 4D 75 74 61 62 6C 65 44 69 63 74 69 6F 6E 61 72 79 A3 1E 20 21 5C 4E 53 44 69 63 74 69 6F 6E 61 72 79 58 4E 53 4F 62 6A 65 63 74 00 08 00 11 00 1A 00 24 00 29 00 32 00 37 00 49 00 4C 00 51 00 53 00 5B 00 61 00 68 00 70 00 7B 00 82 00 85 00 87 00 89 00 8C 00 8E 00 90 00 92 00 A6 00 B9 00 BA 00 BC 00 C1 00 CC 00 D5 00 EB 00 EF 00 FC 00 00 00 00 00 00 02 01 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 05 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 54 6C 61 75 6E 63 68 53 75 73 70 65 6E 64 65 64 50 72 6F 63 65 73 73 57 69 74 68 44 65 76 69 63 65 50 61 74 68 3A 62 75 6E 64 6C 65 49 64 65 6E 74 69 66 69 65 72 3A 65 6E 76 69 72 6F 6E 6D 65 6E 74 3A 61 72 67 75 6D 65 6E 74 73 3A 6F 70 74 69 6F 6E 73 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3""") 42 | 43 | ## 44 | ## 486 45 | ## resume 46 | ressume = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 4C 01 00 00 20 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 02 00 00 00 A8 00 00 00 3C 01 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 98 00 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 8C 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 11 01 E6 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5F 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5A 72 65 73 75 6D 65 50 69 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 67""") 47 | 48 | # 9 49 | # preparedEnvironmentForLaunch:eventsMask: 50 | # {} 51 | # 1335693056 52 | prepare = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 7F 02 00 00 12 00 00 00 00 00 00 00 09 00 00 00 01 00 00 00 02 00 00 00 BB 01 00 00 6F 02 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 AB 01 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 05 01 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A3 0B 0C 13 55 24 6E 75 6C 6C D3 0D 0E 0F 10 11 12 57 4E 53 2E 6B 65 79 73 5A 4E 53 2E 6F 62 6A 65 63 74 73 56 24 63 6C 61 73 73 A0 A0 80 02 D2 14 15 16 17 5A 24 63 6C 61 73 73 6E 61 6D 65 58 24 63 6C 61 73 73 65 73 5F 10 13 4E 53 4D 75 74 61 62 6C 65 44 69 63 74 69 6F 6E 61 72 79 A3 16 18 19 5C 4E 53 44 69 63 74 69 6F 6E 61 72 79 58 4E 53 4F 62 6A 65 63 74 08 11 1A 24 29 32 37 49 4C 51 53 57 5D 64 6C 77 7E 7F 80 82 87 92 9B B1 B5 C2 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 1A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CB 0A 00 00 00 02 00 00 00 8E 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 12 4F 9D 0F 00 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 61 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 28 70 72 65 70 61 72 65 64 45 6E 76 69 72 6F 6E 6D 65 6E 74 46 6F 72 4C 61 75 6E 63 68 3A 65 76 65 6E 74 73 4D 61 73 6B 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 87""") 53 | 54 | # 9 55 | # startCollectionWithPid: 56 | # 486 57 | par = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 CF 00 00 00 21 00 00 00 00 00 00 00 09 00 00 00 01 00 00 00 02 00 00 00 1C 00 00 00 BF 00 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 0C 00 00 00 00 00 00 00 0A 00 00 00 03 00 00 00 E6 01 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 17 73 74 61 72 74 43 6F 6C 6C 65 63 74 69 6F 6E 57 69 74 68 50 69 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 76""") 58 | # 3 59 | # startObservingPid: 60 | # 486 61 | par2 = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 56 01 00 00 22 00 00 00 00 00 00 00 03 00 00 00 01 00 00 00 02 00 00 00 A8 00 00 00 46 01 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 98 00 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 8C 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 11 01 E6 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5F 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 12 73 74 61 72 74 4F 62 73 65 72 76 69 6E 67 50 69 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 71""") 62 | 63 | 64 | # 1 65 | # symbolicatorSignatureForPid:trackingSelector: 66 | # 486 67 | # dyldNotificationReceived: 68 | 69 | sys = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 22 02 00 00 18 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 59 01 00 00 12 02 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 49 01 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 8C 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 11 01 E6 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5F 0A 00 00 00 02 00 00 00 A5 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 19 64 79 6C 64 4E 6F 74 69 66 69 63 61 74 69 6F 6E 52 65 63 65 69 76 65 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 2D 73 79 6D 62 6F 6C 69 63 61 74 6F 72 53 69 67 6E 61 74 75 72 65 46 6F 72 50 69 64 3A 74 72 61 63 6B 69 6E 67 53 65 6C 65 63 74 6F 72 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8C""") 70 | 71 | 72 | # 4294967291 73 | # applicationStateNotification: 74 | # {'execName': 'PrivatePhotoManager', 'state_description': 'Unknown', 'elevated_state_description': 'Unknown', 'displayID': 'com.jimmy.tes1', 'mach_absolute_time': 173662197667, 'appName': 'PrivatePhotoManager', 'elevated_state': 'Unknown', 'timestamp': 1595322598.3195481, 'state': 0, 'pid': 486} 75 | 76 | pem = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 80 03 00 00 DD 01 00 00 00 00 00 00 FB FF FF FF 00 00 00 00 02 00 00 00 C7 02 00 00 70 03 00 00 00 00 00 00 F0 03 00 00 00 00 00 00 B7 02 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 AB 02 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 41 42 58 24 76 65 72 73 69 6F 6E 58 24 6F 62 6A 65 63 74 73 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 12 00 01 86 A0 AF 10 16 07 08 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 36 3C 3D 3E 55 24 6E 75 6C 6C D3 09 0A 0B 0C 17 22 57 4E 53 2E 6B 65 79 73 5A 4E 53 2E 6F 62 6A 65 63 74 73 56 24 63 6C 61 73 73 AA 0D 0E 0F 10 11 12 13 14 15 16 80 02 80 03 80 04 80 05 80 06 80 07 80 08 80 09 80 0A 80 0B AA 18 19 19 1B 1C 1D 19 1F 20 21 80 0C 80 0D 80 0D 80 0E 80 0F 80 10 80 0D 80 11 80 13 80 14 80 15 58 65 78 65 63 4E 61 6D 65 5F 10 11 73 74 61 74 65 5F 64 65 73 63 72 69 70 74 69 6F 6E 5F 10 1A 65 6C 65 76 61 74 65 64 5F 73 74 61 74 65 5F 64 65 73 63 72 69 70 74 69 6F 6E 59 64 69 73 70 6C 61 79 49 44 5F 10 12 6D 61 63 68 5F 61 62 73 6F 6C 75 74 65 5F 74 69 6D 65 57 61 70 70 4E 61 6D 65 5E 65 6C 65 76 61 74 65 64 5F 73 74 61 74 65 59 74 69 6D 65 73 74 61 6D 70 55 73 74 61 74 65 53 70 69 64 5F 10 13 50 72 69 76 61 74 65 50 68 6F 74 6F 4D 61 6E 61 67 65 72 57 55 6E 6B 6E 6F 77 6E 5E 63 6F 6D 2E 6A 69 6D 6D 79 2E 74 65 73 31 13 00 00 00 28 6F 12 D7 A3 5F 10 13 50 72 69 76 61 74 65 50 68 6F 74 6F 4D 61 6E 61 67 65 72 D2 33 0B 34 35 57 4E 53 2E 74 69 6D 65 23 41 C2 63 74 33 28 E6 F3 80 12 D2 37 38 39 3A 5A 24 63 6C 61 73 73 6E 61 6D 65 58 24 63 6C 61 73 73 65 73 56 4E 53 44 61 74 65 A2 39 3B 58 4E 53 4F 62 6A 65 63 74 10 00 11 01 E6 D2 37 38 3F 40 5C 4E 53 44 69 63 74 69 6F 6E 61 72 79 A2 3F 3B 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 43 44 54 72 6F 6F 74 80 01 00 08 00 11 00 1A 00 23 00 2D 00 32 00 37 00 50 00 56 00 5D 00 65 00 70 00 77 00 82 00 84 00 86 00 88 00 8A 00 8C 00 8E 00 90 00 92 00 94 00 96 00 A1 00 A3 00 A5 00 A7 00 A9 00 AB 00 AD 00 AF 00 B1 00 B3 00 B5 00 B7 00 C0 00 D4 00 F1 00 FB 01 10 01 18 01 27 01 31 01 37 01 3B 01 51 01 59 01 68 01 71 01 87 01 8C 01 94 01 9D 01 9F 01 A4 01 AF 01 B8 01 BF 01 C2 01 CB 01 CD 01 D0 01 D5 01 E2 01 E5 01 F7 01 FA 01 FF 00 00 00 00 00 00 02 01 00 00 00 00 00 00 00 45 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 01 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 09 0A 58 24 76 65 72 73 69 6F 6E 58 24 6F 62 6A 65 63 74 73 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 12 00 01 86 A0 A2 07 08 55 24 6E 75 6C 6C 5F 10 1D 61 70 70 6C 69 63 61 74 69 6F 6E 53 74 61 74 65 4E 6F 74 69 66 69 63 61 74 69 6F 6E 3A 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 0B 0C 54 72 6F 6F 74 80 01 08 11 1A 23 2D 32 37 3A 40 60 72 75 7A 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7C""") 77 | 78 | 79 | 80 | # 1 81 | # symbolicatorSignatureForPid:trackingSelector: 82 | # 486 83 | # dyldNotificationReceived: 84 | 85 | coreprofile_cfg2 = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 22 02 00 00 18 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 59 01 00 00 12 02 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 49 01 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 8C 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 11 01 E6 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5F 0A 00 00 00 02 00 00 00 A5 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 19 64 79 6C 64 4E 6F 74 69 66 69 63 61 74 69 6F 6E 52 65 63 65 69 76 65 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 2D 73 79 6D 62 6F 6C 69 63 61 74 6F 72 53 69 67 6E 61 74 75 72 65 46 6F 72 50 69 64 3A 74 72 61 63 6B 69 6E 67 53 65 6C 65 63 74 6F 72 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8C""") 86 | # 1 87 | # symbolicatorSignatureForPid:trackingSelector: 88 | # 0 89 | # dyldNotificationReceived: 90 | sig = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 21 02 00 00 1B 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 58 01 00 00 11 02 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 48 01 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 8B 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 10 00 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5E 0A 00 00 00 02 00 00 00 A5 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 19 64 79 6C 64 4E 6F 74 69 66 69 63 61 74 69 6F 6E 52 65 63 65 69 76 65 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 2D 73 79 6D 62 6F 6C 69 63 61 74 6F 72 53 69 67 6E 61 74 75 72 65 46 6F 72 50 69 64 3A 74 72 61 63 6B 69 6E 67 53 65 6C 65 63 74 6F 72 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8C""") 91 | 92 | stop = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 A8 00 00 00 23 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5E 73 74 6F 70 43 6F 6C 6C 65 63 74 69 6F 6E 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6B""") 93 | 94 | reg2 = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 6A 01 00 00 09 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 02 00 00 00 A6 00 00 00 5A 01 00 00 00 00 00 00 F0 01 00 00 00 00 00 00 96 00 00 00 00 00 00 00 0A 00 00 00 02 00 00 00 8A 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 09 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5D 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 0A 58 24 76 65 72 73 69 6F 6E 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 58 24 6F 62 6A 65 63 74 73 12 00 01 86 A0 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 08 09 54 72 6F 6F 74 80 01 A2 0B 0C 55 24 6E 75 6C 6C 5F 10 28 73 65 74 41 70 70 6C 69 63 61 74 69 6F 6E 53 74 61 74 65 4E 6F 74 69 66 69 63 61 74 69 6F 6E 73 45 6E 61 62 6C 65 64 3A 08 11 1A 24 29 32 37 49 4C 51 53 56 5C 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 87""") 95 | 96 | startServ = load_byte_from_hexdump("""79 5B 3D 1F 20 00 00 00 00 00 01 00 A5 00 00 00 46 01 00 00 01 00 00 00 01 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 95 00 00 00 00 00 00 00 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 09 0A 58 24 76 65 72 73 69 6F 6E 58 24 6F 62 6A 65 63 74 73 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 12 00 01 86 A0 A2 07 08 55 24 6E 75 6C 6C 5B 6D 61 63 68 5F 6B 65 72 6E 65 6C 5F 10 0F 4E 53 4B 65 79 65 64 41 72 63 68 69 76 65 72 D1 0B 0C 54 72 6F 6F 74 80 01 08 11 1A 23 2D 32 37 3A 40 4C 5E 61 66 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 """) 97 | 98 | dt = DTXMessage().from_bytes(startServ) 99 | print(dt.channel_code) 100 | print(archiver.unarchive(dt.get_selector())) 101 | #print(dt.parse_plist_to_xml()) 102 | for i in dt._auxiliaries: 103 | print(auxiliary_to_pyobject(i)) -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | 2 | import plistlib 3 | from ctypes import * 4 | 5 | from bpylist import archiver, bplist 6 | from libimobiledevice import (EVP_aes_256_cbc, EVP_CIPHER_CTX_free, 7 | EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_reset, 8 | EVP_DecryptFinal_ex, EVP_DecryptInit_ex, 9 | EVP_DecryptUpdate, EVP_EncryptFinal_ex, 10 | EVP_EncryptInit_ex, EVP_EncryptUpdate, 11 | plist_free, plist_from_memory, plist_new_data, 12 | plist_to_bin, plist_to_bin_free, plist_to_xml, 13 | plist_to_xml_free) 14 | 15 | 16 | def read_buffer_from_pointer(pointer, length): 17 | return bytes(cast(pointer, POINTER(c_char))[:length]) 18 | 19 | def parse_plist_to_xml(bin_data:bytes): 20 | plist = c_void_p() 21 | format = c_void_p() 22 | plist_from_memory(bin_data, len(bin_data), pointer(plist), pointer(format)) 23 | plist_xml_p = c_void_p() 24 | length = c_int() 25 | plist_to_xml(plist, pointer(plist_xml_p), pointer(length)) 26 | xml = read_buffer_from_pointer(plist_xml_p, length.value) 27 | plist_to_xml_free(plist_xml_p) 28 | plist_free(plist) 29 | plist_free(format) 30 | return xml 31 | 32 | 33 | 34 | def read_data_from_plist_ptr(plist_p): 35 | result = None 36 | 37 | plist_bin_p = c_void_p() 38 | length = c_int() 39 | plist_to_bin(plist_p, pointer(plist_bin_p), pointer(length)) 40 | # print("length", length.value) 41 | 42 | buffer = read_buffer_from_pointer(plist_bin_p, length.value) 43 | # print("buffer.length", len(buffer)) 44 | if buffer and len(buffer) > 0: 45 | #result = bplist.parse(buffer) 46 | result = plistlib.loads(buffer) 47 | plist_to_bin_free(plist_bin_p) 48 | #plist_free(plist_p) 49 | return result 50 | 51 | 52 | def compare_version(v0:str, v1:str): 53 | tmp0 = v0.split(".") 54 | tmp1 = v1.split(".") 55 | length0 = len(tmp0) 56 | length1 = len(tmp1) 57 | i = 0 58 | while i < length0 and i < length1 and tmp0[i] == tmp1[i]: 59 | i += 1 60 | if i < length0 and i < length1: 61 | return int(tmp0[i]) - int(tmp1[i]) 62 | else: 63 | return length0 - length1 64 | 65 | def aes_256_cbc_encrypt(buf, key, iv=None): 66 | out_bytes = c_int() 67 | out = create_string_buffer(8192) 68 | ret = b'' 69 | ctx = EVP_CIPHER_CTX_new() 70 | EVP_CIPHER_CTX_reset(ctx) 71 | if iv is None: iv = b'\x00' * 16 72 | EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), None, key, iv) 73 | cur = 0 74 | try: 75 | while cur < len(buf): 76 | step = min(len(buf) - cur, 4096) 77 | if not EVP_EncryptUpdate(ctx, out, pointer(out_bytes), buf[cur:cur + step], step): 78 | return None 79 | ret += out[:out_bytes.value] 80 | cur += step 81 | if not EVP_EncryptFinal_ex(ctx, out, pointer(out_bytes)): 82 | return None 83 | ret += out[:out_bytes.value] 84 | finally: 85 | EVP_CIPHER_CTX_reset(ctx) 86 | EVP_CIPHER_CTX_free(ctx) 87 | return ret 88 | 89 | def aes_256_cbc_decrypt(buf, key, iv=None): 90 | out_bytes = c_int() 91 | out = create_string_buffer(8192) 92 | ret = b'' 93 | ctx = EVP_CIPHER_CTX_new() 94 | EVP_CIPHER_CTX_reset(ctx) 95 | if iv is None: iv = b'\x00' * 16 96 | EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), None, key, iv) 97 | try: 98 | cur = 0 99 | while cur < len(buf): 100 | step = min(len(buf) - cur, 4096) 101 | if not EVP_DecryptUpdate(ctx, out, pointer(out_bytes), buf[cur:cur + step], step): 102 | return None 103 | ret += out[:out_bytes.value] 104 | cur += step 105 | if not EVP_DecryptFinal_ex(ctx, out, pointer(out_bytes)): 106 | return None 107 | ret += out[:out_bytes.value] 108 | finally: 109 | EVP_CIPHER_CTX_reset(ctx) 110 | EVP_CIPHER_CTX_free(ctx) 111 | return ret 112 | -------------------------------------------------------------------------------- /utils_unittesting.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | from tempfile import NamedTemporaryFile 5 | 6 | from utils import compare_version, \ 7 | aes_256_cbc_decrypt, aes_256_cbc_encrypt 8 | 9 | class UtilsTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | pass 13 | 14 | def test_compare_version(self): 15 | self.assertTrue(compare_version("2", "1") > 0) 16 | self.assertTrue(compare_version("2", "1.0") > 0) 17 | self.assertTrue(compare_version("2", "1.0.3") > 0) 18 | 19 | self.assertTrue(compare_version("2.0", "1") > 0) 20 | self.assertTrue(compare_version("2.0", "1.0") > 0) 21 | self.assertTrue(compare_version("2.0", "1.0.3") > 0) 22 | 23 | self.assertTrue(compare_version("2.0.0", "1") > 0) 24 | self.assertTrue(compare_version("2.0.0", "1.0") > 0) 25 | self.assertTrue(compare_version("2.0.0", "1.0.3") > 0) 26 | 27 | # self.assertTrue(compare_version("1.0", "1.0.0") == 0) 28 | 29 | 30 | def test_crypto_aes_256_cbc(self): 31 | key = b'0' * 32 32 | data = bytes(map(lambda n: n%256, range(1, 10000))) 33 | encrypted = aes_256_cbc_encrypt(data, key) 34 | decrypted = aes_256_cbc_decrypt(encrypted, key) 35 | self.assertEqual(data, decrypted) 36 | 37 | if __name__ == '__main__': 38 | unittest.main() -------------------------------------------------------------------------------- /zeroconf/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkw72n/idb/9a4350330e9076adac8f6a68f206c4b1b45df5a5/zeroconf/py.typed --------------------------------------------------------------------------------