├── assets ├── u-boot.bin ├── adapters │ ├── rpi4.cfg │ ├── rpi1.cfg │ ├── rpi2.cfg │ └── rpi3.cfg └── boards │ └── ed20.cfg ├── payload ├── dropbear_2015.71-r0_qb2.ipk ├── openssh-sftp-server_7.3p1-r10.0_qb2.ipk └── patch_toon.sh ├── LICENSE ├── sshkeys.py ├── .gitignore ├── __main__.py ├── rooter.py └── README.md /assets/u-boot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martenjacobs/ToonRooter/HEAD/assets/u-boot.bin -------------------------------------------------------------------------------- /payload/dropbear_2015.71-r0_qb2.ipk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martenjacobs/ToonRooter/HEAD/payload/dropbear_2015.71-r0_qb2.ipk -------------------------------------------------------------------------------- /payload/openssh-sftp-server_7.3p1-r10.0_qb2.ipk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martenjacobs/ToonRooter/HEAD/payload/openssh-sftp-server_7.3p1-r10.0_qb2.ipk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Marten Jacobs 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 | -------------------------------------------------------------------------------- /sshkeys.py: -------------------------------------------------------------------------------- 1 | def generate_key_pair(password=None): 2 | from cryptography.hazmat.backends import default_backend 3 | from cryptography.hazmat.primitives.asymmetric import rsa 4 | 5 | from cryptography.hazmat.primitives import serialization 6 | 7 | private_key = rsa.generate_private_key( 8 | public_exponent=65537, 9 | key_size=2048, 10 | backend=default_backend() 11 | ) 12 | 13 | key_encryption_algorithm = serialization.NoEncryption() 14 | if password: 15 | key_encryption_algorithm = serialization.BestAvailableEncryption(password) 16 | 17 | private_pem = private_key.private_bytes( 18 | encoding=serialization.Encoding.PEM, 19 | format=serialization.PrivateFormat.PKCS8, 20 | encryption_algorithm=key_encryption_algorithm 21 | ) 22 | 23 | 24 | public_key = private_key.public_key() 25 | 26 | public_openssh = public_key.public_bytes( 27 | encoding=serialization.Encoding.OpenSSH, 28 | format=serialization.PublicFormat.OpenSSH 29 | ) 30 | 31 | return (public_openssh, private_pem) 32 | 33 | def check_public_key(data): 34 | from cryptography.hazmat.primitives import serialization 35 | from cryptography.hazmat.backends import default_backend 36 | try: 37 | key = serialization.load_ssh_public_key( 38 | data, default_backend()) 39 | return True 40 | except: 41 | return False 42 | -------------------------------------------------------------------------------- /assets/adapters/rpi4.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Config for using Raspberry Pi's expansion header 3 | # 4 | # This is best used with a fast enough buffer but also 5 | # is suitable for direct connection if the target voltage 6 | # matches RPi's 3.3V and the cable is short enough. 7 | # 8 | # Do not forget the GND connection, pin 6 of the expansion header. 9 | # 10 | 11 | interface bcm2835gpio 12 | 13 | bcm2835gpio_peripheral_base 0xFE000000 14 | 15 | # Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET 16 | # These depend on system clock 17 | # bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET 18 | bcm2835gpio_speed_coeffs 236181 60 19 | 20 | # Each of the JTAG lines need a gpio number set: tck tms tdi tdo 21 | # Header pin numbers: 23 22 19 21 22 | bcm2835gpio_jtag_nums 11 25 10 9 23 | 24 | # or if you have both connected, 25 | # reset_config trst_and_srst srst_push_pull 26 | 27 | # Each of the SWD lines need a gpio number set: swclk swdio 28 | # Header pin numbers: 22 18 29 | #bcm2835gpio_swd_nums 25 24 30 | 31 | # If you define trst or srst, use appropriate reset_config 32 | # Header pin numbers: TRST - 26, SRST - 18 33 | 34 | bcm2835gpio_trst_num 8 35 | # reset_config trst_only 36 | 37 | bcm2835gpio_srst_num 24 38 | #reset_config srst_only srst_push_pull 39 | #reset_config trst_and_srst separate 40 | 41 | # or if you have both connected, 42 | # reset_config trst_and_srst srst_push_pull 43 | adapter_khz 500 44 | adapter_nsrst_delay 400 45 | reset_config none 46 | -------------------------------------------------------------------------------- /assets/adapters/rpi1.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Config for using Raspberry Pi's expansion header 3 | # 4 | # This is best used with a fast enough buffer but also 5 | # is suitable for direct connection if the target voltage 6 | # matches RPi's 3.3V and the cable is short enough. 7 | # 8 | # Do not forget the GND connection, pin 6 of the expansion header. 9 | # 10 | 11 | interface bcm2835gpio 12 | 13 | bcm2835gpio_peripheral_base 0x20000000 14 | 15 | # Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET 16 | # These depend on system clock, calibrated for stock 17 | # Raspi1 BCM2835 (700Mhz) 18 | # bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET 19 | bcm2835gpio_speed_coeffs 113714 28 20 | 21 | # Each of the JTAG lines need a gpio number set: tck tms tdi tdo 22 | # Header pin numbers: 23 22 19 21 23 | bcm2835gpio_jtag_nums 11 25 10 9 24 | 25 | # or if you have both connected, 26 | # reset_config trst_and_srst srst_push_pull 27 | 28 | # Each of the SWD lines need a gpio number set: swclk swdio 29 | # Header pin numbers: 22 18 30 | #bcm2835gpio_swd_nums 25 24 31 | 32 | # If you define trst or srst, use appropriate reset_config 33 | # Header pin numbers: TRST - 26, SRST - 18 34 | 35 | bcm2835gpio_trst_num 8 36 | # reset_config trst_only 37 | 38 | bcm2835gpio_srst_num 24 39 | #reset_config srst_only srst_push_pull 40 | #reset_config trst_and_srst separate 41 | 42 | # or if you have both connected, 43 | # reset_config trst_and_srst srst_push_pull 44 | adapter_khz 500 45 | adapter_nsrst_delay 400 46 | reset_config none 47 | -------------------------------------------------------------------------------- /assets/adapters/rpi2.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Config for using Raspberry Pi's expansion header 3 | # 4 | # This is best used with a fast enough buffer but also 5 | # is suitable for direct connection if the target voltage 6 | # matches RPi's 3.3V and the cable is short enough. 7 | # 8 | # Do not forget the GND connection, pin 6 of the expansion header. 9 | # 10 | 11 | interface bcm2835gpio 12 | 13 | bcm2835gpio_peripheral_base 0x3F000000 14 | 15 | # Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET 16 | # These depend on system clock, calibrated for stock 17 | # Raspi2 BCM2836 (900Mhz): 18 | # bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET 19 | bcm2835gpio_speed_coeffs 146203 36 20 | 21 | # Each of the JTAG lines need a gpio number set: tck tms tdi tdo 22 | # Header pin numbers: 23 22 19 21 23 | bcm2835gpio_jtag_nums 11 25 10 9 24 | 25 | # or if you have both connected, 26 | # reset_config trst_and_srst srst_push_pull 27 | 28 | # Each of the SWD lines need a gpio number set: swclk swdio 29 | # Header pin numbers: 22 18 30 | #bcm2835gpio_swd_nums 25 24 31 | 32 | # If you define trst or srst, use appropriate reset_config 33 | # Header pin numbers: TRST - 26, SRST - 18 34 | 35 | bcm2835gpio_trst_num 8 36 | # reset_config trst_only 37 | 38 | bcm2835gpio_srst_num 24 39 | #reset_config srst_only srst_push_pull 40 | #reset_config trst_and_srst separate 41 | 42 | # or if you have both connected, 43 | # reset_config trst_and_srst srst_push_pull 44 | adapter_khz 500 45 | adapter_nsrst_delay 400 46 | reset_config none 47 | -------------------------------------------------------------------------------- /assets/adapters/rpi3.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Config for using Raspberry Pi's expansion header 3 | # 4 | # This is best used with a fast enough buffer but also 5 | # is suitable for direct connection if the target voltage 6 | # matches RPi's 3.3V and the cable is short enough. 7 | # 8 | # Do not forget the GND connection, pin 6 of the expansion header. 9 | # 10 | 11 | interface bcm2835gpio 12 | 13 | bcm2835gpio_peripheral_base 0x3F000000 14 | 15 | # Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET 16 | # These depend on system clock, calibrated for stock 17 | # Raspi3 BCM2837 (1200Mhz): 18 | # bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET 19 | bcm2835gpio_speed_coeffs 194938 48 20 | 21 | # Each of the JTAG lines need a gpio number set: tck tms tdi tdo 22 | # Header pin numbers: 23 22 19 21 23 | bcm2835gpio_jtag_nums 11 25 10 9 24 | 25 | # or if you have both connected, 26 | # reset_config trst_and_srst srst_push_pull 27 | 28 | # Each of the SWD lines need a gpio number set: swclk swdio 29 | # Header pin numbers: 22 18 30 | #bcm2835gpio_swd_nums 25 24 31 | 32 | # If you define trst or srst, use appropriate reset_config 33 | # Header pin numbers: TRST - 26, SRST - 18 34 | 35 | bcm2835gpio_trst_num 8 36 | # reset_config trst_only 37 | 38 | bcm2835gpio_srst_num 24 39 | #reset_config srst_only srst_push_pull 40 | #reset_config trst_and_srst separate 41 | 42 | # or if you have both connected, 43 | # reset_config trst_and_srst srst_push_pull 44 | adapter_khz 500 45 | adapter_nsrst_delay 400 46 | reset_config none 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | 104 | id_rsa 105 | id_rsa.pub 106 | -------------------------------------------------------------------------------- /payload/patch_toon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | #create urandom which is missing on a 5.0.4 toon in u-booted shell 5 | if [ ! -e /dev/urandom ] ; then 6 | mknod /dev/urandom c 1 9 7 | fi 8 | 9 | if [ $# -eq 0 ]; then 10 | PASS=`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 10` 11 | else 12 | PASS="$1" 13 | fi 14 | 15 | cd `dirname "$0"` 16 | 17 | PASSENC=`/usr/bin/openssl passwd -crypt $PASS 2>/dev/null` 18 | 19 | echo ">>> Enabling root user. Your root password is: $PASS" 20 | if [[ ! -f /etc/passwd.preroot ]]; then 21 | cp /etc/passwd /etc/passwd.preroot 22 | fi 23 | sed -i -e "s#root:[^:]*#root:$PASSENC#" /etc/passwd 24 | 25 | echo ">>> Opening ports 22, 80 and 10080 on firewall" 26 | if [[ ! -f /etc/default/iptables.conf.preroot ]]; then 27 | cp /etc/default/iptables.conf /etc/default/iptables.conf.preroot 28 | fi 29 | sed -i -e "s/^# These are all closed for Quby\/Toon:/# ADDED WHILE ROOTING\n\ 30 | -A HCB-INPUT -p tcp -m tcp --dport 22 --tcp-flags SYN,RST,ACK SYN -j ACCEPT\n\ 31 | -A HCB-INPUT -p tcp -m tcp --dport 10080 --tcp-flags SYN,RST,ACK SYN -j ACCEPT\n\ 32 | -A HCB-INPUT -p tcp -m tcp --dport 80 --tcp-flags SYN,RST,ACK SYN -j ACCEPT\n\ 33 | # END/" /etc/default/iptables.conf 34 | 35 | echo ">>> Disable VPN on boot" 36 | sed -i 's~ovpn:235~#ovpn:235~g' /etc/inittab 37 | 38 | set +e 39 | 40 | echo ">>> Installing dropbear" 41 | opkg install dropbear_2015.71-r0_qb2.ipk 42 | if [[ $? == 255 ]] ; then 43 | set -e 44 | sh /usr/lib/opkg/info/dropbear.postinst 45 | fi 46 | 47 | set +e 48 | echo ">>> Installing openssh-sftp-server" 49 | opkg install openssh-sftp-server_7.3p1-r10.0_qb2.ipk 50 | if [[ $? == 255 ]] ; then 51 | set -e 52 | sh /usr/lib/opkg/info/dropbear.postinst 53 | fi 54 | 55 | set -e 56 | 57 | mkdir -p /root/.ssh/ 58 | # append our key to the authorized keys file 59 | cat id_rsa.pub >> /root/.ssh/authorized_keys 60 | # append a line feed 61 | echo >> /root/.ssh/authorized_keys 62 | 63 | # TODO: remove duplicate lines from authorized_keys. This may do the trick: 64 | # awk '!a[$0]++' /root/.ssh/authorized_keys 65 | -------------------------------------------------------------------------------- /assets/boards/ed20.cfg: -------------------------------------------------------------------------------- 1 | # ed20.cfg 2 | # 3 | # openocd config file for a prodrive ED 2.0 board. 4 | # 5 | # tested with openocd 0.9.0, newer versions probably work, too. 6 | # 7 | # ED 2.0 is a thermostat, better known as Toon in the Netherlands, and Boxx 8 | # in Belgium. 9 | # 10 | # usage: 11 | # 12 | # openocd -f -f ed20.cfg 13 | # 14 | # Tested with the JLink interface, available from antratek.nl (the real thing) 15 | # or from ebay (cheap china copies, equally functional, from $15,-- and up ;-)). 16 | # 17 | # This file has been derived from imx27lnst.cfg, and lowlevel_init.S, from the 18 | # quby u-boot tree (u-boot_no_passwd_R08). 19 | # 20 | source [find target/imx27.cfg] 21 | $_TARGETNAME configure -event gdb-attach { reset init } 22 | $_TARGETNAME configure -event reset-init { ed20_init } 23 | 24 | # set up NAND flash (according to the bootloader code, HWECC is enabled, not 25 | # sure if it actually works, though): 26 | 27 | nand device ed20.nand mxc imx27.cpu mx27 hwecc 28 | #nand device ed20.nand mxc imx27.cpu mx27 noecc 29 | 30 | proc ed20_init { } { 31 | 32 | # reset the board correctly 33 | adapter_khz 500 34 | reset run 35 | reset halt 36 | 37 | # debug leds on 38 | 39 | mww 0x10015500 0x00020000 40 | mww 0x10015504 0x0000c000 41 | mww 0x10015508 0x0000000c 42 | mww 0x10015520 0xff820080 43 | mww 0x1001551C 0x00020080 44 | 45 | sleep 100 46 | 47 | # init aips 48 | 49 | mww 0x10000000 0x20040304 # Peripheral size register 0 50 | mww 0x10020000 0x07FFC200 # Peripheral size register 0 51 | mww 0x10000004 0xDFFBFCFB # Peripheral size register 1 52 | mww 0x10020004 0xFFFFFFFF # Peripheral size register 1 53 | 54 | # init_clocks 55 | 56 | mww 0x10027000 0x43F00204 # Disable SPLL and MPLL 57 | mww 0x10027004 0x00232022 # MPCTL0 58 | mww 0x1002700C 0x0475206F # SPCTL0 59 | mww 0x10027014 0x00003F00 # OSC26MCTL 60 | mww 0x10027018 0x2008E105 # PCDR0 61 | mww 0x1002701C 0x05020510 # PCDR1 62 | mww 0x10027020 0x06060100 # PCCR0 63 | mww 0x10027024 0x814AC508 # PCCR1 64 | mww 0x10027028 0x00000300 # CCSR 65 | mww 0x10027000 0x43F4020F # CSCR 66 | 67 | sleep 10 68 | 69 | # init SDRAM 70 | # ======================================== 71 | # Configure DDR on CSD0 72 | # ======================================== 73 | 74 | mww 0x10027828 0x00000000 # Drive strength DSCR3 (A15 to A0) 75 | mww 0x10027830 0x00000000 # Drive strength DSCR5 (SD15 to SD0) 76 | mww 0x10027834 0x00000000 # Drive strength DSCR6 (SD31 to SD16) 77 | mww 0x10027838 0x00000000 # Drive strength DSCR7 (Group 95 to 81) 78 | mww 0x1002783C 0x00100000 # Drive strength DSCR8 (Group 111 to 97) 79 | mww 0xD8001010 0x00000008 # Delay line soft reset 80 | 81 | sleep 10 82 | 83 | mww 0xD8001010 0x00000004 # Configure ESDMISC register 84 | mww 0xD8001004 0x0079542F # Configure ESDCFG0 register 85 | mww 0xD8001000 0x92220000 # PRECHARGE command 86 | mww 0xA0000F00 0x00000000 # PRECHARGE 87 | mww 0xD8001000 0xA2220000 # Auto refresh command 88 | mww 0xA0000F00 0x00000000 # Auto refresh #1 89 | mww 0xA0000F00 0x00000000 # Auto refresh #2 90 | mww 0xD8001000 0xB2220000 # Load mode register command 91 | mwb 0xA0000033 0x00 # Load mode register (BA0=BA1=0) 92 | mwb 0xA4000060 0x00 # Load extended mode register (BA0=0, BA1=1) 93 | mww 0xD8001000 0x82226080 # Configure ESDCTL0 register 94 | mww 0xD8001020 0x002C0000 # Configure ESDCDLY1 register 95 | mww 0xD8001024 0x002C0000 # Configure ESDCDLY2 register 96 | mww 0xD8001028 0x002C0000 # Configure ESDCDLY3 register 97 | mww 0xD800102C 0x002C0000 # Configure ESDCDLY4 register 98 | 99 | # set ARM mode 100 | 101 | reg r0 0xd3 102 | reg cpsr 0xd3 103 | 104 | # continue with: 105 | # > soft_reset_halt (may not work the first time, iMX 27 processors are a bit quirky 106 | # about this) 107 | # > load_image u-boot.bin 0xa1f00000 108 | # > resume 0xa1f00000 109 | # ... and the system should boot. Interrupt by typing the password when 110 | # prompted. 111 | } 112 | -------------------------------------------------------------------------------- /__main__.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse, os, re 3 | 4 | 5 | supported_jtag_hardware=['auto'] 6 | try: 7 | for file in os.listdir("assets/adapters"): 8 | m=re.match(r"^(.+)\.cfg$", file) 9 | if m: 10 | supported_jtag_hardware.append(m.group(1)) 11 | except: 12 | pass 13 | 14 | 15 | parser = argparse.ArgumentParser(prog='sudo python .', 16 | description='Root your Toon.') 17 | 18 | parser.add_argument('--serial-port', 19 | metavar='PATH', 20 | help='The path of the serial port to use', 21 | default='/dev/serial0') 22 | 23 | parser.add_argument('--ssh-public-key', 24 | metavar='PATH', 25 | help='The path to an RSA public key which should be an allowed key on the Toon after rooting it. If not supplied, a key pair is generated', 26 | default=None) 27 | parser.add_argument('--output-ssh-key', 28 | metavar='PATH', 29 | help='The path to output a generated key pair (the public key will have a .pub suffix). Default is to write id_rsa and id_rsa.pub in the current directory. Ignored if you\'ve used --ssh-public-key', 30 | default="./id_rsa") 31 | parser.add_argument('--private-key-password', 32 | metavar='PASSWORD', 33 | help='The password that should be used to encrypt the private key when it\'s generated. Default is to use no encryption. Ignored if you\'ve used --ssh-public-key', 34 | default=None) 35 | parser.add_argument('--output-level', 36 | metavar='INFO|DEBUG', 37 | help='The level of output to print to the console', 38 | default="INFO") 39 | 40 | parser.add_argument('--jtag-available', action='store_true', help='Indicates you have a JTAG debugger connected to your Toon\'s JTAG headers') 41 | parser.add_argument('--jtag-hardware', 42 | metavar='TYPE', 43 | help='The JTAG debugger type that we\'re working with. The default is to autodetect the JTAG debugger (which currently only works on Raspberry Pi). Supported values are: {}'.format(', '.join(supported_jtag_hardware)), 44 | default="auto") 45 | 46 | parser.add_argument('--dont-check-uboot', action='store_true', help='Don\'t check whether we can access the installer version of U-Boot before using JTAG to start up the custom one.') 47 | parser.add_argument('--dont-cleanup-payload', action='store_true', help='Leave the payload in /payload. Use this if you want to include more files and do something with them.') 48 | parser.add_argument('--dont-reboot-after', action='store_true', help='Don\'t reboot the Toon after rooting it. Use this if you want to use the serial console after rooting') 49 | parser.add_argument('--uboot-only', action='store_true', help='Only boot to the u-boot environment for manual control.') 50 | parser.add_argument('--boot-only', action='store_true', help='Don\'t install the payload, just boot into the serial console') 51 | 52 | 53 | args = parser.parse_args() 54 | 55 | import logging 56 | logging.basicConfig(level={ 57 | "INFO":logging.INFO, 58 | "DEBUG":logging.DEBUG, 59 | }[args.output_level]) 60 | log = logging.getLogger(__name__) 61 | 62 | def get_cpuinfo(): 63 | info = {} 64 | with open('/proc/cpuinfo') as fo: 65 | for line in fo: 66 | name_value = [s.strip() for s in line.split(':', 1)] 67 | if len(name_value) != 2: 68 | continue 69 | name, value = name_value 70 | if name not in info: 71 | info[name]=[] 72 | info[name].append(value) 73 | return info 74 | def find_rpi_version(): 75 | try: 76 | revision = get_cpuinfo()['Revision'][0] 77 | # Source: https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md 78 | return { 79 | "Beta": "rpi1", 80 | "0002": "rpi1", 81 | "0003": "rpi1", 82 | "0004": "rpi1", 83 | "0005": "rpi1", 84 | "0006": "rpi1", 85 | "0007": "rpi1", 86 | "0008": "rpi1", 87 | "0009": "rpi1", 88 | "000d": "rpi1", 89 | "000e": "rpi1", 90 | "000f": "rpi1", 91 | "0010": "rpi1", 92 | "0011": "rpi1", 93 | "0012": "rpi1", 94 | "0013": "rpi1", 95 | "0014": "rpi1", 96 | "0015": "rpi1", 97 | "900021": "rpi1", 98 | "900032": "rpi1", 99 | "900092": "rpi1", 100 | "900093": "rpi1", 101 | "9000c1": "rpi1", 102 | "9020e0": "rpi3", 103 | "920092": "rpi1", 104 | "920093": "rpi1", 105 | "900061": "rpi1", 106 | "a01040": "rpi2", 107 | "a01041": "rpi2", 108 | "a02082": "rpi3", 109 | "a020a0": "rpi3", 110 | "a020d3": "rpi3", 111 | "a02042": "rpi2", 112 | "a21041": "rpi2", 113 | "a22042": "rpi2", 114 | "a22082": "rpi3", 115 | "a220a0": "rpi3", 116 | "a32082": "rpi3", 117 | "a52082": "rpi3", 118 | "a22083": "rpi3", 119 | "a02100": "rpi3", 120 | "a03111": "rpi4", 121 | "b03111": "rpi4", 122 | "c03111": "rpi4", 123 | }[revision] 124 | except: 125 | pass 126 | return None 127 | 128 | def detect_jtag_hardware(): 129 | hardware=find_rpi_version()# or detect_usb_device() or detect_something_else() 130 | #TODO: implement more checks here 131 | if not hardware: 132 | raise Exception("Cannot autodetect jtag hardware") 133 | return hardware 134 | 135 | def main(): 136 | 137 | log.info("Starting up...") 138 | 139 | import rooter 140 | 141 | serial_path = args.serial_port 142 | jtag_available = args.jtag_available 143 | jtag_hardware = args.jtag_hardware 144 | check_current_bootloader = not args.dont_check_uboot 145 | cleanup_payload = not args.dont_cleanup_payload 146 | reboot_after = not args.dont_reboot_after 147 | uboot_only = args.uboot_only 148 | boot_only = args.boot_only 149 | 150 | if jtag_hardware == "auto": 151 | jtag_hardware = detect_jtag_hardware() 152 | log.info("Detected JTAG hardware '{}'".format(jtag_hardware)) 153 | 154 | import sshkeys 155 | if args.ssh_public_key: 156 | with open(args.ssh_public_key, 'r') as f: 157 | ssh_pubkey_data = f.read() 158 | if not sshkeys.check_public_key(ssh_pubkey_data): 159 | raise Exception("RSA key is not valid") 160 | log.info("Using RSA key in {}".format(args.ssh_public_key)) 161 | else: 162 | (pub, priv) = sshkeys.generate_key_pair(args.private_key_password) 163 | ssh_pubkey_data = pub 164 | with open("{}".format(args.output_ssh_key), 'w') as f: 165 | f.write(priv) 166 | with open("{}.pub".format(args.output_ssh_key), 'w') as f: 167 | f.write(pub) 168 | log.info("Written private and public key pair to {0} and {0}.pub, respectively".format(args.output_ssh_key)) 169 | 170 | import json 171 | params = { 172 | "port" : serial_path, 173 | "ssh_pubkey_data" : ssh_pubkey_data, 174 | "has_jtag" : jtag_available, 175 | "check_uboot" : check_current_bootloader, 176 | "cleanup_payload" : cleanup_payload, 177 | "reboot_after" : reboot_after, 178 | "uboot_only" : uboot_only, 179 | "boot_only" : boot_only, 180 | "jtag_hardware" : jtag_hardware 181 | } 182 | log.debug(json.dumps(params)) 183 | rooter.Rooter(**params).run() 184 | 185 | if __name__ == '__main__' : 186 | try: 187 | main() 188 | except Exception as e: 189 | if args.output_level=="DEBUG": 190 | raise 191 | else: 192 | log.fatal(str(e)) 193 | -------------------------------------------------------------------------------- /rooter.py: -------------------------------------------------------------------------------- 1 | import serial 2 | import logging 3 | import re 4 | import telnetlib 5 | import os 6 | import subprocess 7 | import tarfile 8 | import base64 9 | import string 10 | import random 11 | from time import sleep 12 | from serial.serialutil import Timeout 13 | import StringIO 14 | import tempfile 15 | 16 | logging.basicConfig(level=logging.DEBUG) 17 | log = logging.getLogger(__name__) 18 | 19 | class Rooter(object): 20 | def __init__(self, **params): 21 | if type(params['port']) is str: 22 | params['port']=serial.Serial( 23 | port=params['port'], 24 | baudrate=115200 25 | ) 26 | self._port = params['port'] 27 | self._ssh_pubkey_data = params['ssh_pubkey_data'] 28 | self._has_jtag = params['has_jtag'] 29 | self._check_uboot = params['check_uboot'] 30 | self._cleanup_payload = params['cleanup_payload'] 31 | self._reboot_after = params['reboot_after'] 32 | self._uboot_only = params['uboot_only'] 33 | self._boot_only = params['boot_only'] 34 | self._jtag_hardware = params['jtag_hardware'] 35 | 36 | def run(self): 37 | #TODO: clean this up 38 | port = self._port 39 | ssh_pubkey_data = self._ssh_pubkey_data 40 | has_jtag = self._has_jtag 41 | check_uboot = self._check_uboot 42 | cleanup_payload = self._cleanup_payload 43 | reboot_after = self._reboot_after 44 | uboot_only = self._uboot_only 45 | boot_only = self._boot_only 46 | 47 | if check_uboot: 48 | uboot_passwords={ 49 | "2010.09-R6" : "f4E9J", 50 | "2010.09-R8" : "3BHf2", 51 | "2010.09" : "toon" 52 | } 53 | log.info("Waiting for Toon to restart") 54 | uboot_version=self.read_uboot_version() 55 | log.info("Toon has U-Boot version {}".format(uboot_version)) 56 | if uboot_version in uboot_passwords: 57 | log.info("Using password to log in") 58 | self.access_uboot(uboot_passwords[uboot_version]) 59 | if uboot_only: 60 | log.info("Your Toon is now waiting in u-boot. You can you use a serial console for manual control.") 61 | else: 62 | self.patch_uboot() 63 | if boot_only: 64 | log.info("Your Toon is now booting into a serial console") 65 | else: 66 | log.info("Waiting for boot up") 67 | self.write_payload() 68 | self.patch_toon() 69 | log.info("Your Toon is now rooted. Please wait for it to boot up and try to log in using SSH") 70 | return 71 | elif has_jtag is False: 72 | log.error("Unable to log in using password (need JTAG, but it's disabled)") 73 | return 74 | if has_jtag: 75 | log.info("Loading new bootloader") 76 | self.start_bootloader("assets/u-boot.bin") 77 | port.reset_input_buffer() 78 | self._has_jtag = False 79 | self._check_uboot = True 80 | self.run() 81 | else: 82 | log.error("Need JTAG when rooting without manual reset") 83 | return 84 | 85 | def read_uboot_version(self): 86 | version_line_match = re.compile(r'^U-Boot ([^ ]+)') 87 | while True: 88 | line = self._port.readline().strip() 89 | match = version_line_match.match(line) 90 | if match: 91 | return match.group(1) 92 | 93 | def access_uboot(self, password): 94 | log.info("Logging in to U-Boot") 95 | self._port.write(password) 96 | self._port.flush() 97 | log.debug(self._port.read_until("U-Boot>")) 98 | log.debug("Logged in to U-Boot") 99 | 100 | def patch_uboot(self): 101 | port = self._port 102 | 103 | log.info("Patching U-Boot") 104 | port.reset_input_buffer() 105 | sleep(0.1) 106 | port.write("printenv\n") 107 | port.flush() 108 | add_misc_match = re.compile(r'^addmisc=(.+)$') 109 | add_misc_val = None 110 | 111 | sleep(0.5) 112 | 113 | lines = port.read_until("U-Boot>") 114 | log.debug(lines) 115 | for line in lines.split('\n'): 116 | line = line.strip() 117 | log.debug(line) 118 | match = add_misc_match.match(line) 119 | if match: 120 | add_misc_val = match.group(1) 121 | 122 | if add_misc_val is None: 123 | log.error("Could not find value for addmisc environment variable") 124 | return 125 | 126 | cmd = "setenv addmisc " + re.sub(r'([\$;])',r'\\\1', add_misc_val + " init=/bin/sh") 127 | port.write(cmd + "\n") 128 | port.flush() 129 | log.debug(port.read_until("U-Boot>")) 130 | port.write("run boot_nand\n") 131 | port.flush() 132 | 133 | def create_payload_tar(self): 134 | tar_path = tempfile.mkstemp(suffix=".tar.gz")[1] 135 | ssh_key = self._ssh_pubkey_data 136 | with tarfile.open(tar_path, "w:gz") as tar: 137 | tar.add('payload/', arcname='payload') 138 | 139 | ssh_key_str = StringIO.StringIO(ssh_key) 140 | 141 | info = tarfile.TarInfo(name="payload/id_rsa.pub") 142 | info.size=len(ssh_key) 143 | 144 | tar.addfile(tarinfo=info, fileobj=StringIO.StringIO(ssh_key)) 145 | return tar_path 146 | 147 | def write_payload(self): 148 | port = self._port 149 | tar_path = self.create_payload_tar() 150 | 151 | log.debug(port.read_until("/ # ")) 152 | port.write("base64 -d | tar zxf -\n") 153 | port.flush() 154 | #(tarr, tarw) = os.pipe() 155 | #tar = tarfile.open(mode='w|gz', fileobj=tarw) 156 | #tar.add("payload/patch_toon.sh") 157 | 158 | log.info("Transferring payload") 159 | with open(tar_path, 'r') as f: 160 | base64.encode(f, port) 161 | 162 | os.remove(tar_path) 163 | 164 | port.flush() 165 | port.reset_input_buffer() 166 | port.write("\x04") 167 | port.flush() 168 | 169 | def patch_toon(self): 170 | (port, clean_up, reboot) = ( 171 | self._port, self._cleanup_payload, self._reboot_after) 172 | log.info("Patching Toon") 173 | log.debug(port.read_until("/ # ")) 174 | password = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) 175 | port.write("sh payload/patch_toon.sh \"{}\"\n".format(password)) 176 | try: 177 | while True: 178 | line = read_until(port, ["/ # ", "\n"]) 179 | if line == "/ # ": 180 | break 181 | if line.startswith(">>>"): 182 | log.info(line.strip()) 183 | else: 184 | log.debug(line.strip()) 185 | except: 186 | log.exception("Script failed") 187 | sleep(5) 188 | if clean_up: 189 | log.info("Cleaning up") 190 | port.write("rm -r payload\n") 191 | log.debug(port.read_until("/ # ")) 192 | if reboot: 193 | log.info("Rebooting") 194 | port.write("/etc/init.d/reboot\n") 195 | 196 | def start_bootloader(self, bin_path): 197 | 198 | log.info("Starting openocd") 199 | 200 | proc = subprocess.Popen([ 201 | 'openocd', 202 | '-s', '/usr/share/openocd', 203 | '-f', 'assets/adapters/{}.cfg'.format(self._jtag_hardware), 204 | '-f', 'assets/boards/ed20.cfg' 205 | ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 206 | 207 | try: 208 | wait = 10 209 | log.info("Waiting for {} seconds".format(wait)) 210 | sleep(wait) 211 | client = telnetlib.Telnet('localhost', 4444) 212 | log.debug(client.read_until("> ")) 213 | log.info("Halting CPU") 214 | client.write("soft_reset_halt\n") 215 | log.debug(client.read_until("> ")) 216 | sleep(0.1) 217 | client.write("reset halt\n") 218 | log.debug(client.read_until("> ")) 219 | sleep(0.1) 220 | log.info("Loading new image to RAM") 221 | client.write("load_image {} 0xa1f00000\n".format(bin_path)) 222 | log.debug(client.read_until("> ")) 223 | sleep(0.1) 224 | log.info("Starting up new image") 225 | client.write("resume 0xa1f00000\n") 226 | except: 227 | try: 228 | log.exception(proc.communicate()[0]) 229 | except: 230 | pass 231 | proc.terminate() 232 | raise 233 | 234 | proc.terminate() 235 | 236 | def read_until(port, terminators=None, size=None): 237 | """\ 238 | Read until any of the termination sequences is found ('\n' by default), the size 239 | is exceeded or until timeout occurs. 240 | """ 241 | if not terminators: 242 | terminators = ['\n'] 243 | terms = map(lambda t: (t, len(t)), terminators) 244 | line = bytearray() 245 | timeout = Timeout(port._timeout) 246 | while True: 247 | c = port.read(1) 248 | if c: 249 | line += c 250 | for (terminator, lenterm) in terms: 251 | if line[-lenterm:] == terminator: 252 | # break does not work here because it will only step out of for 253 | return bytes(line) 254 | if size is not None and len(line) >= size: 255 | break 256 | else: 257 | break 258 | if timeout.expired(): 259 | break 260 | return bytes(line) 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ToonRooter 2 | 3 | ## What it does 4 | This application roots your Toon from a Raspberry Pi or another device with a JTAG debugger attached. 5 | 6 | At the moment, the following is implemented: 7 | - Detection of the U-Boot version 8 | - Loading of modified U-Boot version using JTAG 9 | - Logging in to supported versions of U-Boot 10 | - Setting up the U-Boot environment so the Toon boots into a serial console 11 | - Install and set up dropbear and sftp-server to let root user log in using an ssh key 12 | - Modify the firewall settings to enable remote access to ssh and http server 13 | - Generate SSH keys or let the user supply their own 14 | 15 | ### What it might do in the more distant future 16 | - Check the output from a few commands so we know if we're successful... 17 | - More fine-grained control of the installation package 18 | 19 | ## How safe is it? 20 | 21 | I don't know. 22 | 23 | You'll probably be fine. We're not overwriting the bootloader or important parts 24 | of the OS, so I don't see it really breaking anything, but you should make sure all wires are connected 25 | correctly, because otherwise you might short out something and break your Pi, Toon or both. If you are 26 | having issues with your Toon, run this application again with the `--boot-only` flag set. When it's finished 27 | you should be able to access a serial console on the Toon and check out what's wrong. To find some 28 | 'usual suspects', look through `payload/patch_toon.sh`, which is the script that's actually run on the Toon 29 | to enable SSH access. The rest of the application basically just opens an injection vector to make that 30 | possible. 31 | 32 | ## I have a Toon 2 ## 33 | The new Toon 2 isn't rootable using this tool. There are just a few people who can help you root a toon 2. 34 | You can take a look at this forum post if you want to have your Toon 2 rooted: https://www.domoticaforum.eu/viewtopic.php?f=101&t=12162 35 | 36 | ## How to use it? 37 | 38 | Make sure there's no power going in to either of the devices, and double check the connections 39 | before powering up again. 40 | Connect your Toon's debugging header to a Raspberry Pi according to the following pin assignments: 41 | 42 | | Toon | Signal | Pi | 43 | |:----:|:------:|:----:| 44 | | 1 | RTCK | | 45 | | 2 | TRST | 24 | 46 | | 3 | GND | 25 | 47 | | 4 | TCK | 23 | 48 | | 5 | GND | 20 | 49 | | 6 | TMS | 22 | 50 | | 7 | SRST | 18 | 51 | | 8 | TDI | 19 | 52 | | 9 | Vt | | 53 | | 10 | TDO | 21 | 54 | | 11 | RxD | 8 | 55 | | 12 | | | 56 | | 13 | TxD | 10 | 57 | | 14 | GND | 9 | 58 | 59 | If you don't know which pins on the Pi are which, see [pinout.xyz](https://pinout.xyz). The pins on the Toon are numbered similarly to the Pi, starting at the one marked as 1. 60 | 61 | Check if you're running Raspbian Stretch or later. You can check this by running 62 | `cat /etc/issue`, the response should be `Raspbian GNU/Linux 9 \n \l` or later. 63 | Then make sure the serial port on the Pi is enabled and the serial console is disabled 64 | using `sudo raspi-config` and reboot if necessary. Install the dependencies mentioned in the 65 | [Dependencies](#dependencies)-section. 66 | 67 | Then get and run this application: 68 | ```bash 69 | sudo apt install python-serial python-cryptography 70 | git clone https://github.com/martenjacobs/ToonRooter.git 71 | cd ToonRooter 72 | sudo python . --jtag-available 73 | ``` 74 | 75 | Then reset your Toon and let the magic happen :) 76 | 77 | ## Logging in to the Toon after rooting 78 | 79 | During the rooting process, the script will generate a random root password and output it to 80 | the console. It will also generate an ssh private key. You can use either to log in to the 81 | Toon after it is rooted. The script does not use a generic default password for all Toons 82 | because it's considered a security hazard (and there's no easy way to change the password on 83 | the Toon itself). 84 | 85 | The most secure way of logging in is using the SSH private key (even better if you encrypted 86 | the key using the `--private-key-password` argument). 87 | 88 | After rooting, a file called `id_rsa` should be present in your working directory (or something 89 | else if you used `--output-ssh-key`). 90 | Logging in to the Toon can then be done by running: 91 | ```bash 92 | ssh -i id_rsa root@[TOON IP] 93 | ``` 94 | If you used the `--private-key-password` argument, you will now be asked to insert the password 95 | you provided there. If you didn't, it should log in without asking for a password at all. 96 | 97 | In the latter case, please consider encrypting it later using the following command: 98 | ```bash 99 | ssh-keygen -p -f id_rsa 100 | ``` 101 | 102 | If for some reason you lost both the ssh private key and the password, you'll have to 103 | re-root your Toon. 104 | 105 | ## It's not working! 106 | Please re-check your wiring. If you're sure the wiring is correct, try the command with 107 | the `--output-level DEBUG` flag set and head over to 108 | [this friendly forum](https://www.domoticaforum.eu/viewtopic.php?f=101&t=11999) where the 109 | issue has most likely already been solved. If not, post a reply and the active community 110 | will probably help you out. 111 | 112 | ## But I don't have a Pi 113 | 114 | You should definitely get a Pi. 115 | 116 | However, if you're adamant that you want to root your Toon from another device and 117 | you have a JTAG debugger lying around that works with OpenOCD, you should be able to 118 | use this script without issue. Just put the configuration file for your debugger in the 119 | `assets/adapters` directory (make sure it has a `.cfg` extension) and pass the name 120 | of the file (without extension) to the script using the `--jtag-hardware` argument. 121 | I'm pretty sure Windows is not going to work though, so you should use a Linux 122 | (virtual) machine. 123 | 124 | ## Command line arguments 125 | 126 | ``` 127 | usage: sudo python . [-h] [--serial-port PATH] [--ssh-public-key PATH] 128 | [--output-ssh-key PATH] [--private-key-password PASSWORD] 129 | [--output-level INFO|DEBUG] [--jtag-available] 130 | [--dont-check-uboot] [--dont-cleanup-payload] 131 | [--dont-reboot-after] [--boot-only] 132 | 133 | Root your Toon. 134 | 135 | optional arguments: 136 | -h, --help show this help message and exit 137 | --serial-port PATH The path of the serial port to use 138 | --ssh-public-key PATH 139 | The path to an RSA public key which should be an 140 | allowed key on the Toon after rooting it. If not 141 | supplied, a key pair is generated 142 | --output-ssh-key PATH 143 | The path to output a generated key pair (the public 144 | key will have a .pub suffix). Default is to write 145 | id_rsa and id_rsa.pub in the current directory. 146 | Ignored if you've used --ssh-public-key 147 | --private-key-password PASSWORD 148 | The password that should be used to encrypt the 149 | private key when it's generated. Default is to use no 150 | encryption. Ignored if you've used --ssh-public-key 151 | --output-level INFO|DEBUG 152 | The level of output to print to the console 153 | --jtag-available Indicates you have a JTAG debugger connected to your 154 | Toon's JTAG headers 155 | --jtag-hardware TYPE The JTAG debugger type that we're working with. The 156 | default is to autodetect the JTAG debugger (which 157 | currently only works on Raspberry Pi). Supported 158 | values are: auto, rpi1, rpi2, rpi3 159 | --dont-check-uboot Don't check whether we can access the installer 160 | version of U-Boot before using JTAG to start up the 161 | custom one. 162 | --dont-cleanup-payload 163 | Leave the payload in /payload. Use this if you want to 164 | include more files and do something with them. 165 | --dont-reboot-after Don't reboot the Toon after rooting it. Use this if 166 | you want to use the serial console after rooting 167 | --uboot-only Only boot to the u-boot environment for manual control 168 | --boot-only Don't install the payload, just boot into the serial 169 | console 170 | ``` 171 | 172 | ## Dependencies 173 | 174 | - Raspbian Stretch (or later) if you're using a Pi 175 | - Python 2.7 176 | - pySerial 3.4 (or later) 177 | - OpenOCD from git (for newer Toons) (see [instructions](#install-openocd)) 178 | 179 | ## Install OpenOCD 180 | If your Toon has a newer U-Boot version than 2010-R8, a JTAG interface is required to 181 | upload a bootloader that we have access to through the serial console. To do this, 182 | you need to build a version of OpenOCD (at the time of writing the version in apt 183 | doesn't support using the Pi's headers as JTAG debugger). 184 | 185 | ```bash 186 | git clone --recursive git://git.code.sf.net/p/openocd/code openocd 187 | cd openocd 188 | sudo apt install make libtool libtool-bin pkg-config autoconf automake texinfo libusb-1.0 libusb-dev 189 | { 190 | ./bootstrap &&\ 191 | ./configure --enable-sysfsgpio\ 192 | --enable-bcm2835gpio \ 193 | --prefix=/usr\ 194 | &&\ 195 | make -j4 196 | } 2>&1 | tee openocd_build.log 197 | sudo make install 198 | ``` 199 | > these instructions were based on the instructions posted [here](https://www.domoticaforum.eu/viewtopic.php?f=87&t=11230&start=210#p83745) by rboers 200 | 201 | ## Thanks 202 | This application is based on instructions and software written by: 203 | - marcelr 204 | - klaphekje 205 | - rboers 206 | - other [domoticaforum.eu](https://www.domoticaforum.eu/viewtopic.php?f=101) users 207 | --------------------------------------------------------------------------------