乐于分享
好东西不私藏

微软验证器 iOS OTP 备份导出流程

微软验证器 iOS OTP 备份导出流程

大家好,我是紫星。

今天给大家分享一个Microsoft Authenticator(微软验证器)iOS 版 OTP 数据的备份与导出方案

为了提高账号安全性,越来越多的网站和服务开始支持两步验证(2FA)。Google Authenticator、Microsoft Authenticator 等软件,都是大家比较常用的动态验证码工具。

不过,Microsoft Authenticator 的备份机制存在一个不太方便的地方:iOS 版的数据通过 iCloud 备份,Android 版则使用微软账号的云备份,两端的数据无法直接互通。

那么,如果我们准备从 iPhone 换到 Android 手机,难道只能逐个账号重新绑定吗?

本文介绍一种借助越狱设备与 Frida Hook,将 Microsoft Authenticator iOS 版中的 TOTP 数据导出为 otpauth:// URI 的方法。导出的 URI 还可以进一步生成二维码,方便迁移到其他验证器。

[!CAUTION]OTP 密钥相当于账号的第二重密码。导出的 URI、日志、二维码和脚本输出均包含高度敏感的信息。请仅在自己的设备和账号上操作,不要上传到网盘、聊天软件或公共代码仓库;迁移完成后,请妥善加密保存或彻底删除相关文件。

一、准备工作

你需要准备:

  • 一台能够运行 iOS 17 或更高版本的越狱设备;
  • 已安装 Microsoft Authenticator 和 Filza 的设备;
  • 一台安装了 Frida 的电脑;
  • 与原设备登录同一个 Apple ID,并已同步 iCloud 钥匙串及 Microsoft Authenticator 数据;
  • Python 3,以及用于生成二维码的 Python 依赖。

由于新版 Microsoft Authenticator 要求 iOS 17 或更高版本,可用的越狱设备相对有限。部分符合条件的旧款 iPad 可以作为迁移设备使用。如果你拥有自行解密的 IPA,也可以尝试安装到其他兼容设备上,但实际可行性取决于系统版本、应用版本和签名环境。

本文默认你已经完成设备越狱,并了解 Frida 的基本安装和连接方法。不同越狱方案的安装步骤并不完全相同,这里不再展开。

二、同步 Microsoft Authenticator 数据

在越狱设备上登录原设备使用的 Apple ID,并确认 iCloud 钥匙串及 Microsoft Authenticator 的云备份数据已经同步。

打开 Microsoft Authenticator,检查原有账号是否已经正常恢复。只有应用内能够看到这些账号,后续导出才有意义。

三、定位应用数据

安装并打开 Filza,进入以下目录:

/var/mobile/Containers/Data/Application/

Microsoft Authenticator 对应的文件夹名称通常是一串 UUID。Filza 一般会显示应用名称,因此可以直接找到对应目录。

例如,我的设备上对应的路径是:

/var/mobile/Containers/Data/Application/F1412516-75E3-45B4-9F32-234648054C13

进入目录后,可以先将整个 Documents 文件夹压缩并传到电脑上留作备份。解压后,可以看到一个名为 PhoneFactor.db 的数据库文件。

不过,直接查看数据库会发现,真正需要的 OTP 密钥并没有以可直接读取的明文形式保存在其中,而是存放在 iOS Keychain(钥匙串)中。因此,单独复制 PhoneFactor.db 并不能完成导出,我们还需要在 Microsoft Authenticator 的进程内查询 Keychain 数据。

此前也有人介绍过从 Android 版 Microsoft Authenticator 导出密钥的方法,但通常需要一台已经 Root 的 Android 设备,或者事先取得验证器数据库。对于只有 iOS 数据的情况,下面的 Frida 方案更直接。

参考文章:手动导出 Microsoft Authenticator 中的密钥

四、使用 Frida 导出 otpauth:// URI

下面的脚本适用于 Frida 17。将其保存为 dump.js:

