---
# System prepended metadata

title: Firmadyne `inferNetwork.sh`
tags: [security, firmadyne]

---

Firmadyne `inferNetwork.sh`
===
---

###### tags: `firmadyne` `security`
本篇紀錄閱讀 `inferNetwork.sh` source code 筆記

---

**目錄**
[TOC]

# /scripts/inferNetwork.sh
1. Source Code
```shell==
#!/bin/bash

set -e
set -u

if [ -e ./firmadyne.config ]; then
    source ./firmadyne.config
elif [ -e ../firmadyne.config ]; then
    source ../firmadyne.config
else
    echo "Error: Could not find 'firmadyne.config'!"
    exit 1
fi

if check_number $1; then
    echo "Usage: inferNetwork.sh <image ID> [<architecture>]"
    exit 1
fi
IID=${1}

if [ $# -gt 1 ]; then
    if check_arch "${2}"; then
        echo "Error: Invalid architecture!"
        exit 1
    fi

    ARCH=${2}
else
    echo -n "Querying database for architecture... "
    ARCH=$(psql -d firmware -U firmadyne -h 127.0.0.1 -t -q \
         -c "SELECT arch from image WHERE id=${1};")
    ARCH="${ARCH#"${ARCH%%[![:space:]]*}"}"
    echo "${ARCH}"
    if [ -z "${ARCH}" ]; then
        echo "Error: Unable to lookup architecture. \
        Please specify {armel,mipseb,mipsel} as the second argument!"
        exit 1
    fi
fi

echo "Running firmware ${IID}: terminating after 60 secs..."
timeout --preserve-status --signal SIGINT 60 \
  "${SCRIPT_DIR}/run.${ARCH}.sh" "${IID}"
sleep 1

echo "Inferring network..."
"${SCRIPT_DIR}/makeNetwork.py" -i "${IID}" -q -o \ 
  -a "${ARCH}" -S "${SCRATCH_DIR}"

echo "Done!"
```

