项目地址:https://github.com/e-m-b-a/emba
本次分析对象的最后一次提交:https://github.com/e-m-b-a/emba/commit/de9dc53980962c8a62ea1239c9039e87962b32e5
克隆项目

1
git clone https://github.com/e-m-b-a/emba.git

介绍

EMBA 被设计为渗透测试人员的中央固件分析工具。它支持完整的安全分析过程,从固件提取过程开始,通过仿真进行静态分析和动态分析,最后生成 Web 报告。EMBA 自动发现固件中可能存在的弱点和漏洞。例如不安全的二进制文件、旧的和过时的软件组件、可能易受攻击的脚本或硬编码密码。EMBA是一个命令行工具,可以选择生成易于使用的 Web 报告以供进一步分析。

EMBA 结合了多种成熟的分析工具,可以通过一个简单的命令启动。之后,它会测试固件是否存在可能的安全风险和感兴趣的区域以进行进一步调查。无需手动安装所有帮助程序,执行集成安装脚本后,您就可以测试固件了。
EMBA 旨在协助渗透测试人员,而不是作为一个没有人工交互的独立工具。EMBA 应该提供尽可能多的关于固件的信息,测试人员可以决定重点领域,并负责验证和解释结果。

官方视频:
https://youtu.be/_dvdy3klFFY

项目采用纯Shell编写,非常适合这种需要结合许多外部工具并执行大量命令的工具。

项目统计:
Summary
Date : 2023-04-19 10:38:56
Total : 225 files, 28187 codes, 3873 comments, 4716 blanks, all 36776 lines
Languages

language files code comment blank total
Shell Script 173 24,559 3,657 4,361 32,577
Properties 22 1,269 70 20 1,359
XML 2 1,034 1 3 1,038
YAML 17 555 92 86 733
CSS 1 343 14 77 434
Markdown 8 309 14 146 469
HTML 1 106 22 15 143
Docker 1 12 3 8 23

Directories

path files code comment blank total
. 225 28,187 3,873 4,716 36,776
. (Files) 9 812 87 220 1,119
.github 19 549 81 116 746
.github (Files) 2 6 1 15 22
.github\ISSUE_TEMPLATE 2 47 0 19 66
.github\workflows 15 496 80 82 658
config 62 1,395 110 101 1,606
config (Files) 22 1,269 70 20 1,359
config\report_templates 40 126 40 81 247
helpers 26 5,523 729 672 6,924
installer 26 1,734 519 361 2,614
modules 83 18,174 2,347 3,246 23,767
modules (Files) 77 17,704 2,269 3,162 23,135
modules\L10_system_emulation 6 470 78 84 632

由代码量可知其主要功能集中在modules文件夹内。通过后面的分析可以知道,EMBA将根据运行参数分别调用各个模块对固件进行静态或动态的分析。

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
> tree .                 
.
├── check_project.sh
├── CODE_OF_CONDUCT.md
├── config
│ ├── banner
│ │ ├── ...
│ │ └── Vegas_Edt-v1.1.0.txt
│ ├── report_templates
│ │ ├── ...
│ │ └── S99_grepit-pre.sh
│ ├── ...
│ └── trickest_cve-db.txt
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── docker-compose.yml
├── Dockerfile
├── emba
├── helpers
│ ├── ...
│ └── trickest_db_update.sh
├── installer
│ ├── ...
│ └── wickStrictModeFail.sh
├── installer.sh
├── LICENSE
├── licenses
│ └── LICENSE-MIT-firmae-firmadyne.txt
├── modules
│ ├── L10_system_emulation
│ │ ├── ...
│ │ └── run_service.sh
│ ├── ...
│ └── template_module.sh
├── README.md
├── scan-profiles
│ ├── ...
│ └── full-scan.emba
└── SECURITY.md

9 directories, 250 files

描述:

  1. check_project.sh:用shellchecker检查./helpers./modulesemba和自身中的所有shell脚本
  2. config/banner/*:banner的配置文件,用于输出横幅
  3. config/report_templates/*:一些模板输出的脚本,比如打印该文件是什么组件等。
  4. config/*:各种配置文件。包括用于各种特征匹配的正则库:开源协议版本库、证书、启动文件、命令执行、字符串相关操作敏感函数、配置文件、密钥文件、发行版检测、某些摄像头密钥文件、历史文件、http程序文件、网络服务有关文件、编译工具有关文件、已知漏洞&cve&linux、密码文件等等等等。
  5. helpers/*:提供一些有用的脚本,包括修复权限与链接、修复模拟、检查依赖、html生成等脚本。
  6. installer/*:安装有关的脚本。包括安装需要的工具等。
  7. modules/*:各种分析模块,包括二进制检查、vmdk提取、nmap扫描、开源协议摘要等等。
  8. scan-profiles/*:存放了一些EMBA profile,用与配置具体的分析任务。

安装

建议:

  1. 使用最新最新的Kali Linux虚拟机(Ubuntu也可以)
  2. 使用默认docker模式
  3. 如果你不清楚自己在干啥,请不要在开发者模式(-D)下使用EMBA,因为某些检测模块会执行恶意代码从而损坏宿主机。
  4. 经典模式将使用Docker运行。你只需要安装Docker和cve-search。
  5. 只能使用x86-64架构的虚拟机运行
  6. 最低8GB内存,4核CPU,30G以上磁盘空间(若内存低于8G安装 cve-search 时会卡死,并且固件的深度提取会使用大量磁盘空间!)
  7. docker-compose 1.29

典型安装

1
sudo ./installer.sh -d

注意!
国内用户记得配置代理,不然git啥的会报错。另外,sudo运行的命令不会继承当前用户的环境变量。
cve-search 的安装需要配置代理,需要修改安装流程(不然特别特别慢,还容易失败)

1
2
3
4
5
6
7
8
9
10
11
# installer\IF20_cve_search.sh line 38
# we always need the cve-search stuff:
if ! [[ -d external/cve-search ]]; then
git clone https://github.com/EMBA-support-repos/cve-search.git external/cve-search

# external\cve-search\lib\Config.py line 37
default = {
"redisHost": "localhost",
"redisPort": 6379,
"redisQ": 9,
...

这将安装所需要的依赖工具,例如cve-search、下载大约6GB的docker镜像等等。

手动构建:
docker-compose build emba

安装完后测试 cve-search:./external/cve-search/bin/search.py -p busybox
image-20230427174009787
默认方式安装和运行操作都将使用 docker-compose 运行 emba

分析安装过程

首先加载helpersinstaller文件夹下的helpers_emba_load_strict_settings.sh,并执行其中的load_strict_mode_settings函数用于加载严格模式的设置
安装docker
加载所有installer文件夹下的*.sh(但是一般都是以函数的形式存在,加载时并不会安装)
检查参数个数,必须为1
检查参数cCdDFghlr,设置对应的安装标记,然后根据安装标记进行针对性安装。在此过程还会检查wsl等环境进行针对性安装。
cve-search is always installed on the host,使用IF20_cve_search函数。

cve-search使用mongodb存储,并会使用pip安装redis等组件。

使用

静态分析

必须指定日志和固件文件:

1
sudo ./emba.sh -l ./log -f ./firmware
  • 请使用 sudo 运行
  • 日志文件会很大
  • 目前支持的架构:MIPS、ARM、PPC、x86 和 x64
  • 你也可以使用写好的扫描配置文件:scan-profiles/*.emba

检查内核配置

使用 checksec.shkernel checker仅测试内核配置:

1
sudo ./emba.sh -l ./logs/kernel_conf -k ./kernel.config

如果添加-f ./firmware,它将忽略-k并在固件中搜索内核配置
checksec相信打过 pwn 的人应该都很熟悉。关于 checksec 的 kernel checker:
checksec 是一种用于检查Linux系统安全性的工具,其中包括一个kernel checker。checksec kernel checker通过检查当前正在运行的内核的各种安全配置选项来帮助用户评估其安全性。
checksec kernel checker可以检查以下配置选项:

  1. ASLR(address space layout randomization):用于随机化进程虚拟地址空间布局,以减少攻击者利用内存漏洞的可能性。
  2. NX(no execute):用于禁止在可执行内存区域中执行代码,以防止攻击者利用缓冲区溢出等漏洞注入和执行恶意代码。
  3. KASLR(kernel address space layout randomization):用于随机化内核代码和数据的物理地址,以增加攻击者突破内核安全的难度。
  4. Stack Protector:用于在栈上保护函数返回地址,以防止栈溢出攻击。

通过检查这些配置选项,checksec kernel checker可以提供有关系统安全性的重要信息,并帮助用户确定是否需要采取额外的安全措施。

主文件(emba)代码分析

运行主文件。首先定义了一些函数,然后在最后调用主函数,并传递所有参数:
image-20230427174557729
然后解析参数、检查参数、文件、路径、依赖工具等。设置相关环境变量。
监控docker日志和通知:
image-20230427174609748
helpers\helpers_emba_print.sh:
image-20230427174613613
打印固件信息,检查固件大小。固件大小不能超过 400000000 Bytes
如果指定了-t参数,则会设置THREADEDMAX_MOD_THREADS参数(MAX_MOD_THREADS为核心数乘2):

1
2
3
4
5
6
# calculate the maximum threads per module
if [[ ${THREADED} -eq 1 ]] && [[ "${MAX_MOD_THREADS}" -eq 0 ]]; then
# the maximum threads per modules - if this value does not match adjust it via
# local MAX_MOD_THREADS=123 in module area
export MAX_MOD_THREADS="$(( 2* "$(grep -c ^processor /proc/cpuinfo)" ))"
fi

预处理HTML输出

1
2
3
4
5
6
7
8
9
# Change log output to color for web report and prepare report
if [[ ${HTML} -eq 1 ]] ; then
if [[ ${FORMAT_LOG} -eq 0 ]] ; then
FORMAT_LOG=1
print_output "[*] Activate colored log for webreport" "no_log"
fi
print_output "[*] Prepare webreport" "no_log"
prepare_report
fi

-k参数运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#######################################################################################
# Kernel configuration check
#######################################################################################
if [[ "${KERNEL}" -eq 1 ]]; then
if [[ ${IN_DOCKER} -eq 1 ]] && [[ -f "${LOG_DIR}"/kernel_config ]]; then
export KERNEL_CONFIG="${LOG_DIR}"/kernel_config
fi

if ! [[ -f "${KERNEL_CONFIG}" ]] ; then
print_output "[-] Invalid kernel configuration file: ${ORANGE}${KERNEL_CONFIG}${NC}" "no_log"
exit 1
else
if [[ ${IN_DOCKER} -eq 0 ]] ; then
# we copy the kernel config file from outside the container into our log directory
# further modules are using LOG_DIR/kernel_config for accessing the kernel config
if [[ -d "${LOG_DIR}" ]] ; then
cp "${KERNEL_CONFIG}" "${LOG_DIR}"/kernel_config
else
print_output "[!] Missing log directory" "no_log"
exit 1
fi
fi
fi
fi

会创建一个子进程,用来监控剩余空间容量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
disk_space_monitor() {
local DDISK="$LOG_DIR"

while ! [[ -f "$MAIN_LOG" ]]; do
sleep 1
done

while true; do
# print_output "[*] Disk space monitoring active" "no_log"
FREE_SPACE=$(df --output=avail "$DDISK" | awk 'NR==2')
if [[ "$FREE_SPACE" -lt 10000000 ]]; then
print_ln "no_log"
print_output "[!] WARNING: EMBA is running out of disk space!" "main"
print_output "[!] WARNING: EMBA is stopping now" "main"
df -h || true
print_ln "no_log"
# give the container some more seconds for the cleanup process
[[ "$IN_DOCKER" -eq 0 ]] && sleep 5
cleaner 1
fi

if [[ -f "$MAIN_LOG" ]]; then
if grep -q "Test ended\|EMBA failed" "$MAIN_LOG" 2>/dev/null; then
break
fi
fi

sleep 5
done
}

运行容器,并执行分析:
image-20230427174619233
初始化状态栏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
initial_status_bar() {
# PID for box updater threads
export PID_SYSTEM_LOAD=""
export PID_STATUS=""
export PID_MODULES=""
export PID_STATUS_2=""
# Path to status tmp file
# each line is dedicated to a specific function
# 1: Count of boxes visible
# 2: CPU load string (needs to be cached, because it takes about a second to get the information)
# 3: Start time of status bar - not exactly the same as the EMBA timer, but near enough to get an idea
# 4: Count modules
export STATUS_TMP_PATH=""

# overwrites $LINES and "$COLUMNS" with the actual values of the window
shopt -s checkwinsize; (:;:)
local LINE_POS="$(( LINES - 6 ))"
printf "\e[%s;1f\e[0J\e[%s;1f" "$LINE_POS" "$LINE_POS"
reset

# create new tmp file with empty lines
STATUS_TMP_PATH="$TMP_DIR/status"
if [[ ! -f "$STATUS_TMP_PATH" && -d "$TMP_DIR" ]] ; then
echo -e "\\n\\n\\n\\n" > "$STATUS_TMP_PATH"
fi
# calculate boxes fitting and draw them
local INITIAL_STR=""
INITIAL_STR="\e[${LINE_POS};1f\e[0J\e[0;${LINE_POS}r\e[${LINE_POS};1f"
if [[ $LINES -gt 10 ]] ; then
# column has to be increased with 2 characters because of possible arrow column
local ARROW_POS=0
STATUS_BAR_BOX_COUNT=0
if [[ $COLUMNS -ge 27 ]] ; then
INITIAL_STR+="$(draw_box 26 "SYSTEM LOAD" 0)"
STATUS_BAR_BOX_COUNT=1
ARROW_POS=27
fi
if [[ $COLUMNS -ge 54 ]] ; then
INITIAL_STR+="$(draw_box 26 "STATUS" 27)"
STATUS_BAR_BOX_COUNT=2
ARROW_POS=53
fi
if [[ $COLUMNS -ge 80 ]] ; then
INITIAL_STR+="$(draw_box 26 "MODULES" 53)"
STATUS_BAR_BOX_COUNT=3
ARROW_POS=79
fi
if [[ $COLUMNS -ge 104 ]] ; then
INITIAL_STR+="$(draw_box 26 "STATUS 2" 79)"
STATUS_BAR_BOX_COUNT=4
fi

if [[ $STATUS_BAR_BOX_COUNT -lt 4 ]] ; then
INITIAL_STR+="$(draw_arrows "$ARROW_POS")"
fi
fi
if [[ -f "$STATUS_TMP_PATH" ]] ; then
sed -i "1s/.*/$STATUS_BAR_BOX_COUNT/" "$STATUS_TMP_PATH" 2> /dev/null || true
fi
INITIAL_STR+="\e[H"
# set cursor and boxes
printf "%b" "$INITIAL_STR"
box_updaters
}

预检查模块(所有以 P 开头的模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#######################################################################################
# Pre-Check (P-modules)
#######################################################################################
if [[ "${PRE_CHECK}" -eq 1 ]] ; then

print_ln "no_log"
if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] Pre-checking phase started on ""$(date)""\\n""$(indent "${NC}""Firmware binary path: ""${FIRMWARE_PATH}")" "main"
else
print_output "[!] Pre-checking phase started on ""$(date)""\\n""$(indent "${NC}""Firmware binary path: ""${FIRMWARE_PATH}")" "no_log"
fi
write_notification "Pre-checking phase started"

# 'main' functions of imported modules
# in the pre-check phase we execute all modules with P[Number]_Name.sh

run_modules "P" "${THREADED}" "0"

# if we running threaded we ware going to wait for the slow guys here
[[ ${THREADED} -eq 1 ]] && wait_for_pid "${WAIT_PIDS[@]}"

print_ln "no_log"

if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] Pre-checking phase ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "main"
else
print_output "[!] Pre-checking phase ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "no_log"
fi
write_notification "Pre-checking phase finished"

# useful prints for debugging:
# print_output "[!] Firmware value: ${FIRMWARE}"
# print_output "[!] Firmware path: ${FIRMWARE}_PATH"
# print_output "[!] Output dir: $OUTPUT_DIR"
# print_output "[!] LINUX_PATH_COUNTER: $LINUX_PATH_COUNTER"
# print_output "[!] LINUX_PATH_ARRAY: ${#ROOT_PATH[@]}"
fi

关键在run_modules "P" "${THREADED}" "0"这一行,我们看看这个函数的定义:
emba line 111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# $1: module group letter [P, S, L, F]
# $2: 0=single thread 1=multithread
# $3: HTML=1 - generate html file
run_modules()
{
MODULE_GROUP="${1:-}"
printf -v THREADING_SET '%d\n' "${2}" 2>/dev/null
THREADING_MOD_GROUP="${THREADING_SET}"

local SELECT_PRE_MODULES_COUNT=0

for SELECT_NUM in "${SELECT_MODULES[@]}" ; do
if [[ "${SELECT_NUM}" =~ ^["${MODULE_GROUP,,}","${MODULE_GROUP^^}"]{1} ]]; then
...

函数比较长。接收三个参数的作用在声明前写的很清楚。大概功能:根据传入的参数和一些其他条件,动态地加载和运行符合要求的模块脚本,有时候可以进行多线程并发执行。另外,还会检查某个模块是否已经运行过,并在必要时跳过不需要重新运行的模块,提高运行效率。如果开启了 HTML 选项,则会生成报告文件。
固件检查模块(S模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#######################################################################################
# Firmware-Check (S modules)
#######################################################################################
WAIT_PIDS=()
if [[ ${FIRMWARE} -eq 1 ]] ; then
print_output "\n=================================================================\n" "no_log"

if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] Testing phase started on ""$(date)""\\n""$(indent "${NC}""Firmware path: ""${FIRMWARE_PATH}")" "main"
else
print_output "[!] Testing phase started on ""$(date)""\\n""$(indent "${NC}""Firmware path: ""${FIRMWARE_PATH}")" "no_log"
fi
write_notification "Testing phase finished"
write_grep_log "$(date)" "TIMESTAMP"

run_modules "S" "${THREADED}" "${HTML}"

[[ ${THREADED} -eq 1 ]] && wait_for_pid "${WAIT_PIDS[@]}"

print_ln "no_log"

if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] Testing phase ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "main"
else
print_output "[!] Testing phase ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "no_log"
fi
write_notification "Testing phase ended"