// Frida 17/** * 导出 Microsoft Authenticator 中的 TOTP,并生成 otpauth:// URI * * 用法: * frida -U -n ”Authenticator” -l dump.js */”use strict”;function nsDataToString(nsData) { try { return ObjC.classes.NSString.alloc().initWithData_encoding_(nsData, 4).toString(); } catch (_) { return null; }}function main() { if (!ObjC.available) { console.log(”[!] ObjC 不可用”); return; } // 获取 SecItemCopyMatching 函数地址 var SecItemCopyMatching = null; var modules = Process.enumerateModules(); for (var i = 0; i < modules.length; i++) { if (modules[i].name === ”Security”) { var exports = modules[i].enumerateExports(); for (var j = 0; j < exports.length; j++) { if (exports[j].name === ”SecItemCopyMatching”) { SecItemCopyMatching = exports[j].address; break; } } break; } } if (SecItemCopyMatching === null) { console.log(”[!] 找不到 SecItemCopyMatching”); return; } var SecItemCopyMatchingFunc = new NativeFunction( SecItemCopyMatching, ”int”, [”pointer”, ”pointer”] ); var query = ObjC.classes.NSMutableDictionary.alloc().init(); query.setObject_forKey_(ObjC.classes.NSString.stringWithString_(”genp”), ObjC.classes.NSString.stringWithString_(”class”)); query.setObject_forKey_(ObjC.classes.NSString.stringWithString_(”com.microsoft.authenticator”), ObjC.classes.NSString.stringWithString_(”svce”)); query.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), ObjC.classes.NSString.stringWithString_(”r_Data”)); query.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), ObjC.classes.NSString.stringWithString_(”r_Attributes”)); query.setObject_forKey_(ObjC.classes.NSString.stringWithString_(”m_LimitAll”), ObjC.classes.NSString.stringWithString_(”m_Limit”)); var resultPtr = Memory.alloc(Process.pointerSize); resultPtr.writePointer(ptr(”0”)); var status = SecItemCopyMatchingFunc(query.handle, resultPtr); if (status !== 0) { console.log(”[!] Keychain 查询失败,OSStatus: ” + status); return; } var arr = ObjC.Object(resultPtr.readPointer()); var count = arr.count(); console.log(””); console.log(”=== Microsoft Authenticator TOTP 导出(” + count + ” 个账户)===”); console.log(””); for (var i = 0; i < count; i++) { var item = arr.objectAtIndex_(i); var acctData = item.objectForKey_(ObjC.classes.NSString.stringWithString_(”acct”)); var secretData = item.objectForKey_(ObjC.classes.NSString.stringWithString_(”v_Data”)); var acctStr = acctData ? nsDataToString(acctData) : null; var secret = secretData ? nsDataToString(secretData) : null; if (!acctStr || !secret || secret.length === 0continue; var uuid = acctStr.replace(”V1|”, ””); var label = item.objectForKey_(ObjC.classes.NSString.stringWithString_(”labl”)); var labelStr = label ? nsDataToString(label) : null; // 尝试从标签中获取账户信息 var issuer = labelStr || uuid; var name = uuid; var uri = ”otpauth://totp/” + encodeURIComponent(issuer) + ”:” + encodeURIComponent(name) + ”?secret=” + secret + ”&issuer=” + encodeURIComponent(issuer) + ”&algorithm=SHA1&digits=6&period=30”; console.log(uri); } console.log(””); console.log(”=== 导出完成 ===”);}setImmediate(main);

在越狱设备上打开 Microsoft Authenticator,并保持应用在前台运行。然后在电脑上执行:

frida -U -n ”Authenticator” -l dump.js

如果应用的进程名不是 Authenticator,可以先使用以下命令查看当前设备上的进程:

frida-ps -Uai

脚本执行成功后,终端会输出若干以 otpauth:// 开头的 URI。将这些 URI 单独保存到 log.txt,每行一条。

如果使用 Frida 16 或其他版本,部分 API 可能存在差异,需要根据对应版本调整脚本。

五、将 URI 生成为二维码

为了方便其他验证器扫描导入,可以使用 Python 将导出的 URI 转换成二维码。

先安装依赖:

python3 -m pip install qrcode pillow

然后将下面的脚本保存为 generate_qr.py,并确保它和 log.txt 位于同一目录:

