本帖最后由 鲲鹏 于 2026-7-23 14:07 编辑
调试Windows程序时,需向微软符号服务器请求资源,而链接的生成依赖两类PE元数据:
- PDB符号标识:来自PE调试目录中的CodeView记录(RSDS/NB10格式);
- EXE模块标识:来自PE头的`TimeDateStamp`(32位时间戳)和`SizeOfImage`(32位镜像大小),二者组合为EXE模块的唯一标识。
工具基于`pefile`库解析PE文件。核心流程:
- 校验本地文件为PE格式;
- 解析PE的版本资源段提取原始文件名(优先于本地文件名,避免篡改问题);
- 遍历调试目录筛选CodeView记录,兼容RSDS(PDB 7.0+)和NB10(PDB 2.0)两种格式;
- 从PE头提取`TimeDateStamp`和`SizeOfImage`,生成EXE模块唯一标识;
- 按照微软符号服务器的标准格式(`https://msdl.microsoft.com/download/symbols/<模块名>/<标识>/<模块名>`)拼接URL,对文件名做URL编码处理。
代码如下。
[Python] 纯文本查看 复制代码 #!/usr/bin/env python3
import os
import struct
import sys
from urllib.parse import quote
import pefile
SYMBOL_SERVER = "https://msdl.microsoft.com/download/symbols"
def get_original_filename(pe):
"""Return the PE's OriginalFilename version field, if present."""
try:
file_info_groups = getattr(pe, "FileInfo", [])
for group in file_info_groups:
entries = group if isinstance(group, list) else [group]
for file_info in entries:
if getattr(file_info, "Key", None) != b"StringFileInfo":
continue
for table in getattr(file_info, "StringTable", []):
value = table.entries.get(b"OriginalFilename")
if value:
return value.decode(errors="replace")
except (AttributeError, TypeError):
return None
return None
def get_pe_symbol_name(pe, local_path):
"""Return the binary name used by Microsoft Symbol Server."""
local_name = os.path.basename(local_path)
original_name = get_original_filename(pe)
if not original_name:
return local_name
original_name = os.path.basename(original_name)
if not os.path.splitext(original_name)[1]:
local_extension = os.path.splitext(local_name)[1]
original_name += local_extension
return original_name
def get_pe_identity(pe):
"""Return the PE image identifier: TimeDateStamp + SizeOfImage."""
timestamp = pe.FILE_HEADER.TimeDateStamp & 0xFFFFFFFF
image_size = pe.OPTIONAL_HEADER.SizeOfImage
return f"{timestamp:08X}{image_size:X}"
def get_codeview_records(pe):
"""Yield all CodeView records in the PE debug directory."""
for entry in getattr(pe, "DIRECTORY_ENTRY_DEBUG", []):
if entry.struct.Type != pefile.DEBUG_TYPE["IMAGE_DEBUG_TYPE_CODEVIEW"]:
continue
yield pe.get_data(
entry.struct.AddressOfRawData,
entry.struct.SizeOfData,
)
def parse_rsds(cv_data):
"""Parse a PDB 7.0 RSDS CodeView record."""
if len(cv_data) < 25 or cv_data[:4] != b"RSDS":
return None
guid_bytes = cv_data[4:20]
data1, data2, data3, *data4 = struct.unpack("<IHH8B", guid_bytes)
guid = (
f"{data1:08X}"
f"{data2:04X}"
f"{data3:04X}" + "".join(f"{x:02X}" for x in data4)
)
age = int.from_bytes(cv_data[20:24], "little")
pdb_name = cv_data[24:].split(b"\x00", 1)[0].decode("utf-8", errors="replace")
pdb_name = os.path.basename(pdb_name)
if not pdb_name:
return None
return pdb_name, f"{guid}{age:X}"
def parse_nb10(cv_data):
"""Parse a legacy PDB 2.0 NB10 CodeView record."""
if len(cv_data) < 17 or cv_data[:4] != b"NB10":
return None
timestamp = int.from_bytes(cv_data[8:12], "little")
age = int.from_bytes(cv_data[12:16], "little")
pdb_name = cv_data[16:].split(b"\x00", 1)[0].decode("utf-8", errors="replace")
pdb_name = os.path.basename(pdb_name)
if not pdb_name:
return None
return pdb_name, f"{timestamp:08X}{age:X}"
def get_pdb_symbol_url(pe):
"""Return the Microsoft Symbol Server PDB URL, if one can be derived."""
for cv_data in get_codeview_records(pe):
pdb_info = parse_rsds(cv_data) or parse_nb10(cv_data)
if pdb_info:
pdb_name, identifier = pdb_info
encoded_name = quote(pdb_name, safe="")
return f"{SYMBOL_SERVER}/{encoded_name}/{identifier}/{encoded_name}"
return None
def get_pe_symbol_url(pe, path):
"""Return the Microsoft Symbol Server PE image URL."""
image_name = get_pe_symbol_name(pe, path)
identity = get_pe_identity(pe)
encoded_name = quote(image_name, safe="")
return f"{SYMBOL_SERVER}/{encoded_name}/{identity}/{encoded_name}"
def get_symbol_links(path):
"""Return PE image and PDB symbol URLs for a local PE file."""
pe = pefile.PE(path, fast_load=False)
try:
return {
"image": get_pe_symbol_url(pe, path),
"pdb": get_pdb_symbol_url(pe),
}
finally:
pe.close()
def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <pe_file>")
return 2
path = sys.argv[1]
if not os.path.isfile(path):
print(f"[-] File not found: {path}")
return 1
try:
symbols = get_symbol_links(path)
except Exception as e:
print(f"[-] Failed: {e}")
return 1
print("\n[+] PE Symbol URL:")
print(symbols["image"])
print("\n[+] PDB Symbol URL:")
print(symbols["pdb"] or "Not found")
return 0
if __name__ == "__main__":
sys.exit(main())
|