TESTING_DONE=1
fi

同理,会运行所有S模块,并等待子进程结束。必要时会使用多线程。
实时模拟模块(L模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#######################################################################################
# Live Emulation - Check (L-modules)
#######################################################################################
if [[ "${FULL_EMULATION}" -eq 1 ]] ; then
print_output "\n=================================================================\n" "no_log"
if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] System emulation phase started on ""$(date)""\\n""$(indent "${NC}""Firmware path: ""${FIRMWARE_PATH}")" "main"
else
print_output "[!] System emulation phase started on ""$(date)""\\n""$(indent "${NC}""Firmware path: ""${FIRMWARE_PATH}")" "no_log"
fi
write_notification "System emulation phase started"

write_grep_log "$(date)" "TIMESTAMP"
# these modules are not threaded!
run_modules "L" "0" "${HTML}"

print_ln "no_log"
if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] System emulation phase ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "main"
else
print_output "[!] System emulation ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "no_log"
fi
write_notification "System emulation phase ended"
fi

会使用单线程运行。并运行所有L开头的模块。
报告模块(F模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#######################################################################################
# Reporting (F-modules)
#######################################################################################
if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] Reporting phase started on ""$(date)""\\n" "main"
else
print_output "[!] Reporting phase started on ""$(date)""\\n" "no_log"
fi
write_notification "Reporting phase started"

run_modules "F" "0" "${HTML}"

[[ ${DISABLE_STATUS_BAR} -eq 0 ]] && remove_status_bar

write_notification "Reporting phase ended"

if [[ "${TESTING_DONE}" -eq 1 ]]; then
if [[ "${FINAL_FW_RM}" -eq 1 && -d "${LOG_DIR}"/firmware ]]; then
print_output "[*] Removing temp firmware directory\\n" "no_log"
rm -r "${LOG_DIR}"/firmware 2>/dev/null
fi
if [[ "${FINAL_FW_RM}" -eq 1 && -d "${LOG_DIR}"/p61_unblob_eval/unblob_extracted ]]; then
print_output "[*] Removing unblob firmware directory\\n" "no_log"
rm -r "${LOG_DIR}"/p61_unblob_eval/unblob_extracted 2>/dev/null
fi
print_ln "no_log"
if [[ -d "${LOG_DIR}" ]]; then
print_output "[!] Test ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "main"
write_notification "EMBA finished analysis"
rm -r "${TMP_DIR}" 2>/dev/null || true
else
print_output "[!] Test ended on ""$(date)"" and took about ""$(date -d@"${SECONDS}" -u +%H:%M:%S)"" \\n" "no_log"
fi
write_grep_log "$(date)" "TIMESTAMP"
write_grep_log "$(date -d@"${SECONDS}" -u +%H:%M:%S)" "DURATION"
else
print_output "[!] No extracted firmware found" "no_log"
print_output "$(indent "Try using binwalk or something else to extract the firmware")"
exit 1
fi

[[ "${HTML}" -eq 1 ]] && update_index

if [[ -f "${HTML_PATH}"/index.html ]] && [[ "${IN_DOCKER}" -eq 0 ]]; then
print_output "[*] Web report created HTML report in ${ORANGE}${LOG_DIR}/html-report${NC}\\n" "main"
print_output "[*] Open the web-report with${ORANGE} firefox $(abs_path "${HTML_PATH}/index.html")${NC}\\n" "main"
fi

# we need to change the permissions of the LOG_DIR to the orig. user from the host
[[ "${IN_DOCKER}" -eq 1 ]] && restore_permissions
cleaner 0

单线程运行”F”模块的程序,用于处理测试结果并生成报告。如果指定了-B参数则删除状态栏。
在测试结束后,如果指定了-r参数则将产生的临时文件夹和文件删除(只有运行了S模块才会产生)。
总结:主运行文件将检查参数等,并调用各个模块:

  • 预检查模块(P 模块)
  • 固件检查模块(S模块)
  • 实时模拟模块(L模块)
  • 报告模块(F模块)

因此各个模块源代码的审计将是理解固件分析实现的重点。

主要模块梳理

预检查模块(P模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
P02_firmware_bin_file_check.sh	给出有关所提供的固件二进制文件的一些非常基本的信息。
P05_patools_init.sh 用patools工具提取zip、tar、tgz
P10_vmdk_extractor.sh 提取vmdk镜像
P11_dlink_SHRS_enc_extract.sh 提取D-link加密固件镜像
P12_avm_freetz_ng_extract.sh 使用Freetz-NG提取AVM固件映像
P13_uboot_mkimage.sh 显示Uboot映像的内部
P14_ext_mounter.sh 挂载和提取extX映像
P15_ubi_extractor.sh 提取ubi文件系统
P16_EnGenius_decryptor.sh 用EnGenius提取固件镜像
P17_gpg_decompress.sh 提取没有加密的gpg压缩的固件镜像
P18_qnap_decryptor.sh 从QNAP中提取加密固件映像
P19_bsd_ufs_mounter.sh 挂载和提取BSD UFS映像
P20_foscam_decryptor.sh 提取Foscam公司的加密固件映像
P21_buffalo_decryptor.sh 提取Buffalo公司的加密固件映像
P22_Zyxel_zip_decrypt.sh 提取被密码保护的Zyxel固件镜像
P23_qemu_qcow_mounter.sh 挂载和提取Qemu QCOW2镜像
P25_android_ota.sh 提取Android OTA更新文件
P35_UEFI_extractor.sh 提取UEFI镜像与BIOSUtilities
P59_binwalk_extractor.sh 分析固件与binwalk,检查熵和提取固件到日志目录
P60_firmware_bin_extractor.sh 和上面一样
P65_package_extractor.sh 识别和提取典型的软件包档案,如deb, apk, ipk
P70_unblob.sh 将带有unblob的固件提取到模块日志目录(仅用于评估)
P99_prepare_analyzer.sh 一些准备工作(检查固件、架构检查等)

固件检查模块(S模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
S02_UEFI_FwHunt.sh	使用FwHunt来识别可能的UEFI固件中的漏洞
S03_firmware_bin_base_analyzer.sh 对操作系统或架构进行识别
S05_firmware_details.sh 统计可执行文件
S06_distribution_identification.sh 识别发行版
S08_package_mgmt_extractor.sh 搜索已知位置的包管理信息
S09_firmware_base_version_check.sh 搜索二进制文件版本
S100_command_inj_check.sh 搜索web文件并搜索可以代码执行的东西
S106_deep_key_search.sh 搜素文件里的特殊字符串
S107_deep_password_search.sh 搜索文件里的特殊密码
S108_stacs_password_search.sh 使用stacs搜索密码
S109_jtr_local_pw_cracking.sh 破解文件哈希
S10_binaries_basic_check.sh 搜索二进制的风险
S110_yara_check.sh 用yara搜索
S115_usermode_emulator.sh 模拟执行固件获取版本信息(实验功能,需要-E单独开启)
S116_qemu_version_detection.sh 该模块从S115的结果中提取版本信息
S120_cwe_checker.sh 用Ghidra扫描二进制文件中的常见风险
S12_binary_protection.sh 搜索二进制文件的保护机制
S13_weak_func_check.sh 和S10类似
S14_weak_func_radare_check.sh 和S10类似,使用radare
S15_bootloader_check.sh 扫描设备树等
S17_apk_check.sh 识别apk包并用APKHunt扫漏洞
S20_shell_check.sh 扫描shell脚本的漏洞
S21_python_check.sh 扫描python脚本的漏洞
S22_php_check.sh 扫描php脚本的漏洞和php配置文件
S24_kernel_bin_identifier.sh 识别内核文件
S25_kernel_check.sh 扫漏洞,包括版本、描述、内核配置等
S26_kernel_vuln_verifier.sh 内核漏洞识别
S35_http_file_check.sh 搜索http相关文件
S36_lighttpd.sh 识别lighttpd配置文件等
S40_weak_perm_check.sh 检查系统用户相关配置、密码文件的风险
S45_pass_file_check.sh 检查密码相关文件
S50_authentication_check.sh 检查身份验证等
S55_history_file_check.sh 检查历史文件
S60_cert_file_check.sh 检查证书文件
S65_config_file_check.sh 扫描典型的配置文件
S70_hidden_file_check.sh 隐藏文件搜索
S75_network_check.sh 检查网络相关的配置文件
S80_cronjob_check.sh 检查所有定时配置文件
S85_ssh_check.sh 检查ssh相关配置文件
S90_mail_check.sh 搜素邮箱文件
S95_interesting_binaries_check.sh 搜索一些二进制文件
S99_grepit.sh 用grepit扫风险

实时模拟模块(L模块)

1
2
3
4
5
6
7
8
L10_system_emulation	文件夹
L10_system_emulation.sh 基于firmadyne和firmAE进行固件模拟(实验模块,需要-Q单独激活。)
L15_emulated_checks_nmap.sh 测试是否成功模拟
L20_snmp_checks.sh 同上
L25_web_checks.sh 测试模拟的web服务
L30_routersploit.sh 测试
L35_metasploit_check.sh 用metasploit测试
L99_cleanup.sh 停止并清理模拟环境

报告模块(F模块)

1
2
3
4
F10_license_summary.sh	收集许可证信息
F20_vul_aggregator.sh 收集漏洞
F21_cyclonedx_sbom.sh 生成json
F50_base_aggregator.sh 生成一个所有模块的概览

预检查模块(P模块)代码分析

P02_firmware_bin_file_check.sh

给出有关所提供的固件二进制文件的一些非常基本的信息。
主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
P02_firmware_bin_file_check() {
module_log_init "${FUNCNAME[0]}"
module_title "Binary firmware file analyzer"

set_p02_default_exports

local FILE_BIN_OUT
# we set this var global to 1 if we find something UEFI related
export UEFI_DETECTED=0

write_csv_log "Entity" "data" "Notes"
write_csv_log "Firmware path" "$FIRMWARE_PATH" "NA"
if [[ -f "$FIRMWARE_PATH" ]]; then

# 计算固件文件哈希
SHA512_CHECKSUM="$(sha512sum "$FIRMWARE_PATH" | awk '{print $1}')"
write_csv_log "SHA512" "${SHA512_CHECKSUM:-}" "NA"
SHA1_CHECKSUM="$(sha1sum "$FIRMWARE_PATH" | awk '{print $1}')"
write_csv_log "SHA1" "${SHA1_CHECKSUM:-}" "NA"
MD5_CHECKSUM="$(md5sum "$FIRMWARE_PATH" | awk '{print $1}')"
write_csv_log "MD5" "${MD5_CHECKSUM:-}" "NA"

# 使用 ent 计算文件的熵信息
ENTROPY="$(ent "$FIRMWARE_PATH" | grep Entropy | sed -e 's/^Entropy\ \=\ //')"
write_csv_log "Entropy" "${ENTROPY:-}" "NA"

print_output "[*] Entropy testing with binwalk ... "
# we have to change the working directory for binwalk, because everything except the log directory is read-only in
# Docker container and binwalk fails to save the entropy picture there
if [[ $IN_DOCKER -eq 1 ]] ; then
cd "$LOG_DIR" || return

# 使用 binwalk 提取固件,并输出 json 的结果
print_output "$(binwalk -E -F -J "$FIRMWARE_PATH")"
mv "$(basename "$FIRMWARE_PATH".png)" "$LOG_DIR"/firmware_entropy.png 2> /dev/null || true
cd /emba || return
else
print_output "$(binwalk -E -F -J "$FIRMWARE_PATH")"
mv "$(basename "$FIRMWARE_PATH".png)" "$LOG_DIR"/firmware_entropy.png 2> /dev/null || true
fi
fi

local FILE_LS_OUT
FILE_LS_OUT=$(ls -lh "$FIRMWARE_PATH")

print_ln
print_output "[*] Details of the firmware file:"
print_ln
print_output "$(indent "$FILE_LS_OUT")"
print_ln
if [[ -f "$FIRMWARE_PATH" ]]; then
print_ln

# 使用 file 命令提取基本信息
print_output "$(indent "$(file "$FIRMWARE_PATH")")"
print_ln

# 使用 hexdump 输出文件前面的十六进制和ASCII信息
hexdump -C "$FIRMWARE_PATH"| head | tee -a "$LOG_FILE" || true
print_ln
print_output "[*] SHA512 checksum: $ORANGE$SHA512_CHECKSUM$NC"
print_ln
print_output "$(indent "$ENTROPY")"
print_ln
if [[ -x "$EXT_DIR"/pixde ]]; then

# 可视化二进制文件
print_output "[*] Visualized firmware file (first 2000 bytes):\n"
"$EXT_DIR"/pixde -r-0x2000 "$FIRMWARE_PATH" | tee -a "$LOG_DIR"/p02_pixd.txt
print_ln

python3 "$EXT_DIR"/pixd_png.py -i "$LOG_DIR"/p02_pixd.txt -o "$LOG_DIR"/pixd.png -p 10 > /dev/null
write_link "$LOG_DIR"/pixd.png
fi

# 对二进制文件进行检测(将在下方进行分析)
fw_bin_detector "$FIRMWARE_PATH"

# 备份所有的变量
backup_p02_vars
fi

module_end_log "${FUNCNAME[0]}" 1
}

fw_bin_detector函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
fw_bin_detector() {

# 固件路径
local CHECK_FILE="${1:-}"
local FILE_BIN_OUT=""
local DLINK_ENC_CHECK=""
local QNAP_ENC_CHECK=""
local AVM_CHECK=0
local UEFI_CHECK=0

set_p02_default_exports

FILE_BIN_OUT=$(file "$CHECK_FILE")

# 输出第一行用于检查是否为DLINK公司产品
DLINK_ENC_CHECK=$(hexdump -C "$CHECK_FILE" | head -1 || true)

# 从固件中所有的字符串中检查AVM正则出现的行数。用于判断固件是否为AVM公司产品。
AVM_CHECK=$(strings "$CHECK_FILE" | grep -c "AVM GmbH .*. All rights reserved.\|(C) Copyright .* AVM" || true)

# 搜索字符串并只显示包含指定字符串的结果,用于判断是否为QNAP加密文件系统
QNAP_ENC_CHECK=$(binwalk -y "qnap encrypted" "$CHECK_FILE")
# we are running binwalk on the file to analyze the output afterwards:
binwalk "$CHECK_FILE" > "$TMP_DIR"/s02_binwalk_output.txt
UEFI_CHECK=$(grep -c "UEFI" "$TMP_DIR"/s02_binwalk_output.txt || true)
UEFI_CHECK=$(( "$UEFI_CHECK" + "$(grep -c "UEFI" "$CHECK_FILE" || true)" ))

if [[ -f "$KERNEL_CONFIG" ]] && [[ "$KERNEL" -eq 1 ]]; then
# we set the FIRMWARE_PATH to the kernel config path if we have only -k parameter
if [[ "$(md5sum "$KERNEL_CONFIG" | awk '{print $1}')" == "$(md5sum "$FIRMWARE_PATH" | awk '{print $1}')" ]]; then
print_output "[+] Identified Linux kernel configuration file"
write_csv_log "kernel config" "yes" "NA"
export SKIP_PRE_CHECKERS=1
return
fi
fi

if [[ "$UEFI_CHECK" -gt 0 ]]; then
print_output "[+] Identified possible UEFI firmware - using fwhunt-scan vulnerability scanning module"
export UEFI_DETECTED=1
UEFI_AMI_CAPSULE=$(grep -c "AMI.*EFI.*capsule" "$TMP_DIR"/s02_binwalk_output.txt || true)
if [[ "$UEFI_AMI_CAPSULE" -gt 0 ]]; then
print_output "[+] Identified possible UEFI-AMI capsule firmware - using capsule extractors"
fi
write_csv_log "UEFI firmware detected" "yes" "NA"
fi
if [[ "$AVM_CHECK" -gt 0 ]] || [[ "$FW_VENDOR" == *"AVM"* ]]; then
print_output "[+] Identified AVM firmware - using AVM extraction module"
export AVM_DETECTED=1
write_csv_log "AVM firmware detected" "yes" "NA"
fi
# if we have a zip, tgz, tar archive we are going to use the patools extractor
if [[ "$FILE_BIN_OUT" == *"gzip compressed data"* || "$FILE_BIN_OUT" == *"Zip archive data"* || \
"$FILE_BIN_OUT" == *"POSIX tar archive"* || "$FILE_BIN_OUT" == *"ISO 9660 CD-ROM filesystem data"* || \
"$FILE_BIN_OUT" == *"7-zip archive data"* || "$FILE_BIN_OUT" == *"XZ compressed data"* || \
"$FILE_BIN_OUT" == *"bzip2 compressed data"* ]]; then
# as the AVM images are also zip files we need to bypass it here:
if [[ "$AVM_DETECTED" -ne 1 ]]; then
print_output "[+] Identified gzip/zip/tar/iso/xz/bzip2 archive file - using patools extraction module"
export PATOOLS_INIT=1
write_csv_log "basic compressed (patool)" "yes" "NA"
fi
fi
if [[ "$FILE_BIN_OUT" == *"QEMU QCOW2 Image"* ]]; then
print_output "[+] Identified Qemu QCOW image - using QCOW extraction module"
export QCOW_DETECTED=1
write_csv_log "Qemu QCOW firmware detected" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"VMware4 disk image"* ]]; then
print_output "[+] Identified VMWware VMDK archive file - using VMDK extraction module"
export VMDK_DETECTED=1
write_csv_log "VMDK" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"UBI image"* ]]; then
print_output "[+] Identified UBI filesystem image - using UBI extraction module"
export UBI_IMAGE=1
write_csv_log "UBI filesystem" "yes" "NA"
fi

