├── LICENSE ├── README.md ├── fat.config ├── fat.py ├── qemu-builds └── README.txt ├── reset.py └── setup.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Attify, Inc. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firmware Analysis Toolkit 2 | 3 | FAT is a toolkit built in order to help security researchers analyze and identify vulnerabilities in IoT and embedded device firmware. This is also used in the "*[Offensive IoT Exploitation](https://www.attify-store.com/collections/training/products/offensive-iot-exploitation)*" training conducted by [Attify](https://attify.com). 4 | 5 | [![](https://i.ibb.co/zP9htYK/offensive-iot-exploitation-attify-embedded-hacking.png)](https://www.attify-store.com/collections/training/products/offensive-iot-exploitation) 6 | 7 | **Note:** 8 | 9 | + As of now, it is simply a script to automate **[Firmadyne](https://github.com/firmadyne/firmadyne)** which is a tool used for firmware emulation. In case of any issues with the actual emulation, please post your issues in the [firmadyne issues](https://github.com/firmadyne/firmadyne/issues).   10 | 11 | + In case you are facing issues, you can try [AttifyOS](https://github.com/adi0x90/attifyos) which has Firmware analysis toolkit and other tools pre-installed and ready to use. 12 | 13 | --- 14 | 15 | Firmware Analysis Toolkit (FAT henceforth) is based on Firmadyne with some changes. Firmadyne uses a PostgreSQL database to store information about the emulated images. However just for the core functionality i.e. emulating firmware, PostgreSQL is not really needed. Hence FAT doesn't use it. 16 | 17 | ## Setup instructions 18 | 19 | FAT is developed in Python 3. However you need to have both Python 3 and Python 2 installed since parts of Firmadyne and its dependencies use Python 2. It's highly recommended to install FAT inside a Virtual Machine. 20 | 21 | To install just clone the repository and run the script `./setup.sh`. 22 | 23 | ``` 24 | git clone https://github.com/attify/firmware-analysis-toolkit 25 | cd firmware-analysis-toolkit 26 | ./setup.sh 27 | ``` 28 | 29 | After installation is completed, edit the file `fat.config` and provide the sudo password as shown below. Firmadyne requires sudo privileges for some of its operations. The sudo password is provided to automate the process. 30 | 31 | ``` 32 | [DEFAULT] 33 | sudo_password=attify123 34 | firmadyne_path=/home/attify/firmadyne 35 | ``` 36 | 37 | ## Running FAT 38 | 39 | ``` 40 | $ ./fat.py 41 | ``` 42 | 43 | + Provide the firmware filename as an argument to the script. 44 | 45 | + The script would display the IP addresses assigned to the created network interfaces. Note it down. 46 | 47 | + Finally, it will say that running the firmware. Hit ENTER and wait until the firmware boots up. Ping the IP which was shown in the previous step, or open in the browser. 48 | 49 | ***Congrats! The firmware is finally emulated.*** 50 | 51 | To remove all analyzed firmware images, run 52 | 53 | ``` 54 | $ ./reset.py 55 | ``` 56 | 57 | ## Example 1 58 | 59 | ``` 60 | $ ./fat.py DIR-601_REVB_FIRMWARE_2.01.BIN 61 | 62 | __ _ 63 | / _| | | 64 | | |_ __ _ | |_ 65 | | _| / _` | | __| 66 | | | | (_| | | |_ 67 | |_| \__,_| \__| 68 | 69 | Welcome to the Firmware Analysis Toolkit - v0.3 70 | Offensive IoT Exploitation Training http://bit.do/offensiveiotexploitation 71 | By Attify - https://attify.com | @attifyme 72 | 73 | [+] Firmware: DIR-601_REVB_FIRMWARE_2.01.BIN 74 | [+] Extracting the firmware... 75 | [+] Image ID: 1 76 | [+] Identifying architecture... 77 | [+] Architecture: mipseb 78 | [+] Building QEMU disk image... 79 | [+] Setting up the network connection, please standby... 80 | [+] Network interfaces: [('br0', '192.168.0.1')] 81 | [+] All set! Press ENTER to run the firmware... 82 | [+] When running, press Ctrl + A X to terminate qemu 83 | ``` 84 | 85 | **Asciicast** 86 | 87 | [![asciicast](https://asciinema.org/a/5VryIC2ec1j9SEIfGQ0qAWjoH.svg)](https://asciinema.org/a/5VryIC2ec1j9SEIfGQ0qAWjoH) 88 | 89 | ## Example 2 90 | 91 | ``` 92 | $ ./fat.py DIR890A1_FW103b07.bin --qemu 2.5.0 93 | 94 | __ _ 95 | / _| | | 96 | | |_ __ _ | |_ 97 | | _| / _` | | __| 98 | | | | (_| | | |_ 99 | |_| \__,_| \__| 100 | 101 | Welcome to the Firmware Analysis Toolkit - v0.3 102 | Offensive IoT Exploitation Training http://bit.do/offensiveiotexploitation 103 | By Attify - https://attify.com | @attifyme 104 | 105 | [+] Firmware: DIR890A1_FW103b07.bin 106 | [+] Extracting the firmware... 107 | [+] Image ID: 2 108 | [+] Identifying architecture... 109 | [+] Architecture: armel 110 | [+] Building QEMU disk image... 111 | [+] Setting up the network connection, please standby... 112 | [+] Network interfaces: [('br0', '192.168.0.1'), ('br1', '192.168.7.1')] 113 | [+] Using qemu-system-arm from /home/attify/firmware-analysis-toolkit/qemu-builds/2.5.0 114 | [+] All set! Press ENTER to run the firmware... 115 | [+] When running, press Ctrl + A X to terminate qemu 116 | 117 | ``` 118 | ## Additional Notes 119 | 120 | - As of now, the [ARM firmadyne kernel](https://github.com/firmadyne/kernel-v4.1) doesn't work with the latest version of Qemu (2.11.1) available on the Ubuntu 18.04 official repository. However, Qemu (2.5.0) on Ubuntu 16.04 does work. Alternatively you can also use the bundled Qemu (2.5.0) provided with firmadyne as shown in example 2. 121 | 122 | - If no network interfaces are detected, try increasing the timeout value from 60 in `scripts/inferNetwork.sh` as shown below 123 | ``` 124 | echo "Running firmware ${IID}: terminating after 60 secs..." 125 | timeout --preserve-status --signal SIGINT 60 "${SCRIPT_DIR}/run.${ARCH}.sh" "${IID}" 126 | ``` 127 | 128 | ## Known Issues 129 | 130 | - ~~FAT does not work on Ubuntu 20.04. The main reason behind this is some dependencies of Firmadyne (especially binwalk) require Python 2. Unless this is fixed upstream, stick to Ubuntu 18.04 or lower.~~ 131 | Ubuntu 20.04 **is now supported**. The current version of FAT patches the binwalk installation script to workaround the issue. 132 | 133 | ## Building static Qemu 134 | 135 | The repository already includes a static builds of qemu 2.0.0, 2.5.0 & 3.0.0 (in releases) but if you want to build your own follow the steps below. 136 | 137 | On a clean **Ubuntu 16.04** VM run. (It's important to use 16.04, later versions have issues with static compilation). 138 | 139 | ### Qemu 2.0.0 140 | 141 | ```sh 142 | sudo apt update && sudo apt build-dep qemu -y 143 | wget https://download.qemu.org/qemu-2.0.0.tar.bz2 144 | tar xf qemu-2.0.0.tar.bz2 145 | mkdir qemu-2.0.0-build 146 | cd qemu-2.0.0 147 | ./configure --prefix=$(realpath ../qemu-2.0.0-build) --static --target-list=arm-softmmu,mips-softmmu,mipsel-softmmu --disable-smartcard-nss --disable-spice --disable-libusb --disable-usb-redir 148 | make 149 | make install 150 | ``` 151 | The compiled binaries can be found in `qemu-2.0.0-build` directory. 152 | 153 | ### Qemu 2.5.0 154 | 155 | ```sh 156 | sudo apt update && sudo apt build-dep qemu -y 157 | wget https://download.qemu.org/qemu-2.5.0.tar.bz2 158 | tar xf qemu-2.5.0.tar.bz2 159 | mkdir qemu-2.5.0-build 160 | cd qemu-2.5.0 161 | ./configure --prefix=$(realpath ../qemu-2.5.0-build) --static --target-list=arm-softmmu,mips-softmmu,mipsel-softmmu --disable-smartcard --disable-libusb --disable-usb-redir 162 | make 163 | make install 164 | ``` 165 | The compiled binaries can be found in `qemu-2.5.0-build` directory. 166 | 167 | ### Qemu 3.0.0 168 | 169 | ```sh 170 | sudo apt update && sudo apt build-dep qemu -y 171 | wget https://download.qemu.org/qemu-3.0.0.tar.bz2 172 | tar xf qemu-3.0.0.tar.bz2 173 | mkdir qemu-3.0.0-build 174 | cd qemu-3.0.0 175 | ./configure --prefix=$(realpath ../qemu-3.0.0-build) --static --target-list=arm-softmmu,mips-softmmu,mipsel-softmmu --disable-smartcard --disable-libusb --disable-usb-redir 176 | make 177 | make install 178 | ``` 179 | The compiled binaries can be found in `qemu-3.0.0-build` directory. 180 | 181 | 182 | Note: It should also be possible to compile qemu statically on an alpine system but this hasn't been tested. In general compiling on alpine is preferred to Ubuntu as the former comes with musl libc which is better at static linkage than glibc on Ubuntu. 183 | -------------------------------------------------------------------------------- /fat.config: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | sudo_password=attify123 3 | firmadyne_path=/home/attify/firmware-analysis-toolkit/firmadyne 4 | -------------------------------------------------------------------------------- /fat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import os.path 5 | import pexpect 6 | import sys 7 | import argparse 8 | 9 | from configparser import ConfigParser 10 | 11 | config = ConfigParser() 12 | config.read("fat.config") 13 | firmadyne_path = config["DEFAULT"].get("firmadyne_path", "") 14 | sudo_pass = config["DEFAULT"].get("sudo_password", "") 15 | 16 | 17 | def show_banner(): 18 | print (""" 19 | __ _ 20 | / _| | | 21 | | |_ __ _ | |_ 22 | | _| / _` | | __| 23 | | | | (_| | | |_ 24 | |_| \__,_| \__| 25 | 26 | Welcome to the Firmware Analysis Toolkit - v0.3 27 | Offensive IoT Exploitation Training http://bit.do/offensiveiotexploitation 28 | By Attify - https://attify.com | @attifyme 29 | """) 30 | 31 | 32 | def get_next_unused_iid(): 33 | for i in range(1, 1000): 34 | if not os.path.isdir(os.path.join(firmadyne_path, "scratch", str(i))): 35 | return str(i) 36 | return "" 37 | 38 | 39 | def run_extractor(firm_name): 40 | print ("[+] Firmware:", os.path.basename(firm_name)) 41 | print ("[+] Extracting the firmware...") 42 | 43 | extractor_cmd = os.path.join(firmadyne_path, "sources/extractor/extractor.py") 44 | extractor_args = [ 45 | "--", 46 | extractor_cmd, 47 | "-np", 48 | "-nk", 49 | firm_name, 50 | os.path.join(firmadyne_path, "images") 51 | ] 52 | child = pexpect.spawn("sudo", extractor_args, timeout=None) 53 | child.sendline(sudo_pass) 54 | child.expect_exact("Tag: ") 55 | tag = child.readline().strip().decode("utf8") 56 | child.expect_exact(pexpect.EOF) 57 | 58 | image_tgz = os.path.join(firmadyne_path, "images", tag + ".tar.gz") 59 | 60 | if os.path.isfile(image_tgz): 61 | iid = get_next_unused_iid() 62 | if iid == "" or os.path.isfile(os.path.join(os.path.dirname(image_tgz), iid + ".tar.gz")): 63 | print ("[!] Too many stale images") 64 | print ("[!] Please run reset.py or manually delete the contents of the scratch/ and images/ directory") 65 | return "" 66 | 67 | os.rename(image_tgz, os.path.join(os.path.dirname(image_tgz), iid + ".tar.gz")) 68 | print ("[+] Image ID:", iid) 69 | return iid 70 | 71 | return "" 72 | 73 | 74 | def identify_arch(image_id): 75 | print ("[+] Identifying architecture...") 76 | identfy_arch_cmd = os.path.join(firmadyne_path, "scripts/getArch.sh") 77 | identfy_arch_args = [ 78 | os.path.join(firmadyne_path, "images", image_id + ".tar.gz") 79 | ] 80 | child = pexpect.spawn(identfy_arch_cmd, identfy_arch_args, cwd=firmadyne_path) 81 | child.expect_exact(":") 82 | arch = child.readline().strip().decode("utf8") 83 | print ("[+] Architecture: " + arch) 84 | try: 85 | child.expect_exact(pexpect.EOF) 86 | except Exception as e: 87 | child.close(force=True) 88 | 89 | return arch 90 | 91 | 92 | def make_image(arch, image_id): 93 | print ("[+] Building QEMU disk image...") 94 | makeimage_cmd = os.path.join(firmadyne_path, "scripts/makeImage.sh") 95 | makeimage_args = ["--", makeimage_cmd, image_id, arch] 96 | child = pexpect.spawn("sudo", makeimage_args, cwd=firmadyne_path) 97 | child.sendline(sudo_pass) 98 | child.expect_exact(pexpect.EOF) 99 | 100 | 101 | def infer_network(arch, image_id, qemu_dir): 102 | print ("[+] Setting up the network connection, please standby...") 103 | network_cmd = os.path.join(firmadyne_path, "scripts/inferNetwork.sh") 104 | network_args = [image_id, arch] 105 | 106 | if qemu_dir: 107 | path = os.environ["PATH"] 108 | newpath = qemu_dir + ":" + path 109 | child = pexpect.spawn(network_cmd, network_args, cwd=firmadyne_path, env={"PATH":newpath}) 110 | else: 111 | child = pexpect.spawn(network_cmd, network_args, cwd=firmadyne_path) 112 | 113 | child.expect_exact("Interfaces:", timeout=None) 114 | interfaces = child.readline().strip().decode("utf8") 115 | print ("[+] Network interfaces:", interfaces) 116 | child.expect_exact(pexpect.EOF) 117 | 118 | 119 | def final_run(image_id, arch, qemu_dir): 120 | runsh_path = os.path.join(firmadyne_path, "scratch", image_id, "run.sh") 121 | if not os.path.isfile(runsh_path): 122 | print ("[!] Cannot emulate firmware, run.sh not generated") 123 | return 124 | 125 | if qemu_dir: 126 | if arch == "armel": 127 | arch = "arm" 128 | elif arch == "mipseb": 129 | arch = "mips" 130 | print ("[+] Using qemu-system-{0} from {1}".format(arch, qemu_dir)) 131 | cmd = 'sed -i "/QEMU=/c\QEMU={0}/qemu-system-{1}" "{2}"'.format(qemu_dir, arch, runsh_path) 132 | pexpect.run(cmd) 133 | 134 | print ("[+] All set! Press ENTER to run the firmware...") 135 | input ("[+] When running, press Ctrl + A X to terminate qemu") 136 | 137 | print ("[+] Command line:", runsh_path) 138 | run_cmd = ["--", runsh_path] 139 | child = pexpect.spawn("sudo", run_cmd, cwd=firmadyne_path) 140 | child.sendline(sudo_pass) 141 | child.interact() 142 | 143 | 144 | def main(): 145 | show_banner() 146 | parser = argparse.ArgumentParser() 147 | parser.add_argument("firm_path", help="The path to the firmware image", type=str) 148 | parser.add_argument("-q", "--qemu", metavar="qemu_path", help="The qemu version to use (must exist within qemu-builds directory). If not specified, the qemu version installed system-wide will be used", type=str) 149 | args = parser.parse_args() 150 | 151 | qemu_ver = args.qemu 152 | qemu_dir = None 153 | if qemu_ver: 154 | qemu_dir = os.path.abspath(os.path.join("qemu-builds", qemu_ver)) 155 | if not os.path.isdir(qemu_dir): 156 | print ("[!] Directory {0} not found".format(qemu_dir)) 157 | print ("[+] Using system qemu") 158 | qemu_dir = None 159 | 160 | image_id = run_extractor(args.firm_path) 161 | 162 | if image_id == "": 163 | print ("[!] Image extraction failed") 164 | else: 165 | arch = identify_arch(image_id) 166 | make_image(arch, image_id) 167 | infer_network(arch, image_id, qemu_dir) 168 | final_run(image_id, arch, qemu_dir) 169 | 170 | 171 | if __name__ == "__main__": 172 | main() 173 | -------------------------------------------------------------------------------- /qemu-builds/README.txt: -------------------------------------------------------------------------------- 1 | This directory is intended to contain statically compiled qemu binaries with each version in its own folder. 2 | -------------------------------------------------------------------------------- /reset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pexpect 4 | import os.path 5 | from configparser import ConfigParser 6 | 7 | config = ConfigParser() 8 | config.read("fat.config") 9 | firmadyne_path = config["DEFAULT"].get("firmadyne_path", "") 10 | sudo_pass = config["DEFAULT"].get("sudo_password", "") 11 | 12 | print ("[+] Cleaning previous images and created files by firmadyne") 13 | child = pexpect.spawn("/bin/sh" , ["-c", "sudo rm -rf " + os.path.join(firmadyne_path, "images/*.tar.gz")]) 14 | child.sendline(sudo_pass) 15 | child.expect_exact(pexpect.EOF) 16 | 17 | child = pexpect.spawn("/bin/sh", ["-c", "sudo rm -rf " + os.path.join(firmadyne_path, "scratch/*")]) 18 | child.sendline(sudo_pass) 19 | child.expect_exact(pexpect.EOF) 20 | print ("[+] All done. Go ahead and run fat.py to continue firmware analysis") 21 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | sudo apt update 5 | sudo apt install -y python3-pip python3-pexpect unzip busybox-static fakeroot kpartx snmp uml-utilities util-linux vlan qemu-system-arm qemu-system-mips qemu-system-x86 qemu-utils wget tar 6 | 7 | if [ ! -x "$(which lsb_release)" ] 8 | then 9 | sudo apt install -y lsb-core 10 | fi 11 | 12 | echo "Installing binwalk" 13 | git clone --depth=1 https://github.com/ReFirmLabs/binwalk.git 14 | cd binwalk 15 | 16 | # Temporary fix for sasquatch failing to install (From https://github.com/ReFirmLabs/binwalk/pull/601) 17 | sed -i 's;\$SUDO ./build.sh;wget https://github.com/devttys0/sasquatch/pull/47.patch \&\& patch -p1 < 47.patch \&\& \$SUDO ./build.sh;' deps.sh 18 | 19 | # Change to python3 in deps.sh to allow installation on Ubuntu 20.04 (binwalk commit 2b78673) 20 | sed -i '/REQUIRED_UTILS="wget tar python"/c\REQUIRED_UTILS="wget tar python3"' deps.sh 21 | 22 | # Fix for ubi_reader change of branch name + switching to poetry 23 | wget https://github.com/ReFirmLabs/binwalk/pull/639.patch && patch -p1 < 639.patch && rm 639.patch 24 | 25 | # Required as we are not installing ubi_reader using poetry 26 | pip install lzallright 27 | 28 | sudo ./deps.sh --yes 29 | sudo python3 ./setup.py install 30 | sudo -H pip3 install git+https://github.com/ahupp/python-magic 31 | sudo -H pip3 install git+https://github.com/sviehb/jefferson 32 | cd .. 33 | 34 | echo "Installing firmadyne" 35 | # tested with firmadyne commit bcd8bc0 36 | git clone --recursive https://github.com/firmadyne/firmadyne.git 37 | cd firmadyne 38 | ./download.sh 39 | firmadyne_dir=$(realpath .) 40 | 41 | # Set FIRMWARE_DIR in firmadyne.config 42 | sed -i "/FIRMWARE_DIR=/c\FIRMWARE_DIR=$firmadyne_dir" firmadyne.config 43 | 44 | # Comment out psql -d firmware ... in getArch.sh 45 | sed -i 's/psql/#psql/' ./scripts/getArch.sh 46 | 47 | # Change interpreter to python3 48 | sed -i 's/env python/env python3/' ./sources/extractor/extractor.py ./scripts/makeNetwork.py 49 | cd .. 50 | 51 | echo "Setting up firmware analysis toolkit" 52 | chmod +x fat.py 53 | chmod +x reset.py 54 | 55 | # Set firmadyne_path in fat.config 56 | sed -i "/firmadyne_path=/c\firmadyne_path=$firmadyne_dir" fat.config 57 | 58 | cd qemu-builds 59 | 60 | wget -O qemu-system-static-2.0.0.zip "https://github.com/attify/firmware-analysis-toolkit/files/9937453/qemu-system-static-2.0.0.zip" 61 | unzip -qq qemu-system-static-2.0.0.zip && rm qemu-system-static-2.0.0.zip 62 | 63 | wget -O qemu-system-static-2.5.0.zip "https://github.com/attify/firmware-analysis-toolkit/files/4244529/qemu-system-static-2.5.0.zip" 64 | unzip -qq qemu-system-static-2.5.0.zip && rm qemu-system-static-2.5.0.zip 65 | 66 | wget -O qemu-system-static-3.0.0.tar.gz "https://github.com/attify/firmware-analysis-toolkit/files/9937487/qemu-system-static-3.0.0.tar.gz" 67 | tar xf qemu-system-static-3.0.0.tar.gz && rm qemu-system-static-3.0.0.tar.gz 68 | 69 | cd .. 70 | 71 | echo "=====================================================" 72 | echo "Firmware Analysis Toolkit installed successfully!" 73 | echo "Before running fat.py for the first time," 74 | echo "please edit fat.config and provide your sudo password" 75 | echo "=====================================================" 76 | --------------------------------------------------------------------------------