本文仅供合法授权的安全研究与学习使用,请勿用于非法用途。读者应确保行为符合当地法律法规。
前言 二维码已经渗透到日常生活的方方面面——支付、登录、信息分享、广告宣传无处不在。然而,二维码的便利性也使其成为攻击者青睐的载体。攻击者可以将钓鱼URL、恶意APK下载链接、恶意WiFi配置等信息编码到二维码中,通过替换合法二维码、社交平台传播等方式诱导受害者扫描。
在红队演练中,二维码攻击是一种高效的初始访问手段。攻击者可以在目标企业周边张贴恶意二维码,伪装成WiFi登录、门禁扫码等场景;也可以在钓鱼邮件中嵌入二维码图片,绕过传统的URL过滤检测。本文基于真实的Python项目源码,深入讲解二维码扫描器的实现技术,涵盖图像预处理、多格式解码、安全风险检测和数据导出等核心功能。
技术背景 1. 二维码技术概述 二维码(QR Code)是一种矩阵式条码,通过黑白模块的排列组合存储数据。标准的二维码包含以下区域:
定位图案 :三个角落的大方块,用于快速定位二维码位置
对齐图案 :辅助校正的小方块
定时图案 :交替的黑白模块,用于坐标参考
数据区域 :存储实际编码数据
纠错码 :Reed-Solomon纠错算法,支持7%~30%的损坏恢复
2. 常见二维码攻击手法
攻击类型
二维码内容
危害
钓鱼攻击
伪造的银行/社交平台URL
窃取账号密码
恶意软件分发
APK/EXE下载链接
植入木马
WiFi钓鱼
WIFI:协议配置
中间人攻击
支付欺诈
伪造的支付收款码
资金盗取
XSS注入
包含JavaScript的URL
跨站脚本攻击
3. 关键技术点
pyzbar库 :基于ZBar的Python条码/二维码解码库,支持QR Code、EAN、Code128等多种格式
PIL/Pillow库 :图像处理,用于二维码图片的预处理增强
图像预处理技术 :灰度化、对比度增强、二值化、高斯模糊、锐化等
正则表达式 :用于二维码内容分类和安全风险检测
colorlog库 :彩色日志输出,提升用户体验
实现思路 本项目采用多策略解码 架构,通过多种图像预处理方式提高识别率,同时集成安全检测功能:
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 ┌──────────────────────────────────────────────┐ │ 二维码扫描器架构 │ ├──────────────────────────────────────────────┤ │ 输入层 │ │ ├─ 本地文件路径 │ │ └─ 网络URL(自动下载图片) │ ├──────────────────────────────────────────────┤ │ 图像预处理层(多策略并行尝试) │ │ ├─ 原始图像 │ │ ├─ 灰度图像 │ │ ├─ 高对比度图像 │ │ ├─ 二值化图像(多阈值:64/128/192) │ │ ├─ 高斯模糊+二值化 │ │ ├─ 锐化图像 │ │ ├─ 颜色反转图像 │ │ └─ 尺寸放大图像 │ ├──────────────────────────────────────────────┤ │ 解码层 │ │ ├─ pyzbar标准解码 │ │ ├─ 指定QRCODE符号类型解码 │ │ ├─ 多符号类型遍历解码 │ │ └─ 四象限裁剪解码 │ ├──────────────────────────────────────────────┤ │ 安全分析层 │ │ ├─ 内容分类(URL/联系方式/WiFi/文本) │ │ ├─ 风险评级(高/中/低/无) │ │ └─ 安全检查(XSS/恶意文件/IP地址) │ ├──────────────────────────────────────────────┤ │ 输出层 │ │ ├─ 控制台彩色日志输出 │ │ └─ JSON/CSV文件导出 │ └──────────────────────────────────────────────┘
核心代码解析 1. 图像预处理——多策略提升识别率 图像预处理是提高二维码识别率的关键。不同的二维码图片可能存在模糊、反光、颜色反转、尺寸过小等问题,单一处理方式往往无法覆盖所有场景:
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 from PIL import Image, ImageEnhance, ImageFilterdef preprocess_image (img ): """ 预处理图像以提高二维码识别率 返回多种处理方式后的图像列表,供后续逐一尝试解码 """ images = [] images.append(("原始图像" , img)) gray_img = img.convert('L' ) images.append(("灰度图像" , gray_img)) enhancer = ImageEnhance.Contrast(gray_img) contrast_img = enhancer.enhance(2.0 ) images.append(("高对比度图像" , contrast_img)) threshold = 128 binary_img = contrast_img.point(lambda x: 0 if x < threshold else 255 , '1' ) images.append(("二值化图像" , binary_img)) for threshold in [64 , 192 ]: binary_img = contrast_img.point(lambda x: 0 if x < threshold else 255 , '1' ) images.append((f"二值化图像(阈值{threshold} )" , binary_img)) blurred = gray_img.filter (ImageFilter.GaussianBlur(radius=1 )) blurred_binary = blurred.point(lambda x: 0 if x < 128 else 255 , '1' ) images.append(("高斯模糊+二值化" , blurred_binary)) sharpened = img.filter (ImageFilter.SHARPEN) images.append(("锐化图像" , sharpened)) inverted = Image.eval (gray_img, lambda x: 255 - x) images.append(("颜色反转图像" , inverted)) large_img = img.resize((img.width * 2 , img.height * 2 ), Image.LANCZOS) images.append(("放大图像" , large_img)) return images
代码讲解 :
预处理模块返回一个包含9种处理方式的图像列表,每种方式针对不同的图片质量问题:
灰度化 :去除颜色信息,减少干扰。二维码本质是黑白二值图像,灰度化是所有处理的基础。
对比度增强 :enhance(2.0)将对比度提升2倍,使黑白模块的分界更清晰。
二值化 :point()函数按阈值将像素分为纯黑(0)或纯白(255)。提供64/128/192三种阈值,适应不同曝光条件。
高斯模糊+二值化 :先模糊去噪再二值化,适合处理有噪点的手机截图。
颜色反转 :处理白底黑码变黑底白码的反色二维码。
尺寸放大 :使用Lanczos重采样将图片放大2倍,解决小尺寸二维码识别困难的问题。
2. 核心解码函数——多策略容错 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 import pyzbar.pyzbar as pyzbardef decode_qrcode (image_path ): """ 解码给定路径下的二维码图像。 支持本地文件和网络URL,自动尝试多种预处理方式。 """ result = { 'image_path' : image_path, 'content' : None , 'content_type' : None , 'risk_level' : None , 'security_warnings' : [], 'barcode_type' : None , 'timestamp' : datetime.now().isoformat(), 'success' : False } img = None if is_url(image_path): img = download_image(image_path) if img is None : return result else : try : img = Image.open (image_path) except FileNotFoundError: logging.error(f"文件未找到: {image_path} " ) return result try : processed_images = preprocess_image(img) decoded_objects = [] for name, processed_img in processed_images: try : temp_objects = pyzbar.decode(processed_img) if temp_objects: decoded_objects = temp_objects logging.info(f"使用 {name} 成功解码" ) break except Exception as e: logging.debug(f"使用 {name} 解码时出错: {e} " ) continue if not decoded_objects: try : decoded_objects = pyzbar.decode(img, symbols=[pyzbar.ZBarSymbol.QRCODE]) except AttributeError: decoded_objects = pyzbar.decode(img) if not decoded_objects: symbol_types = ['QRCODE' , 'EAN13' , 'EAN8' , 'CODE128' , 'CODE39' , 'UPCA' , 'UPCE' ] for symbol_name in symbol_types: if hasattr (pyzbar.ZBarSymbol, symbol_name): symbol = getattr (pyzbar.ZBarSymbol, symbol_name) try : temp_objects = pyzbar.decode(img, symbols=[symbol]) if temp_objects: decoded_objects = temp_objects break except Exception: continue
代码讲解 :
解码函数采用三级容错策略 :
第一级 :依次尝试9种预处理后的图像,使用默认的pyzbar.decode()解码。一旦成功即停止。
第二级 :如果全部失败,明确指定ZBarSymbol.QRCODE符号类型,避免pyzbar默认启用PDF417解码器导致的断言错误。
第三级 :逐个尝试EAN13、CODE128等其他条码类型,确保不遗漏任何可解码内容。
3. 四象限裁剪解码 当标准方法全部失败时,脚本会将图片裁剪为四个象限分别解码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 width, height = img.size crops = [ ("左上角" , (0 , 0 , width//2 , height//2 )), ("右上角" , (width//2 , 0 , width, height//2 )), ("左下角" , (0 , height//2 , width//2 , height)), ("右下角" , (width//2 , height//2 , width, height)) ] for name, box in crops: cropped_img = img.crop(box) try : temp_objects = pyzbar.decode(cropped_img) if temp_objects: decoded_objects = temp_objects logging.info(f"在裁剪区域 {name} 中成功解码" ) break except Exception as e: continue
代码讲解 :当一张图片中存在多个二维码,或者二维码周围有干扰元素时,裁剪到四象限分别解码可以有效提高识别率。这在实际红队工作中处理复杂场景图片(如截屏、海报)时非常实用。
4. 安全风险检测 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 def classify_qr_content (content ): """ 对二维码内容进行分类和风险评级 """ if not content: return "未知" , "无风险" if re.match (r'^https?://' , content): dangerous_keywords = ['phishing' , 'malware' , 'virus' , 'hack' , 'crack' ] for keyword in dangerous_keywords: if keyword in content.lower(): return "网址链接" , "高风险" return "网址链接" , "低风险" if re.match (r'^tel:|^sms:|^mailto:' , content): return "联系方式" , "无风险" if content.startswith('WIFI:' ): return "WiFi信息" , "中风险" if re.match (r'^bitcoin:|^bc1' , content): return "加密货币地址" , "中风险" return "文本内容" , "无风险" def security_check (content ): """ 对二维码内容进行安全检查,检测潜在的攻击载荷 """ warnings = [] if not content: return warnings if re.search(r'<script.*?>.*?</script>' , content, re.IGNORECASE | re.DOTALL): warnings.append("内容包含JavaScript代码,可能存在XSS攻击风险" ) if re.search(r'\.(exe|bat|cmd|scr|dll|msi|apk|dmg)$' , content, re.IGNORECASE): warnings.append("内容包含可执行文件下载链接,可能存在恶意软件风险" ) if re.search(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' , content): ip_match = re.search(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' , content) if ip_match: ip = ip_match.group() if not (ip.startswith('192.168.' ) or ip.startswith('10.' ) or ip.startswith('172.' ) and 16 <= int (ip.split('.' )[1 ]) <= 31 ): warnings.append(f"内容包含公网IP地址: {ip} ,请谨慎访问" ) return warnings
代码讲解 :
安全检测模块分为两个层次:
内容分类与风险评级 :根据二维码内容的协议前缀进行分类。URL链接会检查是否包含phishing、malware等危险关键词;WiFi配置标记为”中风险”(可能用于WiFi钓鱼);加密货币地址标记为”中风险”(可能用于支付欺诈)。
深度安全检查 :使用正则表达式检测三种威胁——XSS攻击载荷(<script>标签)、恶意文件下载链接(.exe/.apk等后缀)、公网IP地址(排除私有IP范围)。这在红队分析截获的二维码时能快速评估威胁等级。
5. 网络图片下载与编码处理 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 def download_image (url ): """ 从网络地址下载图片 """ try : logging.info(f"正在下载网络图片: {url} " ) response = requests.get(url, timeout=30 ) response.raise_for_status() image = Image.open (BytesIO(response.content)) logging.info("网络图片下载成功" ) return image except requests.exceptions.RequestException as e: logging.error(f"下载网络图片失败: {e} " ) return None def decode_data (raw_data, encoding_list ): """ 尝试按照给定的编码格式列表解码数据 """ for encoding in encoding_list: try : return raw_data.decode(encoding) except UnicodeDecodeError: continue return None
代码讲解 :
网络图片下载 :使用BytesIO将下载的字节流直接转为PIL Image对象,无需保存临时文件。
多编码解码 :二维码内容可能使用UTF-8、GBK、GB2312等不同编码。decode_data函数按优先级依次尝试,确保中文内容正确解码。如果所有编码都失败,则返回十六进制表示。
6. 批量扫描与结果导出 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 def export_results (results, export_format='json' ): """ 导出扫描结果到JSON或CSV文件 """ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S" ) if export_format == 'json' : filename = f"qr_scan_results_{timestamp} .json" with open (filename, 'w' , encoding='utf-8' ) as f: json.dump(results, f, ensure_ascii=False , indent=2 ) logging.info(f"结果已导出到 {filename} " ) elif export_format == 'csv' : filename = f"qr_scan_results_{timestamp} .csv" with open (filename, 'w' , newline='' , encoding='utf-8' ) as f: if results: writer = csv.DictWriter(f, fieldnames=results[0 ].keys()) writer.writeheader() writer.writerows(results) logging.info(f"结果已导出到 {filename} " ) def scan_directory (directory_path ): """ 扫描目录中的所有图片文件 """ image_extensions = ('.png' , '.jpg' , '.jpeg' , '.bmp' , '.gif' , '.tiff' , '.webp' ) image_files = [] for filename in os.listdir(directory_path): if filename.lower().endswith(image_extensions): image_files.append(os.path.join(directory_path, filename)) logging.info(f"在目录 {directory_path} 中找到 {len (image_files)} 个图片文件" ) return image_files
使用方法与运行效果 环境准备 1 2 3 4 5 pip install pyzbar pillow numpy requests colorlog
运行方式 1 2 3 4 5 6 7 二维码扫描器增强版 功能选项: 1. 扫描二维码 (默认) 2. 批量扫描目录 3. 扫描并导出结果 请选择功能 (1-3, 默认为1): 1 请输入二维码图片的路径: qrcode.png
典型运行输出 1 2 3 4 5 6 7 8 9 10 11 12 2026-01-15 10:30:00 - INFO - 开始解码二维码: qrcode.png 2026-01-15 10:30:00 - INFO - 使用 灰度图像 成功解码 2026-01-15 10:30:00 - INFO - 二维码内容: https://phishing-site.com/login 2026-01-15 10:30:00 - INFO - ================================================== 2026-01-15 10:30:00 - INFO - 扫描结果摘要: 2026-01-15 10:30:00 - INFO - 内容类型: 网址链接 2026-01-15 10:30:00 - INFO - 风险等级: 高风险 2026-01-15 10:30:00 - INFO - 二维码内容: https://phishing-site.com/login 2026-01-15 10:30:00 - INFO - 条码类型: QRCODE 2026-01-15 10:30:00 - WARNING - 安全警告: 2026-01-15 10:30:00 - WARNING - - 内容包含公网IP地址: 192.0.2.1,请谨慎访问 2026-01-15 10:30:00 - INFO - ==================================================
在红队工作中的应用场景
钓鱼邮件分析 :提取钓鱼邮件中的二维码图片,扫描后获取钓鱼URL。
物理环境侦察 :扫描目标企业周边的二维码(海报、Wi-Fi、门禁),分析是否存在信息泄露。
恶意二维码溯源 :批量扫描截获的二维码图片,导出结果进行威胁情报分析。
社工攻击 payloads :生成包含恶意URL的二维码,用于物理社工攻击。
防御对策
二维码内容透明化 :在扫描二维码前,设备应先显示解码后的内容(特别是URL),让用户确认后再跳转,而非直接打开链接。
URL安全检测 :将二维码中的URL接入安全网关检测,比对钓鱼网址库和恶意域名库,阻止访问已知恶意网站。
企业扫码策略 :在工作设备上部署二维码扫描管控策略,禁止扫描未经验证的外部二维码,特别是包含可执行文件下载链接的二维码。
WiFi二维码防护 :对于WiFi二维码配置,设备应提示用户确认网络名称和加密类型,防止连接到恶意AP。
安全意识培训 :教育员工不扫描来源不明的二维码,特别是在公共场合遇到的二维码海报。对包含URL的二维码保持高度警惕。
二维码签名机制 :对官方发布的二维码(如支付码、登录码)引入数字签名验证,防止被篡改替换。
总结 本文基于真实项目源码,系统地讲解了二维码扫描器的实现技术。关键要点:
多策略图像预处理 是提高识别率的核心:9种预处理方式覆盖了模糊、反光、反色、小尺寸等常见问题场景。
三级容错解码 确保不遗漏:从预处理尝试到符号类型指定,再到四象限裁剪,层层递进。
安全检测 是红队工具的增值功能:内容分类、风险评级和深度安全检查帮助快速评估二维码威胁等级。
批量处理与导出 提升工作效率:支持目录批量扫描和JSON/CSV格式导出,便于后续分析。
二维码攻击因其隐蔽性和高点击率,已成为红队初始访问的重要手段。掌握二维码扫描与分析技术,不仅有助于红队在物理社工和钓鱼攻击中构建payload,也为蓝队提供了分析恶意二维码的自动化工具。