下面是这 176 bytes header 的原始 HEX 视图。
这一步对应
metadata
|
| entry 范围 | entry_len |
key | value 长度 | value 的含义 |
|---|---|---|---|---|
[45, 129) |
84 | usability |
72 | Base64 文本,解码后是 54-byte 的“独立 IV + 检查值密文”。 |
[129, 176) |
47 | filename |
36 | Base64 文本,解码后是 11-byte 文件名密文 + 16-byte HMAC tag。 |
usability 的 value 是:
x/N+Vj3Ag7pOG+a77jZWD32bk3wD7ReO1ceJXJqhW9AFJVfWnIQZ6IEn9ytKDN815p/b+gHC
Base64 解码后为 54 bytes,按字段切开如下:
usability_iv [16]
c7 f3 7e 56 3d c0 83 ba 4e 1b e6 bb ee 36 56 0f
usability_ciphertext [38]
7d 9b 93 7c 03 ed 17 8e d5 c7 89 5c 9a a1 5b d0
05 25 57 d6 9c 84 19 e8 81 27 f7 2b 4a 0c df 35
e6 9f db fa 01 c2
前 16 bytes 是 usability_iv,剩余 38 bytes 才是固定检查值的密文。
filename 的 value 是:
I1b65fmY6f6uDjcryt+ePZ/yUzEoD2ATjEbi
Base64 解码后为:
23 56 fa e5 f9 98 e9 fe ae 0e 37 | 2b ca df 9e 3d 9f f2 53 31 28 0f 60 13 8c 46 e2
**─────── 文件名密文[11] ───────** **─────────── 16-byte filename tag ────────────**
parser 此时拿到的仍是两个 []byte value,先把它们存在 metadata map 里。
口令检查与 filename tag 验证通过后,Filename 字段才会写入可用于输出路径的 4781595.jpg。
当前验证口令是 0。对于这份样本,三次派生的有效 salt 都是:
3c a1 5e f9 d2 07 6b || 6f 0e 31 5e c8 54 1e d9 7b
也就是 16 bytes:
3c a1 5e f9 d2 07 6b 6f 0e 31 5e c8 54 1e d9 7b
派生结果如下:
| 用途 | PBKDF2 rounds | key[0:16] | key[16:32] |
|---|---|---|---|
| usability | 100 | 20 db 99 27 bc 00 3f 76 b0 a9 fe ab b7 34 b7 4d |
12 42 32 ca 1b e3 c4 75 87 46 17 c6 70 23 fc c5 |
| filename | 1000 | 13 06 2c 4f 4a af 26 57 51 df 36 ce b2 36 b2 87 |
98 53 23 fd f8 4d 83 04 d7 36 f2 18 f9 ec 89 f5 |
| body | 10000 | ac 0e 82 2e c2 d0 54 f4 ae 93 cb 91 00 d8 29 bf |
7d f9 e8 86 d0 87 b3 c5 9d 41 7d 5c 5d a5 df b7 |
usability 路径用第一把 key 和自己的 IV 解密,得到:
teirenfeiniuyunpanbeifencryptoencrypto
严格相等以后,口令才被接受。
这条 filename entry 位于 [129, 176):
[129, 131) 00 2f entry_len = 47
[131, 132) 08 key_len = 8
[132, 140) 66 69 6c 65 6e 61 6d 65
ASCII "filename"
[140, 176) I1b65fmY6f6uDjcryt+ePZ/yUzEoD2ATjEbi
Base64 value,长度 36 bytes
Base64 解码后,恢复器按最后 16 bytes 切开:
packed filename bytes (27 bytes)
23 56 fa e5 f9 98 e9 fe ae 0e 37 | 2b ca df 9e 3d 9f f2 53 31 28 0f 60 13 8c 46 e2
**─────────── ct[11] ───────────** **──────────── tag[16] ────────────────────────**
用 filename 的 1000 次 PBKDF2 key 对左侧 11 bytes 做 HMAC:
HMAC-SHA256(
13062c4f4aaf265751df36ceb236b287985323fdf84d8304d736f218f9ec89f5,
23 56 fa e5 f9 98 e9 fe ae 0e 37,
)[:16]
= 2b ca df 9e 3d 9f f2 53 31 28 0f 60 13 8c 46 e2
计算结果和右侧 tag 完全一致,这个 metadata value 才通过认证。
当前实现随后执行 CTR,解出的文件名会作为 Go string 写入 Reader。
本例文件名的 CTR 变换可以把每一个 byte 对上:
filename_key = 13 06 2c 4f 4a af 26 57 51 df 36 ce b2 36 b2 87 ...
data_iv = b5 61 b6 17 78 ed 6d 5c 61 7e de 08 d4 7d 02 f2
CTR keystream = 17 61 c2 d4 cc a1 dc d0 c4 7e 50
ciphertext = 23 56 fa e5 f9 98 e9 fe ae 0e 37
plaintext = 34 37 38 31 35 39 35 2e 6a 70 67
4 7 8 1 5 9 5 . j p g
文件名和正文共用固定头里的 data_iv。两组 PBKDF2 轮数不同,所以 CTR key 也不同。
认证得到 4781595.jpg 后,原程序这样处理路径:
.fot 目标路径
**
**─ filepath.Dir(...) // 保留恢复目录
**
verified filename = "4781595.jpg"
**
**─ filepath.Join(directory, "4781595.jpg") // 0x74f49c
**
**─ os.OpenFile(..., 0x241, 0x1b6) // 0x74f4d0
**
**─ 后续 CopyBuffer 把正文 plaintext 写入这个文件
[176, 2082) 和 tag [2082, 2098)正文长 1,906 bytes。这里保留首尾各 32 bytes 和 tag:
ciphertext 起始窗口(正文相对偏移):
0000: 36 70 db e3 f3 ac 60 b6 ca 93 50 e1 9b c0 0c 17
0010: 71 03 95 fe 5f 38 b8 45 25 e5 0d 34 25 4d f9 f7
ciphertext 结束窗口(最后 32 bytes):
-020: e0 b2 1e 21 ec 35 68 aa 1b d0 17 bc 41 ab de 02
-010: 43 27 cd 4d d3 35 f7 18 aa f0 19 6d 7a 51 91 08
body tag [16]:
6e f6 47 ff 1e 15 0f 21 e3 7c 53 99 99 43 4f 46
Reader 建好后的状态如下:
底层 reader offset: 176
LimitedReader.N: 1922 (正文 1906 bytes + 尾 tag 16 bytes)
Remain: 1906 (只允许 CTR/HMAC 正文分支消费这部分)
CTR: AES-256-CTR(body_key, data_iv)
BodyHMAC: HMAC-SHA256(body_key),初始为空
Verified: false
LimitedReader.N 包含尾部 tag,正文读完后还能从同一底层流取到它;Remain 只计算正文,避免普通读取分支把最后 16 bytes 当成密文。
DecryptSliceReader.Read 每次最多读取约 4096 bytes 正文。
不过这份对象小于该上限,一次就能读完,处理顺序如下:
ciphertext chunk
**─ BodyHMAC.Write(ciphertext chunk)
**─ AES-256-CTR(body_key, data_iv) → plaintext chunk
**─ CopyBuffer → os.File
全部 1,906 bytes 读完以后,Reader 才读取尾 tag,并计算:
HMAC-SHA256(
ac0e822ec2d054f4ae93cb9100d829bf7df9e886d087b3c59d417d5c5da5dfb7,
ciphertext[176:2082],
)[:16]
= 6e f6 47 ff 1e 15 0f 21 e3 7c 53 99 99 43 4f 46
结果与文件末尾的 tag 一致后,Verified 就变成 true。CTR 输出的首尾字节如下:
plaintext 起始窗口(JPEG header):
0000: ff d8 ff db 00 84 00 08 06 06 07 06 05 08 07 07
0010: 07 09 09 08 0a 0c 14 0d 0c 0b 0b 0c 19 12 13 0f
plaintext 结束窗口(JPEG trailer):
-020: 27 0c 73 c9 ec 2b d8 3c 0c aa be 13 b4 0a 41 c9
-010: 72 71 ee c6 b4 93 bb b9 cb 4a 1e ce 0a 27 ff d9
恢复出的文件 SHA-256:
93e94f000b9be232f7d6299abf499fdc9f517493b4f88c18daef1a2d92174d8a
此时对象的最终状态是:
Filename: 4781595.jpg
Plaintext: 1,906 bytes
调用链在这里:
FOT 文件字节流 (io.Reader)
**
▼
NewDecryptReader
**
▼
NewDecryptSliceReader
**─ 读固定头和 metadata
**─ 验证 usability
**─ 验证并解密 filename
**─ 建立正文 LimitedReader + CTR + HMAC
**
▼
io.CopyBuffer
**
▼
DecryptSliceReader.Read
**─ 读 ciphertext chunk
**─ ciphertext → HMAC
**─ ciphertext → AES-CTR → plaintext
**─ plaintext → os.File
**
▼
读最终 16-byte tag,比较 HMAC
**
▼
认证成功:恢复出的原文件
FOT byte streamNewDecryptReader 接收 FOT 字节流和口令,再把二者交给 NewDecryptSliceReader。
Go 反编译把这里的 io.Reader 显示成 tab/data:tab 是方法表,data 指向实际 Reader。
| 输入字节/状态 | 当前函数 | 状态变化 | 下一跳 |
|---|---|---|---|
| FOT 文件字节流 + password bytes | 0x7240e0 |
构造解密 Reader | 0x724220 |
NewDecryptSliceReader 开头读取 19 bytes,其中包括 magic、version、header_len 和 total_len。
header 补齐后会校验 magic 和 version;header_len 按大端 16 位读取,total_len 则经过 64 位字节翻转。
FOT bytes [0 : 19]
**
**─ magic[8] → 格式识别
**─ version[1] → 版本识别
**─ header_len[2] → 读取剩余头、切 metadata
**─ total_len[8] → 计算正文和最后 tag 的边界
header 补齐后,解析器从第 45 byte 开始读取 metadata。usability 用来检查口令,filename 要等 HMAC 通过后才能解密。
usability
Base64
**─ IV[16] || encrypted_fixed_check[38]
**─ PBKDF2(100) + AES-256-CTR
**─ fixed-check plaintext
filename
Base64
**─ encrypted_filename || hmac_tag[16]
**─ PBKDF2(1000) + HMAC-SHA256,先验 tag
**─ PBKDF2(1000) + AES-256-CTR(data_iv),后解密文件名
原程序拿认证后的文件名调用 filepath.Join,随后由 os.OpenFile 打开输出文件。
metadata["filename"]
**
▼
Base64 decode
**
**─ ciphertext
**─ last 16 bytes tag
**
▼
HMAC verify
** success
▼
AES-CTR decrypt(data_iv)
**
▼
verified filename
**
▼
filepath.Join(Dir(fot_path), filename)
**
▼
os.OpenFile
头、口令和文件名都通过后,NewDecryptSliceReader 返回的 Reader 保存这些状态:
底层 LimitedReader
AES-256-CTR 状态
HMAC-SHA256 状态
remain(正文密文剩余长度)
verified filename
声明的总长度与明文长度
io.CopyBuffer 会持续调用 DecryptSliceReader.Read。
ciphertext chunk 分别送入 HMAC 和 CTR,CTR 输出写进已经打开的文件。
ciphertext chunk
**
**──► HMAC-SHA256.Update(ciphertext chunk)
**
**──► AES-256-CTR.XORKeyStream
**
▼
plaintext chunk
**
▼
io.CopyBuffer
**
▼
os.File.Write
正文 remain 归零时,Reader 从底层再取 16 bytes,与累计 HMAC 的前 16 bytes 做逐字节 XOR 聚合比较。
匹配时返回 EOF,缺失或不匹配就报错。由于文件早已打开,最终 tag 校验失败时,磁盘上可能留有部分明文。
body ciphertext exhausted
**
▼
read final tag[16]
**
▼
computed = HMAC-SHA256(content_key, all ciphertext)[:16]
**
▼
constant-time-style XOR aggregate compare
**
**────**────**
** **
match mismatch / missing tag
** **
▼ ▼
EOF error
下面是离线解密飞牛加密备份文件的实现。
脚本依赖系统的 openssl enc -aes-256-ctr,启动时会用 NIST AES-256-CTR 向量自检。
恢复时不会覆盖已有输出,所有认证通过后才写入最终文件。
那个 RECOVERY_PASSWORD = b"0" 记得换成自己的密码。
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import base64
import hashlib
import hmac
import os
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
FOT_MAGIC = bytes.fromhex("464f54a31c77005e")
FOT_VERSION = 1
FIXED_HEADER_LENGTH = 45
TAG_LENGTH = 16
USABILITY_PLAINTEXT = b"teirenfeiniuyunpanbeifencryptoencrypto"
KDF_PREFIX = bytes.fromhex("3ca15ef9d2076b")
RECOVERY_PASSWORD = b"0"
class FOTError(ValueError):
pass
@dataclass(frozen=True)
class FOTHeader:
header_length: int
total_length: int
data_iv: bytes
salt: bytes
padding: int
metadata: dict[str, bytes]
def derive_key(password: bytes, salt: bytes, iterations: int) -> bytes:
return hashlib.pbkdf2_hmac("sha256", password, KDF_PREFIX + salt, iterations, 32)
def aes_ctr_crypt(key: bytes, iv: bytes, data: bytes) -> bytes:
try:
completed = subprocess.run(
[
"openssl",
"enc",
"-aes-256-ctr",
"-nosalt",
"-K",
key.hex(),
"-iv",
iv.hex(),
],
input=data,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
except FileNotFoundError as error:
raise FOTError("未找到 openssl;需要 OpenSSL 的 aes-256-ctr 支持") from error
if completed.returncode != 0:
message = completed.stderr.decode("utf-8", "replace").strip()
raise FOTError(f"OpenSSL AES-CTR 处理失败:{message}")
return completed.stdout
def require(condition: bool, message: str) -> None:
if not condition:
raise FOTError(message)
def parse_header(blob: bytes) -> FOTHeader:
require(len(blob) >= FIXED_HEADER_LENGTH + TAG_LENGTH, "文件小于最小 FOT 长度")
require(blob[:8] == FOT_MAGIC, "FOT 魔数不匹配")
require(blob[8] == FOT_VERSION, f"不支持的 FOT 版本:{blob[8]}")
header_length = int.from_bytes(blob[9:11], "big")
total_length = int.from_bytes(blob[11:19], "big")
require(header_length >= FIXED_HEADER_LENGTH, "header_len 小于固定头长度")
require(header_length <= len(blob) - TAG_LENGTH, "header_len 超出文件边界")
require(total_length == len(blob), "total_len 与实际文件长度不一致")
metadata: dict[str, bytes] = {}
offset = FIXED_HEADER_LENGTH
while offset < header_length:
require(offset + 3 <= header_length, "元数据条目头被截断")
entry_length = int.from_bytes(blob[offset : offset + 2], "big")
key_length = blob[offset + 2]
require(entry_length >= key_length + 3, "元数据 entry_len 非法")
entry_end = offset + entry_length
require(entry_end <= header_length, "元数据条目越过 header_len")
key_end = offset + 3 + key_length
require(key_end <= entry_end, "元数据键长度非法")
try:
key = blob[offset + 3 : key_end].decode("utf-8")
except UnicodeDecodeError as error:
raise FOTError("元数据键不是 UTF-8") from error
require(key not in metadata, f"重复的元数据键:{key}")
metadata[key] = blob[key_end:entry_end]
offset = entry_end
require(offset == header_length, "元数据未恰好结束于 header_len")
return FOTHeader(
header_length=header_length,
total_length=total_length,
data_iv=blob[19:35],
salt=blob[35:44],
padding=blob[44],
metadata=metadata,
)
def decode_metadata(name: str, metadata: dict[str, bytes]) -> bytes:
try:
encoded = metadata[name]
except KeyError as error:
raise FOTError(f"缺少必需元数据:{name}") from error
try:
return base64.b64decode(encoded, validate=True)
except Exception as error:
raise FOTError(f"{name} 不是合法 Base64") from error
def validate_password(header: FOTHeader, password: bytes) -> None:
usability = decode_metadata("usability", header.metadata)
require(
len(usability) == TAG_LENGTH + len(USABILITY_PLAINTEXT),
"usability 长度不符合 IV(16)+固定检查值密文(38) 的格式",
)
usability_iv = usability[:TAG_LENGTH]
recovered = aes_ctr_crypt(derive_key(password, header.salt, 100), usability_iv, usability[TAG_LENGTH:])
require(
hmac.compare_digest(recovered, USABILITY_PLAINTEXT),
"口令错误,或 usability 元数据已损坏",
)
def decrypt_filename(header: FOTHeader, password: bytes) -> str:
packed = decode_metadata("filename", header.metadata)
require(len(packed) >= TAG_LENGTH, "filename 缺少 16 字节 HMAC 标签")
encrypted_name, stored_tag = packed[:-TAG_LENGTH], packed[-TAG_LENGTH:]
key = derive_key(password, header.salt, 1000)
expected_tag = hmac.new(key, encrypted_name, hashlib.sha256).digest()[:TAG_LENGTH]
require(
hmac.compare_digest(expected_tag, stored_tag),
"filename HMAC 不匹配:口令错误或元数据已损坏",
)
plaintext = aes_ctr_crypt(key, header.data_iv, encrypted_name)
try:
filename = plaintext.decode("utf-8")
except UnicodeDecodeError as error:
raise FOTError("已认证的 filename 不是 UTF-8") from error
require(filename not in ("", ".", ".."), "恢复的文件名为空或无效")
require("/" not in filename and "\\" not in filename, "恢复的文件名包含路径分隔符")
return filename
def decrypt_body(blob: bytes, header: FOTHeader, password: bytes) -> bytes:
ciphertext = blob[header.header_length:-TAG_LENGTH]
stored_tag = blob[-TAG_LENGTH:]
key = derive_key(password, header.salt, 10000)
expected_tag = hmac.new(key, ciphertext, hashlib.sha256).digest()[:TAG_LENGTH]
require(
hmac.compare_digest(expected_tag, stored_tag),
"正文 HMAC 不匹配:口令错误或文件被截断/损坏",
)
return aes_ctr_crypt(key, header.data_iv, ciphertext)
def output_path(output_dir: Path, filename: str) -> Path:
destination = output_dir / filename
resolved_directory = output_dir.resolve()
resolved_destination = destination.resolve()
require(
os.path.commonpath((str(resolved_directory), str(resolved_destination))) == str(resolved_directory),
"恢复目标逃逸出输出目录",
)
require(not destination.exists(), f"拒绝覆盖已有输出文件:{destination}")
return destination
def main() -> int:
parser = argparse.ArgumentParser(
description="离线恢复经过认证的 FOT 文件;仅使用提供的口令,不进行任何口令猜测。"
)
parser.add_argument("input", type=Path, nargs="?", help="输入 .fot 文件")
parser.add_argument("--output-dir", type=Path, help="必须不存在或为空的新恢复目录")
parser.add_argument("--self-test", action="store_true", help="使用 NIST AES-CTR 测试向量验证本机 OpenSSL")
args = parser.parse_args()
if args.self_test:
key = bytes.fromhex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
iv = bytes.fromhex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
plaintext = bytes.fromhex("6bc1bee22e409f96e93d7e117393172a")
expected = bytes.fromhex("601ec313775789a5b7a7f504bbf3d228")
try:
require(aes_ctr_crypt(key, iv, plaintext) == expected, "AES-CTR 测试向量不匹配")
except FOTError as error:
print(f"自检失败:{error}", file=sys.stderr)
return 1
print("AES-256-CTR 自检成功")
return 0
if args.input is None or args.output_dir is None:
parser.error("恢复模式需要 input 和 --output-dir")
try:
blob = args.input.read_bytes()
password = RECOVERY_PASSWORD
require(not args.output_dir.exists() or not any(args.output_dir.iterdir()), "输出目录必须不存在或为空")
header = parse_header(blob)
validate_password(header, password)
filename = decrypt_filename(header, password)
plaintext = decrypt_body(blob, header, password)
args.output_dir.mkdir(parents=True, exist_ok=True)
destination = output_path(args.output_dir, filename)
temporary = destination.with_name(destination.name + ".partial")
require(not temporary.exists(), f"临时输出已存在:{temporary}")
temporary.write_bytes(plaintext)
temporary.replace(destination)
print(f"恢复成功:{destination}")
print(f"明文长度:{len(plaintext)}")
print(f"FOT 头长度:{header.header_length};文件总长度:{header.total_length}")
return 0
except (OSError, FOTError) as error:
print(f"恢复失败:{error}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
保存为 fot_decrypt.py 后,先测试本机 CTR:
python3 fot_decrypt.py --self-test
输出应为:
AES-256-CTR 自检成功
恢复时指定 .fot 和一个不存在或为空的输出目录:
python3 fot_decrypt.py 03aceacb3e8c37ff8fad811ab48874620000000000000772000000006a6a099f.fot --output-dir recovered
下面只保留真正处理 FOT 的四个解密器函数。
Go 反编译里夹着不少运行时内容,例如 tab/data、GC write barrier 和切片边界检查。
读的时候可以略过大部分 vNN,盯住密码学调用和长度即可。
代码里的 // 是我在 IDA 里的注释,IDA不会自己生成这东西,你拖进去看不到这个是正常的。
变体识别时,优先核对以下锚点:
REV16 和大端总长度;usability / filename 字符串和三组 PBKDF2 轮数;NewCTR、hmac.New、LimitedReader;0x7240e0 把底层 Reader 和 password 交给 0x724220。
第一个 FOT 头是在这里构造时解析的。
// git.teiron-inc.cn/services/backup-cloud/crypto.NewDecryptReader
retval_7240E0 __golang git_teiron_inc_cn_services_backup_cloud_crypto_NewDecryptReader(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5)
{
__int64 r0; // x3
__int64 v7; // x4
__int64 v8; // x5
__int64 v9; // x6
_QWORD *v10; // x25
__int64 v13; // [xsp+38h] [xbp-30h]
__int64 v14; // [xsp+48h] [xbp-20h]
retval_7240E0 result; // 0:x0.24
retval_724220 v21; // 0:kr00_24.24
v21 = git_teiron_inc_cn_services_backup_cloud_crypto_NewDecryptSliceReader(); /*0x72410c*/ // 先解析首个 FOT 文件并建立正文解密 Reader。
result._r1 = v21._r1; /*0xf1c0000000000008*/
result._r2 = v21._r2; /*0xf1c000000000000c*/
if ( v21._r1 ) /*0x724110*/
{
result._r0 = 0; /*0x7241d4*/
}
else
{
v14 = *(_QWORD *)(v21._r0 + 104LL); /*0x724124*/
v13 = *(_QWORD *)(v21._r0 + 112LL); /*0x72412c*/
result._r0 = runtime_newobject(&RTYPE_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptReader); /*0x724138*/
if ( dword_BE34B0 ) /*0x724144*/
{
result._r0 = runtime_gcWriteBarrier4(result._r0); /*0x72415c*/
r0 = v21._r0; /*0x724160*/
*v10 = v21._r0; /*0x724164*/
v7 = a2; /*0x724168*/
v10[1] = a2; /*0x72416c*/
v8 = a3; /*0x724170*/
v10[2] = a3; /*0x724174*/
v9 = v14; /*0x724178*/
v10[3] = v14; /*0x72417c*/
}
else
{
r0 = v21._r0; /*0x724148*/
v7 = a2; /*0x72414c*/
v8 = a3; /*0x724150*/
v9 = v14; /*0x724154*/
}
*(_QWORD *)result._r0 = r0; /*0x724180*/ // 保存当前 DecryptSliceReader。
*(_QWORD *)(result._r0 + 8LL) = a1; /*0x724188*/ // 保存底层 io.Reader 的 tab。
*(_QWORD *)(result._r0 + 16LL) = v7; /*0x72418c*/ // 保存底层 io.Reader 的 data。
*(_QWORD *)(result._r0 + 32LL) = a4; /*0x724194*/ // 保存 password 的长度。
*(_QWORD *)(result._r0 + 40LL) = a5; /*0x72419c*/ // 保存 password 的容量。
*(_QWORD *)(result._r0 + 24LL) = v8; /*0x7241a0*/ // 保存 password 数据指针,供串接 FOT 文件继续解析。
*(_QWORD *)(result._r0 + 56LL) = v13; /*0x7241a8*/ // 保存已认证文件名长度。
*(_QWORD *)(result._r0 + 48LL) = v9; /*0x7241ac*/ // 保存已认证文件名指针。
*(_QWORD *)(result._r0 + 64LL) = *(_QWORD *)(r0 + 120); /*0x7241b4*/ // 累计记录当前 FOT 文件的总长度。
*(_QWORD *)(result._r0 + 72LL) = *(_QWORD *)(r0 + 128); /*0x7241bc*/ // 累计记录当前 FOT 文件的正文长度。
result._r1 = 0; /*0x7241c0*/
result._r2 = v21._r2; /*0x7241c4*/
}
return result; /*0x7241d0*/
}
0x724220 负责把 FOT 字节流 Reader 和 password 组装成解密子 Reader,里面保存 LimitedReader、CTR、HMAC、剩余正文长度、原文件名和长度信息。
// git.teiron-inc.cn/services/backup-cloud/crypto.NewDecryptSliceReader
retval_724220 __golang git_teiron_inc_cn_services_backup_cloud_crypto_NewDecryptSliceReader(
void *a1,
void *a2,
__int64 a3,
__int64 a4,
__int64 a5)
{
char v7; // w0
unsigned __int64 v8; // x1
unsigned __int64 v9; // x1
__int64 v10; // x2
__int64 v11; // x0
__int64 v12; // x0
unsigned __int64 v13; // x0
__int64 v14; // x1
unsigned __int64 v15; // x2
signed __int64 v16; // x3
__int64 v17; // x4
__int64 v18; // x5
__int64 v19; // x6
__int64 v20; // x7
__int64 v21; // x8
__int64 v23; // x4
unsigned __int64 v24; // x5
__int64 v25; // x2
__int64 r2; // x3
unsigned __int64 v27; // x9
unsigned __int64 v28; // x10
__int64 v29; // x11
unsigned __int64 v30; // x12
__int64 v31; // x9
__int64 v32; // x13
unsigned __int64 v33; // x14
unsigned __int64 v34; // x4
__int64 v35; // x2
__int64 v36; // x3
_QWORD *v37; // x0
__int64 v38; // x5
_QWORD *v39; // x25
__int64 v40; // x1
__int64 v41; // x7
__int64 v42; // x8
__int64 v43; // x9
uint8 *v44; // x10
__int64 v45; // x0
__int64 v46; // x2
__int64 v47; // x7
__int64 v48; // x8
__int64 v49; // x1
uint8 *v50; // x9
__int64 v51; // x1
unsigned __int8 v52; // w2
char v53; // w4
__int64 v54; // x6
__int64 v55; // x7
__int64 v56; // x8
uint8 *v57; // x9
io_LimitedReader *p_io_LimitedReader; // x0
void *v59; // x1
_QWORD *v60; // x25
io_LimitedReader *v61; // x3
io_LimitedReader *v62; // x4
io_LimitedReader *v63; // x5
io_LimitedReader **v64; // x25
unsigned __int64 v65; // x3
__int64 v66; // x4
_QWORD *v67; // x25
__int64 r0; // x0
unsigned __int64 r1; // x1
__int64 v70; // [xsp+90h] [xbp-368h]
__int64 v71; // [xsp+A8h] [xbp-350h]
__int64 v72; // [xsp+B0h] [xbp-348h]
__int64 v73; // [xsp+B8h] [xbp-340h]
unsigned __int64 v74; // [xsp+C0h] [xbp-338h]
__int64 v75; // [xsp+D0h] [xbp-328h]
__int64 v76; // [xsp+D8h] [xbp-320h]
__int64 v77; // [xsp+E0h] [xbp-318h]
unsigned __int64 v78; // [xsp+E8h] [xbp-310h]
unsigned __int64 v79; // [xsp+F0h] [xbp-308h]
__int64 v80; // [xsp+F8h] [xbp-300h]
__int64 v81; // [xsp+100h] [xbp-2F8h]
__int64 v82; // [xsp+108h] [xbp-2F0h]
__int64 v83; // [xsp+110h] [xbp-2E8h]
__int64 v84; // [xsp+118h] [xbp-2E0h]
__int64 v85; // [xsp+120h] [xbp-2D8h]
unsigned __int64 v86; // [xsp+128h] [xbp-2D0h]
unsigned __int64 v87; // [xsp+130h] [xbp-2C8h]
unsigned __int64 v88; // [xsp+138h] [xbp-2C0h]
__int64 v89; // [xsp+140h] [xbp-2B8h]
__int64 v90; // [xsp+148h] [xbp-2B0h]
__int64 v91; // [xsp+150h] [xbp-2A8h]
__int64 v92; // [xsp+158h] [xbp-2A0h]
__int64 v93; // [xsp+160h] [xbp-298h]
unsigned __int64 v94; // [xsp+170h] [xbp-288h]
__int64 v95; // [xsp+178h] [xbp-280h]
unsigned __int64 v96; // [xsp+198h] [xbp-260h]
__int64 v97; // [xsp+1A0h] [xbp-258h]
unsigned __int64 v98; // [xsp+1A8h] [xbp-250h]
__int64 v99; // [xsp+1A8h] [xbp-250h]
__int64 v100; // [xsp+1A8h] [xbp-250h]
__int64 v101; // [xsp+1A8h] [xbp-250h]
__int64 v102; // [xsp+1A8h] [xbp-250h]
unsigned __int64 v103; // [xsp+1B0h] [xbp-248h]
unsigned __int64 v104; // [xsp+1B0h] [xbp-248h]
__int64 v105; // [xsp+1C8h] [xbp-230h]
io_LimitedReader *v106; // [xsp+1D8h] [xbp-220h]
__int64 v107; // [xsp+1E0h] [xbp-218h]
__int64 v108; // [xsp+1E8h] [xbp-210h]
__int64 v109; // [xsp+1F0h] [xbp-208h]
__int64 v110; // [xsp+200h] [xbp-1F8h]
__int64 v111; // [xsp+208h] [xbp-1F0h]
uint8 *v112; // [xsp+210h] [xbp-1E8h]
uint8 *v113; // [xsp+218h] [xbp-1E0h]
uint8 *v114; // [xsp+220h] [xbp-1D8h]
__int64 v115; // [xsp+228h] [xbp-1D0h]
__int64 v116; // [xsp+230h] [xbp-1C8h]
__int64 v117; // [xsp+238h] [xbp-1C0h]
__int64 v118; // [xsp+240h] [xbp-1B8h]
io_LimitedReader *v119; // [xsp+250h] [xbp-1A8h]
io_LimitedReader *v120; // [xsp+268h] [xbp-190h]
__int64 v121; // [xsp+270h] [xbp-188h]
__int64 v122; // [xsp+278h] [xbp-180h]
__int64 v123; // [xsp+280h] [xbp-178h]
__int64 v124; // [xsp+288h] [xbp-170h]
__int64 v125; // [xsp+288h] [xbp-170h]
__int64 v126; // [xsp+288h] [xbp-170h]
char v127; // [xsp+290h] [xbp-168h] BYREF
__int64 v128; // [xsp+3A0h] [xbp-58h] BYREF
__int64 v129; // [xsp+3A8h] [xbp-50h]
char *v130; // [xsp+3B0h] [xbp-48h]
__int64 v131; // [xsp+3B8h] [xbp-40h]
__int64 v132; // [xsp+3C0h] [xbp-38h]
__int64 v133; // [xsp+3C8h] [xbp-30h]
_QWORD v134[2]; // [xsp+3D0h] [xbp-28h] BYREF
__int64 v135; // [xsp+3E0h] [xbp-18h] BYREF
__int64 v136; // [xsp+3E8h] [xbp-10h]
retval_470A10 v142; // 0:x0.16
retval_470A10 v143; // 0:x0.16
retval_512A20 v144; // 0:x0.16
retval_470A10 v145; // 0:x0.16
retval_512A20 v146; // 0:x0.16
retval_5B4A70 v147; // 0:x0.16
retval_512A20 v148; // 0:x0.16
retval_5B4A70 v149; // 0:x0.16
retval_724220 result; // 0:x0.24
retval_470100 v151; // 0:kr20_24.24
retval_470100 v152; // 0:kr48_24.24
retval_470100 v153; // 0:kr80_24.24
retval_470100 v154; // 0:krE0_24.24
retval_723D20 v155; // 0:kr108_24.24
retval_724B74 v156; // 0:kr140_24.24
retval_470100 v157; // 0:kr168_24.24
retval_723D20 v158; // 0:kr180_24.24
retval_4A96E0 All; // 0:kr00_32.32
retval_4F7320 v160; // 0:kr60_32.32
retval_515D90 v161; // 0:krA0_32.32
retval_4F7320 v162; // 0:krC0_32.32
retval_515D90 v163; // 0:kr120_32.32
retval_515D90 v164; // 0:kr1A0_32.32
v124 = runtime_makeslice(&RTYPE_uint8); /*0x724268*/ // 分配 19-byte 固定头:magic、version、header_len、total_len。
result._r1 = io_ReadAtLeast(a1, a2, v124)._r1; /*0x724284*/ // 首读固定 19 bytes。
if ( result._r1 ) /*0x724288*/
{
result._r0 = 0; /*0x7245bc*/
return result; /*0x7245c8*/
}
if ( qword_B73528 <= 19 ) /*0x724298*/
v7 = runtime_memequal(v124, off_B73520); /*0x7242b0*/ // 比较 FOT 文件开头与全局 magic。
else
v7 = 0; /*0x72429c*/
if ( (v7 & 1) == 0 )
{
All = io_ReadAll(a1, a2); /*0x72447c*/
r0 = All._r0; /*0xf1c0000000000004*/
r1 = All._r1; /*0xf1c0000000000008*/
if ( All._r3 )
{
v135 = 0; /*0x724490*/
v136 = 0; /*0x724490*/
v135 = *(_QWORD *)(All._r3 + 8LL); /*0x72449c*/
v136 = v23; /*0x7244a0*/
fmt_Fprintf(off_9081A0, qword_BB97E8, "[NewDecryptSliceReader]Failed to read remaining data: %v\n", 57, &v135);
r0 = All._r0; /*0x7244d0*/
r1 = All._r1; /*0x7244d4*/
}
v24 = r1 + 19; /*0x7244d8*/
if ( r1 != 0 && r1 < 0xFFFFFFFFFFFFFFEDLL ) /*0x7244dc*/
{
v151 = runtime_growslice(v124, r1 + 19, 19, r1, &RTYPE_uint8); /*0x724508*/
r2 = v151._r2; /*0x72450c*/
v24 = v151._r1; /*0x724510*/
r1 = All._r1; /*0x724514*/
v25 = v151._r0; /*0x724518*/
r0 = All._r0; /*0x72451c*/
}
else
{
v25 = v124; /*0x7244e4*/
r2 = 19; /*0x7244e8*/
}
v89 = r2; /*0x724520*/
v88 = v24; /*0x724524*/
v116 = v25; /*0x724528*/
runtime_memmove(v25 + 19, r0, r1); /*0x724544*/
v134[0] = &RTYPE__slice_uint8; /*0x724568*/
v134[1] = runtime_convTslice(v116, v88, v89); /*0x72456c*/
fmt_Fprintf(off_9081A0, qword_BB97E8, "[NewDecryptSliceReader] full hex: % X\n", 38, v134);
result._r1 = off_B714C0; /*0x7245a0*/
result._r2 = off_B714C8; /*0x7245a8*/
result._r0 = 0; /*0x7245ac*/
return result; /*0x7245b8*/
}
if ( (unsigned __int64)qword_B73528 >= 0x13 ) /*0x7242c4*/
((void (__noreturn *)(void))runtime_panicIndex)(); /*0x724f5c*/
if ( *(unsigned __int8 *)(v124 + qword_B73528) != (unsigned __int8)byte_B859E2 ) /*0x7242dc*/
{
result._r1 = off_B714D0; /*0x724458*/
result._r2 = off_B714D8; /*0x724460*/
result._r0 = 0; /*0x724464*/
return result; /*0x724470*/
}
v8 = qword_B73528 + 3; /*0x7242e0*/
if ( (unsigned __int64)(qword_B73528 + 3) > 0x13 ) /*0x7242e8*/
((void (__noreturn *)(void))runtime_panicSliceAcap)(); /*0x724f54*/
if ( v8 < qword_B73528 + 1 ) /*0x7242f4*/
runtime_panicSliceB(qword_B73528 + 1); /*0x724f4c*/
if ( (unsigned __int64)(16 - qword_B73528) <= 7 ) /*0x724320*/
runtime_panicIndex(7, 16 - qword_B73528); /*0x724f44*/
v79 = (unsigned __int16)__rev16(*(unsigned __int16 *)(v124 + ((qword_B73528 + 1) & ((qword_B73528 - 18) >> 63)))); /*0x72432c*/ // REV16 将大端 header_len 转为宿主序。
v74 = v79 - 19; /*0x724334*/ // 固定头之外仍需读取的 metadata 字节数。
v103 = *(_QWORD *)(v124 + (v8 & ((qword_B73528 - 16) >> 63))); /*0x72433c*/
v123 = runtime_makeslice(&RTYPE_uint8); /*0x724350*/
result._r1 = io_ReadAtLeast(a1, a2, v123)._r1; /*0x72436c*/
if ( result._r1 ) /*0x724370*/
{
result._r0 = 0; /*0x724444*/
return result; /*0x724450*/
}
v9 = v79; /*0x724374*/
if ( v79 > 0x13 ) /*0x72437c*/
{
v152 = runtime_growslice(v124, v79, 19, v74, &RTYPE_uint8); /*0x7243a0*/
v11 = v152._r0; /*0xf1c0000000000020*/
v9 = v152._r1; /*0xf1c0000000000024*/
v10 = v152._r2; /*0xf1c0000000000028*/
}
else
{
v10 = 19; /*0x724380*/
v11 = v124; /*0x724384*/
}
v87 = v10; /*0x7243a4*/
v86 = v9; /*0x7243a8*/
v115 = v11; /*0x7243ac*/
v122 = v11 + 19; /*0x7243b4*/
v12 = runtime_memmove(v11 + 19, v123, v74); /*0x7243c4*/
if ( v87 < 0x23 ) /*0x7243d0*/
runtime_panicSliceAcap(v12, 35); /*0x724f38*/
if ( v87 < 0x2C ) /*0x7243d8*/
runtime_panicSliceAcap(v12, 44); /*0x724f30*/
v128 = 0; /*0x7243e0*/
v129 = 0; /*0x7243e0*/
v130 = nullptr; /*0x7243e8*/
v131 = 0; /*0x7243e8*/
v132 = 0; /*0x7243f0*/
v133 = 0; /*0x7243f0*/
((void (__golang *)(__int64))loc_4762BC)(v12); /*0x724404*/
v130 = &v127; /*0x724410*/
HIDWORD(v129) = runtime_rand32(); /*0x724418*/
v13 = v87; /*0x72441c*/
v14 = v115; /*0x724420*/
v15 = v86; /*0x724424*/
v16 = v79; /*0x724428*/
v17 = 45; /*0x72442c*/
v18 = 0; /*0x724430*/
v19 = 0; /*0x724434*/
v20 = 0; /*0x724438*/
v21 = 0; /*0x72443c*/
while ( 1 ) /*0x724600*/
{
v90 = v20; /*0x724600*/
v117 = v21; /*0x724604*/
if ( v17 >= v16 ) /*0x72460c*/
break; /*0x72460c*/
v27 = v17 + 2; /*0x724610*/
if ( v16 < v17 + 2 ) /*0x724618*/
break; /*0x724618*/
if ( v13 < v27 ) /*0x724620*/
runtime_panicSliceAcap(v13, v17 + 2); /*0x724f28*/
if ( v17 > v27 ) /*0x724628*/
runtime_panicSliceB(v17); /*0x724f1c*/
v28 = v13 - v17; /*0x72462c*/
v29 = (unsigned __int16)__rev16(*(unsigned __int16 *)(v14 + (v17 & ((__int64)(v17 - v13) >> 63)))); /*0x724640*/ // metadata entry 以大端 uint16 entry_len 起始,随后为 key_len、key、value。
v30 = v29 + v17; /*0x724644*/ // 用 entry_len 确定当前 metadata 条目的结束位置。
if ( v16 < v29 + v17 || !v29 ) /*0x724650*/
break; /*0x724650*/
if ( v15 <= v27 ) /*0x724658*/
runtime_panicIndex(v17 + 2, v15); /*0x724f10*/
v31 = *(unsigned __int8 *)(v14 + v27); /*0x72465c*/
if ( v29 <= v31 ) /*0x724664*/
break; /*0x724664*/
v32 = v31 + v17; /*0x724668*/
v33 = v31 + v17 + 3; /*0x72466c*/
if ( v13 < v33 ) /*0x724674*/
runtime_panicSliceAcap(v13, v31 + v17 + 3); /*0x724f04*/
v34 = v17 + 3; /*0x724678*/
if ( v33 < v34 ) /*0x724680*/
runtime_panicSliceB(v34); /*0x724ef8*/
v92 = v29; /*0x724684*/
v76 = v31; /*0x724688*/
v98 = v33; /*0x72468c*/
v105 = v19; /*0x724690*/
v70 = v18; /*0x724694*/
v97 = v32; /*0x724698*/
v96 = v30; /*0x72469c*/
v142 = runtime_slicebytetostring(0, v14 + (v34 & ((__int64)(3 - v28) >> 63)), v31); /*0x7246bc*/
if ( v87 < v96 ) /*0x7246cc*/
runtime_panicSliceAcap(v142._r0, v96); /*0x724eec*/
if ( v98 > v96 ) /*0x7246d8*/
runtime_panicSliceB(v98); /*0x724ee4*/
v77 = v142._r1; /*0x7246dc*/
v111 = v142._r0; /*0x7246e0*/
v143 = runtime_slicebytetostring(0, v115 + (v98 & ((__int64)(3 - (v87 - v97)) >> 63)), v92 - v76 - 3); /*0x724718*/
v121 = v143._r0; /*0x72471c*/
v99 = v143._r1; /*0x724720*/
if ( v77 == 8 ) /*0x72472c*/
{
if ( *(_QWORD *)v111 == 0x656D616E656C6966LL ) /*0x72474c*/ // 识别 filename 元数据;认证通过后恢复原始文件名。
{
v35 = v70; /*0x724750*/
v36 = v105; /*0x724754*/
goto LABEL_27; /*0x724758*/
}
}
else if ( v77 == 9 && *(_QWORD *)v111 == 0x74696C6962617375LL && *(_BYTE *)(v111 + 8) == 121 ) /*0x72478c*/ // 识别 usability 元数据,用于正文读取前的口令检查。
{
v35 = v143._r1; /*0x724790*/
v36 = v143._r0; /*0x724794*/
v143._r1 = v90; /*0x724798*/
v143._r0 = v117; /*0x72479c*/
goto LABEL_27; /*0x7247a0*/
}
v37 = (_QWORD *)runtime_mapassign_faststr(&RTYPE_map_string_string, &v128); /*0x7247b4*/
v37[1] = v99; /*0x7247bc*/
if ( dword_BE34B0 ) /*0x7247c8*/
{
v37 = (_QWORD *)runtime_gcWriteBarrier2(); /*0x7247d4*/
v38 = v121; /*0x7247d8*/
*v39 = v121; /*0x7247dc*/
v39[1] = *v37; /*0x7247e4*/
}
else
{
v38 = v121; /*0x7247cc*/
}
*v37 = v38; /*0x7247e8*/
v35 = v70; /*0x7247ec*/
v36 = v105; /*0x7247f0*/
v143._r1 = v90; /*0x7247f4*/
v143._r0 = v117; /*0x7247f8*/
LABEL_27:
v17 = v96; /*0x7245cc*/
v18 = v35; /*0x7245e0*/
v19 = v36; /*0x7245e4*/
v20 = v143._r1; /*0x7245e8*/
v21 = v143._r0; /*0x7245ec*/
v13 = v87; /*0x7245f0*/
v14 = v115; /*0x7245f4*/
v15 = v86; /*0x7245f8*/
v16 = v79; /*0x7245fc*/
}
v40 = v14 + 35; /*0x724800*/
v109 = v40; /*0x724804*/
if ( !v18 ) /*0x724808*/
goto LABEL_60; /*0x724808*/
v160 = encoding_base64__ptr_Encoding_DecodeString(qword_BB9A38, v19, v18); /*0x72481c*/
if ( v160._r3 ) /*0x724820*/
{
result._r1 = off_B714E0; /*0x724c04*/
result._r2 = off_B714E8; /*0x724c0c*/
result._r0 = 0; /*0x724c10*/
}
else if ( (__int64)v160._r1 < 16 ) /*0x724828*/
{
result._r1 = off_B714F0; /*0x724be4*/
result._r2 = off_B714F8; /*0x724bec*/
result._r0 = 0; /*0x724bf0*/
}
else
{
v41 = qword_B73550; /*0x724850*/
v42 = qword_B73548; /*0x724858*/
v43 = qword_B73548 + 9; /*0x72485c*/
v44 = off_B73540; /*0x72486c*/
if ( qword_B73550 < (unsigned __int64)(qword_B73548 + 9) ) /*0x724874*/
{
v100 = qword_B73548; /*0x724878*/
v153 = runtime_growslice(off_B73540, qword_B73548 + 9, qword_B73550, 9, &RTYPE_uint8); /*0x724894*/
v42 = v100; /*0x724898*/
v44 = (uint8 *)v153._r0; /*0x72489c*/
v43 = v153._r1; /*0x7248a0*/
v41 = v153._r2; /*0x7248a4*/
}
v82 = v43; /*0x7248a8*/
v114 = v44; /*0x7248ac*/
v85 = v41; /*0x7248b0*/
runtime_memmove(&v44[v42], v109, 9); /*0x7248c0*/
v45 = ((__int64 (__golang *)(__int64, __int64, __int64, uint8 *, __int64, __int64, __int64, __int64, __int64 (__golang **)()))golang_org_x_crypto_pbkdf2_Key)( /*0x7248f0*/
a3,
a4,
a5,
v114,
v82,
v85,
100,
32,
off_890CC8); // PBKDF2-SHA256(password, 固定前缀 || file_salt[9], 100, 32),派生 usability 的 AES-CTR 密钥。
v161 = crypto_aes_NewCipher(v45); /*0x7248f4*/
if ( v161._r2 ) /*0x7248f8*/
{
result._r0 = 0; /*0x724bc8*/
result._r1 = v161._r2; /*0x724bcc*/
result._r2 = v161._r3; /*0x724bd0*/
}
else
{
v144 = crypto_cipher_NewCTR(v161._r0, v161._r1, v160._r0, 16, v160._r2); /*0x724908*/ // usability 的前 16 bytes 是独立 CTR IV,余下密文解为固定检查值。
v73 = v144._r0; /*0x72490c*/
v108 = v144._r1; /*0x724910*/
v125 = runtime_makeslice(&RTYPE_uint8); /*0x724930*/
(*(void (__golang **)(__int64, __int64))(v73 + 24))(v108, v125); /*0x72495c*/
if ( qword_B73568 == v160._r1 - 16LL && (runtime_memequal(v125, off_B73560) & 1) != 0 ) /*0x724984*/ // 明文必须匹配内置的 usability 固定检查值;失败即拒绝该口令或损坏的 metadata。
{
v13 = v87; /*0x724988*/
v40 = v109; /*0x72498c*/
v20 = v90; /*0x724994*/
v21 = v117; /*0x724998*/
LABEL_60:
v78 = v13 - 19; /*0x72499c*/
if ( !v20 ) /*0x7249a4*/
{
v145._r0 = 0; /*0x7249a8*/
v46 = 0; /*0x7249ac*/
goto LABEL_79; /*0x7249b0*/
}
v162 = encoding_base64__ptr_Encoding_DecodeString(qword_BB9A38, v21, v20); /*0x7249c4*/
if ( v162._r3 || (__int64)v162._r1 < 16 ) /*0x7249d0*/
{
result._r1 = off_B71510; /*0x7249d8*/
result._r2 = off_B71518; /*0x7249e0*/
result._r0 = 0; /*0x7249e4*/
}
else
{
v95 = v162._r1 - 16LL; /*0x724a00*/
v47 = qword_B73550; /*0x724a18*/
v48 = qword_B73548; /*0x724a20*/
v49 = qword_B73548 + 9; /*0x724a24*/
v50 = off_B73540; /*0x724a34*/
if ( qword_B73550 < (unsigned __int64)(qword_B73548 + 9) ) /*0x724a3c*/
{
v101 = qword_B73548; /*0x724a40*/
v154 = runtime_growslice(off_B73540, v49, qword_B73550, 9, &RTYPE_uint8); /*0x724a58*/
v49 = v154._r1; /*0xf1c000000000006c*/
v48 = v101; /*0x724a5c*/
v50 = (uint8 *)v154._r0; /*0x724a60*/
v47 = v154._r2; /*0x724a64*/
}
v84 = v47; /*0x724a68*/
v113 = v50; /*0x724a6c*/
v81 = v49; /*0x724a70*/
runtime_memmove(&v50[v48], v109, 9); /*0x724a80*/
v155 = golang_org_x_crypto_pbkdf2_Key(a3, a4, a5, v113, v81, v84, 1000, 32, off_890CC8); /*0x724ab0*/ // PBKDF2-SHA256(password, 固定前缀 || file_salt[9], 1000, 32),派生 filename 的 CTR/HMAC 共用密钥。
v163 = crypto_aes_NewCipher(v155._r0); /*0x724ac0*/
if ( v163._r2 ) /*0x724ac4*/
{
result._r0 = 0; /*0x724b90*/
result._r1 = v163._r2; /*0x724b94*/
result._r2 = v163._r3; /*0x724b98*/
}
else
{
v146 = crypto_cipher_NewCTR(v163._r0, v163._r1, v122, 16, v78); /*0x724ad4*/ // 使用固定头 data_iv 解密 filename 的密文部分。
v72 = v146._r0; /*0x724ad8*/
v107 = v146._r1; /*0x724adc*/
v126 = runtime_makeslice(&RTYPE_uint8); /*0x724af4*/
(*(void (__golang **)(__int64, __int64))(v72 + 24))(v107, v126); /*0x724b20*/
v147 = crypto_hmac_New(off_890CC8, v155._r0, v155._r1, v155._r2); /*0x724b38*/ // 创建 filename 的 HMAC-SHA256 状态;usability 没有独立 HMAC。
v75 = v147._r0; /*0x724b3c*/
v110 = v147._r1; /*0x724b40*/
(*(void (__golang **)(_QWORD, _QWORD, __int64, _QWORD))(v147._r0 + 56LL))(v147._r1, v162._r0, v95, v162._r2); /*0x724b58*/ // HMAC 输入仅为 filename 密文,排除末尾 16-byte tag。
v156 = ((retval_724B74 (__golang *)(__int64, _QWORD, _QWORD, _QWORD))*(_QWORD *)(v75 + 48))(v110, 0, 0, 0); /*0x724b74*/
if ( v156._r2 < 0x10u ) /*0x724b7c*/
runtime_panicSliceAcap(v156._r0, 16); /*0x724ed8*/
v51 = 0; /*0x724b84*/
v52 = 0; /*0x724b88*/
while ( v51 < 16 ) /*0x724c38*/ // 比较计算 HMAC 的前 16 bytes 与 filename value 尾部的 16-byte tag。
{
v53 = *(_BYTE *)(v156._r0 + v51) /*0x724c28*/
^ *(_BYTE *)(v162._r0 + ((v162._r1 - 16LL) & (-(v162._r2 - v162._r1 + 16LL) >> 63)) + v51);
++v51; /*0x724c2c*/
v52 |= v53; /*0x724c30*/
}
if ( ((((unsigned __int64)v52 - 1) >> 31) & 1) == 1 ) /*0x724c50*/
{
v145 = runtime_slicebytetostring(0, v126, v95); /*0x724c60*/ // 只有 filename HMAC 校验通过后,才把 CTR 明文作为文件名。
v46 = v145._r1; /*0x724c6c*/
v40 = v109; /*0x724c70*/
LABEL_79:
v91 = v46; /*0x724c74*/
v118 = v145._r0; /*0x724c78*/
v54 = qword_B73550; /*0x724c80*/
v55 = qword_B73548; /*0x724c88*/
v56 = qword_B73548 + 9; /*0x724c8c*/
v57 = off_B73540; /*0x724c94*/
if ( qword_B73550 < (unsigned __int64)(qword_B73548 + 9) ) /*0x724c9c*/
{
v102 = qword_B73548; /*0x724ca0*/
v157 = runtime_growslice(off_B73540, qword_B73548 + 9, qword_B73550, 9, &RTYPE_uint8); /*0x724cbc*/
v55 = v102; /*0x724cc0*/
v57 = (uint8 *)v157._r0; /*0x724cc4*/
v54 = v157._r2; /*0x724cc8*/
v56 = v157._r1; /*0x724ccc*/
v40 = v109; /*0x724cd0*/
}
v83 = v54; /*0x724cd4*/
v80 = v56; /*0x724cd8*/
v112 = v57; /*0x724cdc*/
runtime_memmove(&v57[v55], v40, 9); /*0x724ce8*/
v158 = golang_org_x_crypto_pbkdf2_Key(a3, a4, a5, v112, v80, v83, 10000, 32, off_890CC8); /*0x724d18*/ // PBKDF2-SHA256(password, 固定前缀 || file_salt[9], 10000, 32),派生正文 CTR/HMAC 共用密钥。
v164 = crypto_aes_NewCipher(v158._r0); /*0x724d28*/
if ( v164._r2 ) /*0x724d2c*/
{
result._r0 = 0; /*0x724e9c*/
result._r1 = v164._r2; /*0x724ea0*/
result._r2 = v164._r3; /*0x724ea4*/
}
else
{
v148 = crypto_cipher_NewCTR(v164._r0, v164._r1, v122, 16, v78); /*0x724d3c*/ // 使用固定头 data_iv 建立正文 AES-CTR 解密流。
v93 = v148._r0; /*0x724d40*/
v119 = (io_LimitedReader *)v148._r1; /*0x724d44*/
v149 = crypto_hmac_New(off_890CC8, v158._r0, v158._r1, v158._r2); /*0x724d5c*/ // 建立正文 HMAC-SHA256;后续只写入正文密文。
v71 = v149._r0; /*0x724d60*/
v106 = (io_LimitedReader *)v149._r1; /*0x724d64*/
v104 = bswap64(v103); /*0x724d80*/ // total_len 为大端 uint64,先翻转为宿主序。
v94 = v104 - (qword_B85CE8 + v79); /*0x724d90*/ // 计算正文密文长度,排除 header 和末尾 16-byte tag。
p_io_LimitedReader = (io_LimitedReader *)runtime_newobject(&RTYPE_io_LimitedReader); /*0x724d9c*/
p_io_LimitedReader->R.tab = a1; /*0x724da4*/
if ( dword_BE34B0 ) /*0x724db0*/
{
p_io_LimitedReader = (io_LimitedReader *)runtime_gcWriteBarrier1(); /*0x724dbc*/
v59 = a2; /*0x724dc0*/
*v60 = a2; /*0x724dc4*/
}
else
{
v59 = a2; /*0x724db4*/
}
v120 = p_io_LimitedReader; /*0x724dc8*/
p_io_LimitedReader->R.data = v59; /*0x724dcc*/
p_io_LimitedReader->N = v104 - v79; /*0x724dd4*/ // 限制底层 Reader 到“正文密文 + 16-byte tag”,避免越过当前 FOT 文件。
result._r0 = runtime_newobject(&RTYPE_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader); /*0x724de0*/
*(_QWORD *)result._r0 = off_908260; /*0x724dec*/
if ( dword_BE34B0 ) /*0x724df8*/
{
result._r0 = runtime_gcWriteBarrier3(); /*0x724e0c*/
v61 = v120; /*0x724e10*/
*v64 = v120; /*0x724e14*/
v62 = v119; /*0x724e18*/
v64[1] = v119; /*0x724e1c*/
v63 = v106; /*0x724e20*/
v64[2] = v106; /*0x724e24*/
}
else
{
v61 = v120; /*0x724dfc*/
v62 = v119; /*0x724e00*/
v63 = v106; /*0x724e04*/
}
*(_QWORD *)(result._r0 + 8LL) = v61; /*0x724e28*/
*(_QWORD *)(result._r0 + 16LL) = v93; /*0x724e30*/
*(_QWORD *)(result._r0 + 24LL) = v62; /*0x724e34*/
*(_QWORD *)(result._r0 + 32LL) = v71; /*0x724e3c*/
*(_QWORD *)(result._r0 + 40LL) = v63; /*0x724e40*/
v65 = v94; /*0x724e44*/
*(_QWORD *)(result._r0 + 48LL) = v94; /*0x724e48*/ // remain:仅正文密文的剩余长度,不包括末尾 tag。
*(_BYTE *)(result._r0 + 96LL) = 0; /*0x724e4c*/ // verified 初始为 false,tag 成功后才置位。
*(_QWORD *)(result._r0 + 112LL) = v91; /*0x724e54*/ // 保存已认证 filename 的长度。
if ( dword_BE34B0 ) /*0x724e60*/
{
result._r0 = runtime_gcWriteBarrier1(); /*0x724e6c*/
v66 = v118; /*0x724e70*/
*v67 = v118; /*0x724e74*/
}
else
{
v66 = v118; /*0x724e64*/
}
*(_QWORD *)(result._r0 + 104LL) = v66; /*0x724e78*/ // 保存已认证 filename 指针。
*(_QWORD *)(result._r0 + 120LL) = v104; /*0x724e80*/ // 保存当前 FOT 文件的 total_len。
*(_QWORD *)(result._r0 + 128LL) = v65; /*0x724e84*/ // 保存当前 FOT 文件的正文密文长度。
result._r1 = 0; /*0x724e88*/
result._r2 = 0; /*0x724e8c*/
}
}
else
{
result._r1 = off_B71520; /*0x724eb8*/
result._r2 = off_B71528; /*0x724ec0*/
result._r0 = 0; /*0x724ec4*/
}
}
}
}
else
{
result._r1 = off_B71500; /*0x724bac*/
result._r2 = off_B71508; /*0x724bb4*/
result._r0 = 0; /*0x724bb8*/
}
}
}
return result; /*0x724450*/
}
Read:EOF 后才可能衔接下一个 FOT 对象0x724fa0 先把读取请求转给当前子 Reader。等它返回 EOF,0x725050 才调用头解析器,尝试接上后一个 FOT 对象。
// git.teiron-inc.cn/services/backup-cloud/crypto.(*DecryptReader).Read
retval_7ABA20 __golang git_teiron_inc_cn_services_backup_cloud_crypto__ptr_DecryptReader_Read(
_ptr_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptReader a1,
_slice_uint8 a2)
{
char v2; // w3
char v3; // w0
_ptr_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptReader r4; // x4
_QWORD *v5; // x25
git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader *r0; // x0
retval_7ABA20 result; // 0:x0.24
retval_7ABA20 v13; // 0:kr00_24.24
retval_724220 v14; // 0:kr28_24.24
retval_7ABA20 v15; // 0:kr40_24.24
retval_475E60 v16; // 0:kr78_40.40
v13 = fot_decrypt_reader_read_and_verify_tag(a1->reader, a2); /*0x724fd0*/
result._r0 = v13._r0; /*0xf1c0000000000004*/
result._r1.tab = v13._r1.tab; /*0xf1c0000000000008*/
result._r1.data = v13._r1.data; /*0xf1c000000000000c*/
if ( v13._r0 <= 0 ) /*0x724fd8*/
{
if ( v13._r1.tab ) /*0x724fe4*/
{
if ( v13._r1.tab == off_B70F50 ) /*0x724ff4*/
{
v3 = runtime_ifaceeq(v13._r1.tab, v13._r1.data, off_B70F58); /*0x725018*/
result._r1.tab = v13._r1.tab; /*0x72501c*/
result._r1.data = v13._r1.data; /*0x725020*/
v2 = v3; /*0x725024*/
result._r0 = v13._r0; /*0x725028*/
}
else
{
v2 = 0; /*0x724ff8*/
}
}
else
{
v2 = 0; /*0x725030*/
}
if ( (v2 & 1) != 0 ) /*0x725034*/
{
v14 = fot_parse_header_and_create_decrypt_reader( /*0x725050*/ // 仅当前子 Reader 认证后返回 EOF,才解析下一个串接 FOT 文件。
a1->inReader.tab,
a1->inReader.data,
(__int64)a1->key.array,
a1->key.len,
a1->key.cap);
r0 = (git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader *)v14._r0; /*0xf1c0000000000010*/
if ( v14._r1 ) /*0x725054*/
{
result._r0 = 0; /*0x7250b0*/
result._r1.tab = v13._r1.tab; /*0x7250b4*/
result._r1.data = v13._r1.data; /*0x7250b8*/
}
else
{
r4 = a1; /*0x725058*/
a1->CipherLenght += *(_QWORD *)(v14._r0 + 120LL); /*0x725068*/ // 累加串接文件的声明总长度。
a1->PlainLenght += *(_QWORD *)(v14._r0 + 128LL); /*0x725078*/ // 累加串接文件的正文长度。
if ( dword_BE34B0 ) /*0x725084*/
{
v16 = runtime_gcWriteBarrier2(); /*0x725088*/
r0 = (git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader *)v16._r0; /*0xf1c0000000000028*/
r4 = (_ptr_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptReader)v16._r4; /*0xf1c0000000000038*/
*v5 = v16._r0; /*0x72508c*/
v5[1] = *(_QWORD *)v16._r4; /*0x725094*/
}
r4->reader = r0; /*0x725098*/
v15 = fot_decrypt_reader_read_and_verify_tag(r0, a2); /*0x7250a8*/
result._r0 = v15._r0; /*0xf1c000000000001c*/
result._r1.tab = v15._r1.tab; /*0xf1c0000000000020*/
result._r1.data = v15._r1.data; /*0xf1c0000000000024*/
}
}
}
return result; /*0x7250c4*/
}
Read:密文块、CTR、HMAC 和最后 tag0x725100 把底层 reader 交来的 ciphertext 写入 HMAC,并用 CTR 将结果放进 plaintext buffer。正文读完后,它会再取末尾的 16 bytes tag。
// git.teiron-inc.cn/services/backup-cloud/crypto.(*DecryptSliceReader).Read
fot_read_result_t fot_decrypt_reader_read_and_verify_tag(void *decrypt_reader, go_byte_slice_t output)
{
__int64 v2; // x4
void *v3; // x4
unsigned __int8 *v4; // x4
__int64 v8; // x1
unsigned __int8 v9; // w2
__int64 v10; // x2
__int64 v11; // x0
char v12; // w3
_ptr_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader v13; // x3
signed __int64 v14; // x1
signed __int64 off; // x0
__int64 v16; // x2
RTYPE **v17; // x3
void *v18; // x4
__int64 v19; // x4
__int64 v20; // x1
char v21; // w4
unsigned __int64 v22; // [xsp+50h] [xbp-48h]
__int64 v23; // [xsp+58h] [xbp-40h]
__int64 v24; // [xsp+60h] [xbp-38h]
uint8 *plaintext_chunk; // [xsp+80h] [xbp-18h]
__int64 stored_hmac_tag; // [xsp+88h] [xbp-10h]
__int64 ciphertext_chunk; // [xsp+88h] [xbp-10h]
_ptr_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader v28; // [xsp+A0h] [xbp+8h]
unsigned __int8 *data; // [xsp+A8h] [xbp+10h]
__int64 len; // [xsp+B0h] [xbp+18h]
fot_read_result_t result; // 0:x0.24
_slice_uint8 v32; // 0:x1.24
retval_725314 v33; // 0:kr28_24.24
retval_72528C computed_hmac_tag; // 0:kr40_24.24
v28 = (_ptr_git_teiron_inc_cn_services_backup_cloud_crypto_DecryptSliceReader)decrypt_reader; /*0x725120*/
if ( (__int64)(*((_QWORD *)decrypt_reader + 8) - *((_QWORD *)decrypt_reader + 10)) <= 0 )
{
*((_QWORD *)decrypt_reader + 8) = 0; /*0x725210*/
*((_QWORD *)decrypt_reader + 10) = 0; /*0x725214*/
*((_BYTE *)decrypt_reader + 88) = 0; /*0x725218*/
output.cap = *((_QWORD *)decrypt_reader + 6); /*0x72521c*/
if ( output.cap <= 0 ) /*0x725224*/
{
if ( (*((_BYTE *)decrypt_reader + 96) & 1) == 0 ) /*0x72522c*/
{
stored_hmac_tag = runtime_makeslice(&RTYPE_uint8); /*0x725244*/
if ( io_ReadAtLeast(v28->reader.tab, v28->reader.data, stored_hmac_tag)._r1 )// 正文结束后必须读取 16 字节 HMAC 截断标签;缺失表示文件不完整。 /*0x725268*/
{
result.err_tab = off_B71530; /*0x7252ac*/
result.err_data = off_B71538; /*0x7252b4*/
result.count = 0; /*0x7252b8*/
return result; /*0x7252c4*/
}
computed_hmac_tag = ((retval_72528C (__golang *)(void *, _QWORD, _QWORD, _QWORD))*((_QWORD *)v28->tagHasher.tab /*0x72528c*/
+ 6))(
v28->tagHasher.data,
0,
0,
0);
if ( computed_hmac_tag._r2 < 0x10u ) /*0x725294*/
runtime_panicSliceAcap(computed_hmac_tag._r0, 16); /*0x725590*/
v8 = 0; /*0x72529c*/
v9 = 0; /*0x7252a0*/
while ( v8 < 16 ) /*0x725524*/
{
v21 = *(_BYTE *)(stored_hmac_tag + v8) ^ *(_BYTE *)(computed_hmac_tag._r0 + v8);// 逐字节 XOR 聚合比较计算的 HMAC 前 16 字节与文件尾标签;不匹配通常表示口令错误或文件损坏。 /*0x725514*/
++v8; /*0x725518*/
v9 |= v21; /*0x72551c*/
}
if ( ((((unsigned __int64)v9 - 1) >> 31) & 1) != 1 ) /*0x72553c*/
{
result.err_tab = off_B71540; /*0x725570*/
result.err_data = off_B71548; /*0x725578*/
result.count = 0; /*0x72557c*/
return result; /*0x725588*/
}
v28->verified = 1; /*0x725548*/
}
result.err_tab = off_B70F50; /*0x725550*/
result.err_data = off_B70F58; /*0x725558*/
result.count = 0; /*0x72555c*/
return result; /*0x725568*/
}
len = output.len; /*0x7252c8*/
data = output.data; /*0x7252cc*/
if ( output.cap >= 4096 ) /*0x7252d8*/
v10 = 4096; /*0x7252d8*/ // 单次最多读取 4096 bytes ciphertext。
else
v10 = *((_QWORD *)decrypt_reader + 6); /*0x7252d8*/
v22 = v10; /*0x7252dc*/
ciphertext_chunk = runtime_makeslice(&RTYPE_uint8); /*0x7252f0*/
v33 = ((retval_725314 (__golang *)(void *, __int64, unsigned __int64, unsigned __int64))*((_QWORD *)v28->reader.tab /*0x725314*/
+ 3))(
v28->reader.data,
ciphertext_chunk,
v22,
v22);
result.err_tab = (void *)v33._r1; /*0xf1c0000000000014*/
result.err_data = (void *)v33._r2; /*0xf1c0000000000018*/
if ( v33._r0 > 0 ) /*0x725324*/
{
v28->remain -= v33._r0; /*0x725338*/ // 只扣减正文密文剩余量;末尾 tag 不计入 remain。
v11 = runtime_makeslice(&RTYPE_uint8); /*0x72534c*/
if ( v33._r0 > v22 ) /*0x72535c*/
runtime_panicSliceAcap(v11, v33._r0); /*0x725508*/
plaintext_chunk = (uint8 *)v11; /*0x725360*/
(*((void (__golang **)(void *, __int64, _QWORD))v28->decryptor.tab + 3))(v28->decryptor.data, v11, v33._r0); /*0x725390*/ // 对当前 ciphertext chunk 执行 CTR,得到 plaintext_chunk。
(*((void (__golang **)(void *, __int64, _QWORD, unsigned __int64))v28->tagHasher.tab + 7))( /*0x7253b0*/
v28->tagHasher.data,
ciphertext_chunk,
v33._r0,
v22); // 同一 ciphertext chunk 写入正文 HMAC;认证对象不是明文。
v32.array = plaintext_chunk; /*0x7253bc*/
v32.len = v33._r0; /*0x7253c0*/
v32.cap = v33._r0; /*0x7253c4*/
bytes__ptr_Buffer_Write(&v28->buffer, v32); /*0x7253c8*/
result.err_tab = (void *)v33._r1; /*0x7253cc*/
result.err_data = (void *)v33._r2; /*0x7253d0*/
}
if ( result.err_tab
&& (result.err_tab == off_B70F50
? (v12 = runtime_ifaceeq(result.err_tab, result.err_data, off_B70F58) ^ 1,
result.err_tab = (void *)v33._r1,
result.err_data = (void *)v33._r2)
: (void *)(v12 = 1),
(v12 & 1) != 0) )
{
result.count = 0; /*0x725418*/
}
else
{
v13 = v28; /*0x725428*/
v28->buffer.lastRead = 0; /*0x72542c*/
v14 = v28->buffer.buf.len; /*0x725430*/
off = v28->buffer.off; /*0x725434*/
if ( v14 > off ) /*0x72543c*/
{
if ( v14 < (unsigned __int64)off ) /*0x725480*/
runtime_panicSliceB(off); /*0x725504*/
v19 = v14 - off; /*0x725484*/
v20 = (__int64)&v28->buffer.buf.array[off & ((off - v28->buffer.buf.cap) >> 63)]; /*0x72549c*/
if ( len <= v19 ) /*0x7254a8*/
v16 = len; /*0x7254a8*/
else
v16 = v19; /*0x7254a8*/
if ( data != (unsigned __int8 *)v20 ) /*0x7254b4*/
{
v23 = v16; /*0x7254b8*/
runtime_memmove(data, v20, v16); /*0x7254bc*/
v16 = v23; /*0x7254c0*/
v13 = v28; /*0x7254c4*/
}
v13->buffer.off += v16; /*0x7254d0*/
if ( v16 > 0 ) /*0x7254d8*/
v13->buffer.lastRead = -1; /*0x7254e0*/
v17 = nullptr; /*0x7254e4*/
v18 = nullptr; /*0x7254e8*/
}
else
{
v28->buffer.buf.len = 0; /*0x725444*/
v28->buffer.off = 0; /*0x725448*/
v28->buffer.lastRead = 0; /*0x72544c*/
if ( len ) /*0x725454*/
{
v17 = off_B70F50; /*0x72546c*/
v18 = off_B70F58; /*0x725474*/
v16 = 0; /*0x725478*/
}
else
{
v16 = 0; /*0x725458*/
v17 = nullptr; /*0x72545c*/
v18 = nullptr; /*0x725460*/
}
}
result.count = v16; /*0x7254ec*/
result.err_tab = v17; /*0x7254f0*/
result.err_data = v18; /*0x7254f4*/
}
}
else
{
*((_BYTE *)decrypt_reader + 88) = 0; /*0x725138*/
output.cap = *((_QWORD *)decrypt_reader + 8); /*0x72513c*/
v2 = *((_QWORD *)decrypt_reader + 10); /*0x725140*/
if ( output.cap > v2 ) /*0x725148*/
{
if ( output.cap < (unsigned __int64)v2 ) /*0x725188*/
runtime_panicSliceB(*((_QWORD *)decrypt_reader + 10)); /*0x72559c*/
output.cap -= v2; /*0x72518c*/
v4 = (unsigned __int8 *)(*((_QWORD *)decrypt_reader + 7) + (v2 & ((v2 - *((_QWORD *)decrypt_reader + 9)) >> 63))); /*0x7251a4*/
if ( output.len > output.cap ) /*0x7251ac*/
output.len = output.cap; /*0x7251ac*/
if ( output.data != v4 ) /*0x7251b4*/
{
v24 = output.len; /*0x7251b8*/
runtime_memmove(output.data, v4, output.len); /*0x7251c4*/
decrypt_reader = v28; /*0x7251c8*/
output.len = v24; /*0x7251cc*/
}
*((_QWORD *)decrypt_reader + 10) += output.len; /*0x7251d8*/
if ( output.len > 0 ) /*0x7251e0*/
*((_BYTE *)decrypt_reader + 88) = -1; /*0x7251e8*/
output.cap = 0; /*0x7251ec*/
v3 = nullptr; /*0x7251f0*/
}
else
{
*((_QWORD *)decrypt_reader + 8) = 0; /*0x725150*/
*((_QWORD *)decrypt_reader + 10) = 0; /*0x725154*/
*((_BYTE *)decrypt_reader + 88) = 0; /*0x725158*/
if ( output.len ) /*0x72515c*/
{
output.cap = (__int64)off_B70F50; /*0x725174*/
v3 = off_B70F58; /*0x72517c*/
output.len = 0; /*0x725180*/
}
else
{
output.len = 0; /*0x725160*/
output.cap = 0; /*0x725164*/
v3 = nullptr; /*0x725168*/
}
}
result.count = output.len; /*0x7251f4*/
result.err_tab = (void *)output.cap; /*0x7251f8*/
result.err_data = v3; /*0x7251fc*/
}
return result; /*0x725208*/
}
把上面的函数名串起来,是这条路径:
FOT reader + password bytes
**
**─ NewDecryptReader(reader, password)
** **─ 在构造阶段完成头、usability、filename 的检查
**
**─ 取已认证的 filename
**─ 在指定恢复目录下创建同名文件
**
**─ io.CopyBuffer(输出文件, decryptReader)
**─ 持续驱动正文 Reader,直到正文 tag 验证成功或失败
完整反编译能追出数据流,下面的汇编只拿来核对读取长度和字节序。
受 Go ABI 影响,反编译里的 io.ReadAtLeast 参数不太直观。
MOV X1, #19 以及随后传入的三个 19,可以确认首读范围为 magic + version + header_len + total_len。
724254: a0 03 00 d0 adrp x0, 0x79a000
724258: 00 00 21 91 add x0, x0, #2112
72425c: 61 02 80 d2 mov x1, #19 // 固定头首次读取长度:19 bytes。
724260: e2 03 01 aa mov x2, x1 // 保留同一长度用于分配缓冲区。
724264: 73 2f f5 97 bl 0x470030
724268: e0 3b 01 f9 str x0, [sp, #624]
72426c: e1 fb 41 f9 ldr x1, [sp, #1008]
724270: e2 03 00 aa mov x2, x0
724274: 63 02 80 d2 mov x3, #19 // io.ReadAtLeast 的最小读取长度。
724278: e4 03 03 aa mov x4, x3 // 传入缓冲区长度 19。
72427c: e5 03 03 aa mov x5, x3 // 传入最小读取长度 19。
724280: e0 f7 41 f9 ldr x0, [sp, #1000]
724284: d7 11 f6 97 bl 0x4a89e0
header_len 和 metadata entry 的字节序反编译把这段还原成 __rev16,对应的实际指令是 REV16。
它交换 16-bit 字段中的两个字节,后续长度计算使用交换后的值。
724318: 64 68 64 78 ldrh w4, [x3, x4] // 取文件中的 16-bit header_len。
72431c: df 1c 00 f1 cmp x6, #7 // 先做越界保护。
724320: e9 60 00 54 b.ls 0x724f3c
724324: 84 04 c0 5a rev16 w4, w4 // 大端 header_len 转宿主序。
724328: 84 3c 40 d3 ubfx x4, x4, #0, #16 // 限定为 uint16。
72432c: e4 6f 00 f9 str x4, [sp, #216] // 保存 header_len。
724330: 82 4c 00 d1 sub x2, x4, #19 // 计算固定头后仍需读入的字节数。
724630: eb 03 0a cb neg x11, x10
724634: 8b fc 8b 8a and x11, x4, x11, asr #63
724638: 2b 68 6b 78 ldrh w11, [x1, x11] // 取当前 metadata entry 的 entry_len。
72463c: 6b 05 c0 5a rev16 w11, w11 // 大端 entry_len 转宿主序。
724640: 6b 3d 40 d3 ubfx x11, x11, #0, #16 // 限定为 uint16。
724644: 6c 01 04 8b add x12, x11, x4 // 得到该条目的结束偏移。
724648: 7f 00 0c eb cmp x3, x12 // 必须仍位于 header 边界内。
前半段处理 header_len,后半段处理每个 metadata entry 的 entry_len,两者都按 big-endian 解析。
total_len 与正文边界total_len 是 64 位大端字段,REV X7, X7 负责翻转字节。
后面的两次 SUB 算出解密 Reader 记录的长度和正文边界,末尾 16 bytes tag 不会进入 CTR。
724d70: e6 6f 40 f9 ldr x6, [sp, #216]
724d74: a5 00 06 8b add x5, x5, x6
724d78: e7 cf 40 f9 ldr x7, [sp, #408] // 取文件中的 64-bit total_len。
724d7c: e7 0c c0 da rev x7, x7 // 大端 total_len 转宿主序。
724d80: e7 cf 00 f9 str x7, [sp, #408] // 保存转换后的总长度。
724d84: e6 00 06 cb sub x6, x7, x6 // 计算从 header 末尾到文件末尾的受限读取长度。
724d88: e6 bf 00 f9 str x6, [sp, #376]
724d8c: e5 00 05 cb sub x5, x7, x5 // 计算正文密文长度,扣除 header 与 16-byte tag。
724d90: e5 af 00 f9 str x5, [sp, #344]
724d94: c0 05 00 90 adrp x0, 0x7dc000
724d98: 00 80 32 91 add x0, x0, #3232
724d9c: d1 a4 f3 97 bl 0x40e0e0
这段汇编只确认字节序和长度边界。
tag 仍由 DecryptSliceReader.Read 读取并比较:0x725268 读取 16 bytes,0x725514 对照累计 HMAC 的前 16 bytes。
课后作业,延伸思考
你说飞牛为什么不提供离线解密工具呢?