#!/usr/bin/env python3”””将 otpauth:// URI 转换为二维码:每个账户一张,并生成一张汇总图。”””import refrom pathlib import Pathfrom urllib.parse import unquoteimport qrcodefrom PIL import Image, ImageDraw, ImageFontLOG_FILE = Path(__file__).parent / ”log.txt”OUTPUT_DIR = Path(__file__).parent / ”otp_qrcodes”def extract_label(uri: str) -> str: match = re.search(r”otpauth://totp/(.+?)\?”, uri) if not match: return ”unknown” return unquote(match.group(1))def make_qr(data: str, box_size: int = 10) -> Image.Image: qr = qrcode.QRCode( error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=box_size, border=2, ) qr.add_data(data) qr.make(fit=True) return qr.make_image(fill_color=”black”, back_color=”white”).convert(”RGB”)def try_load_font(size: int): for name in [ ”/System/Library/Fonts/PingFang.ttc”, ”/System/Library/Fonts/STHeiti Light.ttc”, ”/System/Library/Fonts/Helvetica.ttc”, ]: try: return ImageFont.truetype(name, size) except (OSError, IOError): continue return ImageFont.load_default()def main(): text = LOG_FILE.read_text(encoding=”utf-8”) uris = [ line.strip() for line in text.splitlines() if line.strip().startswith(”otpauth://”) ] if not uris: print(”未找到 otpauth:// URI”) return OUTPUT_DIR.mkdir(exist_ok=True) font = try_load_font(20) cards: list[Image.Image] = [] for i, uri in enumerate(uris): label = extract_label(uri) qr_img = make_qr(uri, box_size=8) qw, qh = qr_img.size card_w = max(qw + 40360) card_h = qh + 70 card = Image.new(”RGB”, (card_w, card_h), ”white”) draw = ImageDraw.Draw(card) draw.text((2015), label, fill=”black”, font=font) card.paste(qr_img, ((card_w - qw) // 250)) filename = re.sub(r”[^\w\-.]”, ”_”, label) + ”.png” card.save(OUTPUT_DIR / filename) print(f”[{i + 1}/{len(uris)}] {filename}”) cards.append(card)# 生成三列布局的汇总图 cols = 3 rows = (len(cards) + cols - 1) // cols card_width = max(card.width for card in cards) card_height = max(card.height for card in cards) padding = 10 total_width = cols * card_width + (cols + 1) * padding total_height = rows * card_height + (rows + 1) * padding summary = Image.new(”RGB”, (total_width, total_height), ”#f0f0f0”) for index, card in enumerate(cards): row, col = divmod(index, cols) x = padding + col * (card_width + padding) y = padding + row * (card_height + padding) summary.paste(card, (xy)) summary_path = OUTPUT_DIR / ”ALL_OTP_CODES.png” summary.save(summary_path) print(f”\n汇总图:{summary_path}”) print(”完成!”)if __name__ == ”__main__”: main()

运行脚本:

python3 generate_qr.py

生成的二维码会保存在 otp_qrcodes 目录中,其中包括:

  • 每个账户对应的一张独立二维码;
  • 一张名为 ALL_OTP_CODES.png 的汇总图。

接下来,使用目标设备上的验证器逐一扫描二维码即可完成迁移。

六、迁移后的检查与清理

迁移完成后,不要立即删除旧设备中的账号。建议先逐项检查:

    确认迁移无误后,请删除或加密保存以下敏感文件:

    • log.txt;
    • Frida 的终端输出记录;
    • 单独生成的二维码;
    • ALL_OTP_CODES.png 汇总图;
    • 从设备中复制出的应用数据备份。

    总结

    Microsoft Authenticator 的 iOS 与 Android 备份无法直接互通,但在拥有越狱设备的前提下,可以通过 Frida 在应用进程内读取 Keychain 数据,将 TOTP 密钥整理为标准的 otpauth:// URI,再生成二维码导入其他验证器。

    整个流程并不复杂,真正需要注意的是数据安全:任何一条导出的 URI 或二维码,都可能被用于生成对应账号的动态验证码。操作过程中务必使用可信设备,并在迁移结束后及时清理明文数据。

    以上就是 Microsoft Authenticator iOS 版 OTP 数据的备份与导出流程,希望能帮到有跨平台迁移需求的朋友。