六. Ghidra逆向工程实战
6.1 Ghidra脚本:自动分析固件漏洞
// FindFirmwareVulnerabilities.java
// Ghidra脚本:自动查找固件中的常见漏洞模式
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.block.*;
import ghidra.program.model.data.*;
import ghidra.program.model.address.*;
import ghidra.util.exception.*;
import java.util.*;
import java.util.regex.*;
public class FindFirmwareVulnerabilities extends GhidraScript {
// 危险函数列表
private static final String[] DANGEROUS_FUNCTIONS = {
"strcpy", "strcat", "sprintf", "vsprintf",
"gets", "scanf", "sscanf", "vscanf",
"memcpy", "memmove", "strncpy",
"system", "popen", "exec", "execl", "execv",
"printf", "fprintf", "snprintf"
};
// 硬编码字符串模式
private static final Pattern[] SENSITIVE_PATTERNS = {
Pattern.compile("(?i)(?:password|passwd|pwd)[=:]\s*([^\\s;]+)"),
Pattern.compile("(?i)admin[=:]\s*([^\\s;]+)"),
Pattern.compile("(?i)root[=:]\s*([^\\s;]+)"),
Pattern.compile("(?i)(?:api[_-]?key|secret)[=:]\s*([a-fA-F0-9]{20,})"),
Pattern.compile("(?:\\d{1,3}\\.){3}\\d{1,3}"),
Pattern.compile("https?://[^\\s]+")
};
// 结果存储
private Map<String, List<Vulnerability>> vulnerabilities = new HashMap<>();
class Vulnerability {
String type;
Address address;
String description;
String context;
Vulnerability(String type, Address address, String description, String context) {
this.type = type;
this.address = address;
this.description = description;
this.context = context;
}
@Override
public String toString() {
return String.format("%s @ %s: %s\nContext: %s",
type, address, description, context);
}
}
@Override
public void run() throws Exception {
println("=== 固件漏洞分析开始 ===");
println("固件: " + currentProgram.getName());
println("架构: " + currentProgram.getLanguageID().getIdAsString());
// 初始化结果分类
vulnerabilities.put("buffer_overflow", new ArrayList<>());
vulnerabilities.put("command_injection", new ArrayList<>());
vulnerabilities.put("hardcoded_creds", new ArrayList<>());
vulnerabilities.put("weak_crypto", new ArrayList<>());
vulnerabilities.put("debug_backdoor", new ArrayList<>());
// 执行分析
analyzeDangerousFunctions();
analyzeStringConstants();
analyzeNetworkFunctions();
analyzeEncryptionFunctions();
// 生成报告
generateReport();
println("=== 分析完成 ===");
}
private void analyzeDangerousFunctions() {
println("\n[1/4] 分析危险函数调用...");
FunctionManager funcManager = currentProgram.getFunctionManager();
ReferenceManager refManager = currentProgram.getReferenceManager();
for (String dangerousFunc : DANGEROUS_FUNCTIONS) {
// 查找函数引用
Function func = findFunctionByName(dangerousFunc);
if (func != null) {
println("找到危险函数: " + dangerousFunc + " @ " + func.getEntryPoint());
// 查找所有调用点
Reference[] refs = refManager.getReferencesTo(func.getEntryPoint());
for (Reference ref : refs) {
if (ref.getReferenceType().isCall()) {
Address callAddr = ref.getFromAddress();
analyzeFunctionCall(dangerousFunc, callAddr);
}
}
}
}
}
private Function findFunctionByName(String name) {
FunctionManager funcManager = currentProgram.getFunctionManager();
FunctionIterator funcIter = funcManager.getFunctions(true);
while (funcIter.hasNext()) {
Function func = funcIter.next();
if (func.getName().equalsIgnoreCase(name)) {
return func;
}
}
// 在符号表中查找
SymbolTable symTable = currentProgram.getSymbolTable();
SymbolIterator symIter = symTable.getSymbols(name);
while (symIter.hasNext()) {
Symbol sym = symIter.next();
if (sym.getSymbolType() == SymbolType.FUNCTION) {
return funcManager.getFunctionAt(sym.getAddress());
}
}
return null;
}
private void analyzeFunctionCall(String funcName, Address callAddr) {
try {
// 获取调用上下文
Listing listing = currentProgram.getListing();
Instruction instr = listing.getInstructionAt(callAddr);
if (instr == null) return;
String context = getFunctionContext(callAddr);
Vulnerability vuln = null;
// 根据函数类型分类
if (funcName.equals("strcpy") || funcName.equals("strcat") ||
funcName.equals("sprintf")) {
vuln = new Vulnerability("buffer_overflow", callAddr,
"潜在缓冲区溢出: " + funcName, context);
vulnerabilities.get("buffer_overflow").add(vuln);
} else if (funcName.equals("system") || funcName.equals("popen") ||
funcName.startsWith("exec")) {
vuln = new Vulnerability("command_injection", callAddr,
"潜在命令注入: " + funcName, context);
vulnerabilities.get("command_injection").add(vuln);
}
if (vuln != null) {
println("发现漏洞: " + vuln.description);
}
} catch (Exception e) {
println("分析函数调用失败: " + e.getMessage());
}
}
private String getFunctionContext(Address addr) {
// 获取函数上下文
FunctionManager funcManager = currentProgram.getFunctionManager();
Function func = funcManager.getFunctionContaining(addr);
if (func != null) {
return "函数: " + func.getName() +
", 地址: " + addr.toString();
}
return "地址: " + addr.toString();
}
private void analyzeStringConstants() {
println("\n[2/4] 分析字符串常量...");
DataIterator dataIter = currentProgram.getListing().getDefinedData(true);
while (dataIter.hasNext() && !monitor.isCancelled()) {
Data data = dataIter.next();
if (data.getDataType() instanceof StringDataType) {
String strValue = null;
try {
strValue = (String) data.getValue();
} catch (Exception e) {
try {
strValue = new String(data.getBytes(), "ASCII");
} catch (Exception e2) {
continue;
}
}
if (strValue != null && strValue.length() > 3) {
analyzeStringForSecrets(strValue, data.getAddress());
}
}
}
}
private void analyzeStringForSecrets(String str, Address addr) {
for (Pattern pattern : SENSITIVE_PATTERNS) {
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
Vulnerability vuln = new Vulnerability("hardcoded_creds", addr,
"发现硬编码敏感信息: " + matcher.group(), str);
vulnerabilities.get("hardcoded_creds").add(vuln);
println("发现硬编码信息: " + str.substring(0, Math.min(50, str.length())) + "...");
break;
}
}
// 额外的检查
if (str.toLowerCase().contains("debug") &&
(str.toLowerCase().contains("true") || str.contains("1"))) {
Vulnerability vuln = new Vulnerability("debug_backdoor", addr,
"发现调试模式字符串", str);
vulnerabilities.get("debug_backdoor").add(vuln);
}
}
private void analyzeNetworkFunctions() {
println("\n[3/4] 分析网络相关函数...");
String[] networkFunctions = {
"socket", "bind", "listen", "accept", "connect",
"send", "recv", "recvfrom", "sendto"
};
for (String funcName : networkFunctions) {
Function func = findFunctionByName(funcName);
if (func != null) {
println("找到网络函数: " + funcName);
// 检查是否监听特权端口
checkPrivilegedPorts(func);
}
}
}
private void checkPrivilegedPorts(Function func) {
// 查找函数中的端口号
// 这里简化处理,实际需要更复杂的分析
ReferenceManager refManager = currentProgram.getReferenceManager();
Reference[] refs = refManager.getReferencesTo(func.getEntryPoint());
for (Reference ref : refs) {
if (ref.getReferenceType().isCall()) {
Address callAddr = ref.getFromAddress();
// 分析调用上下文,查找立即数(端口号)
// 实现端口号提取逻辑...
}
}
}
private void analyzeEncryptionFunctions() {
println("\n[4/4] 分析加密函数...");
String[] cryptoFunctions = {
"md5", "sha1", "sha256", "aes", "des", "rc4",
"rand", "srand", "crypt", "encrypt", "decrypt"
};
for (String funcName : cryptoFunctions) {
Function func = findFunctionByName(funcName);
if (func != null) {
println("找到加密函数: " + funcName);
// 检查弱加密使用
if (funcName.equals("md5") || funcName.equals("sha1") ||
funcName.equals("des") || funcName.equals("rc4")) {
Vulnerability vuln = new Vulnerability("weak_crypto",
func.getEntryPoint(),
"使用弱加密算法: " + funcName,
"请考虑升级到更安全的算法");
vulnerabilities.get("weak_crypto").add(vuln);
}
}
}
}
private void generateReport() {
println("\n=== 生成分析报告 ===");
int totalVulns = 0;
for (List<Vulnerability> vulnList : vulnerabilities.values()) {
totalVulns += vulnList.size();
}
println("\n发现漏洞总数: " + totalVulns);
println("\n分类统计:");
for (Map.Entry<String, List<Vulnerability>> entry : vulnerabilities.entrySet()) {
String type = entry.getKey();
List<Vulnerability> vulns = entry.getValue();
println(String.format("%-20s: %d 个",
getChineseType(type), vulns.size()));
if (!vulns.isEmpty()) {
for (int i = 0; i < Math.min(3, vulns.size()); i++) {
Vulnerability vuln = vulns.get(i);
println(" " + (i+1) + ". " + vuln.description);
println(" 地址: " + vuln.address);
}
if (vulns.size() > 3) {
println(" ... 还有 " + (vulns.size() - 3) + " 个");
}
}
}
// 保存详细报告
saveDetailedReport();
}
private String getChineseType(String type) {
switch (type) {
case "buffer_overflow": return "缓冲区溢出";
case "command_injection": return "命令注入";
case "hardcoded_creds": return "硬编码凭证";
case "weak_crypto": return "弱加密";
case "debug_backdoor": return "调试后门";
default: return type;
}
}
private void saveDetailedReport() {
String fileName = currentProgram.getName() + "漏洞报告" +
new Date().toString().replace(" ", "_") + ".txt";
try {
String report = generateTextReport();
java.io.FileWriter writer = new java.io.FileWriter(fileName);
writer.write(report);
writer.close();
println("\n详细报告已保存到: " + fileName);
} catch (Exception e) {
println("保存报告失败: " + e.getMessage());
}
}
private String generateTextReport() {
StringBuilder sb = new StringBuilder();
sb.append("=".repeat(80)).append("\n");
sb.append("固件漏洞分析报告\n");
sb.append("固件文件: ").append(currentProgram.getName()).append("\n");
sb.append("分析时间: ").append(new Date()).append("\n");
sb.append("=".repeat(80)).append("\n\n");
for (Map.Entry<String, List<Vulnerability>> entry : vulnerabilities.entrySet()) {
String type = getChineseType(entry.getKey());
List<Vulnerability> vulns = entry.getValue();
if (!vulns.isEmpty()) {
sb.append("# ").append(type).append(" (").append(vulns.size()).append(" 个)\n\n");
for (int i = 0; i < vulns.size(); i++) {
Vulnerability vuln = vulns.get(i);
sb.append("## 漏洞 #").append(i + 1).append("\n");
sb.append("- 类型: ").append(vuln.type).append("\n");
sb.append("- 地址: ").append(vuln.address.toString()).append("\n");
sb.append("- 描述: ").append(vuln.description).append("\n");
sb.append("- 上下文: ").append(vuln.context).append("\n\n");
}
sb.append("-".repeat(60)).append("\n\n");
}
}
// 添加建议
sb.append("# 安全建议\n\n");
sb.append("1. 缓冲区溢出漏洞:\n");
sb.append(" - 使用安全的字符串函数(strncpy代替strcpy)\n");
sb.append(" - 添加边界检查\n");
sb.append(" - 启用栈保护(-fstack-protector)\n\n");
sb.append("2. 命令注入漏洞:\n");
sb.append(" - 避免使用system()函数\n");
sb.append(" - 使用exec族函数并严格过滤参数\n");
sb.append(" - 实施最小权限原则\n\n");
sb.append("3. 硬编码凭证:\n");
sb.append(" - 移除所有硬编码的密码和密钥\n");
sb.append(" - 使用安全的密钥存储方案\n");
sb.append(" - 实施动态凭证管理\n\n");
sb.append("4. 弱加密:\n");
sb.append(" - 使用强加密算法(AES-256, SHA-256)\n");
sb.append(" - 避免使用已知的弱算法(MD5, SHA-1, DES)\n");
sb.append(" - 使用经过验证的加密库\n\n");
sb.append("5. 调试后门:\n");
sb.append(" - 移除所有调试代码\n");
sb.append(" - 禁用生产环境的调试接口\n");
sb.append(" - 实施访问控制和审计\n\n");
sb.append("=".repeat(80)).append("\n");
sb.append("报告结束\n");
sb.append("注意:此报告由自动化工具生成,需要人工验证。\n");
return sb.toString();
}
}
6.2 Python自动化分析脚本
#!/usr/bin/env python3
"""
Ghidra自动化分析脚本
通过Ghidra的Python接口进行批量分析
"""
import os
import sys
import json
import time
import argparse
from pathlib import Path
from typing import Dict, List, Any
import subprocess
import threading
import queue
class GhidraBatchAnalyzer:
"""Ghidra批量分析器"""
def init(self, ghidra_path: str, project_path: str):
self.ghidra_path = Path(ghidra_path)
self.project_path = Path(project_path)
self.project_path.mkdir(parents=True, exist_ok=True)
# 确保Ghidra路径存在
if not self.ghidra_path.exists():
raise FileNotFoundError(f"Ghidra路径不存在: {ghidra_path}")
# 分析结果存储
self.results = {}
self.analysis_queue = queue.Queue()
def analyze_firmware(self, firmware_path: str,
analyze_headless: bool = True) -> Dict[str, Any]:
"""分析单个固件文件"""
firmware = Path(firmware_path)
if not firmware.exists():
raise FileNotFoundError(f"固件文件不存在: {firmware_path}")
print(f"开始分析固件: {firmware.name}")
if analyze_headless:
return self._analyze_headless(firmware)
else:
return self._analyze_interactive(firmware)
def _analyze_headless(self, firmware: Path) -> Dict[str, Any]:
"""使用headless模式分析"""
# 生成Ghidra分析脚本
analysis_script = self._generate_analysis_script(firmware)
# 准备Ghidra命令
cmd = [
str(self.ghidra_path / "support" / "analyzeHeadless"),
str(self.project_path),
"FirmwareAnalysis",
"-import", str(firmware),
"-postScript", analysis_script,
"-deleteProject"
]
print(f"执行命令: {' '.join(cmd)}")
try:
# 执行分析
start_time = time.time()
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300 # 5 分钟超时
)
elapsed_time = time.time() - start_time
# 解析输出
analysis_result = self._parse_ghidra_output(result.stdout, result.stderr)
analysis_result['analysis_time'] = elapsed_time
analysis_result['firmware'] = str(firmware)
# 保存结果
output_file = self.project_path / f"{firmware.stem}_analysis.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(analysis_result, f, indent=2, ensure_ascii=False)
print(f"分析完成,耗时: {elapsed_time:.2f}秒")
print(f"结果保存到: {output_file}")
return analysis_result
except subprocess.TimeoutExpired:
print(f"分析超时: {firmware.name}")
return {'error': '分析超时', 'firmware': str(firmware)}
except Exception as e:
print(f"分析失败: {e}")
return {'error': str(e), 'firmware': str(firmware)}
def _generate_analysis_script(self, firmware: Path) -> str:
"""生成Ghidra分析脚本"""
script_content = '''
// Ghidra分析脚本
// 自动分析固件文件
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*;
import java.util.*;
public class FirmwareAnalyzer extends GhidraScript {
public void run() throws Exception {
println("=== 固件分析开始 ===");
println("固件: " + currentProgram.getName());
Map<String, Object> results = new HashMap<>();
// 基本信息
results.put("program_name", currentProgram.getName());
results.put("language", currentProgram.getLanguageID().toString());
results.put("function_count", analyzeFunctions());
results.put("string_count", analyzeStrings());
results.put("imports", analyzeImports());
results.put("vulnerabilities", findVulnerabilities());
// 输出结果
println("\\n=== 分析结果 ===");
for (Map.Entry<String, Object> entry : results.entrySet()) {
println(entry.getKey() + ": " + entry.getValue());
}
// 保存结果到文件
saveResults(results);
}
private int analyzeFunctions() {
FunctionManager funcManager = currentProgram.getFunctionManager();
return funcManager.getFunctionCount();
}
private int analyzeStrings() {
int count = 0;
DataIterator dataIter = currentProgram.getListing().getDefinedData(true);
while (dataIter.hasNext() && !monitor.isCancelled()) {
Data data = dataIter.next();
if (data.getDataType() instanceof ghidra.program.model.data.StringDataType) {
count++;
}
}
return count;
}
private List<String> analyzeImports() {
List<String> imports = new ArrayList<>();
SymbolTable symTable = currentProgram.getSymbolTable();
SymbolIterator symIter = symTable.getExternalSymbols();
while (symIter.hasNext() && imports.size() < 50) {
Symbol sym = symIter.next();
imports.add(sym.getName());
}
return imports;
}
private Map<String, List<String>> findVulnerabilities() {
Map<String, List<String>> vulns = new HashMap<>();
vulns.put("dangerous_functions", findDangerousFunctions());
vulns.put("hardcoded_strings", findHardcodedStrings());
return vulns;
}
private List<String> findDangerousFunctions() {
List<String> dangerousFuncs = Arrays.asList(
"strcpy", "strcat", "sprintf", "gets",
"system", "popen", "exec"
);
List<String> found = new ArrayList<>();
FunctionManager funcManager = currentProgram.getFunctionManager();
FunctionIterator funcIter = funcManager.getFunctions(true);
while (funcIter.hasNext()) {
Function func = funcIter.next();
String name = func.getName().toLowerCase();
for (String dangerous : dangerousFuncs) {
if (name.contains(dangerous)) {
found.add(func.getName() + " @ " + func.getEntryPoint());
break;
}
}
}
return found;
}
private List<String> findHardcodedStrings() {
List<String> found = new ArrayList<>();
DataIterator dataIter = currentProgram.getListing().getDefinedData(true);
while (dataIter.hasNext() && found.size() < 20) {
Data data = dataIter.next();
if (data.getDataType() instanceof ghidra.program.model.data.StringDataType) {
try {
String str = (String) data.getValue();
if (str != null) {
// 检查敏感信息
if (str.toLowerCase().contains("password") ||
str.toLowerCase().contains("admin") ||
str.toLowerCase().contains("root") ||
str.matches(".*\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}.*")) {
found.add(str.substring(0, Math.min(50, str.length())) +
" @ " + data.getAddress());
}
}
} catch (Exception e) {
// 忽略转换错误
}
}
}
return found;
}
private void saveResults(Map<String, Object> results) {
try {
// 将结果保存到临时文件
String outputFile = System.getProperty("java.io.tmpdir") +
"/ghidra_analysis_" +
currentProgram.getName() + ".json";
java.io.FileWriter writer = new java.io.FileWriter(outputFile);
writer.write(new org.json.JSONObject(results).toString(2));
writer.close();
println("结果已保存到: " + outputFile);
} catch (Exception e) {
println("保存结果失败: " + e.getMessage());
}
}
}
'''
# 保存脚本到临时文件
script_file = self.project_path / "FirmwareAnalyzer.java"
with open(script_file, 'w', encoding='utf-8') as f:
f.write(script_content)
return str(script_file)
def _parse_ghidra_output(self, stdout: str, stderr: str) -> Dict[str, Any]:
"""解析Ghidra输出"""
result = {
'stdout': stdout,
'stderr': stderr,
'analysis_success': False,
'findings': {}
}
# 简单的输出解析
lines = stdout.split('\n')
for line in lines:
if 'function_count:' in line:
parts = line.split(':')
if len(parts) > 1:
result['findings']['function_count'] = parts[1].strip()
elif 'string_count:' in line:
parts = line.split(':')
if len(parts) > 1:
result['findings']['string_count'] = parts[1].strip()
elif 'dangerous_functions:' in line:
result['analysis_success'] = True
return result
def batch_analyze(self, firmware_dir: str, max_workers: int = 2):
"""批量分析多个固件"""
firmware_dir = Path(firmware_dir)
if not firmware_dir.exists():
raise FileNotFoundError(f"固件目录不存在: {firmware_dir}")
# 收集所有固件文件
firmware_files = []
for ext in ['.bin', '.elf', '.so', '.dll', '.exe']:
firmware_files.extend(firmware_dir.glob(f'*{ext}'))
print(f"找到 {len(firmware_files)} 个固件文件")
# 创建线程池
threads = []
for i in range(min(max_workers, len(firmware_files))):
thread = threading.Thread(target=self._worker)
thread.daemon = True
thread.start()
threads.append(thread)
# 添加任务到队列
for firmware in firmware_files:
self.analysis_queue.put(firmware)
# 等待所有任务完成
self.analysis_queue.join()
print(f"\n批量分析完成")
print(f"成功分析: {sum(1 for r in self.results.values() if 'error' not in r)}")
print(f"失败: {sum(1 for r in self.results.values() if 'error' in r)}")
# 保存批量分析报告
self._save_batch_report()
def _worker(self):
"""工作线程"""
while True:
try:
firmware = self.analysis_queue.get(timeout=1)
try:
result = self.analyze_firmware(str(firmware))
self.results[firmware.name] = result
finally:
self.analysis_queue.task_done()
except queue.Empty:
break
except Exception as e:
print(f"工作线程错误: {e}")
self.analysis_queue.task_done()
def _save_batch_report(self):
"""保存批量分析报告"""
report = {
'analysis_time': time.strftime("%Y-%m-%d %H:%M:%S"),
'total_firmwares': len(self.results),
'results': self.results,
'summary': self._generate_summary()
}
report_file = self.project_path / "batch_analysis_report.json"
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"批量分析报告已保存: {report_file}")
def _generate_summary(self) -> Dict[str, Any]:
"""生成分析摘要"""
summary = {
'total_analyzed': len(self.results),
'successful': 0,
'failed': 0,
'total_functions': 0,
'total_strings': 0,
'dangerous_functions_found': 0,
'vulnerabilities_by_type': {}
}
for result in self.results.values():
if 'error' in result:
summary['failed'] += 1
else:
summary['successful'] += 1
if 'findings' in result:
findings = result['findings']
if 'function_count' in findings:
try:
summary['total_functions'] += int(findings['function_count'])
except:
pass
if 'dangerous_functions' in findings:
summary['dangerous_functions_found'] += 1
return summary
def generate_html_report(self):
"""生成HTML格式的报告"""
html_template = '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>固件批量分析报告</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; }
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
.summary { background: #e8f4f8; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
.firmware-result { border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; border-radius: 5px; }
.success { border-left: 5px solid #28a745; }
.error { border-left: 5px solid #dc3545; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f2f2f2; }
pre { background: #f8f9fa; padding: 10px; border-radius: 3px; overflow: auto; }
.danger { color: #dc3545; font-weight: bold; }
.warning { color: #ffc107; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>固件批量安全分析报告</h1>
<p>分析时间: {analysis_time}</p>
</div>
<div class="summary">
<h2>分析摘要</h2>
<p>总共分析固件: {total_firmwares} 个</p>
<p>成功分析: {successful} 个</p>
<p>分析失败: {failed} 个</p>
<p>发现危险函数: {dangerous_functions_found} 个固件</p>
</div>
<h2>详细结果</h2>
{firmware_results}
</div>
</body>
</html>
'''
# 准备数据
summary = self._generate_summary()
firmware_results = ""
for firmware_name, result in self.results.items():
if 'error' in result:
status_class = "error"
status_text = f"<span class='danger'>分析失败: {result['error']}</span>"
else:
status_class = "success"
status_text = "<span style='color:#28a745;'>分析成功</span>"
findings = result.get('findings', {})
if 'dangerous_functions' in findings and findings['dangerous_functions']:
status_text += " <span class='warning'>(发现危险函数)</span>"
firmware_results += f'''
<div class="firmware-result {status_class}">
<h3>{firmware_name}</h3>
<p><strong>状态:</strong> {status_text}</p>
<p><strong>分析时间:</strong> {result.get('analysis_time', 'N/A')} 秒</p>
<table>
<tr>
<th>项目</th>
<th>结果</th>
</tr>
<tr>
<td>函数数量</td>
<td>{findings.get('function_count', 'N/A')}</td>
</tr>
<tr>
<td>字符串数量</td>
<td>{findings.get('string_count', 'N/A')}</td>
</tr>
</table>
<h4>危险函数:</h4>
<pre>{json.dumps(findings.get('dangerous_functions', []), indent=2, ensure_ascii=False)}</pre>
</div>
'''
# 填充模板
html_content = html_template.format(
analysis_time=time.strftime("%Y-%m-%d %H:%M:%S"),
total_firmwares=summary['total_analyzed'],
successful=summary['successful'],
failed=summary['failed'],
dangerous_functions_found=summary['dangerous_functions_found'],
firmware_results=firmware_results
)
# 保存HTML文件
html_file = self.project_path / "analysis_report.html"
with open(html_file, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"HTML报告已生成: {html_file}")
return str(html_file)
# 命令行接口
def main():
parser = argparse.ArgumentParser(description='Ghidra固件批量分析工具')
parser.add_argument('--ghidra', required=True, help='Ghidra安装路径')
parser.add_argument('--project', default='./ghidra_projects', help='项目路径')
parser.add_argument('--firmware', help='单个固件文件路径')
parser.add_argument('--directory', help='固件目录路径(批量分析)')
parser.add_argument('--workers', type=int, default=2, help='工作线程数')
args = parser.parse_args()
try:
analyzer = GhidraBatchAnalyzer(args.ghidra, args.project)
if args.firmware:
# 分析单个固件
result = analyzer.analyze_firmware(args.firmware)
print(json.dumps(result, indent=2, ensure_ascii=False))
elif args.directory:
# 批量分析
analyzer.batch_analyze(args.directory, args.workers)
# 生成HTML报告
html_report = analyzer.generate_html_report()
print(f"\n打开报告: file://{os.path.abspath(html_report)}")
else:
parser.print_help()
except Exception as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if name == "main":
main()
七. 实战案例:路由器固件漏洞挖掘
7.1 真实漏洞分析:CVE-2021-XXXXX
#!/usr/bin/env python3
"""
路由器固件漏洞分析实战
以真实CVE漏洞为例,演示完整分析流程
"""
import os
import re
import json
import hashlib
import struct
from pathlib import Path
from typing import Optional, Dict, List, Tuple
import subprocess
import tempfile
class RouterFirmwareAnalyzer:
"""路由器固件漏洞分析器"""
def init(self, firmware_path: str):
self.firmware_path = Path(firmware_path)
self.work_dir = Path(tempfile.mkdtemp(prefix="router_analysis_"))
self.extracted_dir = self.work_dir / "extracted"
self.results = {}
print(f"工作目录: {self.work_dir}")
def analyze(self) -> Dict:
"""执行完整分析"""
print("=== 路由器固件分析开始 ===")
# 1. 基础分析
self.results['basic_info'] = self.get_basic_info()
# 2. 提取文件系统
if self.extract_filesystem():
# 3. 分析Web管理界面
self.results['web_analysis'] = self.analyze_web_interface()
# 4. 分析网络服务
self.results['network_services'] = self.analyze_network_services()
# 5. 查找已知漏洞模式
self.results['vulnerabilities'] = self.find_vulnerabilities()
# 6. 分析认证机制
self.results['authentication'] = self.analyze_authentication()
# 7. 生成报告
report = self.generate_report()
print(f"\n=== 分析完成 ===")
print(f"报告已保存: {self.work_dir}/report.json")
return report
def get_basic_info(self) -> Dict:
"""获取固件基本信息"""
print("获取固件基本信息...")
info = {}
info['filename'] = self.firmware_path.name
info['size'] = self.firmware_path.stat().st_size
# 计算哈希
with open(self.firmware_path, 'rb') as f:
data = f.read()
info['md5'] = hashlib.md5(data).hexdigest()
info['sha256'] = hashlib.sha256(data).hexdigest()
# 识别固件类型
file_result = subprocess.run(
'file', str(self.firmware_path)\], capture_output=True, text=True ) info\['file_type'\] = file_result.stdout.strip() *#* *使用binwalk识别* binwalk_result = subprocess.run( \['binwalk', str(self.firmware_path)\], capture_output=True, text=True ) info\['binwalk_info'\] = binwalk_result.stdout.split('\\n')\[:10
return info
def extract_filesystem(self) -> bool:
"""提取文件系统"""
print("提取文件系统...")
self.extracted_dir.mkdir(exist_ok=True)
try:
result = subprocess.run(
'binwalk', '-eq', '-C', str(self.extracted_dir), str(self.firmware_path)\], capture_output=True, text=True, timeout=60 ) if result.returncode == 0: print(f"提取成功,文件数: {len(list(self.extracted_dir.rglob('\*')))}") return True else: print(f"提取失败: {result.stderr}") return False except subprocess.TimeoutExpired: print("提取超时") return False def analyze_web_interface(self) -\> Dict: """分析Web管理界面""" print("分析Web管理界面...") web_analysis = { 'web_files': \[\], 'cgi_binaries': \[\], 'php_files': \[\], 'javascript_files': \[\], 'configuration_files': \[
}
# 查找Web文件
web_dirs = ['www', 'htdocs', 'web', 'html']
for web_dir in web_dirs:
web_path = self.extracted_dir / web_dir
if web_path.exists():
# 收集所有Web文件
for file in web_path.rglob('*'):
if file.is_file():
rel_path = str(file.relative_to(self.extracted_dir))
web_analysis['web_files'].append(rel_path)
# 分类
if 'cgi' in file.suffix or 'cgi-bin' in str(file):
web_analysis['cgi_binaries'].append(rel_path)
elif file.suffix == '.php':
web_analysis['php_files'].append(rel_path)
elif file.suffix == '.js':
web_analysis['javascript_files'].append(rel_path)
elif file.suffix in ['.conf', '.cfg', '.config']:
web_analysis['configuration_files'].append(rel_path)
# 分析关键文件
for category, files in web_analysis.items():
if files:
print(f" 发现 {category}: {len(files)} 个")
return web_analysis
def analyze_network_services(self) -> Dict:
"""分析网络服务"""
print("分析网络服务...")
services = {
'listening_ports': [],
'services': [],
'firewall_rules': [],
'network_config': {}
}
# 查找网络配置文件
network_configs = [
'/etc/services',
'/etc/inetd.conf',
'/etc/xinetd.conf',
'/etc/xinetd.d/*',
'/etc/init.d/*',
'/etc/rc.d/*',
'/etc/network/*',
'/etc/firewall.*'
]
for pattern in network_configs:
for file in self.extracted_dir.glob(pattern.lstrip('/')):
if file.exists():
rel_path = str(file.relative_to(self.extracted_dir))
try:
content = file.read_text(errors='ignore')
# 查找端口绑定
port_matches = re.findall(r'(\d+)/tcp|(\d+)/udp', content)
for match in port_matches:
port = match[0] or match[1]
if port and port not in services['listening_ports']:
services['listening_ports'].append(port)
# 查找服务定义
service_matches = re.findall(r'(\w+)\s+(\d+)/(tcp|udp)', content)
for match in service_matches:
services['services'].append({
'name': match[0],
'port': match[1],
'protocol': match[2]
})
# 防火墙规则
if 'firewall' in str(file).lower():
services['firewall_rules'].append(rel_path)
# 网络配置
if 'network' in str(file).lower():
services['network_config'][rel_path] = content[:500]
except Exception as e:
continue
# 查找二进制文件中的网络服务
for bin_file in self.extracted_dir.rglob('*'):
if bin_file.is_file() and self.is_executable(bin_file):
try:
# 使用strings查找网络相关关键词
strings_result = subprocess.run(
'strings', str(bin_file)\], capture_output=True, text=True ) strings_output = strings_result.stdout.lower() *#* *检查是否可能是网络服务* network_keywords = \[ 'listen', 'bind', 'socket', 'port', 'http', 'telnet', 'ssh', 'ftp', 'tftp'
keyword_count = sum(1 for keyword in network_keywords
if keyword in strings_output)
if keyword_count > 3:
rel_path = str(bin_file.relative_to(self.extracted_dir))
# 查找监听的端口号
port_matches = re.findall(r'port[=:]\s*(\d+)',
strings_result.stdout,
re.IGNORECASE)
for port in set(port_matches):
services['listening_ports'].append(port)
except:
continue
# 去重和排序
services['listening_ports'] = sorted(set(services['listening_ports']))
return services
def is_executable(self, filepath: Path) -> bool:
"""判断文件是否为可执行文件"""
try:
result = subprocess.run(
'file', str(filepath)\], capture_output=True, text=True ) output = result.stdout.lower() return any(x in output for x in \['elf', 'executable', 'shared object'\]) except: return False def find_vulnerabilities(self) -\> Dict: """查找已知漏洞模式""" print("查找漏洞模式...") vulnerabilities = { 'command_injection': \[\], 'buffer_overflow': \[\], 'path_traversal': \[\], 'authentication_bypass': \[\], 'information_disclosure': \[
}
# 分析CGI二进制文件
for cgi_path in self.results.get('web_analysis', {}).get('cgi_binaries', []):
full_path = self.extracted_dir / cgi_path
if full_path.exists():
cgi_vulns = self.analyze_cgi_binary(full_path)
for vuln_type, vulns in cgi_vulns.items():
vulnerabilities[vuln_type].extend(vulns)
# 分析配置文件
config_patterns = {
'command_injection': [
r'system\s*\(\s*[\'\"]([^\'\"]+)[\'\"]',
r'popen\s*\(\s*[\'\"]([^\'\"]+)[\'\"]',
r'exec\s*\(\s*[\'\"]([^\'\"]+)[\'\"]'
],
'path_traversal': [
r'\.\./',
r'\.\.\\',
r'/etc/passwd',
r'/etc/shadow'
]
}
for config_path in self.extracted_dir.rglob('*.conf'):
try:
content = config_path.read_text(errors='ignore')
for vuln_type, patterns in config_patterns.items():
for pattern in patterns:
matches = re.findall(pattern, content)
for match in matches:
vulnerabilities[vuln_type].append({
'file': str(config_path.relative_to(self.extracted_dir)),
'pattern': pattern,
'context': match[:100]
})
except:
continue
return vulnerabilities
def analyze_cgi_binary(self, cgi_path: Path) -> Dict:
"""分析CGI二进制文件"""
cgi_vulns = {
'command_injection': [],
'buffer_overflow': [],
'path_traversal': []
}
# 使用strings分析
try:
strings_result = subprocess.run(
'strings', str(cgi_path)\], capture_output=True, text=True ) content = strings_result.stdout *#* *查找危险函数* dangerous_funcs = \['system', 'popen', 'exec', 'strcpy', 'strcat', 'sprintf'
for func in dangerous_funcs:
if func in content:
if func in ['system', 'popen', 'exec']:
vuln_type = 'command_injection'
else:
vuln_type = 'buffer_overflow'
cgi_vulns[vuln_type].append({
'binary': str(cgi_path.relative_to(self.extracted_dir)),
'dangerous_function': func,
'risk': 'high' if func in ['system', 'strcpy'] else 'medium'
})
# 查找用户输入处理
input_patterns = [
r'QUERY_STRING',
r'GET\s+',
r'POST\s+',
r'CONTENT_LENGTH'
]
for pattern in input_patterns:
if re.search(pattern, content):
cgi_vulns['command_injection'].append({
'binary': str(cgi_path.relative_to(self.extracted_dir)),
'input_handling': pattern,
'risk': 'medium'
})
except Exception as e:
print(f"分析CGI文件失败 {cgi_path}: {e}")
return cgi_vulns
def analyze_authentication(self) -> Dict:
"""分析认证机制"""
print("分析认证机制...")
auth_analysis = {
'password_files': [],
'session_files': [],
'authentication_binaries': [],
'hardcoded_credentials': [],
'weak_authentication': []
}
# 查找密码文件
password_files = [
'/etc/passwd',
'/etc/shadow',
'/etc/master.passwd',
'/etc/htpasswd',
'/etc/lighttpd/htpasswd'
]
for pw_file in password_files:
full_path = self.extracted_dir / pw_file.lstrip('/')
if full_path.exists():
auth_analysis['password_files'].append(str(full_path.relative_to(self.extracted_dir)))
try:
content = full_path.read_text(errors='ignore')
# 查找弱密码
weak_passwords = self.find_weak_passwords(content)
if weak_passwords:
auth_analysis['weak_authentication'].extend(weak_passwords)
except:
pass
# 查找认证相关的二进制文件
auth_keywords = ['login', 'auth', 'passwd', 'password']
for bin_file in self.extracted_dir.rglob('*'):
if bin_file.is_file() and self.is_executable(bin_file):
filename = bin_file.name.lower()
if any(keyword in filename for keyword in auth_keywords):
auth_analysis['authentication_binaries'].append(
str(bin_file.relative_to(self.extracted_dir))
)
# 查找硬编码凭证
hardcoded_creds = self.find_hardcoded_credentials()
auth_analysis['hardcoded_credentials'] = hardcoded_creds
return auth_analysis
def find_weak_passwords(self, passwd_content: str) -> List[Dict]:
"""查找弱密码"""
weak_passwords = []
lines = passwd_content.split('\n')
for line in lines:
if ':' in line:
parts = line.split(':')
if len(parts) >= 2:
username = parts[0]
password_hash = parts[1] if len(parts) > 1 else ''
# 检查空密码
if not password_hash or password_hash == '*':
weak_passwords.append({
'username': username,
'issue': '空密码或禁用账户',
'risk': 'high'
})
# 检查弱密码哈希(示例检查)
elif password_hash == 'x': # 使用shadow文件
pass
elif len(password_hash) < 3: # 非常短的密码
weak_passwords.append({
'username': username,
'issue': '密码哈希过短',
'risk': 'high'
})
return weak_passwords
def find_hardcoded_credentials(self) -> List[Dict]:
"""查找硬编码凭证"""
print("查找硬编码凭证...")
credentials = []
# 在所有文件中搜索
for file in self.extracted_dir.rglob('*'):
if file.is_file():
try:
# 只处理文本文件和二进制文件
if file.stat().st_size < 10 * 1024 * 1024: # 小于10MB
content = file.read_bytes()
# 转换为文本(尝试)
try:
text_content = content.decode('utf-8', errors='ignore')
except:
text_content = content.decode('ascii', errors='ignore')
# 搜索模式
patterns = [
(r'(?i)password[=:]\s*([^\s;]+)', '密码'),
(r'(?i)admin[=:]\s*([^\s;]+)', '管理员账户'),
(r'(?i)root[=:]\s*([^\s;]+)', 'root账户'),
(r'[\'\"]([a-f0-9]{32})[\'\"]', 'MD5哈希'),
(r'[\'\"]([a-f0-9]{64})[\'\"]', 'SHA256哈希')
]
for pattern, cred_type in patterns:
matches = re.findall(pattern, text_content)
for match in matches:
if len(match) > 3: # 过滤太短的结果
credentials.append({
'file': str(file.relative_to(self.extracted_dir)),
'type': cred_type,
'value': match[:50],
'context': self.get_context(text_content, match)
})
except Exception as e:
continue
return credentials[:50] # 只返回前50个
def get_context(self, content: str, target: str, context_len: int = 100) -> str:
"""获取上下文"""
try:
idx = content.find(target)
if idx != -1:
start = max(0, idx - context_len // 2)
end = min(len(content), idx + len(target) + context_len // 2)
return content[start:end].replace('\n', ' ')
except:
pass
return target[:50]
def generate_report(self) -> Dict:
"""生成分析报告"""
print("生成分析报告...")
report = {
'analysis_info': {
'firmware': str(self.firmware_path),
'analysis_date': self.get_current_time(),
'work_directory': str(self.work_dir)
},
'results': self.results,
'risk_assessment': self.assess_risk(),
'recommendations': self.generate_recommendations()
}
# 保存报告
report_path = self.work_dir / 'report.json'
with open(report_path, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
# 生成Markdown报告
self.generate_markdown_report(report)
return report
def get_current_time(self):
"""获取当前时间"""
from datetime import datetime
return datetime.now().isoformat()
def assess_risk(self) -> Dict:
"""风险评估"""
print("进行风险评估...")
risk_score = 0
findings = []
# 检查漏洞
vulns = self.results.get('vulnerabilities', {})
for vuln_type, vuln_list in vulns.items():
if vuln_list:
risk_score += len(vuln_list) * self.get_vuln_weight(vuln_type)
findings.append(f"发现 {len(vuln_list)} 个{vuln_type}漏洞")
# 检查认证问题
auth = self.results.get('authentication', {})
if auth.get('hardcoded_credentials'):
risk_score += len(auth['hardcoded_credentials']) * 5
findings.append(f"发现 {len(auth['hardcoded_credentials'])} 个硬编码凭证")
if auth.get('weak_authentication'):
risk_score += len(auth['weak_authentication']) * 10
findings.append(f"发现 {len(auth['weak_authentication'])} 个弱认证问题")
# 确定风险等级
if risk_score > 100:
risk_level = "严重"
elif risk_score > 50:
risk_level = "高危"
elif risk_score > 20:
risk_level = "中危"
else:
risk_level = "低危"
return {
'score': risk_score,
'level': risk_level,
'findings': findings,
'description': self.get_risk_description(risk_level)
}
def get_vuln_weight(self, vuln_type: str) -> int:
"""获取漏洞类型权重"""
weights = {
'command_injection': 10,
'buffer_overflow': 8,
'path_traversal': 6,
'authentication_bypass': 12,
'information_disclosure': 4
}
return weights.get(vuln_type, 5)
def get_risk_description(self, risk_level: str) -> str:
"""获取风险描述"""
descriptions = {
'严重': '发现多个高危漏洞,系统面临严重威胁,建议立即修复。',
'高危': '发现重要安全漏洞,存在被攻击的风险,建议尽快修复。',
'中危': '存在一些安全问题,建议在下一个版本中修复。',
'低危': '风险较低,但仍建议进行安全改进。'
}
return descriptions.get(risk_level, '风险未知')
def generate_recommendations(self) -> List[Dict]:
"""生成修复建议"""
recommendations = []
# 漏洞修复建议
vulns = self.results.get('vulnerabilities', {})
if vulns.get('command_injection'):
recommendations.append({
'issue': '命令注入漏洞',
'recommendation': '使用安全的命令执行函数,严格验证和过滤用户输入',
'priority': 'high'
})
if vulns.get('buffer_overflow'):
recommendations.append({
'issue': '缓冲区溢出漏洞',
'recommendation': '使用安全的字符串函数,添加边界检查,启用栈保护',
'priority': 'high'
})
# 认证修复建议
auth = self.results.get('authentication', {})
if auth.get('hardcoded_credentials'):
recommendations.append({
'issue': '硬编码凭证',
'recommendation': '移除所有硬编码的密码和密钥,使用安全的密钥管理方案',
'priority': 'critical'
})
if auth.get('weak_authentication'):
recommendations.append({
'issue': '弱认证机制',
'recommendation': '实施强密码策略,使用安全的密码哈希算法',
'priority': 'high'
})
# 通用建议
recommendations.extend([
{
'issue': '安全开发流程',
'recommendation': '建立安全开发生命周期(SDL),进行代码安全审查',
'priority': 'medium'
},
{
'issue': '固件安全',
'recommendation': '实施固件签名验证,安全的OTA更新机制',
'priority': 'medium'
},
{
'issue': '网络服务安全',
'recommendation': '最小化开放端口,使用防火墙,定期更新服务',
'priority': 'medium'
}
])
return recommendations
def generate_markdown_report(self, report: Dict):
"""生成Markdown格式报告"""
print("生成Markdown报告...")
md_content = f"""# 路由器固件安全分析报告
基本信息
-
**固件文件**: {report['analysis_info']['firmware']}
-
**分析时间**: {report['analysis_info']['analysis_date']}
-
**文件大小**: {report['results']['basic_info']['size']:,} 字节
-
**MD5哈希**: `{report['results']['basic_info']['md5']}`
-
**SHA256哈希**: `{report['results']['basic_info']['sha256']}`
风险评估
**风险等级**: **{report['risk_assessment']['level']}** (评分: {report['risk_assessment']['score']})
**发现的问题**:
"""
for finding in report['risk_assessment']['findings']:
md_content += f"- {finding}\n"
md_content += f"""
**描述**: {report['risk_assessment']['description']}
详细分析结果
文件系统提取
"""
web_analysis = report['results'].get('web_analysis', {})
if web_analysis:
md_content += f"""
**Web文件分析**:
-
Web文件总数: {len(web_analysis.get('web_files', []))}
-
CGI二进制文件: {len(web_analysis.get('cgi_binaries', []))}
-
PHP文件: {len(web_analysis.get('php_files', []))}
-
配置文件: {len(web_analysis.get('configuration_files', []))}
"""
network_services = report['results'].get('network_services', {})
if network_services.get('listening_ports'):
md_content += f"""
**网络服务**:
-
监听端口: {', '.join(network_services['listening_ports'][:10])}
-
发现服务: {len(network_services.get('services', []))} 个
"""
# 漏洞详情
vulns = report['results'].get('vulnerabilities', {})
if any(vulns.values()):
md_content += "### 发现的漏洞\n\n"
for vuln_type, vuln_list in vulns.items():
if vuln_list:
md_content += f"**{vuln_type}** ({len(vuln_list)} 个):\n"
for vuln in vuln_list[:3]: # 只显示前3个
md_content += f"- {vuln.get('binary', vuln.get('file', '未知'))}\n"
if len(vuln_list) > 3:
md_content += f" ... 还有 {len(vuln_list) - 3} 个\n"
md_content += "\n"
# 认证问题
auth = report['results'].get('authentication', {})
if auth.get('hardcoded_credentials'):
md_content += "### 硬编码凭证\n\n"
for cred in auth['hardcoded_credentials'][:5]: # 只显示前5个
md_content += f"- `{cred['value']}` ({cred['type']}) in {cred['file']}\n"
md_content += "\n"
# 修复建议
md_content += """## 安全建议
按优先级排序的修复建议:
"""
# 按优先级排序
recommendations = sorted(report['recommendations'],
key=lambda x: {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}[x['priority']])
for rec in recommendations:
priority_emoji = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🟢'}[rec['priority']]
md_content += f"{priority_emoji} **{rec['issue']}**\n"
md_content += f"建议: {rec['recommendation']}\n\n"
# 免责声明
md_content += """## 免责声明
本报告仅用于安全研究和教育目的。所有分析和测试应在合法授权的环境下进行。作者不对任何滥用本报告内容的行为负责。
*报告生成工具: RouterFirmwareAnalyzer*
*注意: 本报告由自动化工具生成,需要人工验证和确认*
"""
# 保存Markdown文件
md_path = self.work_dir / 'report.md'
with open(md_path, 'w', encoding='utf-8') as f:
f.write(md_content)
print(f"Markdown报告已保存: {md_path}")
# 使用示例
if name == "main":
import sys
if len(sys.argv) < 2:
print("用法: python router_analyzer.py <固件文件>")
sys.exit(1)
firmware_file = sys.argv[1]
if not os.path.exists(firmware_file):
print(f"错误: 文件不存在: {firmware_file}")
sys.exit(1)
print(f"开始分析路由器固件: {firmware_file}")
print("-" * 50)
analyzer = RouterFirmwareAnalyzer(firmware_file)
try:
report = analyzer.analyze()
print(f"\n分析完成!")
print(f"风险等级: {report['risk_assessment']['level']}")
print(f"工作目录: {analyzer.work_dir}")
print(f"报告文件: {analyzer.work_dir}/report.md")
except KeyboardInterrupt:
print("\n分析被用户中断")
except Exception as e:
print(f"分析过程中出错: {e}")
import traceback
traceback.print_exc()
八. 固件安全防护:从开发到部署
8.1 固件安全开发指南
# Makefile.security - 安全编译选项
# 安全编译标志
SECURITY_CFLAGS := \
-fstack-protector-strong \ # 栈保护
-fPIE -pie \ # 位置无关可执行文件
-D_FORTIFY_SOURCE=2 \ # 加强安全检查
-Wformat -Wformat-security \ # 格式字符串检查
-Werror=format-security \ # 格式字符串错误
-fno-common \ # 不将未初始化变量放在公共段
-Wl,-z,relro \ # 只读重定位
-Wl,-z,now \ # 立即绑定
-Wl,-z,noexecstack # 禁止栈执行
# 额外的安全选项
EXTRA_SECURITY := \
-fstack-clash-protection \ # 栈冲突保护
-fcf-protection=full \ # 控制流完整性
-ftrivial-auto-var-init=zero # 自动变量初始化为0
# 根据架构调整
ifeq ($(ARCH),arm)
SECURITY_CFLAGS += -mstrict-align
endif
# 使用安全的内存函数
SAFE_FUNCTIONS := \
-D_FORTIFY_SOURCE=3 \
-D_GLIBCXX_ASSERTIONS
# 最终编译标志
CFLAGS += (SECURITY_CFLAGS) (EXTRA_SECURITY) $(SAFE_FUNCTIONS)
LDFLAGS += -Wl,-z,defs -Wl,--no-undefined
# 检查工具
CHECK_TOOLS := checksec flawfinder cppcheck infer
# 安全构建目标
.PHONY: security-check
security-check:
@echo "执行安全检查..."
@# 检查安全编译标志
@echo "检查编译标志..."
@grep -q "fstack-protector" $(BUILD_LOG) && \
echo "✓ 栈保护已启用" || echo "✗ 栈保护未启用"
@# 检查二进制安全特性
@if [ -f $(TARGET) ]; then \
checksec --file=$(TARGET); \
fi
.PHONY: static-analysis
static-analysis:
@echo "执行静态代码分析..."
@flawfinder --quiet --minlevel=3 src/
@cppcheck --enable=all --suppress=missingInclude src/
@# 更多静态分析工具...
# 自动安全加固
.PHONY: harden
harden: $(TARGET)
@echo "加固二进制文件..."
@# 移除调试符号
@strip --strip-debug --strip-unneeded $(TARGET)
@# 设置正确的文件权限
@chmod 755 $(TARGET)
@echo "加固完成"
8.2 Python安全配置检查
#!/usr/bin/env python3
"""
固件安全配置检查工具
检查常见的安全配置问题
"""
import os
import re
import json
import hashlib
from pathlib import Path
from typing import Dict, List, Set, Tuple
import configparser
import yaml
import xml.etree.ElementTree as ET
class SecurityConfigChecker:
"""安全配置检查器"""
def init(self, firmware_path: str):
self.firmware_path = Path(firmware_path)
self.extracted_dir = self.firmware_path.parent / f"{self.firmware_path.stem}_extracted"
self.findings = []
self.checks = []
# 注册检查项
self.register_checks()
def register_checks(self):
"""注册安全检查项"""
self.checks.extend([
self.check_default_credentials,
self.check_debug_mode,
self.check_weak_crypto,
self.check_network_security,
self.check_file_permissions,
self.check_service_security,
self.check_web_security,
self.check_update_security
])
def run_checks(self) -> List[Dict]:
"""运行所有安全检查"""
print("运行安全配置检查...")
if not self.extracted_dir.exists():
print("错误: 请先提取固件文件系统")
return []
for check_func in self.checks:
try:
check_func()
except Exception as e:
print(f"检查失败 {check_func.name}: {e}")
return self.findings
def check_default_credentials(self):
"""检查默认凭证"""
print("检查默认凭证...")
# 常见默认凭证模式
default_creds = [
('admin', 'admin'),
('root', 'root'),
('admin', 'password'),
('root', 'password'),
('admin', '123456'),
('root', '123456'),
('admin', 'admin123'),
('user', 'user')
]
# 在配置文件中搜索
for config_file in self.extracted_dir.rglob('*.conf'):
try:
content = config_file.read_text(errors='ignore')
for username, password in default_creds:
if username in content and password in content:
self.add_finding(
"default_credentials",
"高危",
f"发现默认凭证: {username}/{password}",
str(config_file.relative_to(self.extracted_dir))
)
except:
continue
# 在Web文件中搜索
web_dirs = ['www', 'htdocs', 'web']
for web_dir in web_dirs:
web_path = self.extracted_dir / web_dir
if web_path.exists():
for file in web_path.rglob('*.php'):
try:
content = file.read_text(errors='ignore')
if 'default' in content.lower() and 'password' in content.lower():
self.add_finding(
"default_credentials",
"中危",
"可能包含默认凭证的PHP文件",
str(file.relative_to(self.extracted_dir))
)
except:
continue
def check_debug_mode(self):
"""检查调试模式"""
print("检查调试模式...")
debug_patterns = [
r'debug\s*=\s*true',
r'DEBUG\s*=\s*1',
r'debug_mode\s*=\s*on',
r'development\s*=\s*true'
]
for config_file in self.extracted_dir.rglob('*'):
if config_file.suffix in ['.conf', '.cfg', '.ini', '.json', '.yml']:
try:
content = config_file.read_text(errors='ignore')
for pattern in debug_patterns:
if re.search(pattern, content, re.IGNORECASE):
self.add_finding(
"debug_mode_enabled",
"中危",
"发现调试模式启用配置",
str(config_file.relative_to(self.extracted_dir))
)
break
except:
continue
# 检查环境变量
for script_file in self.extracted_dir.rglob('*.sh'):
try:
content = script_file.read_text(errors='ignore')
if 'DEBUG=1' in content or 'debug=true' in content:
self.add_finding(
"debug_mode_enabled",
"中危",
"Shell脚本中启用调试模式",
str(script_file.relative_to(self.extracted_dir))
)
except:
continue
def check_weak_crypto(self):
"""检查弱加密算法"""
print("检查弱加密算法...")
weak_crypto_patterns = [
r'md5\s*\(',
r'sha1\s*\(',
r'des\s*\(',
r'rc4\s*\(',
r'base64\s*\(',
r'xor\s*encrypt'
]
# 检查源代码
for source_file in self.extracted_dir.rglob('*.c'):
try:
content = source_file.read_text(errors='ignore')
for pattern in weak_crypto_patterns:
if re.search(pattern, content, re.IGNORECASE):
self.add_finding(
"weak_cryptography",
"高危",
f"使用弱加密算法: {pattern}",
str(source_file.relative_to(self.extracted_dir))
)
except:
continue
# 检查配置文件中的加密设置
for config_file in self.extracted_dir.rglob('*.conf'):
try:
content = config_file.read_text(errors='ignore')
if 'md5' in content.lower() and 'password' in content.lower():
self.add_finding(
"weak_cryptography",
"高危",
"配置中使用MD5存储密码",
str(config_file.relative_to(self.extracted_dir))
)
except:
continue
def check_network_security(self):
"""检查网络安全配置"""
print("检查网络安全配置...")
# 检查防火墙配置
firewall_files = list(self.extracted_dir.glob('**/firewall*'))
firewall_files.extend(self.extracted_dir.glob('**/iptables*'))
for fw_file in firewall_files:
if fw_file.exists():
try:
content = fw_file.read_text(errors='ignore')
# 检查是否允许所有流量
if 'ACCEPT all' in content or 'DROP' not in content:
self.add_finding(
"weak_firewall",
"中危",
"防火墙配置可能过于宽松",
str(fw_file.relative_to(self.extracted_dir))
)
except:
continue
# 检查SSH配置
ssh_config = self.extracted_dir / 'etc/ssh/sshd_config'
if ssh_config.exists():
try:
content = ssh_config.read_text(errors='ignore')
# 检查不安全配置
checks = [
('PermitRootLogin yes', '允许root登录', '高危'),
('PasswordAuthentication yes', '允许密码认证', '中危'),
('Protocol 1', '使用SSHv1协议', '高危'),
('X11Forwarding yes', '启用X11转发', '低危')
]
for pattern, description, severity in checks:
if pattern in content:
self.add_finding(
"insecure_ssh_config",
severity,
f"SSH配置问题: {description}",
str(ssh_config.relative_to(self.extracted_dir))
)
except:
pass
def check_file_permissions(self):
"""检查文件权限"""
print("检查文件权限...")
# 检查关键文件权限
critical_files = [
'/etc/passwd',
'/etc/shadow',
'/etc/sudoers',
'/root/.ssh/authorized_keys'
]
for crit_file in critical_files:
full_path = self.extracted_dir / crit_file.lstrip('/')
if full_path.exists():
try:
stat_info = full_path.stat()
# 检查权限
mode = stat_info.st_mode
# 检查是否可写
if mode & 0o022: # 检查group和other的写权限
self.add_finding(
"insecure_file_permissions",
"高危",
f"关键文件可写: {crit_file}",
f"权限: {oct(mode)[-3:]}"
)
# 检查shadow文件是否可读
if 'shadow' in crit_file and mode & 0o004:
self.add_finding(
"insecure_file_permissions",
"高危",
"shadow文件其他人可读",
f"权限: {oct(mode)[-3:]}"
)
except:
continue
def check_service_security(self):
"""检查服务安全配置"""
print("检查服务安全配置...")
# 检查init脚本
init_dirs = ['/etc/init.d', '/etc/rc.d', '/etc/systemd/system']
for init_dir in init_dirs:
init_path = self.extracted_dir / init_dir.lstrip('/')
if init_path.exists():
for init_script in init_path.iterdir():
if init_script.is_file():
try:
content = init_script.read_text(errors='ignore')
# 检查是否以root权限运行
if 'root' in content and 'run as' not in content.lower():
self.add_finding(
"service_security",
"中危",
f"服务可能以root权限运行: {init_script.name}",
str(init_script.relative_to(self.extracted_dir))
)
except:
continue
def check_web_security(self):
"""检查Web应用安全"""
print("检查Web应用安全...")
web_dirs = ['www', 'htdocs', 'web', 'html']
for web_dir in web_dirs:
web_path = self.extracted_dir / web_dir
if web_path.exists():
# 检查PHP安全配置
for php_file in web_path.rglob('*.php'):
try:
content = php_file.read_text(errors='ignore')
# 检查危险函数
dangerous_funcs = ['system(', 'exec(', 'passthru(', 'shell_exec(']
for func in dangerous_funcs:
if func in content:
self.add_finding(
"web_security",
"高危",
f"PHP文件包含危险函数: {func}",
str(php_file.relative_to(self.extracted_dir))
)
break
except:
continue
# 检查配置文件
for config_file in web_path.rglob('config*'):
if config_file.suffix in ['.php', '.inc']:
try:
content = config_file.read_text(errors='ignore')
# 检查数据库密码硬编码
db_patterns = [
r'\$password\s*=\s*[\'"][^\'"]+[\'"]',
r'DB_PASSWORD\s*=\s*[\'"][^\'"]+[\'"]'
]
for pattern in db_patterns:
if re.search(pattern, content):
self.add_finding(
"web_security",
"高危",
"Web配置文件中发现硬编码数据库密码",
str(config_file.relative_to(self.extracted_dir))
)
break
except:
continue
def check_update_security(self):
"""检查更新安全"""
print("检查固件更新安全...")
# 检查更新脚本
update_scripts = list(self.extracted_dir.glob('**/update*'))
update_scripts.extend(self.extracted_dir.glob('**/upgrade*'))
for script in update_scripts:
if script.is_file():
try:
content = script.read_text(errors='ignore')
# 检查是否验证签名
if 'signature' not in content.lower() and 'verify' not in content.lower():
self.add_finding(
"update_security",
"高危",
"固件更新未验证签名",
str(script.relative_to(self.extracted_dir))
)
# 检查是否使用HTTP(而非HTTPS)
if 'http://' in content and 'https://' not in content:
self.add_finding(
"update_security",
"中危",
"使用HTTP进行固件更新(不安全)",
str(script.relative_to(self.extracted_dir))
)
except:
continue
def add_finding(self, category: str, severity: str,
description: str, location: str):
"""添加发现的问题"""
finding = {
'category': category,
'severity': severity,
'description': description,
'location': location,
'recommendation': self.get_recommendation(category)
}
self.findings.append(finding)
def get_recommendation(self, category: str) -> str:
"""获取修复建议"""
recommendations = {
'default_credentials': '更改默认用户名和密码,实施强密码策略',
'debug_mode_enabled': '在生产环境中禁用调试模式',
'weak_cryptography': '使用强加密算法(如AES-256、SHA-256)',
'weak_firewall': '配置严格的防火墙规则,默认拒绝所有入站连接',
'insecure_ssh_config': '禁用root登录,使用密钥认证,禁用SSHv1',
'insecure_file_permissions': '设置正确的文件权限,遵循最小权限原则',
'service_security': '使用非特权用户运行服务',
'web_security': '验证用户输入,避免使用危险函数,加密敏感配置',
'update_security': '实施固件签名验证,使用HTTPS进行更新'
}
return recommendations.get(category, '请参考相关安全最佳实践')
def generate_report(self) -> str:
"""生成安全报告"""
report = {
'firmware': str(self.firmware_path),
'total_findings': len(self.findings),
'findings_by_severity': {},
'details': self.findings
}
# 统计严重程度
for finding in self.findings:
severity = finding['severity']
report['findings_by_severity'][severity] = \
report['findings_by_severity'].get(severity, 0) + 1
# 生成Markdown报告
md_content = f"""# 固件安全配置检查报告
概览
-
**固件文件**: {report['firmware']}
-
**发现问题总数**: {report['total_findings']}
-
**检查时间**: {self.get_current_time()}
统计信息
"""
for severity, count in report['findings_by_severity'].items():
emoji = {'高危': '🔴', '中危': '🟠', '低危': '🟡'}.get(severity, '⚪')
md_content += f"- {emoji} **{severity}**: {count} 个\n"
md_content += """
详细问题列表
"""
# 按严重程度排序
severity_order = {'高危': 0, '中危': 1, '低危': 2}
sorted_findings = sorted(
report['details'],
key=lambda x: severity_order.get(x['severity'], 3)
)
for finding in sorted_findings:
emoji = {'高危': '🔴', '中危': '🟠', '低危': '🟡'}.get(finding['severity'], '⚪')
md_content += f"### {emoji} {finding['category']}\n"
md_content += f"**严重程度**: {finding['severity']}\n\n"
md_content += f"**问题描述**: {finding['description']}\n\n"
md_content += f"**位置**: `{finding['location']}`\n\n"
md_content += f"**修复建议**: {finding['recommendation']}\n\n"
md_content += "---\n\n"
# 总结和建议
md_content += """## 总结与建议
立即行动(高危问题)
-
更改所有默认凭证
-
修复弱加密实现
-
配置正确的文件权限
-
实施固件签名验证
短期改进(中危问题)
-
禁用调试模式
-
加强防火墙配置
-
加固服务安全配置
-
修复Web应用安全问题
长期规划
-
建立安全开发流程
-
实施自动化安全测试
-
定期进行安全审计
-
保持固件和依赖项更新
免责声明
本报告仅用于安全研究和教育目的。在进行任何安全修复前,请确保充分测试。
*报告生成时间: {report_time}*
*工具版本: SecurityConfigChecker v1.0*
""".format(report_time=self.get_current_time())
return md_content
def get_current_time(self):
"""获取当前时间"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def save_report(self, output_file: str = None):
"""保存报告"""
if output_file is None:
output_file = self.firmware_path.parent / f"{self.firmware_path.stem}_security_report.md"
report_content = self.generate_report()
with open(output_file, 'w', encoding='utf-8') as f:
f.write(report_content)
print(f"安全报告已保存: {output_file}")
return output_file
# 使用示例
if name == "main":
import sys
if len(sys.argv) < 2:
print("用法: python security_checker.py <固件文件> [输出报告]")
sys.exit(1)
firmware = sys.argv[1]
output = sys.argv[2] if len(sys.argv) > 2 else None
if not os.path.exists(firmware):
print(f"错误: 固件文件不存在: {firmware}")
sys.exit(1)
print(f"开始安全配置检查: {firmware}")
checker = SecurityConfigChecker(firmware)
findings = checker.run_checks()
if findings:
print(f"\n发现 {len(findings)} 个安全问题:")
# 按严重程度分组显示
by_severity = {}
for finding in findings:
severity = finding['severity']
by_severity.setdefault(severity, []).append(finding)
for severity in ['高危', '中危', '低危']:
if severity in by_severity:
print(f"\n{severity}问题 ({len(by_severity[severity])} 个):")
for finding in by_severity[severity][:3]: # 只显示前3个
print(f" - {finding['description']}")
if len(by_severity[severity]) > 3:
print(f" ... 还有 {len(by_severity[severity]) - 3} 个")
else:
print("\n未发现安全问题")
# 生成并保存报告
report_file = checker.save_report(output)
print(f"\n详细报告: {report_file}")
8.3 自动化固件加固脚本
#!/bin/bash
# firmware_hardening.sh - 自动化固件加固脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "{BLUE}\[INFO\]{NC} $1"; }
log_success() { echo -e "{GREEN}\[SUCCESS\]{NC} $1"; }
log_warning() { echo -e "{YELLOW}\[WARNING\]{NC} $1"; }
log_error() { echo -e "{RED}\[ERROR\]{NC} $1"; }
# 显示使用说明
usage() {
echo "固件自动化加固工具"
echo "用法: $0 [选项] <固件文件>"
echo ""
echo "选项:"
echo " -o, --output FILE 指定输出文件"
echo " -c, --config FILE 指定配置文件"
echo " -k, --keep-temp 保留临时文件"
echo " -v, --verbose 详细输出"
echo " -h, --help 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 firmware.bin -o firmware_hardened.bin"
exit 1
}
# 检查依赖
check_dependencies() {
local deps=("binwalk" "unsquashfs" "mksquashfs" "cpio" "file")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v $dep &> /dev/null; then
missing+=("$dep")
fi
done
if [ ${#missing[@]} -ne 0 ]; then
log_error "缺少依赖: ${missing[*]}"
exit 1
fi
}
# 解析命令行参数
parse_args() {
OUTPUT_FILE=""
CONFIG_FILE=""
KEEP_TEMP=false
VERBOSE=false
while [[ $# -gt 0 ]]; do
case $1 in
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-c|--config)
CONFIG_FILE="$2"
shift 2
;;
-k|--keep-temp)
KEEP_TEMP=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
usage
;;
-*)
log_error "未知选项: $1"
usage
;;
*)
FIRMWARE_FILE="$1"
shift
;;
esac
done
if [ -z "$FIRMWARE_FILE" ]; then
log_error "未指定固件文件"
usage
fi
if [ ! -f "$FIRMWARE_FILE" ]; then
log_error "固件文件不存在: $FIRMWARE_FILE"
exit 1
fi
# 设置默认输出文件名
if [ -z "$OUTPUT_FILE" ]; then
OUTPUT_FILE="${FIRMWARE_FILE%.*}_hardened.bin"
fi
}
# 创建临时目录
setup_workspace() {
WORKSPACE=$(mktemp -d -t firmware_harden_XXXXXX)
EXTRACTED_DIR="$WORKSPACE/extracted"
MODIFIED_DIR="$WORKSPACE/modified"
BACKUP_DIR="$WORKSPACE/backup"
mkdir -p "EXTRACTED_DIR" "MODIFIED_DIR" "$BACKUP_DIR"
log_info "工作目录: $WORKSPACE"
}
# 提取固件
extract_firmware() {
log_info "提取固件文件系统..."
if binwalk -eq -C "EXTRACTED_DIR" "FIRMWARE_FILE" &> /dev/null; then
log_success "提取成功"
# 统计提取的文件
FILE_COUNT=(find "EXTRACTED_DIR" -type f | wc -l)
log_info "提取文件数: $FILE_COUNT"
# 备份原始文件
cp -r "EXTRACTED_DIR" "BACKUP_DIR/original"
else
log_error "提取失败"
exit 1
fi
}
# 移除调试信息
remove_debug_info() {
log_info "移除调试信息和符号..."
local removed_count=0
# 查找并移除调试文件
find "$EXTRACTED_DIR" -type f \( -name "*.debug" -o -name "*.dbg" \) -delete
# 使用strip移除符号
for binary in (find "EXTRACTED_DIR" -type f -executable); do
if file "$binary" | grep -q "ELF.*executable"; then
if strip --strip-debug --strip-unneeded "$binary" 2>/dev/null; then
removed_count=$((removed_count + 1))
fi
fi
done
log_success "移除了 $removed_count 个二进制文件的调试符号"
}
# 加固配置文件
harden_configurations() {
log_info "加固配置文件..."
local hardened_count=0
# 加固SSH配置
local sshd_config="$EXTRACTED_DIR/etc/ssh/sshd_config"
if [ -f "$sshd_config" ]; then
log_info "加固SSH配置..."
# 备份原始配置
cp "sshd_config" "BACKUP_DIR/sshd_config.original"
# 应用安全配置
sed -i '/^#/!s/PermitRootLogin.*/PermitRootLogin no/g' "$sshd_config"
sed -i '/^#/!s/PasswordAuthentication.*/PasswordAuthentication no/g' "$sshd_config"
sed -i '/^#/!s/Protocol.*/Protocol 2/g' "$sshd_config"
sed -i '/^#/!s/X11Forwarding.*/X11Forwarding no/g' "$sshd_config"
hardened_count=$((hardened_count + 1))
fi
# 移除默认凭证
log_info "检查默认凭证..."
# 常见默认凭证文件
local default_cred_files=(
"$EXTRACTED_DIR/etc/passwd"
"$EXTRACTED_DIR/etc/shadow"
"$EXTRACTED_DIR/etc/lighttpd/htpasswd"
)
for cred_file in "${default_cred_files[@]}"; do
if [ -f "$cred_file" ]; then
# 备份
cp "cred_file" "BACKUP_DIR/(basename "cred_file").original"
# 示例:移除已知的默认条目
sed -i '/^admin:/d' "$cred_file" 2>/dev/null || true
sed -i '/^root::/d' "$cred_file" 2>/dev/null || true
hardened_count=$((hardened_count + 1))
fi
done
log_success "加固了 $hardened_count 个配置文件"
}
# 修复文件权限
fix_permissions() {
log_info "修复文件权限..."
local fixed_count=0
# 关键文件权限
declare -A secure_permissions=(
"/etc/passwd"\]="644" \["/etc/shadow"\]="600" \["/etc/sudoers"\]="440" \["/root/.ssh/authorized_keys"\]="600" ) for rel_path in "${!secure_permissions\[@\]}"; do local full_path="$EXTRACTED_DIR/${rel_path#/}" if \[ -f "$full_path" \]; then local expected_perm="${secure_permissions\[$rel_path\]}" local current_perm=$(stat -c "%a" "$full_path" 2\>/dev/null \|\| echo "000") if \[ "$current_perm" != "$expected_perm" \]; then chmod "$expected_perm" "$full_path" log_info "修复权限: $rel_path ($current_perm -\> $expected_perm)" fixed_count=$((fixed_count + 1)) fi fi done *#* *确保关键目录不可写* local sensitive_dirs=( "/etc" "/bin" "/sbin" "/usr/bin" "/usr/sbin" ) for dir in "${sensitive_dirs\[@\]}"; do local full_dir="$EXTRACTED_DIR/${dir#/}" if \[ -d "$full_dir" \]; then *#* *移除其他用户的写权限* chmod o-w "$full_dir" 2\>/dev/null \|\| true fi done log_success "修复了 $fixed_count 个文件的权限" } *# 禁用危险服务* disable_dangerous_services() { log_info "禁用危险服务..." local disabled_count=0 *#* *危险服务列表* local dangerous_services=( "telnet" "ftp" "tftp" "rsh" "rexec" "rlogin" ) *#* *检查init.d脚本* local init_dir="$EXTRACTED_DIR/etc/init.d" if \[ -d "$init_dir" \]; then for service in "${dangerous_services\[@\]}"; do for service_file in "$init_dir/"\*"$service"\*; do if \[ -f "$service_file" \]; then *#* *禁用服务(重命名或移除执行权限)* mv "$service_file" "$service_file.disabled" 2\>/dev/null \|\| true chmod -x "$service_file" 2\>/dev/null \|\| true log_info "禁用服务: $(basename "$service_file")" disabled_count=$((disabled_count + 1)) fi done done fi *#* *检查inetd/xinetd配置* local inetd_configs=( "$EXTRACTED_DIR/etc/inetd.conf" "$EXTRACTED_DIR/etc/xinetd.conf" ) for config in "${inetd_configs\[@\]}"; do if \[ -f "$config" \]; then *#* *备份原始配置* cp "$config" "$BACKUP_DIR/$(basename "$config").original" *#* *注释掉危险服务* for service in "${dangerous_services\[@\]}"; do sed -i "/\^\[\^#\].\*$service/s/\^/# /" "$config" done disabled_count=$((disabled_count + 1)) fi done log_success "禁用了 $disabled_count 个危险服务" } *# 添加安全监控* add_security_monitoring() { log_info "添加安全监控..." *#* *创建简单的安全监控脚本* local security_script="$EXTRACTED_DIR/etc/security_monitor.sh" cat \> "$security_script" \<\< 'EOF' #!/bin/sh # 安全监控脚本 LOG_FILE="/var/log/security.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') log_event() { echo "\[$TIMESTAMP\] $1" \>\> "$LOG_FILE" } # 检查关键文件修改 check_file_integrity() { local critical_files="/etc/passwd /etc/shadow /etc/sudoers" for file in $critical_files; do if \[ -f "$file" \]; then local current_hash=$(md5sum "$file" 2\>/dev/null \| cut -d' ' -f1) local stored_hash="" # 这里应该从安全的地方读取存储的哈希值 # 简化版本:只记录变化 if \[ -f "/var/lib/checksums/${file##\*/}.md5" \]; then stored_hash=$(cat "/var/lib/checksums/${file##\*/}.md5") fi if \[ -n "$stored_hash" \] \&\& \[ "$current_hash" != "$stored_hash" \]; then log_event "警告: $file 被修改" fi # 存储新的哈希值 mkdir -p /var/lib/checksums echo "$current_hash" \> "/var/lib/checksums/${file##\*/}.md5" fi done } # 检查登录尝试 check_auth_logs() { if \[ -f "/var/log/auth.log" \]; then local failed_logins=$(grep "Failed password" /var/log/auth.log \| wc -l) if \[ "$failed_logins" -gt 10 \]; then log_event "警告: 检测到多次失败登录尝试 ($failed_logins 次)" fi fi } # 主循环 main() { while true; do check_file_integrity check_auth_logs sleep 300 # 每5分钟检查一次 done } # 启动监控 main \& EOF *#* *设置脚本权限* chmod 755 "$security_script" *#* *添加到启动脚本* local rc_local="$EXTRACTED_DIR/etc/rc.local" if \[ -f "$rc_local" \]; then echo "# 启动安全监控" \>\> "$rc_local" echo "/etc/security_monitor.sh" \>\> "$rc_local" fi log_success "添加了安全监控脚本" } *# 重新打包固件* repack_firmware() { log_info "重新打包固件..." *#* *复制修改后的文件* cp -r "$EXTRACTED_DIR" "$MODIFIED_DIR" *#* *这里需要根据固件类型使用不同的打包工具* *#* *示例:处理squashfs文件系统* *#* *查找原始的文件系统镜像* local rootfs_img=$(find "$BACKUP_DIR/original" -name "\*.img" -o -name "rootfs\*" \| head -1) if \[ -n "$rootfs_img" \] \&\& file "$rootfs_img" \| grep -q "Squashfs"; then log_info "检测到Squashfs文件系统,重新打包..." *#* *使用mksquashfs重新打包* local compression="gzip" *#* *尝试确定原始压缩算法* if unsquashfs -s "$rootfs_img" 2\>/dev/null \| grep -q "lzma"; then compression="lzma" elif unsquashfs -s "$rootfs_img" 2\>/dev/null \| grep -q "xz"; then compression="xz" fi *#* *重新打包* mksquashfs "$MODIFIED_DIR" "$WORKSPACE/rootfs_new.img" \\ -comp "$compression" \\ -b 131072 \\ -noappend \\ -no-exports \\ -all-root \\ -no-progress *#* *替换原始固件中的文件系统* *#* *这里需要更复杂的逻辑来处理不同的固件格式* *#* *简化版本:直接使用新的文件系统* cp "$WORKSPACE/rootfs_new.img" "$OUTPUT_FILE" else log_warning "无法自动重新打包,保存修改后的文件" tar -czf "$OUTPUT_FILE" -C "$MODIFIED_DIR" . fi log_success "固件已重新打包: $OUTPUT_FILE" } *# 生成安全报告* generate_report() { log_info "生成安全加固报告..." local report_file="${OUTPUT_FILE%.\*}_report.txt" cat \> "$report_file" \<\< EOF # 固件安全加固报告 ## 基本信息 - 原始固件: $FIRMWARE_FILE - 加固后固件: $OUTPUT_FILE - 加固时间: $(date) - 工作目录: $WORKSPACE ## 执行的加固操作 ### 1. 移除调试信息 - 移除了 .debug 和 .dbg 文件 - 使用strip移除了ELF文件的调试符号 ### 2. 加固配置文件 - SSH配置加固: \* 禁用root登录 \* 禁用密码认证 \* 强制使用SSHv2协议 \* 禁用X11转发 - 移除已知的默认凭证 ### 3. 修复文件权限 - 设置关键文件的安全权限: \* /etc/passwd: 644 \* /etc/shadow: 600 \* /etc/sudoers: 440 \* /root/.ssh/authorized_keys: 600 - 确保系统目录不可被其他用户写入 ### 4. 禁用危险服务 - 禁用的服务:telnet, ftp, tftp, rsh, rexec, rlogin - 修改了inetd/xinetd配置 ### 5. 添加安全监控 - 添加了文件完整性检查脚本 - 添加了登录监控 - 配置了自动启动 ## 文件变更 原始备份保存在: $BACKUP_DIR ## 验证建议 1. 测试加固后的固件功能 2. 验证安全配置是否生效 3. 测试关键服务是否正常工作 4. 监控安全日志 ## 注意事项 - 本报告由自动化工具生成,需要人工验证 - 加固可能影响某些功能,请充分测试 - 建议定期更新和维护安全配置 --- 加固工具: firmware_hardening.sh 版本: 1.0 EOF log_success "报告已生成: $report_file" } *# 清理工作空间* cleanup() { if \[ "$KEEP_TEMP" = false \]; then log_info "清理临时文件..." rm -rf "$WORKSPACE" else log_info "临时文件保留在: $WORKSPACE" fi } *# 主函数* main() { log_info "=== 固件安全加固工具 ===" check_dependencies parse_args "$@" setup_workspace *#* *执行加固步骤* extract_firmware remove_debug_info harden_configurations fix_permissions disable_dangerous_services add_security_monitoring repack_firmware generate_report cleanup log_success "=== 加固完成 ===" log_info "输出文件: $OUTPUT_FILE" log_info "报告文件: ${OUTPUT_FILE%.\*}_report.txt" } *# 错误处理* trap 'log_error "脚本执行失败"; cleanup; exit 1' ERR *# 运行主函数* main "$@" ### 九. 总结与资源推荐 **9.1 固件安全检查清单** # 固件安全检查清单 ## 第一阶段:信息收集 - \[ \] 固件版本信息收集 - \[ \] 硬件架构识别 - \[ \] 文件系统类型识别 - \[ \] 网络服务识别 ## 第二阶段:静态分析 - \[ \] 硬编码凭证搜索 - \[ \] 配置文件分析 - \[ \] 二进制文件逆向 - \[ \] 源码审计(如果有) ## 第三阶段:动态分析 - \[ \] 固件模拟运行 - \[ \] 网络服务测试 - \[ \] Web界面测试 - \[ \] API接口测试 ## 第四阶段:漏洞验证 - \[ \] 命令注入验证 - \[ \] 缓冲区溢出验证 - \[ \] 路径遍历验证 - \[ \] 认证绕过验证 ## 第五阶段:报告生成 - \[ \] 详细漏洞描述 - \[ \] 风险等级评估 - \[ \] 修复建议 - \[ \] 验证步骤 **9.2 推荐工具链** | **工具类别** | **推荐工具** | **用途** | **备注** | |-----------|-------------------|---------|------------| | **固件提取** | binwalk, dd | 解包固件 | 基础必备 | | **文件分析** | file, strings | 初步分析 | 系统自带 | | **逆向工程** | Ghidra, IDA Pro | 深度分析 | Ghidra免费强大 | | **动态分析** | QEMU, GDB | 运行时分析 | 需要配置环境 | | **漏洞挖掘** | AFL, firmadyne | 自动化测试 | 需要学习曲线 | | **Web测试** | Burp Suite, nikto | Web安全测试 | Burp功能全面 | **9.3 学习资源** **在线课程** * **PentesterAcademy IoT Security Course** - 全面的IoT安全课程 * **Offensive Security IoT Exploitation** - 实战导向的课程 * **Coursera Embedded Systems Security** - 学术性较强的课程 **书籍推荐** 1. **《The Hardware Hacking Handbook》** - 硬件安全入门经典 2. **《Practical IoT Hacking》** - IoT渗透测试实践 3. **《Embedded Systems Security》** - 嵌入式系统安全理论 **CTF平台** * [pwnable.tw](https://pwnable.tw/ "pwnable.tw") - 台湾的Pwnable题目平台 * **Microcorruption** - 嵌入式CTF平台 * **IoT Village** - 专门的IoT CTF **社区与论坛** * **GitHub Security Lab** - 安全研究社区 * **IoT Security Forum** - IoT安全专业论坛 * **Reddit /r/netsec** - 网络安全社区 **9.4 后续学习路径** 1. **初级阶段** (1-3个月) * 掌握binwalk基本使用 * 学会strings和file分析 * 理解常见漏洞类型 2. **中级阶段** (3-6个月) * 熟练使用Ghidra进行逆向 * 掌握固件模拟技术 * 能够独立挖掘漏洞 3. **高级阶段** (6-12个月) * 深入理解硬件安全 * 掌握漏洞利用开发 * 能够进行安全架构设计 4. **专家阶段** (1年以上) * 原创漏洞研究 * 安全产品开发 * 安全标准制定 ### 十. 免责声明与道德准则 ### ⚠️ 重要安全声明 **\*\*法律声明\*\***: 1. 本教程仅用于教育目的和安全研究 2. 所有技术应在合法授权范围内使用 3. 未经授权对他人设备进行测试是非法的 4. 作者不对任何非法使用承担责任 **\*\*道德准则\*\***: 1. 始终获取适当授权 2. 尊重用户隐私 3. 负责任地披露漏洞 4. 为安全社区做贡献 **\*\*法规遵守\*\***: 1. 遵守《网络安全法》 2. 遵守《个人信息保护法》 3. 遵守相关行业规定 4. 遵守国际网络安全公约 **\*\*最佳实践\*\***: 1. 在隔离环境中测试 2. 记录所有测试步骤 3. 及时报告发现的问题 4. 帮助厂商修复漏洞 *** ** * ** *** **结语** 固件安全是一个复杂但极其重要的领域。随着物联网设备的普及,固件安全的重要性日益凸显。通过本文的学习,你应该已经掌握了从固件提取到逆向分析,再到漏洞挖掘的全套技能。 记住,**技术是一把双刃剑**。我们学习这些技能的目的是为了更好地保护系统,而不是破坏它们。希望你能将所学知识用于正当的安全研究和防御工作中。 **不断学习,保持好奇,坚持道德,安全第一。** 如果你有任何问题或建议,欢迎在评论区讨论。让我们一起为构建更安全的物联网世界而努力!