2. Explaining
    ```shell==21
    if [ $# -gt 1 ]; then
    ```
    判斷參數是否大於一個，以下是官方示範裡使用的方式
    ```shell
    ./scripts/inferNetwork.sh 1
    ```
    所以這種用法會走 Line 28 的 else
    
    ---
    
    在 Line 28 後的 else 中，有這麼一段
    
    ```shell==30
    ARCH=$(psql -d firmware -U firmadyne -h 127.0.0.1 -t -q \
         -c "SELECT arch from image WHERE id=${1};")
    ARCH="${ARCH#"${ARCH%%[![:space:]]*}"}"
    ```
    此段從 Database `firmware` 撈出 image id 為第一個參數的 arch
    經實測，撈出的資料會類似以下
    ![](https://i.imgur.com/UVSU3dF.png)
    注意前面有空格
    Line 32 主要用途是拿來消去所有 prefix 的空格
    
    ---
    
    ```shell==40
    timeout --preserve-status --signal SIGINT 60 \
      "${SCRIPT_DIR}/run.${ARCH}.sh" "${IID}"
    ```
    若 $IID = 1 且 ARCH 為 mipseb，會 run /scripts/run.mipseb.sh 1
    關於這個腳本解釋 請看 `run.mipseb.sh` 的 Source Code
    
    ---
    
    經過 Line 40 讓 qemu 跑了 60 秒後，產生記錄檔
    `scratch/${IID}/qemu.initial.serial.log` 檔案 
    (${IID} 為 Image ID)
    這個檔案將在 `makeNetwork.py` 用到
    
    ```shell==47
    "${SCRIPT_DIR}/makeNetwork.py" -i "${IID}" -q -o \ 
      -a "${ARCH}" -S "${SCRATCH_DIR}"
    ```
    
    若 $IID 為 1
    $ARCH 為 mipseb
    此指令代換後為
    ```shell==
    "${FIRMWARE_DIR}/scripts/makeNetwork.py" -i "1" -q -o \ 
      -a "mipseb" -S "${FIRMWARE_DIR}/scratch/"
    ```
    
    接著請繼續看 `makeNetwork.sh`
    
# /scripts/run.mipseb.sh
1. Source Code
```shell==
#!/bin/bash

set -e
set -u

if [ -e ./firmadyne.config ]; then
    source ./firmadyne.config
elif [ -e ../firmadyne.config ]; then
    source ../firmadyne.config
else
    echo "Error: Could not find 'firmadyne.config'!"
    exit 1
fi

if check_number $1; then
    echo "Usage: run.mipseb.sh <image ID>"
    exit 1
fi
IID=${1}

WORK_DIR=`get_scratch ${IID}`
IMAGE=`get_fs ${IID}`
KERNEL=`get_kernel "mipseb"`

qemu-system-mips -m 256 -M malta -kernel ${KERNEL} \
-drive if=ide,format=raw,file=${IMAGE} \
-append "firmadyne.syscall=1 \
  root=/dev/sda1 console=ttyS0 \
  nandsim.parts=64,64,64,64,64,64,64,64,64,64 \
  rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel \
  print-fatal-signals=1" \
-serial file:${WORK_DIR}/qemu.initial.serial.log \
-serial unix:/tmp/qemu.${IID}.S1,server,nowait \
-monitor unix:/tmp/qemu.${IID},server,nowait \
-display none -net nic,vlan=0 -net socket,vlan=0,listen=:2000 \
-net nic,vlan=1 -net socket,vlan=1,listen=:2001 \
-net nic,vlan=2 -net socket,vlan=2,listen=:2002 \
-net nic,vlan=3 -net socket,vlan=3,listen=:2003
```
2. Explaining
    ```shell==21
    WORK_DIR=`get_scratch ${IID}`
    IMAGE=`get_fs ${IID}`
    KERNEL=`get_kernel "mipseb"`
    ```
    `get_scratch`, `get_fs`, `get_kernel` 都是定義在 `firmadyne.config` 的 function
    主要用途是回傳相關檔案的位置
    
    ---
    
    從 Line 25 之後是實際上 run qemu 的部分，以下解釋部分參數
    - `-m 256 -M malta` 
        設定 ram size 以及模擬 malta motherboard
    - `-serial file:${WORK_DIR}/qemu.initial.serial.log`
        設定 console 輸出會存到的位置
        `inferNetwork.sh` 會呼叫到 `makeNetwork.py`
        而 `makeNetwork.py` 將會用到此檔案
        
# /scripts/makeNetwork.py
1. Source Code
```python=
#!/usr/bin/env python

import sys
import getopt
import re
import struct
import socket
import stat
import os

debug = 0

QEMUCMDTEMPLATE = """#!/bin/bash
set -u
ARCHEND=%(ARCHEND)s
IID=%(IID)i
if [ -e ./firmadyne.config ]; then
    source ./firmadyne.config
elif [ -e ../firmadyne.config ]; then
    source ../firmadyne.config
elif [ -e ../../firmadyne.config ]; then
    source ../../firmadyne.config
else
    echo "Error: Could not find 'firmadyne.config'!"
    exit 1
fi
IMAGE=`get_fs ${IID}`
KERNEL=`get_kernel ${ARCHEND}`
QEMU=`get_qemu ${ARCHEND}`
QEMU_MACHINE=`get_qemu_machine ${ARCHEND}`
QEMU_ROOTFS=`get_qemu_disk ${ARCHEND}`
WORK_DIR=`get_scratch ${IID}`
%(START_NET)s
function cleanup {
    pkill -P $$
    %(STOP_NET)s
}
trap cleanup EXIT
echo "Starting firmware emulation... use Ctrl-a + x to exit"
sleep 1s
%(QEMU_ENV_VARS)s ${QEMU} -m 256 -M ${QEMU_MACHINE} -kernel ${KERNEL} \\
    %(QEMU_DISK)s -append "root=${QEMU_ROOTFS} console=ttyS0 nandsim.parts=64,64,64,64,64,64,64,64,64,64 rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel print-fatal-signals=1 user_debug=31 firmadyne.syscall=0" \\
    -nographic \\
    %(QEMU_NETWORK)s | tee ${WORK_DIR}/qemu.final.serial.log
"""

def stripTimestamps(data):
    lines = data.split("\n")
    #throw out the timestamps
    lines = [re.sub(r"^\[[^\]]*\] firmadyne: ", "", l) for l in lines]
    return lines

def findMacChanges(data, endianness):
    lines = stripTimestamps(data)
    candidates = filter(lambda l: l.startswith("ioctl_SIOCSIFHWADDR"), lines)
    if debug:
        print("Mac Changes %r" % candidates)

    result = []
    if endianness == "eb":
        fmt = ">I"
    elif endianness == "el":
        fmt = "<I"
    for c in candidates:
        g = re.match(r"^ioctl_SIOCSIFHWADDR\[[^\]]+\]: dev:([^ ]+) mac:0x([0-9a-f]+) 0x([0-9a-f]+)", c)
        if g:
            (iface, mac0, mac1) = g.groups()
            m0 = struct.pack(fmt, int(mac0, 16))[2:]
            m1 = struct.pack(fmt, int(mac1, 16))
            mac = "%02x:%02x:%02x:%02x:%02x:%02x" % struct.unpack("BBBBBB", m0+m1)
            result.append((iface, mac))
    return result

# Get the netwokr interfaces in the router, except 127.0.0.1
def findNonLoInterfaces(data, endianness):
    #lines = data.split("\r\n")
    lines = stripTimestamps(data)
    candidates = filter(lambda l: l.startswith("__inet_insert_ifa"), lines) # logs for the inconfig process
    if debug:
        print("Candidate ifaces: %r" % candidates)
    result = []
    if endianness == "eb":
        fmt = ">I"
    elif endianness == "el":
        fmt = "<I"
    for c in candidates:
        g = re.match(r"^__inet_insert_ifa\[[^\]]+\]: device:([^ ]+) ifa:0x([0-9a-f]+)", c)
        if g:
            (iface, addr) = g.groups()
            addr = socket.inet_ntoa(struct.pack(fmt, int(addr, 16)))
            if addr != "127.0.0.1" and addr != "0.0.0.0":
                result.append((iface, addr))
    return result

def findIfacesForBridge(data, brif):
    #lines = data.split("\r\n")
    lines = stripTimestamps(data)
    result = []
    candidates = filter(lambda l: l.startswith("br_dev_ioctl") or l.startswith("br_add_if"), lines)
    for c in candidates:
        for p in [r"^br_dev_ioctl\[[^\]]+\]: br:%s dev:(.*)", r"^br_add_if\[[^\]]+\]: br:%s dev:(.*)"]:
            pat = p % brif
            g = re.match(pat, c)
            if g:
                iface = g.group(1)
                #we only add it if the interface is not the bridge itself
                #there are images that call brctl addif br0 br0 (e.g., 5152)
                if iface != brif:
                    result.append(iface.strip())
    return result

def findVlanInfoForDev(data, dev):
    #lines = data.split("\r\n")
    lines = stripTimestamps(data)
    results = []
    candidates = filter(lambda l: l.startswith("register_vlan_dev"), lines)
    for c in candidates:
        g = re.match(r"register_vlan_dev\[[^\]]+\]: dev:%s vlan_id:([0-9]+)" % dev, c)
        if g:
            results.append(int(g.group(1)))
    return results

def ifaceNo(dev):
    g = re.match(r"[^0-9]+([0-9]+)", dev)
    return int(g.group(1)) if g else -1

def qemuArchNetworkConfig(i, arch, n):
    if not n:
        if arch == "arm":
            return "-device virtio-net-device,netdev=net%(I)i -netdev socket,id=net%(I)i,listen=:200%(I)i" % {'I': i}
        else:
            return "-net nic,vlan=%(VLAN)i -net socket,vlan=%(VLAN)i,listen=:200%(I)i" % {'I': i, 'VLAN' : i}
    else:
        (ip, dev, vlan, mac) = n
         # newer kernels use virtio only
        if arch == "arm":
            return "-device virtio-net-device,netdev=net%(I)i -netdev tap,id=net%(I)i,ifname=${TAPDEV_%(I)i},script=no" % {'I': i}
        else:
            vlan_id = vlan if vlan else i
            mac_str = "" if not mac else ",macaddr=%s" % mac
            return "-net nic,vlan=%(VLAN)i%(MAC)s -net tap,vlan=%(VLAN)i,id=net%(I)i,ifname=${TAPDEV_%(I)i},script=no" % { 'I' : i, 'MAC' : mac_str, 'VLAN' : vlan_id}

def qemuNetworkConfig(arch, network):
    output = []
    assigned = []
    for i in range(0, 4):
        for j, n in enumerate(network):
            # need to connect the jth emulated network interface to the corresponding host interface
            if i == ifaceNo(n[1]):
                output.append(qemuArchNetworkConfig(j, arch, n))
                assigned.append(n)
                break

        # otherwise, put placeholder socket connection
        if len(output) <= i:
            output.append(qemuArchNetworkConfig(i, arch, None))

    # find unassigned interfaces
    for j, n in enumerate(network):
        if n not in assigned:
            # guess assignment
            print("Warning: Unmatched interface: %s" % (n,))
            output[j] = qemuArchNetworkConfig(j, arch, n)
            assigned.append(n)

    return ' '.join(output)

def buildConfig(brif, iface, vlans, macs):
    #there should be only one ip
    ip = brif[1]
    br = brif[0]

    #strip vlanid from interface name (e.g., eth2.2 -> eth2)
    dev = iface.split(".")[0]

    #check whether there is a different mac set
    mac = None
    d = dict(macs)
    if br in d:
        mac = d[br]
    elif dev in d:
        mac = d[dev]

    vlan_id = None
    if len(vlans):
        vlan_id = vlans[0]

    return (ip, dev, vlan_id, mac)

def getIP(ip):
    tups = [int(x) for x in ip.split(".")]
    if tups[3] != 1:
        tups[3] -= 1
    else:
        tups[3] = 2
    return ".".join([str(x) for x in tups])

def startNetwork(network):
    template_1 = """
TAPDEV_%(I)i=tap${IID}_%(I)i
HOSTNETDEV_%(I)i=${TAPDEV_%(I)i}
echo "Creating TAP device ${TAPDEV_%(I)i}..."
sudo tunctl -t ${TAPDEV_%(I)i} -u ${USER}
"""

    template_vlan = """
echo "Initializing VLAN..."
HOSTNETDEV_%(I)i=${TAPDEV_%(I)i}.%(VLANID)i
sudo ip link add link ${TAPDEV_%(I)i} name ${HOSTNETDEV_%(I)i} type vlan id %(VLANID)i
sudo ip link set ${HOSTNETDEV_%(I)i} up
"""

    template_2 = """
echo "Bringing up TAP device..."
sudo ip link set ${HOSTNETDEV_%(I)i} up
sudo ip addr add %(HOSTIP)s/24 dev ${HOSTNETDEV_%(I)i}
echo "Adding route to %(GUESTIP)s..."
sudo ip route add %(GUESTIP)s via %(GUESTIP)s dev ${HOSTNETDEV_%(I)i}
"""

    output = []
    for i, (ip, dev, vlan, mac) in enumerate(network):
        output.append(template_1 % {'I' : i})
        if vlan:
            output.append(template_vlan % {'I' : i, 'VLANID' : vlan})
        output.append(template_2 % {'I' : i, 'HOSTIP' : getIP(ip), 'GUESTIP': ip})
    return '\n'.join(output)

def stopNetwork(network):
    template_1 = """
echo "Deleting route..."
sudo ip route flush dev ${HOSTNETDEV_%(I)i}
echo "Bringing down TAP device..."
sudo ip link set ${TAPDEV_%(I)i} down
"""

    template_vlan = """
echo "Removing VLAN..."
sudo ip link delete ${HOSTNETDEV_%(I)i}
"""

    template_2 = """
echo "Deleting TAP device ${TAPDEV_%(I)i}..."
sudo tunctl -d ${TAPDEV_%(I)i}
"""

    output = []
    for i, (ip, dev, vlan, mac) in enumerate(network):
        output.append(template_1 % {'I' : i})
        if vlan:
            output.append(template_vlan % {'I' : i})
        output.append(template_2 % {'I' : i})
    return '\n'.join(output)

def qemuCmd(iid, network, arch, endianness):
    if arch == "mips":
        qemuEnvVars = ""
        qemuDisk = "-drive if=ide,format=raw,file=${IMAGE}"
        if endianness != "eb" and endianness != "el":
            raise Exception("You didn't specify a valid endianness")
    elif arch == "arm":
        qemuDisk = "-drive if=none,file=${IMAGE},format=raw,id=rootfs -device virtio-blk-device,drive=rootfs"
        if endianness == "el":
            qemuEnvVars = "QEMU_AUDIO_DRV=none"
        elif endianness == "eb":
            raise Exception("armeb currently not supported")
        else:
            raise Exception("You didn't specify a valid endianness")
    else:
        raise Exception("Unsupported architecture")

    return QEMUCMDTEMPLATE % {'IID': iid,
                              'ARCHEND' : arch + endianness,
                              'START_NET' : startNetwork(network),
                              'STOP_NET' : stopNetwork(network),
                              'QEMU_DISK' : qemuDisk,
                              'QEMU_NETWORK' : qemuNetworkConfig(arch, network),
                              'QEMU_ENV_VARS' : qemuEnvVars}

def process(infile, iid, arch, endianness=None, makeQemuCmd=False, outfile=None):
    brifs = []
    vlans = []
    data = open(infile).read()
    network = set()
    success = False

    #find interfaces with non loopback ip addresses
    ifacesWithIps = findNonLoInterfaces(data, endianness)

    #find changes of mac addresses for devices
    macChanges = findMacChanges(data, endianness)

    print("Interfaces: %r" % ifacesWithIps)

    deviceHasBridge = False
    for iwi in ifacesWithIps:
        #find all interfaces that are bridged with that interface
        brifs = findIfacesForBridge(data, iwi[0])
        if debug:
            print("brifs for %s %r" % (iwi[0], brifs))
        for dev in brifs:
            #find vlan_ids for all interfaces in the bridge
            vlans = findVlanInfoForDev(data, dev)
            #create a config for each tuple
            network.add((buildConfig(iwi, dev, vlans, macChanges)))
            deviceHasBridge = True

        #if there is no bridge just add the interface
        if not brifs and not deviceHasBridge:
            vlans = findVlanInfoForDev(data, iwi[0])
            network.add((buildConfig(iwi, iwi[0], vlans, macChanges)))

    ips = set()
    pruned_network = []
    for n in network:
        if n[0] not in ips:
            ips.add(n[0])
            pruned_network.append(n)
        else:
            if debug:
                print("duplicate ip address for interface: ", n)

    if makeQemuCmd:
        qemuCommandLine = qemuCmd(iid, pruned_network, arch, endianness)
    if qemuCommandLine:
        success = True
    if outfile:
        with open(outfile, "w") as out:
            out.write(qemuCommandLine)
        os.chmod(outfile, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
    else:
        print(qemuCommandLine)

    return success

def archEnd(value):
    arch = None
    end = None

    tmp = value.lower()
    if tmp.startswith("mips"):
        arch = "mips"
    elif tmp.startswith("arm"):
        arch = "arm"
    if tmp.endswith("el"):
        end = "el"
    elif tmp.endswith("eb"):
        end = "eb"
    return (arch, end)

def main():
    infile = None
    makeQemuCmd = False
    iid = None
    outfile = None
    arch = None
    endianness = None
    (opts, argv) = getopt.getopt(sys.argv[1:], 'f:i:S:a:oqd')
    for (k, v) in opts:
        if k == '-f':
            infile = v
        if k == '-d':
            global debug
            debug += 1
        if k == '-q':
            makeQemuCmd = True
        if k == '-i':
            iid = int(v)
        if k == '-S':
            SCRATCHDIR = v
        if k == '-o':
            outfile = True
        if k == '-a':
            (arch, endianness) = archEnd(v)

    if not arch or not endianness:
        raise Exception("Either arch or endianness not found try mipsel/mipseb/armel/armeb")

    if not infile and iid:
        infile = "%s/%i/qemu.initial.serial.log" % (SCRATCHDIR, iid)
    if outfile and iid:
        outfile = """%s/%i/run.sh""" % (SCRATCHDIR, iid)
    if debug:
        print("processing %i" % iid)
    if infile:
        process(infile, iid, arch, endianness, makeQemuCmd, outfile)

if __name__ == "__main__":
    main()
```
2. Explainging
    進入口為 Line 351 main()
    ```python=358
    (opts, argv) = getopt.getopt(sys.argv[1:], 'f:i:S:a:oqd')
    ```
    創造後面跟 1 個 option 的 arguments: `-f`, `-i`, `-S`, `-a`
    以及後面不跟 option 的 arguments: `-o`, `-q`, `-d`
    以下假設 `inferNetwork.sh` 是如此呼叫 `makeNetwork.py` 的:
    ```shell==
    "${FIRMWARE_DIR}/scripts/makeNetwork.py" -i "1" -q -o \ 
      -a "mipseb" -S "${FIRMWARE_DIR}/scratch/"
    ```
    
    ---
    
    有被執行到的 if 在下面條列:
    - `-i 1`
        ```python=367
            if k == '-i':
                iid = int(v)
        ```
    - `-q`
        ```python=365
            if k == '-q':
                makeQemuCmd = True
        ```
    - `-o`
        ```python=371
            if k == '-o':
                outfile = True
        ```
    - `-a "mipseb"`
        ```python=373
            if k == '-a':
                (arch, endianness) = archEnd(v)
        ```
        `archEnd` 只是很簡單的把 `"mipseb"` 切成 `"mips"`, `"eb"` 
        並存回 `(arch, endianness)`
    - `-S "${FIRMWARE_DIR}/scratch/"`
        ```python=369
            if k == '-S':
                SCRATCHDIR = v
        ```
    
    ---
    
    ```python=376
    if not arch or not endianness:
    ```
    不會進if。
    
    ---
    
    ```python=379
    if not infile and iid:
        infile = "%s/%i/qemu.initial.serial.log" % (SCRATCHDIR, iid)
    ```
    會進if。
    設定 `infile = "${FIRMWARE_DIR}/scratch//1/qemu.initial.serial.log"`
    此檔案在 run qemu 後產生
    
    ---
    
    ```python=381
    if outfile and iid:
        outfile = """%s/%i/run.sh""" % (SCRATCHDIR, iid)
    ```
    會進if。
    設定 `outfile = "${FIRMWARE_DIR}/scratch//1/run.sh"`
    
    ---
    
    ```python=383
    if debug:
    ```
    不會進if。
    
    ---
    
    ```python=385
    if infile:
        process(infile, iid, arch, endianness, makeQemuCmd, outfile)
    ```
    會進 if。
    參數代換後，如下
    ```python=
    process("${FIRMWARE_DIR}/scratch//1/qemu.initial.serial.log", \
         1, "mips", "eb", True, "${FIRMWARE_DIR}/scratch//1/run.sh")
    ```
    
    接著繼續追看 process 的 Source Code (Line 280)
    
    ---
    
    ```python=283
    data = open(infile).read()
    ```
    將 infile 打開讀取
    
    ```python=287
    #find interfaces with non loopback ip addresses
    ifacesWithIps = findNonLoInterfaces(data, endianness)
    ```
    ```python=293
    print("Interfaces: %r" % ifacesWithIps)
    ```
    
    輸出結果如下
    ![](https://i.imgur.com/VpBExvv.png)
    (輸出的第一行為我個人多加的，方便觀察實際的參數值)
    
    現在就繼續追 `findNonLoInterfaces` 的 Source code (Line 75)
    
    ---
    
    ```python=77
    lines = stripTimestamps(data)
    ```
    `stripTimestamps` 的功用簡單來說就是
    將 data 切成一行行的 line
    並將每行開頭的(若有的話) `[......] firmadyne:` 砍掉
    並存回 lines 陣列回傳。
    
    ---
    
    ```python=78
    candidates = filter(lambda l: l.startswith("__inet_insert_ifa"), lines) # logs for the inconfig process
    ```
    將開頭是 "__inet_insert_ifa" 的 line 存進 candidates 中
    
    ---
    
    ```python=81
    result = []
    if endianness == "eb":
        fmt = ">I"
    elif endianness == "el":
        fmt = "<I"
    ```
    初始化 result 陣列
    並依據 endianness 來決定如何 pack
    詳情請見 python module `struct.pack`
    
    ---
    
    先來看看若是 __inet_insert_ifa 開頭的 Log 會長怎樣
    ![](https://i.imgur.com/mccl8sE.png)


    再看以下 code:
    ```python=86
    for c in candidates:
        g = re.match(r"^__inet_insert_ifa\[[^\]]+\]: device:([^ ]+) ifa:0x([0-9a-f]+)", c)
        if g:
            (iface, addr) = g.groups()
            addr = socket.inet_ntoa(struct.pack(fmt, int(addr, 16)))
            if addr != "127.0.0.1" and addr != "0.0.0.0":
                result.append((iface, addr))
    return result
    ```
    
    ---
    
    簡單來說
    `inferNetwork.sh` 先執行了 `run.${ARCH}.sh` 60秒
    產生了 qemu.initial.serial.log
    再呼叫 `makeNetwork.py` 去從 log 中找 `__inet_insert_ifa` pattern 