# 识别DLINK固件
if [[ "$DLINK_ENC_CHECK" == *"SHRS"* ]]; then
print_output "[+] Identified D-Link SHRS encrpyted firmware - using D-Link extraction module"
export DLINK_ENC_DETECTED=1
write_csv_log "D-Link SHRS" "yes" "NA"
fi
if [[ "$DLINK_ENC_CHECK" =~ 00000000\ \ 00\ 00\ 00\ 00\ 00\ 00\ 0.\ ..\ \ 00\ 00\ 0.\ ..\ 31\ 32\ 33\ 00 ]]; then
print_output "[+] Identified EnGenius encrpyted firmware - using EnGenius extraction module"
export ENGENIUS_ENC_DETECTED=1
write_csv_log "EnGenius encrypted" "yes" "NA"
fi
if [[ "$DLINK_ENC_CHECK" =~ 00000000\ \ 00\ 00\ 00\ 00\ 00\ 00\ 01\ 01\ \ 00\ 00\ 0.\ ..\ 33\ 2e\ 3[89]\ 2e ]]; then
print_output "[+] Identified EnGenius encrpyted firmware - using EnGenius extraction module"
export ENGENIUS_ENC_DETECTED=1
write_csv_log "EnGenius encrypted" "yes" "NA"
fi
if [[ "$DLINK_ENC_CHECK" == *"encrpted_img"* ]]; then
print_output "[+] Identified D-Link encrpted_img encrpyted firmware - using D-Link extraction module"
export DLINK_ENC_DETECTED=2
write_csv_log "D-Link encrpted_img encrypted" "yes" "NA"
fi

# 识别u-boot固件
if [[ "$FILE_BIN_OUT" == *"u-boot legacy uImage"* ]]; then
print_output "[+] Identified u-boot firmware - using u-boot module"
export UBOOT_IMAGE=1
write_csv_log "Uboot image" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"Unix Fast File system [v2]"* ]]; then
print_output "[+] Identified UFS filesytem - using UFS filesytem extraction module"
export BSD_UFS=1
write_csv_log "BSD UFS filesystem" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"Linux rev 1.0 ext2 filesystem data"* ]]; then
print_output "[+] Identified Linux ext2 filesytem - using EXT filesytem extraction module"
export EXT_IMAGE=1
write_csv_log "EXT2 filesystem" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"Linux rev 1.0 ext3 filesystem data"* ]]; then
print_output "[+] Identified Linux ext3 filesytem - using EXT filesytem extraction module"
export EXT_IMAGE=1
write_csv_log "EXT3 filesystem" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"Linux rev 1.0 ext4 filesystem data"* ]]; then
print_output "[+] Identified Linux ext4 filesytem - using EXT filesytem extraction module"
export EXT_IMAGE=1
write_csv_log "EXT4 filesystem" "yes" "NA"
fi
if [[ "$QNAP_ENC_CHECK" == *"QNAP encrypted firmware footer , model"* ]]; then
print_output "[+] Identified QNAP encrpyted firmware - using QNAP extraction module"
export QNAP_ENC_DETECTED=1
write_csv_log "QNAP encrypted filesystem" "yes" "NA"
fi
# probably we need to take a deeper look to identify the gpg compressed firmware files better.
# Currently this detection mechanism works quite good on the known firmware images
if [[ "$DLINK_ENC_CHECK" =~ 00000000\ \ a3\ 01\ ]]; then
GPG_CHECK="$(gpg --list-packets "$FIRMWARE_PATH" | grep "compressed packet:")"
if [[ "$GPG_CHECK" == *"compressed packet: algo="* ]]; then
print_output "[+] Identified GPG compressed firmware - using GPG extraction module"
export GPG_COMPRESS=1
write_csv_log "GPG compressed firmware" "yes" "NA"
fi
fi
if [[ "$DLINK_ENC_CHECK" == *"CrAU"* ]]; then
print_output "[+] Identified Android OTA payload.bin update file - using Android extraction module"
export ANDROID_OTA=1
write_csv_log "Android OTA update" "yes" "NA"
fi
if [[ "$FILE_BIN_OUT" == *"openssl enc'd data with salted password"* ]]; then
print_output "[+] Identified OpenSSL encrypted file - trying OpenSSL module for Foscam firmware"
export OPENSSL_ENC_DETECTED=1
write_csv_log "OpenSSL encrypted" "yes" "NA"
fi
# This check is currently only tested on one firmware - further tests needed:
if [[ "$DLINK_ENC_CHECK" =~ 00000000\ \ 62\ 67\ 6e\ 00\ 00\ 00\ 00\ 00\ \ 00\ 00\ 00\ b9\ 01\ ]]; then
print_output "[+] Identified Buffalo encrpyted firmware - using Buffalo extraction module"
export BUFFALO_ENC_DETECTED=1
write_csv_log "Buffalo encrypted" "yes" "NA"
fi
if [[ "$(basename "$CHECK_FILE")" =~ .*\.ri ]] && [[ "$FILE_BIN_OUT" == *"data"* ]]; then
# ri files are usually used by zyxel
if [[ $(find "$LOG_DIR"/firmware -name "$(basename -s .ri "$CHECK_FILE")".bin | wc -l) -gt 0 ]]; then
# if we find a bin file with the same name then it is a Zyxel firmware image
print_output "[+] Identified ZyXel encrpyted ZIP firmware - using ZyXel extraction module"
export ZYXEL_ZIP=1
write_csv_log "ZyXel encrypted ZIP" "yes" ""
fi
fi
print_ln
}

P05_patools_init.sh

用patools工具提取zip、tar、tgz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
export PRE_THREAD_ENA=0

P05_patools_init() {
local NEG_LOG=0

if [[ "$PATOOLS_INIT" -eq 1 ]]; then
module_log_init "${FUNCNAME[0]}"
module_title "Initial extractor of different archive types via patools"
pre_module_reporter "${FUNCNAME[0]}"

EXTRACTION_DIR="$LOG_DIR"/firmware/patool_extraction/

patools_extractor "$FIRMWARE_PATH" "$EXTRACTION_DIR"

if [[ "$FILES_PATOOLS" -gt 0 ]]; then
MD5_DONE_DEEP+=( "$(md5sum "$FIRMWARE_PATH" | awk '{print $1}')" )
export FIRMWARE_PATH="$LOG_DIR"/firmware/
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
fi

NEG_LOG=1
module_end_log "${FUNCNAME[0]}" "$NEG_LOG"
fi
}

