python

QRCode二维码扫描解析器

2026-07-05 #python

本文仅供合法授权的安全研究与学习使用,请勿用于非法用途。读者应确保行为符合当地法律法规。

前言

二维码已经渗透到日常生活的方方面面——支付、登录、信息分享、广告宣传无处不在。然而,二维码的便利性也使其成为攻击者青睐的载体。攻击者可以将钓鱼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, ImageFilter

def 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 pyzbar

def 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
}

# 判断是本地文件还是网络URL
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:
# 明确指定只解码二维码,避免PDF417解码器冲突
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

代码讲解

解码函数采用三级容错策略

  1. 第一级:依次尝试9种预处理后的图像,使用默认的pyzbar.decode()解码。一旦成功即停止。
  2. 第二级:如果全部失败,明确指定ZBarSymbol.QRCODE符号类型,避免pyzbar默认启用PDF417解码器导致的断言错误。
  3. 第三级:逐个尝试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 "未知", "无风险"

# URL链接 - 检查危险关键词
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 "联系方式", "无风险"

# WiFi信息
if content.startswith('WIFI:'):
return "WiFi信息", "中风险"

# Bitcoin地址
if re.match(r'^bitcoin:|^bc1', content):
return "加密货币地址", "中风险"

return "文本内容", "无风险"


def security_check(content):
"""
对二维码内容进行安全检查,检测潜在的攻击载荷
"""
warnings = []

if not content:
return warnings

# 检查XSS攻击载荷
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("内容包含可执行文件下载链接,可能存在恶意软件风险")

# 检查公网IP地址
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()
# 排除私有IP地址
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链接会检查是否包含phishingmalware等危险关键词;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
# pyzbar还需要安装ZBar动态链接库
# Windows: 下载安装 zbar.dll
# Linux: apt install libzbar0
# macOS: brew install zbar

运行方式

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 - ==================================================

在红队工作中的应用场景

  1. 钓鱼邮件分析:提取钓鱼邮件中的二维码图片,扫描后获取钓鱼URL。
  2. 物理环境侦察:扫描目标企业周边的二维码(海报、Wi-Fi、门禁),分析是否存在信息泄露。
  3. 恶意二维码溯源:批量扫描截获的二维码图片,导出结果进行威胁情报分析。
  4. 社工攻击 payloads:生成包含恶意URL的二维码,用于物理社工攻击。

防御对策

  1. 二维码内容透明化:在扫描二维码前,设备应先显示解码后的内容(特别是URL),让用户确认后再跳转,而非直接打开链接。

  2. URL安全检测:将二维码中的URL接入安全网关检测,比对钓鱼网址库和恶意域名库,阻止访问已知恶意网站。

  3. 企业扫码策略:在工作设备上部署二维码扫描管控策略,禁止扫描未经验证的外部二维码,特别是包含可执行文件下载链接的二维码。

  4. WiFi二维码防护:对于WiFi二维码配置,设备应提示用户确认网络名称和加密类型,防止连接到恶意AP。

  5. 安全意识培训:教育员工不扫描来源不明的二维码,特别是在公共场合遇到的二维码海报。对包含URL的二维码保持高度警惕。

  6. 二维码签名机制:对官方发布的二维码(如支付码、登录码)引入数字签名验证,防止被篡改替换。

总结

本文基于真实项目源码,系统地讲解了二维码扫描器的实现技术。关键要点:

  • 多策略图像预处理是提高识别率的核心:9种预处理方式覆盖了模糊、反光、反色、小尺寸等常见问题场景。
  • 三级容错解码确保不遗漏:从预处理尝试到符号类型指定,再到四象限裁剪,层层递进。
  • 安全检测是红队工具的增值功能:内容分类、风险评级和深度安全检查帮助快速评估二维码威胁等级。
  • 批量处理与导出提升工作效率:支持目录批量扫描和JSON/CSV格式导出,便于后续分析。

二维码攻击因其隐蔽性和高点击率,已成为红队初始访问的重要手段。掌握二维码扫描与分析技术,不仅有助于红队在物理社工和钓鱼攻击中构建payload,也为蓝队提供了分析恶意二维码的自动化工具。

评论
分享