diff --git a/README.md b/README.md index 761c5d7..5b6a163 100644 --- a/README.md +++ b/README.md @@ -148,4 +148,22 @@ make install ``` The compiled binaries can be found in `qemu-build` directory. +On a clean **Archlinux** VM run + +``` +sudo pacman -Syu +sudo pacman -S qemu-emulators-full ninja base-devel git #qemu-full will install all the archs + +# the above will setup all the configures for archs but just for fun here + +git clone --depth 1 https://github.com/qemu/QEMU +cd qemu +mkdir build +cd build +./configure --prefix=$(realpath ../qemu-build) --static --target-list=arm-softmmu,mips-softmmu,mipsel-softmmu --disable-smartcard --disable-libusb --disable-usb-redir +make +sudo make install +``` +Note: qemu-emulators-full package provides all the archs + 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. diff --git a/fat.py b/fat.py index 90ed99d..ffa95f2 100755 --- a/fat.py +++ b/fat.py @@ -30,59 +30,84 @@ def show_banner(): def get_next_unused_iid(): - for i in range(1, 1000): - if not os.path.isdir(os.path.join(firmadyne_path, "scratch", str(i))): - return str(i) + + for index in range(1, 1000): + + if not os.path.isdir(os.path.join(firmadyne_path, "scratch", str(index))): + + return str(index) + return "" def run_extractor(firm_name): + print ("[+] Firmware:", os.path.basename(firm_name)) + print ("[+] Extracting the firmware...") - extractor_cmd = os.path.join(firmadyne_path, "sources/extractor/extractor.py") + extractor_command = os.path.join(firmadyne_path, "sources/extractor/extractor.py") extractor_args = [ "--", - extractor_cmd, + extractor_command, "-np", "-nk", firm_name, os.path.join(firmadyne_path, "images") ] + child = pexpect.spawn("sudo", extractor_args, timeout=None) + child.sendline(sudo_pass) + child.expect_exact("Tag: ") + tag = child.readline().strip().decode("utf8") + child.expect_exact(pexpect.EOF) image_tgz = os.path.join(firmadyne_path, "images", tag + ".tar.gz") if os.path.isfile(image_tgz): + iid = get_next_unused_iid() + if iid == "" or os.path.isfile(os.path.join(os.path.dirname(image_tgz), iid + ".tar.gz")): + print ("[!] Too many stale images") print ("[!] Please run reset.py or manually delete the contents of the scratch/ and images/ directory") + return "" os.rename(image_tgz, os.path.join(os.path.dirname(image_tgz), iid + ".tar.gz")) + print ("[+] Image ID:", iid) + return iid return "" def identify_arch(image_id): + print ("[+] Identifying architecture...") + identfy_arch_cmd = os.path.join(firmadyne_path, "scripts/getArch.sh") identfy_arch_args = [ os.path.join(firmadyne_path, "images", image_id + ".tar.gz") ] + child = pexpect.spawn(identfy_arch_cmd, identfy_arch_args, cwd=firmadyne_path) + child.expect_exact(":") + arch = child.readline().strip().decode("utf8") + print ("[+] Architecture: " + arch) + try: child.expect_exact(pexpect.EOF) + except Exception as e: child.close(force=True) @@ -90,69 +115,94 @@ def identify_arch(image_id): def make_image(arch, image_id): + print ("[+] Building QEMU disk image...") + makeimage_cmd = os.path.join(firmadyne_path, "scripts/makeImage.sh") makeimage_args = ["--", makeimage_cmd, image_id, arch] + child = pexpect.spawn("sudo", makeimage_args, cwd=firmadyne_path) child.sendline(sudo_pass) child.expect_exact(pexpect.EOF) def infer_network(arch, image_id, qemu_dir): + print ("[+] Setting up the network connection, please standby...") + network_cmd = os.path.join(firmadyne_path, "scripts/inferNetwork.sh") network_args = [image_id, arch] if qemu_dir: + path = os.environ["PATH"] newpath = qemu_dir + ":" + path child = pexpect.spawn(network_cmd, network_args, cwd=firmadyne_path, env={"PATH":newpath}) else: + child = pexpect.spawn(network_cmd, network_args, cwd=firmadyne_path) child.expect_exact("Interfaces:", timeout=None) interfaces = child.readline().strip().decode("utf8") + print ("[+] Network interfaces:", interfaces) + child.expect_exact(pexpect.EOF) def final_run(image_id, arch, qemu_dir): + runsh_path = os.path.join(firmadyne_path, "scratch", image_id, "run.sh") if not os.path.isfile(runsh_path): + print ("[!] Cannot emulate firmware, run.sh not generated") + return if qemu_dir: + if arch == "armel": + arch = "arm" elif arch == "mipseb": + arch = "mips" print ("[+] Using qemu-system-{0} from {1}".format(arch, qemu_dir)) - cmd = 'sed -i "/QEMU=/c\QEMU={0}/qemu-system-{1}" "{2}"'.format(qemu_dir, arch, runsh_path) - pexpect.run(cmd) + + command = 'sed -i "/QEMU=/c\QEMU={0}/qemu-system-{1}" "{2}"'.format(qemu_dir, arch, runsh_path) + + pexpect.run(command) print ("[+] All set! Press ENTER to run the firmware...") input ("[+] When running, press Ctrl + A X to terminate qemu") print ("[+] Command line:", runsh_path) - run_cmd = ["--", runsh_path] - child = pexpect.spawn("sudo", run_cmd, cwd=firmadyne_path) + + run_command = ["--", runsh_path] + + child = pexpect.spawn("sudo", run_command, cwd=firmadyne_path) child.sendline(sudo_pass) child.interact() def main(): + show_banner() + parser = argparse.ArgumentParser() parser.add_argument("firm_path", help="The path to the firmware image", type=str) 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) + args = parser.parse_args() qemu_ver = args.qemu qemu_dir = None + if qemu_ver: + qemu_dir = os.path.abspath(os.path.join("qemu-builds", qemu_ver)) if not os.path.isdir(qemu_dir): + print ("[!] Directory {0} not found".format(qemu_dir)) print ("[+] Using system qemu") qemu_dir = None @@ -160,13 +210,17 @@ def main(): image_id = run_extractor(args.firm_path) if image_id == "": + print ("[!] Image extraction failed") else: + arch = identify_arch(image_id) + make_image(arch, image_id) infer_network(arch, image_id, qemu_dir) final_run(image_id, arch, qemu_dir) if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0f49e68 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pexpect +argparse +os_sys +configparse diff --git a/reset.py b/reset.py index f86c7f8..f99394d 100755 --- a/reset.py +++ b/reset.py @@ -6,10 +6,13 @@ config = ConfigParser() config.read("fat.config") + firmadyne_path = config["DEFAULT"].get("firmadyne_path", "") + sudo_pass = config["DEFAULT"].get("sudo_password", "") print ("[+] Cleaning previous images and created files by firmadyne") + child = pexpect.spawn("/bin/sh" , ["-c", "sudo rm -rf " + os.path.join(firmadyne_path, "images/*.tar.gz")]) child.sendline(sudo_pass) child.expect_exact(pexpect.EOF) @@ -17,4 +20,5 @@ child = pexpect.spawn("/bin/sh", ["-c", "sudo rm -rf " + os.path.join(firmadyne_path, "scratch/*")]) child.sendline(sudo_pass) child.expect_exact(pexpect.EOF) -print ("[+] All done. Go ahead and run fat.py to continue firmware analysis") + +print ("[+] All done. Now you can go ahead and run fat.py to continue firmware analysis")