patools_extractor() {
sub_module_title "Patool filesystem extractor"

local FIRMWARE_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
FILES_PATOOLS=0
local DIRS_PATOOLS=0
local FIRMWARE_NAME_=""

if ! [[ -f "$FIRMWARE_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

FIRMWARE_NAME_="$(basename "$FIRMWARE_PATH_")"

[[ "$STRICT_MODE" -eq 1 ]] && set +e

# 使用patool测试
patool -v test "$FIRMWARE_PATH_" | tee -a "$LOG_PATH_MODULE"/paextract_test_"$FIRMWARE_NAME_".log

[[ "$STRICT_MODE" -eq 1 ]] && set -e

cat "$LOG_PATH_MODULE"/paextract_test_"$FIRMWARE_NAME_".log >> "$LOG_FILE"

if ! [[ -d "$EXTRACTION_DIR_" ]]; then
mkdir "$EXTRACTION_DIR_"
fi

if grep -q "patool: ... tested ok." "$LOG_PATH_MODULE"/paextract_test_"$FIRMWARE_NAME_".log ; then

print_ln
print_output "[*] Valid compressed file detected - extraction process via patool started"

# 提取
patool -v extract "$FIRMWARE_PATH_" --outdir "$EXTRACTION_DIR_" | tee -a "$LOG_PATH_MODULE"/paextract_extract_"$FIRMWARE_NAME_".log
cat "$LOG_PATH_MODULE"/paextract_extract_"$FIRMWARE_NAME_".log >> "$LOG_FILE"

else
# Fallback if unzip does not work:
print_ln
print_output "[*] No valid compressed file detected - extraction process via binwalk started"

# P60里的函数,下方给出分析
binwalk_deep_extract_helper 0 "$FIRMWARE_PATH_" "$EXTRACTION_DIR_"
fi

print_ln
print_output "[*] Using the following firmware directory ($ORANGE$EXTRACTION_DIR_$NC) as base directory:"
find "$EXTRACTION_DIR_" -xdev -maxdepth 1 -ls | tee -a "$LOG_FILE"
print_ln

FILES_PATOOLS=$(find "$EXTRACTION_DIR_" -type f | wc -l)
DIRS_PATOOLS=$(find "$EXTRACTION_DIR_" -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_PATOOLS$NC files and $ORANGE$DIRS_PATOOLS$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "Patool extractor" "$FIRMWARE_PATH_" "$EXTRACTION_DIR_" "$FILES_PATOOLS" "$DIRS_PATOOLS" "NA"
print_ln
}

binwalk_deep_extract_helper函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
binwalk_deep_extract_helper() {
# Matryoshka mode is first parameter: 1 - enable, 0 - disable
local MATRYOSHKA_="${1:-0}"
local FILE_TO_EXTRACT_="${2:-}"
local DEST_FILE_="${3:-}"

if ! [[ -f "$FILE_TO_EXTRACT_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

# 前面通过检查binwalk的版本,使用不同方法提取
if [[ "$BINWALK_VER_CHECK" == 1 ]]; then
if [[ "$MATRYOSHKA_" -eq 1 ]]; then

# 使用binwalk提取
binwalk --run-as=root --preserve-symlinks --dd='.*' -e -M -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
else
# no more Matryoshka mode ... we are doing it manually and check the files every round via MD5
binwalk --run-as=root --preserve-symlinks --dd='.*' -e -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
fi
else
if [[ "$MATRYOSHKA_" -eq 1 ]]; then
binwalk --dd='.*' -e -M -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
else
binwalk --dd='.*' -e -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
fi
fi
}

P10_vmdk_extractor.sh

提取vmdk镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
P10_vmdk_extractor() {
local NEG_LOG=0

if [[ "${VMDK_DETECTED-0}" -eq 1 ]]; then
module_log_init "${FUNCNAME[0]}"
module_title "VMDK (Virtual Machine Disk) extractor"
EXTRACTION_DIR="$LOG_DIR"/firmware/vmdk_extractor/

# 提取vmdk
vmdk_extractor "$FIRMWARE_PATH" "$EXTRACTION_DIR"

if [[ "$VMDK_FILES" -gt 0 ]]; then
MD5_DONE_DEEP+=( "$(md5sum "$FIRMWARE_PATH" | awk '{print $1}')" )
export FIRMWARE_PATH="$LOG_DIR"/firmware/
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
fi
NEG_LOG=1
module_end_log "${FUNCNAME[0]}" "$NEG_LOG"
fi
}

vmdk_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
vmdk_extractor() {
local VMDK_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
local MOUNT_DEV=""
local DEV_NAME=""
local TMP_VMDK_MNT="$TMP_DIR/vmdk_mount_$RANDOM"
local VMDK_DIRS=0
local RET=0
VMDK_FILES=0

if ! [[ -f "$VMDK_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

# 设置子模块title
sub_module_title "VMDK (Virtual Machine Disk) extractor"

print_output "[*] Enumeration of devices in VMDK images $ORANGE$VMDK_PATH_$NC"
disable_strict_mode "$STRICT_MODE" 0

# 使用virt工具输出文件系统信息
virt-filesystems -a "$VMDK_PATH_" > "$TMP_DIR"/vmdk.log
RET="$?"

if [[ "$RET" -ne 0 ]]; then

# 用7z解压vmdk(7z挺强大)
# backup with 7z
7z x -o"$EXTRACTION_DIR_" "$VMDK_PATH_"

# 获取7z结果返回的状态码,并进行错误处理
RET="$?"
if [[ "$RET" -ne 0 ]]; then
print_output "[-] WARNING: VMDK filesystem not enumerated"
enable_strict_mode "$STRICT_MODE" 0
return
fi
else
mapfile -t VMDK_VIRT_FS < "$TMP_DIR"/vmdk.log
for MOUNT_DEV in "${VMDK_VIRT_FS[@]}"; do
print_output "[*] Found device $ORANGE$MOUNT_DEV$NC"
done
fi
enable_strict_mode "$STRICT_MODE" 0

mkdir -p "$TMP_VMDK_MNT" || true

for MOUNT_DEV in "${VMDK_VIRT_FS[@]}"; do
DEV_NAME=$(basename "$MOUNT_DEV")

# 尝试挂载vmdk
print_output "[*] Trying to mount $ORANGE$MOUNT_DEV$NC to $ORANGE$TMP_VMDK_MNT$NC directory"
# if troubles ahead with vmdk mount, remove the error redirection
guestmount -a "$VMDK_PATH_" -m "$MOUNT_DEV" --ro "$TMP_VMDK_MNT" 2>/dev/null || true
if mount | grep -q vmdk_mount; then
print_output "[*] Copying $ORANGE$MOUNT_DEV$NC to firmware directory $ORANGE$EXTRACTION_DIR_/$DEV_NAME$NC"
mkdir -p "$EXTRACTION_DIR_"/"$DEV_NAME"/ || true
cp -pri "$TMP_VMDK_MNT"/* "$EXTRACTION_DIR_"/"$DEV_NAME"/ || true
umount "$TMP_VMDK_MNT"
fi
done

# 统计提取出的文件和文件夹数量
if [[ -d "$EXTRACTION_DIR_" ]]; then
VMDK_FILES=$(find "$EXTRACTION_DIR_" -type f | wc -l)
VMDK_DIRS=$(find "$EXTRACTION_DIR_" -type d | wc -l)
fi

# 输出结果
if [[ "$VMDK_FILES" -gt 0 ]]; then
print_ln
print_output "[*] Extracted $ORANGE$VMDK_FILES$NC files and $ORANGE$VMDK_DIRS$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "VMDK extractor" "$VMDK_PATH_" "$EXTRACTION_DIR_" "$VMDK_FILES" "$VMDK_DIRS" "NA"
# currently unblob has issues with VMDKs. We need to disable it for this extraction process
safe_echo 0 > "$TMP_DIR"/unblob_disable.cfg
fi

# 删除临时用于挂载的文件夹
rm -r "$TMP_VMDK_MNT" || true
}

提取D-link加密固件镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
P11_dlink_SHRS_enc_extract() {
local NEG_LOG=0

if [[ "$DLINK_ENC_DETECTED" -ne 0 ]] && [[ "$MODULE_DISABLED" -ne 1 ]]; then
module_log_init "${FUNCNAME[0]}"
module_title "DLink encrypted firmware extractor"
pre_module_reporter "${FUNCNAME[0]}"
EXTRACTION_FILE="$LOG_DIR"/firmware/firmware_dlink_dec.bin

# 有不同的加密方式,使用对应的方式解密
if [[ "$DLINK_ENC_DETECTED" -eq 1 ]]; then
dlink_SHRS_enc_extractor "$FIRMWARE_PATH" "$EXTRACTION_FILE"n=
elif [[ "$DLINK_ENC_DETECTED" -eq 2 ]]; then
dlink_enc_img_extractor "$FIRMWARE_PATH" "$EXTRACTION_FILE"
fi

NEG_LOG=1
module_end_log "${FUNCNAME[0]}" "$NEG_LOG"
fi
}

dlink_SHRS_enc_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
dlink_SHRS_enc_extractor() {
local DLINK_ENC_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"
if ! [[ -f "$DLINK_ENC_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi

local MODULE_DISABLED=1

if [[ "$MODULE_DISABLED" -eq 1 ]]; then
print_output "[*] Module ${FUNCNAME[0]} is deprecated and will be removed in the future"
return
fi

sub_module_title "DLink encrypted firmware extractor"

hexdump -C "$DLINK_ENC_PATH_" | head | tee -a "$LOG_FILE" || true

print_ln

# 使用DLINK密钥对固件进行AES解密
# 参考:https://www.bleepingcomputer.com/news/security/d-link-blunder-firmware-encryption-key-exposed-in-unencrypted-image/
dd if="$DLINK_ENC_PATH_" skip=1756 iflag=skip_bytes|openssl aes-128-cbc -d -p -nopad -nosalt -K "c05fbf1936c99429ce2a0781f08d6ad8" -iv "67c6697351ff4aec29cdbaabf2fbe346" --nosalt -in /dev/stdin -out "$EXTRACTION_FILE_" 2>&1 || true | tee -a "$LOG_FILE"

print_ln
if [[ -f "$EXTRACTION_FILE_" ]]; then
print_output "[+] Decrypted D-Link firmware file to $ORANGE$EXTRACTION_FILE_$NC"
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "DLink SHRS decryptor" "$DLINK_ENC_PATH_" "$EXTRACTION_FILE_" "1" "NA" "NA"
export FIRMWARE_PATH="$EXTRACTION_FILE_"
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="D-Link"
backup_var "FW_VENDOR" "$FW_VENDOR"
fi
else
print_output "[-] Decryption of D-Link firmware file failed"
fi
}

dlink_enc_img_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
dlink_enc_img_extractor(){
local TMP_DIR="$LOG_DIR""/tmp"
local DLINK_ENC_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"
local TMP_IMAGE_FILE="$TMP_DIR/image.bin"
if ! [[ -f "$DLINK_ENC_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi
local IMAGE_SIZE=0
local OFFSET=0
local ITERATION=0

local MODULE_DISABLED=1

if [[ "$MODULE_DISABLED" -eq 1 ]]; then
print_output "[*] Module ${FUNCNAME[0]} is deprecated and will be removed in the future"
return
fi

sub_module_title "DLink encrpted_image extractor"

hexdump -C "$DLINK_ENC_PATH_" | head | tee -a "$LOG_FILE" || true

# 从偏移量为 16 的位置开始复制到另外一个文件 $TMP_IMAGE_FILE 中
dd if="$DLINK_ENC_PATH_" skip=16 iflag=skip_bytes of="$TMP_IMAGE_FILE" 2>&1 | tee -a "$LOG_FILE"

IMAGE_SIZE=$(stat -c%s "$TMP_IMAGE_FILE")
(( ROOF=IMAGE_SIZE/131072 ))

# 每 131072(128KB) 为一块进行处理
for ((ITERATION=0; ITERATION<ROOF; ITERATION++)); do
if [[ "$ITERATION" -eq 0 ]]; then
OFFSET=0
else
(( OFFSET=131072*ITERATION ))
fi

# aes解密
dd if="$TMP_IMAGE_FILE" skip="$OFFSET" iflag=skip_bytes count=256| openssl aes-256-cbc -d -in /dev/stdin -out /dev/stdout \
-K "6865392d342b4d212964363d6d7e7765312c7132613364316e26322a5a5e2538" -iv "4a253169516c38243d6c6d2d3b384145" --nopad \
--nosalt | dd if=/dev/stdin of="$EXTRACTION_FILE_" oflag=append conv=notrunc 2>&1 | tee -a "$LOG_FILE"
done
# Now it should be a .ubi file thats somewhat readable and extractable via ubireader
print_ln
if [[ -f "$EXTRACTION_FILE_" ]]; then
UBI_OUT=$(file "$EXTRACTION_FILE_")
if [[ "$UBI_OUT" == *"UBI image, version"* ]]; then
print_output "[+] Decrypted D-Link firmware file to $ORANGE$EXTRACTION_FILE_$NC"
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "DLink enc_img decryptor" "$DLINK_ENC_PATH_" "$EXTRACTION_FILE_" "1" "NA" "NA"
export FIRMWARE_PATH="$EXTRACTION_FILE_"
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="D-Link"
backup_var "FW_VENDOR" "$FW_VENDOR"
fi
EXTRACTION_DIR="$LOG_DIR/firmware/dlink_ubi_extracted"
mkdir -p "$EXTRACTION_DIR" || true

# ubi提取
ubi_extractor "$FIRMWARE_PATH" "$EXTRACTION_DIR"
else
print_output "[-] Further extraction of D-Link firmware file via deep extraction"
fi
else
print_output "[-] Decryption of D-Link firmware file failed"
fi
}

P12_avm_freetz_ng_extract.sh

使用Freetz-NG提取AVM固件映像
avm_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
avm_extractor() {
local AVM_FW_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
if ! [[ -f "$AVM_FW_PATH_" ]]; then
return
fi
local FRITZ_DIRS=0
local FIT_IMAGES=()
local FIT_IMAGE=""
local RAM_DISKS=()
local RAM_DISK=""
local RAM_DISK_NAME=""
export FRITZ_FILE=0
export FRITZ_VERSION=""

local MODULE_DISABLED=1

if [[ "$MODULE_DISABLED" -eq 1 ]]; then
print_output "[*] Module ${FUNCNAME[0]} is deprecated and will be removed in the future"
return
fi

sub_module_title "AVM freetz-ng firmware extractor"

# read only filesystem bypass:
# 将 freetz-ng 工具链的 .config 文件复制到临时目录中
cp "$EXT_DIR"/freetz-ng/.config "$TMP_DIR"/.config
# 使用 freetz-ng/fwmod 工具从 AVM 固件中提取文件
"$EXT_DIR"/freetz-ng/fwmod -u -i "$TMP_DIR"/.config -d "$EXTRACTION_DIR_" "$AVM_FW_PATH_" | tee -a "$LOG_FILE" || true

if [[ -d "$EXTRACTION_DIR_" ]]; then

# FRITZ 是来自德国 AVM 公司生产的一系列家用网络设备,包括路由器、网关、电话和其他相关产品。
# 统计提取出的文件和文件夹数量
FRITZ_FILES=$(find "$EXTRACTION_DIR_" -type f | wc -l)
FRITZ_DIRS=$(find "$EXTRACTION_DIR_" -type d | wc -l)
# 提取版本信息
FRITZ_VERSION=$(grep "detected firmware version:" "$LOG_FILE" | cut -d ":" -f2- || true)
if [[ -z "$FRITZ_VERSION" ]]; then
FRITZ_VERSION="NA"
else
print_output "[+] Detected Fritz version: $ORANGE$FRITZ_VERSION$NC"
fi

# fitimages are handled here with fitimg - binwalk and unblob are also able to handle these images
# but it is currently more beautiful doing the AVM extraction in one place here
mapfile -t FIT_IMAGES < <(find "$EXTRACTION_DIR_" -type f -name "fit-image")
# 在提取的文件中,该函数会检测是否存在名为 "fit-image" 的文件
# 如果存在则调用 fitimg 工具对其解压缩
# 解压缩结果输出到 EXTRACTION_DIR/fit-image-extraction 目录中
# 并从中提取 RAM_DISK
if [[ "${#FIT_IMAGES[@]}" -gt 0 ]]; then
if [[ -f "$EXT_DIR"/fitimg-0.8/fitimg ]]; then
for FIT_IMAGE in "${FIT_IMAGES[@]}"; do
print_output "[*] Detected fit-image: $ORANGE$FIT_IMAGE$NC"
print_output "[*] Extracting fit-image with fitimg to $ORANGE$EXTRACTION_DIR/fit-image-extraction$NC"
mkdir -p "$EXTRACTION_DIR/fit-image-extraction"
"$EXT_DIR"/fitimg-0.8/fitimg -x "$FIT_IMAGE" -d "$EXTRACTION_DIR"/fit-image-extraction || true
mapfile -t RAM_DISKS < <(find "$EXTRACTION_DIR_"/fit-image-extraction -type f -name "*ramdisk")
print_ln
done
else
print_output "[-] Fitimg installation not available - check your installation"
fi
fi
if [[ "${#RAM_DISKS[@]}" -gt 0 ]]; then
for RAM_DISK in "${RAM_DISKS[@]}"; do
print_output "[*] Detected AVM ramdisk: $ORANGE$RAM_DISK$NC"
RAM_DISK_NAME="$(basename "$RAM_DISK")"

# 对RAM_DISK进行深度提取
binwalk_deep_extract_helper 1 "$RAM_DISK" "$EXTRACTION_DIR_"/fit-image-extraction/"$RAM_DISK_NAME"_binwalk
print_ln
done
fi

if [[ "$FRITZ_FILES" -gt 0 ]]; then
print_ln
print_output "[*] Extracted $ORANGE$FRITZ_FILES$NC files and $ORANGE$FRITZ_DIRS$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "AVM extractor" "$AVM_FW_PATH_" "$EXTRACTION_DIR_" "$FRITZ_FILES" "$FRITZ_DIRS" "$FRITZ_VERSION"
export DEEP_EXTRACTOR=1
MD5_DONE_DEEP+=( "$(md5sum "$AVM_FW_PATH_" | awk '{print $1}')" )

if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="AVM"
backup_var "FW_VENDOR" "$FW_VENDOR"
fi
if [[ -z "${FW_VERSION:-}" && "$FRITZ_VERSION" != "NA" ]]; then
FW_VERSION="$FRITZ_VERSION"
fi
fi
fi
}

P13_uboot_mkimage.sh

显示Uboot映像的内部
P13_uboot_mkimage函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
P13_uboot_mkimage() {
local NEG_LOG=0
if [[ "$UBOOT_IMAGE" -eq 1 ]]; then
module_log_init "${FUNCNAME[0]}"
local IMAGE_NAME=""
local IMAGE_TYPE=""
module_title "Uboot image details"
pre_module_reporter "${FUNCNAME[0]}"

# 使用mkimage工具读取固件
mkimage -l "$FIRMWARE_PATH" | tee -a "$LOG_FILE"
IMAGE_NAME=$(grep "Image Name" "$LOG_FILE" 2>/dev/null | awk '{print $3,$4,$5,$6,$7,$8,$9,$10}' || true)
IMAGE_TYPE=$(grep "Image Type" "$LOG_FILE" 2>/dev/null | awk '{print $3,$4,$5,$6,$7,$8,$9,$10}' || true)
write_csv_log "Identifier" "Value"
write_csv_log "ImageName" "$IMAGE_NAME"
write_csv_log "ImageType" "$IMAGE_TYPE"
NEG_LOG=1
module_end_log "${FUNCNAME[0]}" "$NEG_LOG"
fi
}

P14_ext_mounter.sh

挂载和提取extX映像
ext_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ext_extractor() {
local EXT_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
local TMP_EXT_MOUNT="$TMP_DIR""/ext_mount_$RANDOM"
local DIRS_EXT_MOUNT=0
FILES_EXT_MOUNT=0

if ! [[ -f "$EXT_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi

sub_module_title "EXT filesystem extractor"

mkdir -p "$TMP_EXT_MOUNT" || true
print_output "[*] Trying to mount $ORANGE$EXT_PATH_$NC to $ORANGE$TMP_EXT_MOUNT$NC directory"
mount -o ro "$EXT_PATH_" "$TMP_EXT_MOUNT"
if mount | grep -q ext_mount; then
print_output "[*] Copying $ORANGE$TMP_EXT_MOUNT$NC to firmware tmp directory ($EXTRACTION_DIR_)"
mkdir -p "$EXTRACTION_DIR_"
cp -pri "$TMP_EXT_MOUNT"/* "$EXTRACTION_DIR_"
print_ln
print_output "[*] Using the following firmware directory ($ORANGE$EXTRACTION_DIR_$NC) as base directory:"
find "$EXTRACTION_DIR_" -xdev -maxdepth 1 -ls | tee -a "$LOG_FILE"
print_ln
print_output "[*] Unmounting $ORANGE$TMP_EXT_MOUNT$NC directory"

FILES_EXT_MOUNT=$(find "$EXTRACTION_DIR_" -type f | wc -l)
DIRS_EXT_MOUNT=$(find "$EXTRACTION_DIR_" -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_EXT_MOUNT$NC files and $ORANGE$DIRS_EXT_MOUNT$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "EXT filesystem extractor" "$EXT_PATH_" "$EXTRACTION_DIR_" "$FILES_EXT_MOUNT" "$DIRS_EXT_MOUNT" "NA"
umount "$TMP_EXT_MOUNT" || true
fi
rm -r "$TMP_EXT_MOUNT"
}

P15_ubi_extractor.sh

提取ubi文件系统
ubi_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
ubi_extractor() {
local UBI_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
local UBI_FILE=""
local UBI_INFO=""
local UBI_1st_ROUND=""
local UBI_DATA=""
local DIRS_UBI_EXT=0
FILES_UBI_EXT=0
if ! [[ -f "$UBI_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

sub_module_title "UBI filesystem extractor"

print_output "[*] Extracts UBI firmware image $ORANGE$UBI_PATH_$NC with ${ORANGE}ubireader_extract_images$NC."
print_output "[*] File details: $ORANGE$(file "$UBI_PATH_" | cut -d ':' -f2-)$NC"

# 提取ubi镜像
# 工具仓库:https://github.com/jrspruitt/ubi_reader
ubireader_extract_images -i -v -w -o "$EXTRACTION_DIR_"/ubi_images "$UBI_PATH_" | tee -a "$LOG_FILE" || true
FILES_UBI_EXT=$(find "$EXTRACTION_DIR_"/ubi_images -type f | wc -l)
DIRS_UBI_EXT=$(find "$EXTRACTION_DIR_"/ubi_images -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_UBI_EXT$NC files and $ORANGE$DIRS_UBI_EXT$NC directories from the firmware image via UBI extraction round 1."

print_output "[*] Extracts UBI firmware image $ORANGE$UBI_PATH_$NC with ${ORANGE}ubireader_extract_files$NC."

ubireader_extract_files -i -v -w -o "$EXTRACTION_DIR_"/ubi_files "$UBI_PATH_" | tee -a "$LOG_FILE" || true
FILES_UBI_EXT=$(find "$EXTRACTION_DIR_"/ubi_files -type f | wc -l)
DIRS_UBI_EXT=$(find "$EXTRACTION_DIR_"/ubi_files -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_UBI_EXT$NC files and $ORANGE$DIRS_UBI_EXT$NC directories from the firmware image via UBI extraction round 2."

# 深度提取,继续查找UBI文件镜像,然后提取
if [[ -d "$EXTRACTION_DIR_" ]]; then
mapfile -t UBI_1st_ROUND < <(find "$EXTRACTION_DIR_" -type f -exec file {} \; | grep "UBI image" || true)

for UBI_DATA in "${UBI_1st_ROUND[@]}"; do
UBI_FILE=$(safe_echo "$UBI_DATA" | cut -d: -f1)
UBI_INFO=$(safe_echo "$UBI_DATA" | cut -d: -f2)
if [[ "$UBI_INFO" == *"UBIfs image"* ]]; then
sub_module_title "UBIfs deep extraction"
print_output "[*] Extracts UBIfs firmware image $ORANGE$UBI_PATH_$NC with ${ORANGE}ubireader_extract_files$NC."
print_output "[*] File details: $ORANGE$(file "$UBI_FILE" | cut -d ':' -f2-)$NC"
ubireader_extract_files -l -i -w -v -o "$EXTRACTION_DIR_"/UBIfs_extracted "$UBI_FILE" | tee -a "$LOG_FILE" || true
FILES_UBI_EXT=$(find "$EXTRACTION_DIR_"/UBIfs_extracted -type f | wc -l)
DIRS_UBI_EXT=$(find "$EXTRACTION_DIR_"/UBIfs_extracted -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_UBI_EXT$NC files and $ORANGE$DIRS_UBI_EXT$NC directories from the firmware image via UBI deep extraction."
fi
done

print_ln
FILES_UBI_EXT=$(find "$EXTRACTION_DIR_" -type f | wc -l)
DIRS_UBI_EXT=$(find "$EXTRACTION_DIR_" -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_UBI_EXT$NC files and $ORANGE$DIRS_UBI_EXT$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "UBI filesystem extractor" "$UBI_PATH_" "$EXTRACTION_DIR_" "$FILES_UBI_EXT" "$DIRS_UBI_EXT" "NA"
else
print_output "[-] First round UBI extractor failed!"
fi
}

P16_EnGenius_decryptor.sh

用EnGenius提取固件镜像
engenius_enc_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
engenius_enc_extractor() {
local ENGENIUS_ENC_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"

if ! [[ -f "$ENGENIUS_ENC_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi

sub_module_title "EnGenius encrypted firmware extractor"

hexdump -C "$ENGENIUS_ENC_PATH_" | head | tee -a "$LOG_FILE" || true

if [[ -f "$EXT_DIR"/engenius-decrypt.py ]]; then

# 运行解密脚本
python3 "$EXT_DIR"/engenius-decrypt.py "$ENGENIUS_ENC_PATH_" > "$EXTRACTION_FILE_"
else
print_output "[-] Decryptor not found - check your installation"
fi

print_ln
if [[ -f "$EXTRACTION_FILE_" ]]; then
print_output "[+] Decrypted EnGenius firmware file to $ORANGE$EXTRACTION_FILE_$NC"
export FIRMWARE_PATH="$EXTRACTION_FILE_"
MD5_DONE_DEEP+=( "$(md5sum "$ENGENIUS_ENC_PATH_" | awk '{print $1}')" )
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "EnGenius decryptor" "$ENGENIUS_ENC_PATH_" "$EXTRACTION_FILE_" "1" "NA" "NA"
if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="EnGenius"
backup_var "FW_VENDOR" "$FW_VENDOR"
fi
else
print_output "[-] Decryption of EnGenius firmware file failed"
fi
}

P17_gpg_decompress.sh

提取没有加密的gpg压缩的固件镜像
gpg_decompress_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
gpg_decompress_extractor() {
local GPG_FILE_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"

if ! [[ -f "$GPG_FILE_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

sub_module_title "GPG compressed firmware extractor"

# 列出包列表并解密
gpg --list-packets "$GPG_FILE_PATH_" 2>/dev/null | tee -a "$LOG_FILE"
gpg --decrypt "$GPG_FILE_PATH_" > "$EXTRACTION_FILE_" || true

print_ln
if [[ -f "$EXTRACTION_FILE_" ]]; then
print_output "[+] Extracted GPG compressed firmware file to $ORANGE$EXTRACTION_FILE_$NC"
export FIRMWARE_PATH="$EXTRACTION_FILE_"
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "GPG decompression" "$GPG_FILE_PATH_" "$EXTRACTION_FILE_" "1" "NA" "NA"
else
print_output "[-] Extraction of GPG compressed firmware file failed"
fi
}

P18_qnap_decryptor.sh

从QNAP中提取加密固件映像
首先调用 qnap_enc_extractor 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
qnap_enc_extractor() {
local QNAP_ENC_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"
export QNAP=0

if ! [[ -f "$QNAP_ENC_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi

sub_module_title "QNAP encrypted firmware extractor"

hexdump -C "$QNAP_ENC_PATH_" | head | tee -a "$LOG_FILE" || true

if [[ -f "$EXT_DIR"/PC1 ]]; then
print_ln
print_output "[*] Decrypting QNAP firmware with leaked key material ..."
print_ln

# 如果有PC1工具,则用PC1工具提取
"$EXT_DIR"/PC1 d QNAPNASVERSION4 "$QNAP_ENC_PATH_" "$EXTRACTION_FILE_" | tee -a "$LOG_FILE"
else
print_output "[-] QNAP decryptor not found - check your installation"
fi

print_ln
if [[ -f "$EXTRACTION_FILE_" && "$(file "$EXTRACTION_FILE_")" == *"gzip compressed data"* ]]; then
print_output "[+] Decrypted QNAP firmware file to $ORANGE$EXTRACTION_FILE_$NC"
MD5_DONE_DEEP+=( "$(md5sum "$QNAP_ENC_PATH_" | awk '{print $1}')" )
export FIRMWARE_PATH="$EXTRACTION_FILE_"
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"

# 如果提取出来是gzip文件,那再提取一次,使用下面的函数
export QNAP=1
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
print_ln
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "QNAP decryptor" "$QNAP_ENC_PATH_" "$EXTRACTION_FILE_" "1" "NA" "gzip compressed data"
if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="QNAP"
backup_var "FW_VENDOR" "$FW_VENDOR"
fi

else
print_output "[-] Decryption of QNAP firmware file failed"
fi
}

qnap_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
qnap_extractor() {
local DECRYPTED_FW_="${1:-}"
if ! [[ -f "$DECRYPTED_FW_" ]]; then
return
fi

sub_module_title "QNAP firmware extraction"
print_output "[!] WARNING: This module is in an very early alpha state."
print_output "[!] WARNING: Some areas of this module are not tested."

# This module is a full copy of https://github.com/max-boehm/qnap-utils/blob/master/extract_qnap_fw.sh
# some areas of this code are completely untested. Please report bugs via https://github.com/e-m-b-a/emba/issues

QNAP_EXTRACTION_ROOT="$LOG_DIR"/firmware/qnap_extraction
QNAP_EXTRACTION_ROOT_DST="$QNAP_EXTRACTION_ROOT"/root_filesystem
mkdir -p "$QNAP_EXTRACTION_ROOT_DST" || true

if file "$DECRYPTED_FW_" | grep -q ": gzip" ; then
print_output "[*] Extracting $ORANGE$DECRYPTED_FW_$NC into $ORANGE$QNAP_EXTRACTION_ROOT$NC."
mkdir -p "$QNAP_EXTRACTION_ROOT" || true

# 解压gzip文件
tar xvf "$DECRYPTED_FW_" -C "$QNAP_EXTRACTION_ROOT" 2>/dev/null || true | tee -a "$LOG_FILE"
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$QNAP_EXTRACTION_ROOT$NC):"

# 统计输出
find "$QNAP_EXTRACTION_ROOT" -xdev -ls | tee -a "$LOG_FILE"
print_files_dirs
print_bar ""
else
print_output "[-] No QNAP firmware file found"
return 1
fi

UIMAGE="$QNAP_EXTRACTION_ROOT/uImage" # x31,x31+
UBI="$QNAP_EXTRACTION_ROOT/rootfs2.ubi" # x31,x31+
IMAGE="$QNAP_EXTRACTION_ROOT_DST/image"

# initial ramdisk root filesystem
INITRAMFS="$QNAP_EXTRACTION_ROOT_DST/initramfs" # x31,x31+
INITRD="$QNAP_EXTRACTION_ROOT/initrd.boot" # x10,x12,x19,x20,x21
if [ ! -e "$INITRD" ]; then
INITRD="$QNAP_EXTRACTION_ROOT/initrd" # x51,x53
fi

ROOTFS2="$QNAP_EXTRACTION_ROOT/rootfs2.tgz"
ROOTFS2_BZ="$QNAP_EXTRACTION_ROOT/rootfs2.bz"
ROOTFS2_IMG="$QNAP_EXTRACTION_ROOT/rootfs2.img"
ROOTFS_EXT="$QNAP_EXTRACTION_ROOT/rootfs_ext.tgz"
QPKG="$QNAP_EXTRACTION_ROOT/qpkg.tar"

if [ -e "$UBI" ]; then
ROOTFS2="$QNAP_EXTRACTION_ROOT_DST/rootfs2.tgz"
ROOTFS_EXT="$QNAP_EXTRACTION_ROOT_DST/rootfs_ext.tgz"
QPKG="$QNAP_EXTRACTION_ROOT_DST/qpkg.tar"
fi

SYSROOT="$QNAP_EXTRACTION_ROOT_DST/sysroot"
mkdir "$SYSROOT"

if [ -e "$UIMAGE" ]; then
print_ln
print_output "[*] Scanning $ORANGE$UIMAGE$NC for (gzipped) parts..."

# 识别是否为gzip文件,并定位到首次出现gzip文件头的偏移,用于后面的dd命令
a=$(od -t x1 -w4 -Ad -v "$UIMAGE" | grep '1f 8b 08 00' | awk '{print $1}')

if [ -n "$a" ]; then

# 按照块大小(block size) $a 跳过第一个块,然后将其余的内容复制到输出文件(output file) $IMAGE.gz 中
dd if="$UIMAGE" bs="$a" skip=1 of="$IMAGE.gz" status=none

# 解压
gunzip --quiet "$IMAGE.gz" || [ $? -eq 2 ]
print_output "[+] Extracted and uncompressed $ORANGE$IMAGE$NC at offset $ORANGE$a$NC"

i=0
for a in $(od -t x1 -w4 -Ad -v "$IMAGE" | grep '1f 8b 08 00' | awk '{print $1}'); do
i=$((i+1))

# 再识别IMAGE文件里的所有gzip文件,分别提取所有部分
dd if="$IMAGE" bs="$a" skip=1 of="$IMAGE.part$i.gz" status=none
gunzip --quiet "$IMAGE.part$i.gz" || [ $? -eq 2 ]
print_output "[+] Extracted and uncompressed '$IMAGE.part$i' at offset $a"
done

if [ $i -gt 0 ]; then
mv "$IMAGE.part$i" "$INITRAMFS" || true
print_output "[*] Renamed $ORANGE$IMAGE.part$i$NC to $ORANGE$INITRAMFS$NC"
rm "$IMAGE" || true
fi
fi
print_files_dirs
print_bar ""
fi

if [ -e "$UBI" ]; then
# rootfs2.ubi
print_ln
print_output "[*] Unpacking $ORANGE$UBI$NC."
# TODO: we should evaluate moving to the EMBA UBI extractor in the future

# see http://trac.gateworks.com/wiki/linux/ubi
#
# apt-get install mtd-utils

# 256MB flash
modprobe -r nandsim || true
if [ -e /dev/mtdblock0 ]; then
print_output "[-] /dev/mtdblock0 does already exist! Exiting to not overwrite it."; exit
fi
modprobe nandsim first_id_byte=0x2c second_id_byte=0xda third_id_byte=0x90 fourth_id_byte=0x95

print_output "[*] Copy UBI image into simulated flash device"
# populate NAND with an existing ubi:
modprobe mtdblock
dd if="$UBI" of=/dev/mtdblock0 bs=2048 status=none

print_output "[*] Attach simulated flash device"
# attach ubi
modprobe ubi
ubiattach /dev/ubi_ctrl -m0 -O2048
# ubinfo -a

print_output "[*] Mounting ubifs file system"
# mount the ubifs to host
modprobe ubifs
local TMP_EXT_MOUNT="$TMP_DIR""/ext_mount_$RANDOM"
mkdir -p "$TMP_EXT_MOUNT" || true
mount -t ubifs ubi0 "$TMP_EXT_MOUNT"
if mount | grep -q ext_mount; then
print_output "[*] Copying contents from UBI mount"
cp -a "$TMP_EXT_MOUNT"/boot/* "$QNAP_EXTRACTION_ROOT_DST" || true
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$QNAP_EXTRACTION_ROOT_DST$NC):"
find "$QNAP_EXTRACTION_ROOT_DST" -xdev -ls | tee -a "$LOG_FILE"

print_output "[*] UBI cleanup"
umount "$TMP_EXT_MOUNT"
else
print_output "[-] Something went wrong!"
fi
rm -r "$TMP_EXT_MOUNT" || true
ubidetach /dev/ubi_ctrl -m0
modprobe -r nandsim
print_files_dirs
print_bar ""
fi

# 提取初始内存文件系统
if [ -e "$INITRAMFS" ]; then
print_ln
print_output "[*] Extracting $ORANGE$INITRAMFS$NC."
(cd "$SYSROOT" && (cpio -i --make-directories||true) ) < "$INITRAMFS"
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
print_files_dirs
print_bar ""
fi

if [ -e "$INITRD" ]; then
print_ln

# 如果是lzma压缩文件,则解压
if file "$INITRD" | grep -q LZMA ; then
print_output "[*] Extracting $ORANGE$INITRD$NC (LZMA)."
lzma -d <"$INITRD" | (cd "$SYSROOT" && (cpio -i --make-directories||true) )
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
print_files_dirs
print_bar ""
fi

# 如果是gzip,则解压
if file "$INITRD" | grep -q gzip ; then
print_ln
print_output "[*] Extracting $ORANGE$INITRD$NC (gzip)."
gzip -d <"$INITRD" >"$QNAP_EXTRACTION_ROOT_DST/initrd.$$"
print_output "[*] Mounting $ORANGE$INITRD$NC."
local TMP_EXT_MOUNT="$TMP_DIR""/ext_mount_$RANDOM"
mkdir -p "$TMP_EXT_MOUNT" || true
mount -t ext2 "$QNAP_EXTRACTION_ROOT_DST/initrd.$$" "$TMP_EXT_MOUNT" -oro,loop
if mount | grep -q ext_mount; then
cp -a "$TMP_EXT_MOUNT"/* "$SYSROOT" || true
umount "$TMP_EXT_MOUNT"
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
rm "$QNAP_EXTRACTION_ROOT_DST/initrd.$$" || true
else
print_output "[-] Something went wrong!"
fi
rm -r "$TMP_EXT_MOUNT" || true
print_files_dirs
print_bar ""
fi
fi

# 用tar解压
if [ -e "$ROOTFS2" ]; then
print_ln
print_output "[*] Extracting $ORANGE$ROOTFS2$NC (gzip, tar)."
tar -xvzf "$ROOTFS2" -C "$SYSROOT"
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
print_files_dirs
print_bar ""
fi

# 用lzma解压
if [ -e "$ROOTFS2_BZ" ]; then
print_ln
if file "$ROOTFS2_BZ" | grep -q "LZMA"; then
print_output "[*] Extracting $ORANGE$ROOTFS2_BZ$NC (LZMA)."
lzma -d <"$ROOTFS2_BZ" | (cd "$SYSROOT" && (cpio -i --make-directories||true) )
else
print_output "[*] Extracting $ORANGE$ROOTFS2_BZ$NC (bzip2, tar)."
tar -xvjf "$ROOTFS2_BZ" -C "$SYSROOT"
fi
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
print_files_dirs
print_bar ""
fi

if [ -f "$ROOTFS2_IMG" ]; then
print_ln
print_output "[*] Extracting $ORANGE$ROOTFS2_IMG$NC (ext2)..."
local TMP_EXT_MOUNT="$TMP_DIR""/ext_mount_$RANDOM"
mkdir -p "$TMP_EXT_MOUNT" || true
mount -t ext2 "$ROOTFS2_IMG" "$TMP_EXT_MOUNT" -oro,loop
if mount | grep -q ext_mount; then

# 挂载ROOTFS2_IMG,然后解压里面的rootfs2.bz
tar -xvjf "$TMP_EXT_MOUNT"/rootfs2.bz -C "$SYSROOT"
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
umount "$TMP_EXT_MOUNT"
else
print_output "[-] Something went wrong!"
fi
rm -r "$TMP_EXT_MOUNT" || true
print_files_dirs
print_bar ""
fi

if [ -e "$ROOTFS_EXT" ]; then
print_ln
print_output "[*] Extracting EXT filesystem $ORANGE$ROOTFS_EXT$NC."

# tar解压ext根文件系统
tar xzvf "$ROOTFS_EXT" -C "$QNAP_EXTRACTION_ROOT_DST"
print_output "[*] Mounting EXT filesystem $ORANGE$ROOTFS_EXT$NC."
local TMP_EXT_MOUNT="$TMP_DIR""/ext_mount_$RANDOM"
mkdir -p "$TMP_EXT_MOUNT" || true

# 挂载ext根文件系统,然后复制出根文件系统下的所有文件
mount "$QNAP_EXTRACTION_ROOT_DST"/rootfs_ext.img "$TMP_EXT_MOUNT" -oro,loop
if mount | grep -q ext_mount; then
cp -a "$TMP_EXT_MOUNT"/* "$SYSROOT" || true
umount "$TMP_EXT_MOUNT"
fi
print_output "[*] Removing EXT filesystem ${ORANGE}rootfs_ext.img$NC."
rm "$QNAP_EXTRACTION_ROOT_DST"/rootfs_ext.img || true
rm -r "$TMP_EXT_MOUNT" || true
print_ln
print_output "[*] Extracted firmware structure ($ORANGE$SYSROOT$NC):"
find "$SYSROOT" -xdev -ls | tee -a "$LOG_FILE"
print_files_dirs
print_bar ""
fi

# 查找根文件系统/opt/source下的所有tgz文件并解压到/usr/local下
USR_LOCAL=$(find "$SYSROOT/opt/source" -name "*.tgz" 2>/dev/null)
# if [[ "${#USR_LOCAL[@]}" -gt 0 ]]; then
if [[ -v USR_LOCAL[@] ]]; then
print_ln
for f in "${USR_LOCAL[@]}"; do
print_output "[*] Extracting $ORANGE$f$NC -> ${ORANGE}sysroot/usr/local$NC ..."
mkdir -p "$SYSROOT/usr/local" || true
tar xvzf "$f" -C "$SYSROOT/usr/local"
done
print_files_dirs
print_bar ""
fi

# "$QNAP_EXTRACTION_ROOT/qpkg.tar"
if [ -e "$QPKG" ]; then
print_ln
print_output "[*] Extracting $ORANGE$QPKG$NC."
mkdir -p "$QNAP_EXTRACTION_ROOT_DST/qpkg" || true

# 解压
tar xvf "$QPKG" -C "$QNAP_EXTRACTION_ROOT_DST/qpkg"
# 从解压出来的文件夹里继续搜tgz解压
for f in "$QNAP_EXTRACTION_ROOT_DST"/qpkg/*.tgz; do
if file "$f" | grep -q gzip; then
print_output "[*] Extracting QPKG $ORANGE$f$NC."
tar tvzf "$f" > "$f".txt
fi
done
print_files_dirs
print_bar ""
fi

# 解压出一些服务的tgz
for name in apache_php5 mysql5 mariadb5; do
if [ -e "$QNAP_EXTRACTION_ROOT_DST/qpkg/$name.tgz" ]; then
print_output "[*] Extracting ${ORANGE}qpkg/$name.tgz$NC -> ${ORANGE}sysroot/usr/local$NC ..."
tar xvzf "$QNAP_EXTRACTION_ROOT_DST/qpkg/$name.tgz" -C "$SYSROOT/usr/local"
fi
done

# Boost C++库解压到$SYSROOT/usr/lib下
if [ -e "$QNAP_EXTRACTION_ROOT_DST"/qpkg/libboost.tgz ]; then
print_ln
print_output "[*] Extracting ${ORANGE}qpkg/libboost.tgz$NC -> ${ORANGE}sysroot/usr/lib$NC."
mkdir -p "$SYSROOT/usr/lib" || true
tar xvzf "$QNAP_EXTRACTION_ROOT_DST"/qpkg/libboost.tgz -C "$SYSROOT/usr/lib"
elif [ -e "$QNAP_EXTRACTION_ROOT_DST"/qpkg/DSv3.tgz ]; then
print_output "[*] Extracting ${ORANGE}libboost$NC from ${ORANGE}qpkg/DSv3.tgz$NC -> ${ORANGE}sysroot/usr/lib$NC."
tar tzf "$QNAP_EXTRACTION_ROOT_DST"/qpkg/DSv3.tgz |grep libboost | tar xzf "$QNAP_EXTRACTION_ROOT_DST"/qpkg/DSv3.tgz -C "$SYSROOT" -T -
fi

if [[ -d "$SYSROOT"/usr/lib ]]; then
HOME_DIR="$(pwd)"

# 创建软链接
(cd "$SYSROOT/usr/lib" || exit; for f in libboost*.so.1.42.0; do ln -s "$f" "${f%.1.42.0}"; done)
cd "$HOME_DIR" || exit
fi
print_files_dirs
print_bar ""
}

P19_bsd_ufs_mounter.sh

挂载和提取BSD UFS映像
ufs_extractor函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ufs_extractor() {
local UFS_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
local TMP_UFS_MOUNT="$TMP_DIR""/ufs_mount_$RANDOM"
local DIRS_UFS_MOUNT=0
FILES_UFS_MOUNT=0

if ! [[ -f "$UFS_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

sub_module_title "UFS filesystem extractor"

mkdir -p "$TMP_UFS_MOUNT" 2>/dev/null || true
print_output "[*] Trying to mount $ORANGE$UFS_PATH_$NC to $ORANGE$TMP_UFS_MOUNT$NC directory"
# 装载ufs内核模块
modprobe ufs
# 挂载ufs镜像
mount -r -t ufs -o ufstype=ufs2 "$UFS_PATH_" "$TMP_UFS_MOUNT"

if mount | grep -q ufs_mount; then
print_output "[*] Copying $ORANGE$TMP_UFS_MOUNT$NC to firmware tmp directory ($ORANGE$EXTRACTION_DIR_$NC)"
mkdir -p "$EXTRACTION_DIR_" 2>/dev/null || true

# 把ufs里所有文件拷贝出来
cp -pri "$TMP_UFS_MOUNT"/* "$EXTRACTION_DIR_" 2>/dev/null || true
print_ln

# 统计文件
print_output "[*] Using the following firmware directory ($ORANGE$EXTRACTION_DIR_$NC) as base directory:"
find "$EXTRACTION_DIR_" -xdev -maxdepth 1 -ls | tee -a "$LOG_FILE"
print_ln
print_output "[*] Unmounting $ORANGE$TMP_UFS_MOUNT$NC directory"

FILES_UFS_MOUNT=$(find "$EXTRACTION_DIR_" -type f | wc -l)
DIRS_UFS_MOUNT=$(find "$EXTRACTION_DIR_" -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_UFS_MOUNT$NC files and $ORANGE$DIRS_UFS_MOUNT$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "UFS filesystem extractor" "$UFS_PATH_" "$EXTRACTION_DIR_" "$FILES_UFS_MOUNT" "$DIRS_UFS_MOUNT" "NA"
umount "$TMP_UFS_MOUNT" 2>/dev/null || true
fi
rm -r "$TMP_UFS_MOUNT"
}

P20_foscam_decryptor.sh

提取Foscam公司的加密固件映像
首先调用foscam_enc_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
foscam_enc_extractor() {
local FOSCAM_ENC_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"
local FOSCAM_FILE_CHECK=""
local KEY_FILE="$CONFIG_DIR/foscam_enc_keys.txt"

if ! [[ -f "$FOSCAM_ENC_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi

# config文件夹下的foscam_enc_keys.txt保存密钥列表
if ! [[ -f "$KEY_FILE" ]]; then
print_output "[-] No key file found in config directory"
return
fi

sub_module_title "Foscam encrypted firmware extractor"

hexdump -C "$FOSCAM_ENC_PATH_" | head | tee -a "$LOG_FILE" || true

# 保存密钥列表
mapfile FOSCAM_KEYS < <(grep -v "ID" "$KEY_FILE" | cut -d\; -f2 | tr -d \')
for _FOSCAM_KEY in "${FOSCAM_KEYS[@]}"; do
FOSCAM_DECRYTED=0
print_output "[*] Testing FOSCAM decryption key $ORANGE$_FOSCAM_KEY$NC."

# shellcheck disable=SC2086
# aes解密
openssl enc -d -aes-128-cbc -md md5 -k $_FOSCAM_KEY -in "$FOSCAM_ENC_PATH_" > "$EXTRACTION_FILE_" || true

if [[ -f "$EXTRACTION_FILE_" ]]; then

# 识别解密后的文件类型
FOSCAM_FILE_CHECK=$(file "$EXTRACTION_FILE_")
if [[ "$FOSCAM_FILE_CHECK" =~ .*gzip\ compressed\ data.* ]]; then

# 如果是gzip文件,那就到后面进行ubi提取
print_ln
print_output "[+] Decrypted Foscam firmware file to $ORANGE$EXTRACTION_FILE_$NC"
export FIRMWARE_PATH="$EXTRACTION_FILE_"
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "Foscam decryptor" "$FOSCAM_ENC_PATH_" "$EXTRACTION_FILE_" "1" "NA" "NA"
FOSCAM_DECRYTED=1
if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="Foscam"
fi

MD5_DONE_DEEP+=( "$(md5sum "$FOSCAM_ENC_PATH_" | awk '{print $1}')" )
foscam_ubi_extractor "$EXTRACTION_FILE_"
# as we have already found a working key we can now exit the loop
break
fi
fi
done

if [[ "$FOSCAM_DECRYTED" -ne 1 ]]; then
print_output "[-] Decryption of Foscam firmware file failed"
fi
}

foscam_ubi_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# TODO: Check if we can improve our ubifs extractor in a way to support this Foscam thing
# without the following function! Currently it is working together with our
# deep extractor quite fine.
foscam_ubi_extractor() {
local FIRMWARE_PATH_="${1:-}"
local MTD_DEVICE=""
local UBI_MNT_PT="$LOG_DIR"/tmp/ubi_mnt_foscam
local EXTRACTION_DIR_="$LOG_DIR/firmware/foscam_ubi_extractor"
local EXTRACTION_DIR_GZ="$LOG_DIR/firmware/foscam_gz_extractor"
local UBI_DEV=""
local UBI_DEVS=()
FOSCAM_UBI_FILES=0
FOSCAM_UBI_DIRS=0

if ! [[ -f "$FIRMWARE_PATH_" ]]; then
print_output "[-] No file for extraction found"
return
fi

print_output "[*] Extracting decrypted firmware to $ORANGE$EXTRACTION_DIR_GZ$NC"
mkdir -p "$EXTRACTION_DIR_GZ" || true

# 创建文件夹,然后解压到文件夹内
tar -xzf "$FIRMWARE_PATH_" -C "$EXTRACTION_DIR_GZ" || true

# check if we have the kernel modules available - special interest in docker
if ! [[ -d "/lib/modules/" ]]; then
print_output "[-] Kernel modules not mounted from host system - please update your docker-compose file!"
return
fi

if [[ -f "$EXTRACTION_DIR_GZ"/app_ubifs ]]; then
print_output "[*] 2nd extraction round successful - ${ORANGE}app_ubifs$NC found"
print_output "[*] Loading nandsim kernel module"
if lsmod | grep -q "^nandsim[[:space:]]"; then
# we need to load nandsim with some parameters - unload it before
modprobe -r nandsim
fi
# 像前面的模块一样加载内核模块
modprobe nandsim first_id_byte=0x2c second_id_byte=0xac third_id_byte=0x90 fourth_id_byte=0x15
MTD_DEVICE=$(grep "mtd[0-9]" /proc/mtd | cut -d: -f1)
print_output "[*] Found $ORANGE/dev/$MTD_DEVICE$NC MTD device"
print_output "[*] Erasing $ORANGE/dev/$MTD_DEVICE$NC MTD device"
flash_erase /dev/"$MTD_DEVICE" 0 0 || true
print_output "[*] Formating $ORANGE/dev/$MTD_DEVICE$NC MTD device"
ubiformat /dev/"$MTD_DEVICE" -O 2048 -f "$EXTRACTION_DIR_GZ"/app_ubifs || true
if ! lsmod | grep -q "^ubi[[:space:]]"; then
print_output "[*] Loading ubi kernel module"
modprobe ubi
fi
print_output "[*] Attaching ubi device"
ubiattach -p /dev/"$MTD_DEVICE" -O 2048

# should be only one UBI dev, but just in case ...
mapfile -t UBI_DEVS < <(find /dev -iname "ubi[0-9]_[0-9]")
for UBI_DEV in "${UBI_DEVS[@]}"; do
local UBI_MNT_PT="$UBI_MNT_PT-$RANDOM"
print_output "[*] Mounting $ORANGE$UBI_DEV$NC ubi device to $ORANGE$UBI_MNT_PT$NC"
mkdir -p "$UBI_MNT_PT" || true

# 挂载ubi设备
mount -t ubifs "$UBI_DEV" "$UBI_MNT_PT"
print_output "[*] Copy mounted ubi device to $ORANGE$EXTRACTION_DIR_/$UBI_DEV$NC"
mkdir -p "$EXTRACTION_DIR_/$UBI_DEV"

# 复制文件,然后取消挂载、删除。
cp -pri "$UBI_MNT_PT" "$EXTRACTION_DIR_/$UBI_DEV"
umount "$UBI_MNT_PT" || true
rm -r "$UBI_MNT_PT" || true
done

# 取消附加、解除内核模块加载
# do some cleanup
print_output "[*] Detaching ubi device"
ubidetach -d 0 || true
print_output "[*] Unloading nandsim module"
modprobe -r nandsim || true
print_output "[*] Unloading ubi module"
modprobe -r ubi || true

if [[ -d "$EXTRACTION_DIR_" ]]; then
FOSCAM_UBI_FILES=$(find "$EXTRACTION_DIR_" -type f | wc -l)
FOSCAM_UBI_DIRS=$(find "$EXTRACTION_DIR_" -type d | wc -l)
fi

if [[ "$FOSCAM_UBI_FILES" -gt 0 ]]; then
print_ln
print_output "[*] Extracted $ORANGE$FOSCAM_UBI_FILES$NC files and $ORANGE$FOSCAM_UBI_DIRS$NC directories from the firmware image."
write_csv_log "Foscam UBI extractor" "$FIRMWARE_PATH_" "$EXTRACTION_DIR_" "$FOSCAM_UBI_FILES" "$FOSCAM_UBI_DIRS" "NA"
export FIRMWARE_PATH="$LOG_DIR"/firmware
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
write_csv_log "Foscam decryptor/extractor" "$FIRMWARE_PATH_" "$EXTRACTION_DIR_" "$FOSCAM_UBI_FILES" "$FOSCAM_UBI_DIRS" "NA"
fi
fi
}

P21_buffalo_decryptor.sh

提取Buffalo公司的加密固件映像
buffalo_enc_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
buffalo_enc_extractor() {
local BUFFALO_ENC_PATH_="${1:-}"
local EXTRACTION_FILE_="${2:-}"
local BUFFALO_FILE_CHECK=""

if ! [[ -f "$BUFFALO_ENC_PATH_" ]]; then
print_output "[-] No file for decryption provided"
return
fi

sub_module_title "Buffalo encrypted firmware extractor"

hexdump -C "$BUFFALO_ENC_PATH_" | head | tee -a "$LOG_FILE" || true
print_ln

BUFFALO_DECRYTED=0
local BUFFALO_ENC_PATH_STRIPPED
BUFFALO_ENC_PATH_STRIPPED="$LOG_DIR/firmware/$(basename "$BUFFALO_ENC_PATH_").stripped"

print_output "[*] Removing initial 208 bytes from header to prepare firmware for decryption"

# 去除前面多余的头
dd bs=208 skip=1 if="$BUFFALO_ENC_PATH_" of="$BUFFALO_ENC_PATH_STRIPPED"
hexdump -C "$BUFFALO_ENC_PATH_STRIPPED" | head | tee -a "$LOG_FILE" || true
print_ln

print_output "[*] Decrypting firmware ..."

# 使用扩展下的解密工具
"$EXT_DIR"/buffalo-enc.elf -d -i "$BUFFALO_ENC_PATH_STRIPPED" -o "$EXTRACTION_FILE_"
hexdump -C "$EXTRACTION_FILE_" | head | tee -a "$LOG_FILE" || true
print_ln

if [[ -f "$EXTRACTION_FILE_" ]]; then

# 检查文件类型,是不是要的文件
BUFFALO_FILE_CHECK=$(file "$EXTRACTION_FILE_")
if [[ "$BUFFALO_FILE_CHECK" =~ .*u-boot\ legacy\ uImage,\ .* ]]; then
print_ln
print_output "[+] Decrypted Buffalo firmware file to $ORANGE$EXTRACTION_FILE_$NC"
MD5_DONE_DEEP+=( "$(md5sum "$BUFFALO_ENC_PATH_" | awk '{print $1}')" )
export FIRMWARE_PATH="$EXTRACTION_FILE_"
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
print_ln
print_output "[*] Firmware file details: $ORANGE$(file "$EXTRACTION_FILE_")$NC"
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "Buffalo decryptor" "$BUFFALO_ENC_PATH_" "$EXTRACTION_FILE_" "1" "NA" "NA"
BUFFALO_DECRYTED=1
if [[ -z "${FW_VENDOR:-}" ]]; then
FW_VENDOR="BUFFALO"
fi
fi
fi

if [[ "$BUFFALO_DECRYTED" -ne 1 ]]; then
print_output "[-] Decryption of Buffalo firmware file failed"
fi
}

P22_Zyxel_zip_decrypt.sh

提取被密码保护的Zyxel固件镜像
zyxel_zip_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
zyxel_zip_extractor() {
local RI_FILE_="${1:-}"
local EXTRACTION_DIR_="${2:-}"

local RI_FILE_BIN=""
local ZLD_DIR=""
local RI_FILE_BIN_PATH=""
local ZLD_BINS=()
local ZLD_BIN=""

sub_module_title "Zyxel protected ZIP firmware extractor"

if ! [[ -f "$RI_FILE_" ]]; then
print_output "[-] Zyxel - No file for extraction provided"
return
fi
if ! [[ "$RI_FILE_" =~ .*\.ri ]]; then
print_output "[-] Zyxel - No valid ri file for extraction provided"
return
fi

# 直接用binwalk深度提取,下面给出此函数分析
binwalk_deep_extract_helper 1 "$RI_FILE_" "$EXTRACTION_DIR_"
print_ln

# 如果有这个命令
if command -v jchroot > /dev/null; then
CHROOT="jchroot"
# OPTS see https://github.com/vincentbernat/jchroot#security-note
OPTS=(-n emba -U -u 0 -g 0 -M "0 $(id -u) 1" -G "0 $(id -g) 1")
print_output "[*] Using ${ORANGE}jchroot${NC} for building more secure chroot environments"
else
print_output "[-] No jchroot binary found ..."
return
fi

# 在提取出的文件夹里找zld_fsextract的文件
mapfile -t ZLD_BINS < <(find "$EXTRACTION_DIR_" -name "zld_fsextract")

# 去掉.ri后缀然后接上.bin后缀
RI_FILE_BIN="$(basename -s .ri "$RI_FILE_")".bin

for ZLD_BIN in "${ZLD_BINS[@]}"; do
local FILES_ZYXEL=0
local DIRS_ZYXEL=0
print_output "[*] Checking $ORANGE$ZLD_BIN$NC"

# 获取文件夹名
ZLD_DIR=$(dirname "$ZLD_BIN")

# 找到第一个名字叫这个的文件
RI_FILE_BIN_PATH=$(find "$LOG_DIR"/firmware -name "$RI_FILE_BIN" | head -1)
# => this should be the protected Zip file

# 压缩包是一个加密压缩包,但是密钥肯定在程序执行时会出现在内存中
if [[ $(file "$ZLD_BIN") == *"ELF"* ]] && [[ $(file "$RI_FILE_BIN_PATH") == *"Zip archive data"* ]]; then
print_output "[*] Found Zyxel environment in $ORANGE$ZLD_DIR$NC"
# now we know that we have an elf for extraction and and unzip binary in the extraction dir
# this is everything we need for the key
if ( file "$ZLD_BIN" | grep -q "ELF 32-bit MSB executable, MIPS, N32 MIPS64 rel2 version 1" ) ; then
# todo: check if Zyxel also uses other architectures
local EMULATOR="qemu-mipsn32-static"
print_output "[*] Found valid emulator $ORANGE$EMULATOR$NC"
else
print_output "[-] WARNING: Unsupported architecture for key identification:"
print_output "$(indent "$(file "$ZLD_BIN")")"
print_output "[-] Please open an issue at https://github.com/e-m-b-a/emba/issues"
continue
fi

print_output "[*] Running Zyxel emulation for key extraction ..."

if ! [[ -e "$(command -v "$EMULATOR")" ]]; then
print_output "[-] No valid emulator ($ORANGE$EMULATOR$NC) found in your environment"
return
fi

cp "$(command -v "$EMULATOR")" "$ZLD_DIR" || ( print_output "[-] Something went wrong" && return)
cp "$RI_FILE_BIN_PATH" "$ZLD_DIR" || ( print_output "[-] Something went wrong" && return)
ZLD_BIN=$(basename "$ZLD_BIN")

# 模拟执行
timeout --preserve-status --signal SIGINT 2s "$CHROOT" "${OPTS[@]}" "$ZLD_DIR" -- ./"$EMULATOR" -strace ./"$ZLD_BIN" "$RI_FILE_BIN" AABBCCDD >> "$LOG_PATH_MODULE"/zld_strace.log 2>&1 || true

# 删除模拟器
rm "$ZLD_DIR"/"$EMULATOR" || true

if [[ -f "$LOG_PATH_MODULE"/zld_strace.log ]] && [[ -s "$LOG_PATH_MODULE"/zld_strace.log ]]; then

# 在模拟执行的跟踪日志中匹配密钥
ZIP_KEY=$(grep -a -E "^[0-9]+\ execve.*AABBCCDD\",\"-o" "$LOG_PATH_MODULE"/zld_strace.log| cut -d, -f6 | sort -u | sed 's/^\"//' | sed 's/\"$//')
else
print_output "[-] No qemu strace log generated -> no further processing possible"
fi

# 如果能找到密钥
# if we have found a ZIP_KEY:
if [[ -v ZIP_KEY ]]; then
print_ln
print_output "[+] Possible ZIP key detected: $ORANGE$ZIP_KEY$NC" "" "$LOG_PATH_MODULE/zld_strace.log"

# 使用密钥解压
7z x -p"$ZIP_KEY" -o"$EXTRACTION_DIR_"/firmware_zyxel_extracted "$RI_FILE_BIN_PATH" || true

FILES_ZYXEL=$(find "$EXTRACTION_DIR_"/firmware_zyxel_extracted -type f | wc -l)
DIRS_ZYXEL=$(find "$EXTRACTION_DIR_"/firmware_zyxel_extracted -type d | wc -l)

print_ln
print_output "[*] Zyxel 1st stage - Extracted $ORANGE$FILES_ZYXEL$NC files and $ORANGE$DIRS_ZYXEL$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "Zyxel extractor" "$RI_FILE_BIN_PATH" "$EXTRACTION_DIR_/firmware_zyxel_extracted" "$FILES_ZYXEL" "$DIRS_ZYXEL" "NA"
else
print_output "[-] No ZIP key detected -> no further processing possible"
fi

# if it was possible to extract something with the key:
if [[ "$FILES_ZYXEL" -gt 0 ]]; then
# compress.img ist the firmware -> letz search for it
COMPRESS_IMG=$(find "$EXTRACTION_DIR_"/firmware_zyxel_extracted -type f -name compress.img | sort -u)
if [[ $(file "$COMPRESS_IMG") == *"Squashfs"* ]]; then
print_output "[+] Found valid ${ORANGE}compress.img$GREEN and extract it now"
binwalk_deep_extract_helper 1 "$COMPRESS_IMG" "$EXTRACTION_DIR_/firmware_zyxel_extracted/compress_img_extracted"
FILES_ZYXEL=$(find "$EXTRACTION_DIR_"/firmware_zyxel_extracted/compress_img_extracted -type f | wc -l)
DIRS_ZYXEL=$(find "$EXTRACTION_DIR_"/firmware_zyxel_extracted/compress_img_extracted -type d | wc -l)
print_output "[*] Zyxel 2nd stage - Extracted $ORANGE$FILES_ZYXEL$NC files and $ORANGE$DIRS_ZYXEL$NC directories from the firmware image."
write_csv_log "Zyxel extractor" "$RI_FILE_BIN_PATH" "$EXTRACTION_DIR_/firmware_zyxel_extracted/compress_img_extracted" "$FILES_ZYXEL" "$DIRS_ZYXEL" "NA"
export FIRMWARE_PATH="$LOG_DIR"/firmware/
backup_var "FIRMWARE_PATH" "$FIRMWARE_PATH"
print_ln
break
else
print_output "[-] No valid ${ORANGE}compress.img$NC file found"
fi
else
print_output "[-] 1st stage Zip extraction failed"
fi
print_ln
else
print_output "[-] No environment for Zyxel decryption found"
fi
done
}

binwalk_deep_extract_helper函数(在P60_firmware_bin_extractor.sh文件内定义):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
binwalk_deep_extract_helper() {
# Matryoshka mode is first parameter: 1 - enable, 0 - disable
local MATRYOSHKA_="${1:-0}"
local FILE_TO_EXTRACT_="${2:-}"
local DEST_FILE_="${3:-}"

if ! [[ -f "$FILE_TO_EXTRACT_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

if [[ "$BINWALK_VER_CHECK" == 1 ]]; then

# 如果参数是1,则递归提取
if [[ "$MATRYOSHKA_" -eq 1 ]]; then

# -M:Recursively scan extracted files(递归扫描提取的文件)
binwalk --run-as=root --preserve-symlinks --dd='.*' -e -M -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
else
# no more Matryoshka mode ... we are doing it manually and check the files every round via MD5
binwalk --run-as=root --preserve-symlinks --dd='.*' -e -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
fi
else
if [[ "$MATRYOSHKA_" -eq 1 ]]; then
binwalk --dd='.*' -e -M -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
else
binwalk --dd='.*' -e -C "$DEST_FILE_" "$FILE_TO_EXTRACT_" | tee -a "$LOG_FILE" || true
fi
fi
}

P23_qemu_qcow_mounter.sh

挂载和提取Qemu QCOW2镜像
qcow_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
qcow_extractor() {
local QCOW_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"
local TMP_QCOW_MOUNT="$TMP_DIR""/qcow_mount_$RANDOM"
local DIRS_QCOW_MOUNT=0
FILES_QCOW_MOUNT=0

if ! [[ -f "$QCOW_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

sub_module_title "Qemu QCOW filesystem extractor"

mkdir -p "$TMP_QCOW_MOUNT" 2>/dev/null || true
print_output "[*] Trying to mount $ORANGE$QCOW_PATH_$NC to $ORANGE$TMP_QCOW_MOUNT$NC directory"

# 取消nbd内核模块的挂载
if lsmod | grep -q nbd; then
rmmod nbd || true
fi

# 加一个锁
if ! [[ -d /var/lock ]]; then
mkdir /var/lock || true
fi

# 加载内核模块
print_output "[*] Load kernel module ${ORANGE}nbd$NC."
modprobe nbd max_part=8
print_output "[*] Qemu disconnect device ${ORANGE}/dev/nbd$NC."
qemu-nbd --disconnect /dev/nbd0
print_output "[*] Qemu connect device ${ORANGE}/dev/nbd$NC."
qemu-nbd --connect=/dev/nbd0 "$QCOW_PATH_"

print_output "[*] Identification of partitions on ${ORANGE}/dev/nbd$NC."

# 确保存在nbd设备
mapfile -t NBD_DEVS < <(fdisk -l /dev/nbd0 | grep "^/dev/" | awk '{print $1}' || true)
if [[ "${#NBD_DEVS[@]}" -eq 0 ]]; then
# sometimes we are not able to find the partitions with fdisk -> fallback
NBD_DEVS+=( "/dev/nbd0" )
fi

print_ln
fdisk /dev/nbd0 -l
print_ln

for NBD_DEV in "${NBD_DEVS[@]}"; do
print_output "[*] Extract data from partition $ORANGE$NBD_DEV$NC"

# 将镜像挂载到nbd设备上
mount "$NBD_DEV" "$TMP_QCOW_MOUNT" || true

if mount | grep -q "$NBD_DEV"; then
EXTRACTION_DIR_FINAL="$EXTRACTION_DIR_"/"$(basename "$NBD_DEV")"

# 调用copy_qemu_nbd函数,下方给出分析
copy_qemu_nbd "$TMP_QCOW_MOUNT" "$EXTRACTION_DIR_FINAL"

FILES_QCOW_MOUNT=$(find "$EXTRACTION_DIR_FINAL" -type f | wc -l)
DIRS_QCOW_MOUNT=$(find "$EXTRACTION_DIR_FINAL" -type d | wc -l)
fi
print_output "[*] Extracted $ORANGE$FILES_QCOW_MOUNT$NC files and $ORANGE$DIRS_QCOW_MOUNT$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "Qemu QCOW filesystem extractor" "$QCOW_PATH_" "$EXTRACTION_DIR_FINAL" "$FILES_QCOW_MOUNT" "$DIRS_QCOW_MOUNT" "NA"

print_output "[*] Unmounting $ORANGE$TMP_QCOW_MOUNT$NC directory"
umount "$TMP_QCOW_MOUNT"
done
qemu-nbd --disconnect /dev/nbd0
rm -r "$TMP_QCOW_MOUNT"
}

copy_qemu_nbd函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
copy_qemu_nbd() {
local SOURCE_CP="${1:-}"
local DEST_CP="${2:-}"
if ! [[ -d "$SOURCE_CP" ]]; then
return
fi

print_output "[*] Copying $ORANGE$SOURCE_CP$NC to firmware tmp directory ($ORANGE$DEST_CP$NC)"
mkdir -p "$DEST_CP" 2>/dev/null || true

# 复制所有内容到提取的文件夹。
# -p表示保留文件属性,-r表示递归复制子目录,-i表示覆盖前进行询问。
cp -pri "$SOURCE_CP"/* "$DEST_CP" 2>/dev/null || true
print_ln
print_output "[*] Using the following firmware directory ($ORANGE$DEST_CP$NC) as base directory:"

# 通过不跨越设备、最大深度1级子目录进行搜索,统计文件。
find "$DEST_CP" -xdev -maxdepth 1 -ls | tee -a "$LOG_FILE"
print_ln
}

P25_android_ota.sh

提取Android OTA更新文件
android_ota_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
android_ota_extractor() {
local OTA_INIT_PATH_="$1"
local EXTRACTION_DIR_="$2"
local DIRS_OTA=0
FILES_OTA=0

if ! [[ -f "$OTA_INIT_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

sub_module_title "Android OTA extractor"

hexdump -C "$OTA_INIT_PATH_" | head | tee -a "$LOG_FILE" || true

if [[ -d "$EXT_DIR"/payload_dumper ]]; then
print_ln
print_output "[*] Extracting Android OTA payload.bin file ..."
print_ln

# 使用扩展工具进行提取。
python3 "$EXT_DIR"/payload_dumper/payload_dumper.py --out "$EXTRACTION_DIR_" "$OTA_INIT_PATH_" | tee -a "$LOG_FILE"

FILES_OTA=$(find "$EXTRACTION_DIR_" -type f | wc -l)
DIRS_OTA=$(find "$EXTRACTION_DIR_" -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_OTA$NC files and $ORANGE$DIRS_OTA$NC directories from the firmware image."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "Android OTA extractor" "$OTA_INIT_PATH_" "$EXTRACTION_DIR_" "$FILES_OTA" "$DIRS_OTA" "via payload_dumper.py"
else
print_output "[-] Android OTA payload.bin extractor not found - check your installation"
fi
}

P35_UEFI_extractor.sh

提取UEFI镜像与BIOSUtilities
uefi_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
uefi_extractor(){
sub_module_title "UEFI Extractor"

local FIRMWARE_PATH_="${1:-}"
local EXTRACTION_DIR_="${2:-}"

local FIRMWARE_NAME_=""
local UEFI_EXTRACT_REPORT_FILE=""

local UEFI_EXTRACT_BIN="$EXT_DIR""/UEFITool/UEFIExtract"
local FILES_UEFI=0
local DIRS_UEFI=0
local NVARS=0
local PE32_IMAGE=0
local EFI_ARCH=""

if ! [[ -f "$FIRMWARE_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

FIRMWARE_NAME_="$(basename "$FIRMWARE_PATH_")"
if ! [[ -d "$EXTRACTION_DIR_" ]]; then
mkdir -p "$EXTRACTION_DIR_"
fi
cp "$FIRMWARE_PATH_" "$EXTRACTION_DIR_"
"$UEFI_EXTRACT_BIN" "$EXTRACTION_DIR_"firmware all &> "$LOG_PATH_MODULE"/uefi_extractor_"$FIRMWARE_NAME_".log
UEFI_EXTRACT_REPORT_FILE="$EXTRACTION_DIR_"firmware.report.txt
mv "$UEFI_EXTRACT_REPORT_FILE" "$LOG_PATH_MODULE"
UEFI_EXTRACT_REPORT_FILE="$LOG_PATH_MODULE"/firmware.report.txt
if [[ -f "$EXTRACTION_DIR_"/firmware ]]; then
rm "$EXTRACTION_DIR_"/firmware
fi

if [[ -f "$LOG_PATH_MODULE"/uefi_extractor_"$FIRMWARE_NAME_".log ]]; then
tee -a "$LOG_FILE" < "$LOG_PATH_MODULE"/uefi_extractor_"$FIRMWARE_NAME_".log
fi

print_ln
print_output "[*] Using the following firmware directory ($ORANGE${EXTRACTION_DIR_}firmware.dump$NC) as base directory:"
find "$EXTRACTION_DIR_"firmware.dump -xdev -maxdepth 1 -ls | tee -a "$LOG_FILE"
print_ln

NVARS=$(grep -c "NVAR entry" "$UEFI_EXTRACT_REPORT_FILE" || true)
PE32_IMAGE=$(grep -c "PE32 image" "$UEFI_EXTRACT_REPORT_FILE" || true)
DRIVER_COUNT=$(grep -c "DXE driver" "$UEFI_EXTRACT_REPORT_FILE" || true)
EFI_ARCH=$(find "$EXTRACTION_DIR_" -name 'info.txt' -exec grep 'Machine type:' {} \; | sed -E 's/Machine\ type\:\ //g' | uniq | head -n 1)

if [[ -n "$EFI_ARCH" ]]; then
print_output "[*] Found $ORANGE$PE32_IMAGE$NC PE32 images for architecture $ORANGE$EFI_ARCH$NC drivers."
print_output "[+] Possible architecture details found ($ORANGE UEFI Extractor $GREEN): $ORANGE$EFI_ARCH$NC"
export EFI_ARCH
backup_var "EFI_ARCH" "$EFI_ARCH"
fi

FILES_UEFI=$(grep -c "File" "$UEFI_EXTRACT_REPORT_FILE" || true)
DIRS_UEFI=$(find "$EXTRACTION_DIR_" -type d | wc -l)
print_output "[*] Extracted $ORANGE$FILES_UEFI$NC files and $ORANGE$DIRS_UEFI$NC directories from the firmware image."
print_output "[*] Found $ORANGE$NVARS$NC NVARS and $ORANGE$DRIVER_COUNT$NC drivers."
write_csv_log "Extractor module" "Original file" "extracted file/dir" "file counter" "directory counter" "further details"
write_csv_log "UEFI extractor" "$FIRMWARE_PATH_" "$EXTRACTION_DIR_" "$FILES_UEFI" "$DIRS_UEFI" "NA"
print_ln
}

P59_binwalk_extractor.sh

分析固件与binwalk,检查熵和提取固件到日志目录
P59_binwalk_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
P59_binwalk_extractor() {
module_log_init "${FUNCNAME[0]}"
module_title "Binwalk firmware extractor"
pre_module_reporter "${FUNCNAME[0]}"

export LINUX_PATH_COUNTER=0

# 重新定义unblob下的sasquatch工具指向binwalk
# we need to check if sasquatch is the correct one for binwalk:
if ! [[ "$(readlink -q -f "$UNBLOB_PATH"/sasquatch)" == "/usr/local/bin/sasquatch_binwalk" ]]; then
if [[ -L "$UNBLOB_PATH"/sasquatch ]]; then
rm "$UNBLOB_PATH"/sasquatch
fi
ln -s /usr/local/bin/sasquatch_binwalk "$UNBLOB_PATH"/sasquatch || true
fi

# typically FIRMWARE_PATH is only a file if none of the EMBA extractors were able to extract something
# This means we are using binwalk in Matryoshka mode here
# if we have a directory with multiple files in it we automatically pass here and run into the deep extractor
if [[ -f "$FIRMWARE_PATH" ]]; then

# 首先调用binwalking,此函数将在下方给出分析。
# 作用:对固件进行简单地分析并给出熵图。
# we love binwalk ... this is our first chance for extracting everything
binwalking "$FIRMWARE_PATH"
fi

# 将在下方给出分析
# 功能:通过特殊二进制路径来推测文件系统根目录在哪
# FIRMWARE_PATH_CP is typically /log/firmware - shellcheck is probably confused here
# shellcheck disable=SC2153
detect_root_dir_helper "$FIRMWARE_PATH_CP"

print_ln

# 统计提取的文件
FILES_EXT=$(find "$FIRMWARE_PATH_CP" -xdev -type f | wc -l )
UNIQUE_FILES=$(find "$FIRMWARE_PATH_CP" "${EXCL_FIND[@]}" -xdev -type f -exec md5sum {} \; 2>/dev/null | sort -u -k1,1 | cut -d\ -f3 | wc -l )
DIRS_EXT=$(find "$FIRMWARE_PATH_CP" -xdev -type d | wc -l )
BINS=$(find "$FIRMWARE_PATH_CP" "${EXCL_FIND[@]}" -xdev -type f -exec file {} \; | grep -c "ELF" || true)

if [[ "$BINS" -gt 0 || "$UNIQUE_FILES" -gt 0 ]]; then
sub_module_title "Firmware extraction details"

# 将在下方给出分析
# 主要功能:用于统计linux文件系统中基本的特征文件或文件名
linux_basic_identification_helper "$FIRMWARE_PATH_CP"
print_ln
print_output "[*] Found $ORANGE$FILES_EXT$NC files ($ORANGE$UNIQUE_FILES$NC unique files) and $ORANGE$DIRS_EXT$NC directories at all."
print_output "[*] Found $ORANGE$BINS$NC binaries."
print_output "[*] Additionally the Linux path counter is $ORANGE$LINUX_PATH_COUNTER$NC."
print_ln
tree -csh "$FIRMWARE_PATH_CP" | tee -a "$LOG_FILE"

# now it should be fine to also set the FIRMWARE_PATH ot the FIRMWARE_PATH_CP
export FIRMWARE_PATH="$FIRMWARE_PATH_CP"

if [[ "${#ROOT_PATH[@]}" -gt 0 ]] ; then
write_csv_log "FILES" "UNIQUE_FILES" "DIRS" "Binaries" "LINUX_PATH_COUNTER" "Root PATH detected"
for R_PATH in "${ROOT_PATH[@]}"; do
write_csv_log "$FILES_EXT" "$UNIQUE_FILES" "$DIRS_EXT" "$BINS" "$LINUX_PATH_COUNTER" "$R_PATH"
done
fi
backup_var "FILES_EXT" "$FILES_EXT"
backup_var "FILES_EXT" "$UNIQUE_FILES"
backup_var "FILES_EXT" "$DIRS_EXT"
fi

module_end_log "${FUNCNAME[0]}" "$FILES_EXT"
}

binwalking函数:
对固件进行简单地分析并给出熵图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# this function is for the first round of binwalk
# in case no other EMBA extractor did something and the
# current firmware file is a file and not multiple files
binwalking() {
local FIRMWARE_PATH_="${1:-}"
export OUTPUT_DIR_BINWALK=""

if ! [[ -f "$FIRMWARE_PATH_" ]]; then
print_output "[-] No file for extraction provided"
return
fi

sub_module_title "Analyze binary firmware blob with binwalk"

# 简单地用binwalk分析一下
print_output "[*] Basic analysis with binwalk"
binwalk "$FIRMWARE_PATH_" | tee -a "$LOG_FILE"

print_ln "no_log"
# we use the original FIRMWARE_PATH for entropy testing, just if it is a file
if [[ -f $FIRMWARE_PATH_BAK ]] && ! [[ -f "$LOG_DIR"/firmware_entropy.png ]]; then
print_output "[*] Entropy testing with binwalk ... "
# we have to change the working directory for binwalk, because everything except the log directory is read-only in
# Docker container and binwalk fails to save the entropy picture there
if [[ $IN_DOCKER -eq 1 ]] ; then
cd "$LOG_DIR" || return

# 用binwalk生成熵图
print_output "$(binwalk -E -F -J "$FIRMWARE_PATH_BAK")"
mv "$(basename "$FIRMWARE_PATH_".png)" "$LOG_DIR"/firmware_entropy.png 2> /dev/null || true
cd /emba || return
else
print_output "$(binwalk -E -F -J "$FIRMWARE_PATH_BAK")"
mv "$(basename "$FIRMWARE_PATH_".png)" "$LOG_DIR"/firmware_entropy.png 2> /dev/null || true
fi
fi

OUTPUT_DIR_BINWALK=$(basename "$FIRMWARE_PATH_")
OUTPUT_DIR_BINWALK="$FIRMWARE_PATH_CP""/""$OUTPUT_DIR_BINWALK"_binwalk_emba

print_ln
print_output "[*] Extracting firmware to directory $ORANGE$OUTPUT_DIR_BINWALK$NC"
# this is not working in background. I have created a new function that gets executed in the background
# probably there is a more elegant way
# binwalk is executed in Matryoshka mode
binwalk_deep_extract_helper 1 "$FIRMWARE_PATH_" "$OUTPUT_DIR_BINWALK" &
WAIT_PIDS+=( "$!" )
wait_for_extractor
WAIT_PIDS=( )

MD5_DONE_DEEP+=( "$(md5sum "$FIRMWARE_PATH_" | awk '{print $1}')" )
}

detect_root_dir_helper函数:
通过特殊二进制路径来推测文件系统根目录在哪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
detect_root_dir_helper() {
SEARCH_PATH="${1:-}"

print_output "[*] Root directory auto detection for $ORANGE$SEARCH_PATH$NC (could take some time)\\n"
export ROOT_PATH=()
local R_PATH
local MECHANISM=""

# 过滤出解释器的可执行文件
mapfile -t INTERPRETER_FULL_PATH < <(find "$SEARCH_PATH" -ignore_readdir_race -type f -exec file {} \; 2>/dev/null | grep "ELF" | grep "interpreter" | sed s/.*interpreter\ // | sed s/,\ .*$// | sort -u 2>/dev/null || true)

# 获取解释器路径
if [[ "${#INTERPRETER_FULL_PATH[@]}" -gt 0 ]]; then
for INTERPRETER_PATH in "${INTERPRETER_FULL_PATH[@]}"; do
# now we have a result like this "/lib/ld-uClibc.so.0"
# lets escape it
# 将/替换为\/
INTERPRETER_ESCAPED=$(echo "$INTERPRETER_PATH" | sed -e 's/\//\\\//g')
mapfile -t INTERPRETER_FULL_RPATH < <(find "$SEARCH_PATH" -ignore_readdir_race -wholename "*$INTERPRETER_PATH" 2>/dev/null | sort -u)
for R_PATH in "${INTERPRETER_FULL_RPATH[@]}"; do
# remove the interpreter path from the full path:
R_PATH="${R_PATH//$INTERPRETER_ESCAPED/}"
if [[ -v R_PATH ]] && [[ -d "$R_PATH" ]]; then
ROOT_PATH+=( "$R_PATH" )
MECHANISM="binary interpreter"
fi
done
done
fi

# 找到所有解释器的根目录
# if we can't find the interpreter we fall back to a search for something like "*root/bin/* and take this:
mapfile -t ROOTx_PATH < <(find "$SEARCH_PATH" -xdev \( -path "*extracted/bin" -o -path "*root/bin" \) -exec dirname {} \; 2>/dev/null)
for R_PATH in "${ROOTx_PATH[@]}"; do
if [[ -d "$R_PATH" ]]; then
ROOT_PATH+=( "$R_PATH" )
if [[ -z "$MECHANISM" ]]; then
MECHANISM="dir names"
elif [[ -n "$MECHANISM" ]] && ! echo "$MECHANISM" | grep -q "dir names"; then
MECHANISM="$MECHANISM / dir names"
fi
fi
done

mapfile -t ROOTx_PATH < <(find "$SEARCH_PATH" -xdev \( -path "*/sbin" -o -path "*/bin" -o -path "*/lib" -o -path "*/etc" -o -path "*/root" -o -path "*/dev" -o -path "*/opt" -o -path "*/proc" -o -path "*/lib64" -o -path "*/boot" -o -path "*/home" \) -exec dirname {} \; | sort | uniq -c | sort -r)
for R_PATH in "${ROOTx_PATH[@]}"; do
CNT=$(echo "$R_PATH" | awk '{print $1}')
if [[ "$CNT" -lt 5 ]]; then
# we only use paths with more then 4 matches as possible root path
continue
fi
R_PATH=$(echo "$R_PATH" | awk '{print $2}')
if [[ -d "$R_PATH" ]]; then
ROOT_PATH+=( "$R_PATH" )
if [[ -z "$MECHANISM" ]]; then
MECHANISM="dir names"
elif [[ -n "$MECHANISM" ]] && ! echo "$MECHANISM" | grep -q "dir names"; then
MECHANISM="$MECHANISM / dir names"
fi
fi
done

mapfile -t ROOTx_PATH < <(find "$SEARCH_PATH" -xdev -path "*bin/busybox" | sed -E 's/\/.?bin\/busybox//')
for R_PATH in "${ROOTx_PATH[@]}"; do
if [[ -d "$R_PATH" ]]; then
ROOT_PATH+=( "$R_PATH" )
if [[ -z "$MECHANISM" ]]; then
MECHANISM="busybox"
elif [[ -n "$MECHANISM" ]] && ! echo "$MECHANISM" | grep -q "busybox"; then
MECHANISM="$MECHANISM / busybox"
fi
fi
done

mapfile -t ROOTx_PATH < <(find "$SEARCH_PATH" -xdev -path "*bin/bash" -exec file {} \; | grep "ELF" | cut -d: -f1 | sed -E 's/\/.?bin\/bash//' || true)
for R_PATH in "${ROOTx_PATH[@]}"; do
if [[ -d "$R_PATH" ]]; then
ROOT_PATH+=( "$R_PATH" )
if [[ -z "$MECHANISM" ]]; then
MECHANISM="shell"
elif [[ -n "$MECHANISM" ]] && ! echo "$MECHANISM" | grep -q "shell"; then
MECHANISM="$MECHANISM / shell"
fi
fi
done

mapfile -t ROOTx_PATH < <(find "$SEARCH_PATH" -xdev -path "*bin/sh" -exec file {} \; | grep "ELF" | cut -d: -f1 | sed -E 's/\/.?bin\/sh//' || true)
for R_PATH in "${ROOTx_PATH[@]}"; do
if [[ -d "$R_PATH" ]]; then
ROOT_PATH+=( "$R_PATH" )
if [[ -z "$MECHANISM" ]]; then
MECHANISM="shell"
elif [[ -n "$MECHANISM" ]] && ! echo "$MECHANISM" | grep -q "shell"; then
MECHANISM="$MECHANISM / shell"
fi
fi
done

if [[ ${#ROOT_PATH[@]} -eq 0 ]]; then
export RTOS=1
ROOT_PATH+=( "$SEARCH_PATH" )
MECHANISM="last resort"
else
export RTOS=0
fi

eval "ROOT_PATH=($(for i in "${ROOT_PATH[@]}" ; do echo "\"$i\"" ; done | sort -u))"
if [[ -v ROOT_PATH[@] && "$RTOS" -eq 0 ]]; then
print_output "[*] Found $ORANGE${#ROOT_PATH[@]}$NC different root directories:"
write_link "s05#file_dirs"
fi

for R_PATH in "${ROOT_PATH[@]}"; do
if [[ "$MECHANISM" == "last resort" ]]; then
print_output "[*] Found no real root directory - setting it to: $ORANGE$R_PATH$NC via $ORANGE$MECHANISM$NC."
else
print_output "[+] Found the following root directory: $ORANGE$R_PATH$GREEN via $ORANGE$MECHANISM$GREEN."
fi
write_link "s05#file_dirs"
done
}

linux_basic_identification_helper函数:
用于统计linux文件系统中基本的特征文件或文件名

1
2
3
4
5
6
7
8
9
10
linux_basic_identification_helper() {
local FIRMWARE_PATH_CHECK="${1:-}"
if ! [[ -d "$FIRMWARE_PATH_CHECK" ]]; then
LINUX_PATH_COUNTER=0
return
fi
LINUX_PATH_COUNTER="$(find "$FIRMWARE_PATH_CHECK" "${EXCL_FIND[@]}" -xdev -type d -iname bin -o -type f -iname busybox -o -type f -name shadow -o -type f -name passwd -o -type d -iname sbin -o -type d -iname etc 2> /dev/null | wc -l)"
backup_var "LINUX_PATH_COUNTER" "$LINUX_PATH_COUNTER"
}

P60_firmware_bin_extractor.sh

分析固件与binwalk,检查熵和提取固件到日志目录
P60_firmware_bin_extractor函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
P60_firmware_bin_extractor() {
module_log_init "${FUNCNAME[0]}"
module_title "Binary firmware deep extractor"
pre_module_reporter "${FUNCNAME[0]}"

export DISK_SPACE_CRIT=0

# If we have not found a linux filesystem we try to do an extraction round on every file multiple times
if [[ $RTOS -eq 0 ]] ; then
module_end_log "${FUNCNAME[0]}" 0
return
fi

# 跟前面一样,检查sasquatch链接
# we need to check if sasquatch is the correct one for binwalk:
if ! [[ "$(readlink -q -f "$UNBLOB_PATH"/sasquatch)" == "/usr/local/bin/sasquatch_binwalk" ]]; then
if [[ -L "$UNBLOB_PATH"/sasquatch ]]; then
rm "$UNBLOB_PATH"/sasquatch
fi
ln -s /usr/local/bin/sasquatch_binwalk "$UNBLOB_PATH"/sasquatch || true
fi

# 用于检查磁盘空间,如果没超过最大的额外空间,就会开启深度提取
check_disk_space
if ! [[ "$DISK_SPACE" -gt "$MAX_EXT_SPACE" ]]; then
deep_extractor
else
print_output "[!] $(date) - Extractor needs too much disk space $DISK_SPACE" "main"
print_output "[!] $(date) - Ending extraction processes - no deep extraction performed" "main"
DISK_SPACE_CRIT=1
fi

print_ln

FILES_EXT=$(find "$FIRMWARE_PATH_CP" -xdev -type f | wc -l )
UNIQUE_FILES=$(find "$FIRMWARE_PATH_CP" "${EXCL_FIND[@]}" -xdev -type f -exec md5sum {} \; 2>/dev/null | sort -u -k1,1 | cut -d\ -f3 | wc -l )
DIRS_EXT=$(find "$FIRMWARE_PATH_CP" -xdev -type d | wc -l )
BINS=$(find "$FIRMWARE_PATH_CP" "${EXCL_FIND[@]}" -xdev -type f -exec file {} \; | grep -c "ELF" || true)

if [[ "$BINS" -gt 0 || "$UNIQUE_FILES" -gt 0 ]]; then
export LINUX_PATH_COUNTER=0
linux_basic_identification_helper "$FIRMWARE_PATH_CP"
print_ln
print_output "[*] Found $ORANGE$FILES_EXT$NC files ($ORANGE$UNIQUE_FILES$NC unique files) and $ORANGE$DIRS_EXT$NC directories at all."
print_output "[*] Found $ORANGE$BINS$NC binaries."
print_output "[*] Additionally the Linux path counter is $ORANGE$LINUX_PATH_COUNTER$NC."
print_ln
tree -csh "$FIRMWARE_PATH_CP" | tee -a "$LOG_FILE"

# now it should be fine to also set the FIRMWARE_PATH ot the FIRMWARE_PATH_CP
export FIRMWARE_PATH="$FIRMWARE_PATH_CP"

if [[ "${#ROOT_PATH[@]}" -gt 0 ]] ; then
write_csv_log "FILES" "UNIQUE_FILES" "DIRS" "Binaries" "LINUX_PATH_COUNTER" "Root PATH detected"
for R_PATH in "${ROOT_PATH[@]}"; do
write_csv_log "$FILES_EXT" "$UNIQUE_FILES" "$DIRS_EXT" "$BINS" "$LINUX_PATH_COUNTER" "$R_PATH"
done
fi
backup_var "FILES_EXT" "$FILES_EXT"
fi

module_end_log "${FUNCNAME[0]}" "$FILES_EXT"
}

check_disk_space函数:
用于检查磁盘空间

1
2
3
4
check_disk_space() {
export DISK_SPACE
DISK_SPACE=$(du -hm "$FIRMWARE_PATH_CP" --max-depth=1 --exclude="proc" 2>/dev/null | awk '{ print $1 }' | sort -hr | head -1 || true)
}

deep_extractor函数:
深度提取模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
deep_extractor() {
sub_module_title "Deep extraction mode"

FILE_MD5=""

FILES_BEFORE_DEEP=$(find "$FIRMWARE_PATH_CP" -xdev -type f | wc -l )

# if we run into the deep extraction mode we always do at least one extraction round:
if [[ "$DISK_SPACE_CRIT" -eq 0 ]]; then
print_output "[*] Deep extraction - 1st round"
print_output "[*] Walking through all files and try to extract what ever possible"

# 进行深度提取,将在下方给出分析
deeper_extractor_helper
detect_root_dir_helper "$FIRMWARE_PATH_CP"
fi

# 进行多轮提取
if [[ $RTOS -eq 1 && "$DISK_SPACE_CRIT" -eq 0 ]]; then
print_output "[*] Deep extraction - 2nd round"
print_output "[*] Walking through all files and try to extract what ever possible"

deeper_extractor_helper
detect_root_dir_helper "$FIRMWARE_PATH_CP"
fi

if [[ $RTOS -eq 1 && "$DISK_SPACE_CRIT" -eq 0 ]]; then
print_output "[*] Deep extraction - 3rd round"
print_output "[*] Walking through all files and try to extract what ever possible"

deeper_extractor_helper
detect_root_dir_helper "$FIRMWARE_PATH_CP"
fi

if [[ $RTOS -eq 1 && "$DISK_SPACE_CRIT" -eq 0 ]]; then
print_output "[*] Deep extraction - 4th round"
print_output "[*] Walking through all files and try to extract what ever possible with binwalk matryoshka mode"
print_output "[*] WARNING: This is the last extraction round that is executed."

# if we are already that far we do a final matryoshka extraction mode
deeper_extractor_helper "M"
detect_root_dir_helper "$FIRMWARE_PATH_CP"
fi

FILES_AFTER_DEEP=$(find "$FIRMWARE_PATH_CP" -xdev -type f | wc -l )

print_output "[*] Before deep extraction we had $ORANGE$FILES_BEFORE_DEEP$NC files, after deep extraction we have now $ORANGE$FILES_AFTER_DEEP$NC files extracted."
}

deeper_extractor_helper函数:
进行深度提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
deeper_extractor_helper() {

if [[ -v 1 ]] && [[ "$1" == "M" ]]; then
local MATRYOSHKA=1
else
local MATRYOSHKA=0
fi
local FILE_TMP=""
local FILE_MD5=""

# 将特定文件排除,并对文件本身进行去重,记录路径到FILE_ARR_LIMITED中
prepare_file_arr_limited "$FIRMWARE_PATH_CP"

# 遍历每一个独立文件
for FILE_TMP in "${FILE_ARR_LIMITED[@]}"; do

FILE_MD5="$(md5sum "$FILE_TMP" | awk '{print $1}')"
# let's check the current md5sum against our array of unique md5sums - if we have a match this is already extracted
# already extracted stuff is now ignored

if [[ ! " ${MD5_DONE_DEEP[*]} " =~ ${FILE_MD5} ]]; then

print_output "[*] Details of file: $ORANGE$FILE_TMP$NC"
print_output "$(indent "$(file "$FILE_TMP")")"

# do a quick check if EMBA should handle the file or we give it to binwalk:
# fw_bin_detector is a function from p02
fw_bin_detector "$FILE_TMP"

# 进行一轮提取
if [[ "$VMDK_DETECTED" -eq 1 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
vmdk_extractor "$FILE_TMP" "${FILE_TMP}_vmdk_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
vmdk_extractor "$FILE_TMP" "${FILE_TMP}_vmdk_extracted"
fi
elif [[ "$UBI_IMAGE" -eq 1 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
ubi_extractor "$FILE_TMP" "${FILE_TMP}_ubi_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
ubi_extractor "$FILE_TMP" "${FILE_TMP}_ubi_extracted"
fi
elif [[ "$DLINK_ENC_DETECTED" -eq 1 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
dlink_SHRS_enc_extractor "$FILE_TMP" "${FILE_TMP}_shrs_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
dlink_SHRS_enc_extractor "$FILE_TMP" "${FILE_TMP}_shrs_extracted"
fi
elif [[ "$DLINK_ENC_DETECTED" -eq 2 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
dlink_enc_img_extractor "$FILE_TMP" "${FILE_TMP}_enc_img_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
dlink_enc_img_extractor "$FILE_TMP" "${FILE_TMP}_enc_img_extracted"
fi
elif [[ "$EXT_IMAGE" -eq 1 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
ext_extractor "$FILE_TMP" "${FILE_TMP}_ext_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
ext_extractor "$FILE_TMP" "${FILE_TMP}_ext_extracted"
fi
elif [[ "$ENGENIUS_ENC_DETECTED" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
engenius_enc_extractor "$FILE_TMP" "${FILE_TMP}_engenius_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
engenius_enc_extractor "$FILE_TMP" "${FILE_TMP}_engenius_extracted"
fi
elif [[ "$BSD_UFS" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
ufs_extractor "$FILE_TMP" "${FILE_TMP}_bsd_ufs_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
ufs_extractor "$FILE_TMP" "${FILE_TMP}_bsd_ufs_extracted"
fi
elif [[ "$ANDROID_OTA" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
android_ota_extractor "$FILE_TMP" "${FILE_TMP}_android_ota_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
android_ota_extractor "$FILE_TMP" "${FILE_TMP}_android_ota_extracted"
fi
elif [[ "$OPENSSL_ENC_DETECTED" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
foscam_enc_extractor "$FILE_TMP" "${FILE_TMP}_foscam_enc_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
foscam_enc_extractor "$FILE_TMP" "${FILE_TMP}_foscam_enc_extracted"
fi
elif [[ "$BUFFALO_ENC_DETECTED" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
buffalo_enc_extractor "$FILE_TMP" "${FILE_TMP}_buffalo_enc_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
buffalo_enc_extractor "$FILE_TMP" "${FILE_TMP}_buffalo_enc_extracted"
fi
elif [[ "$ZYXEL_ZIP" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
zyxel_zip_extractor "$FILE_TMP" "${FILE_TMP}_zyxel_enc_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
zyxel_zip_extractor "$FILE_TMP" "${FILE_TMP}_zyxel_enc_extracted"
fi
elif [[ "$QCOW_DETECTED" -ne 0 ]]; then
if [[ "$THREADED" -eq 1 ]]; then
qcow_extractor "$FILE_TMP" "${FILE_TMP}_qemu_qcow_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
qcow_extractor "$FILE_TMP" "${FILE_TMP}_qemu_qcow_extracted"
fi

else
# default case to binwalk
if [[ "$THREADED" -eq 1 ]]; then
binwalk_deep_extract_helper "$MATRYOSHKA" "$FILE_TMP" "${FILE_TMP}_binwalk_extracted" &
BIN_PID="$!"
store_kill_pids "$BIN_PID"
disown "$BIN_PID" 2> /dev/null || true
WAIT_PIDS_P20+=( "$BIN_PID" )
else
binwalk_deep_extract_helper "$MATRYOSHKA" "$FILE_TMP" "${FILE_TMP}_binwalk_extracted"
fi
fi

MD5_DONE_DEEP+=( "$FILE_MD5" )
max_pids_protection "$MAX_MOD_THREADS" "${WAIT_PIDS_P20[@]}"
fi

check_disk_space

FREE_SPACE=$(df --output=avail "$LOG_DIR" | awk 'NR==2')
if [[ "$FREE_SPACE" -lt 100000 ]]; then
# this stops the complete EMBA test
print_output "[!] $(date) - The system is running out of disk space $ORANGE$FREE_SPACE$NC" "main"
print_output "[!] $(date) - Ending EMBA firmware analysis processes" "main"
cleaner 1
exit
elif [[ "$DISK_SPACE" -gt "$MAX_EXT_SPACE" ]]; then
# this stops the deep extractor but not EMBA
print_output "[!] $(date) - Extractor needs too much disk space $DISK_SPACE" "main"
print_output "[!] $(date) - Ending extraction processes" "main"
DISK_SPACE_CRIT=1
break
fi
done

[[ "$THREADED" -eq 1 ]] && wait_for_pid "${WAIT_PIDS_P20[@]}"
}

P65_package_extractor.sh

识别和提取典型的软件包档案,如deb, apk, ipk
逻辑比较简单,识别文件名然后针对性解包

P70_unblob.sh

将带有unblob的固件提取到模块日志目录(仅用于评估)
逻辑比较简单,直接使用unblob进行提取,然后识别文件系统根目录、统计信息。

P99_prepare_analyzer.sh

一些准备工作(检查固件、架构检查等)
检查固件、准备二进制文件列表、架构检查、文件系统根目录识别、设置配置文件路径、准备文件列表。

⬆︎TOP